Workaround unreliable GDK geometry scaling for matching monitors (#768)

* Workaround unreliable GDK geometry scaling for matching monitors

* Extra monitor matching handling for transforms/rotation and fractional scaling

* Add monitor key matching debug logging

* Fix fractionally scaled monitor matching, plus slight refactor

* Fix rotation logic

* Use console.debug for debug logging, run linting

* Fix Type errors

* Update debugging comment after testing

* Linting again..
This commit is contained in:
wistfulbrick
2025-02-14 08:41:45 +00:00
committed by GitHub
parent 0d5f80ff5c
commit 4424a523bf

View File

@@ -4,12 +4,14 @@ import { BarLayout, BarLayouts } from 'src/lib/types/options';
const hyprlandService = AstalHyprland.get_default();
type GdkMonitor = {
key: string;
model: string;
used: boolean;
};
type GdkMonitors = {
[key: string]: {
key: string;
model: string;
used: boolean;
};
[key: string]: GdkMonitor;
};
export const getLayoutForMonitor = (monitor: number, layouts: BarLayouts): BarLayout => {
@@ -62,6 +64,8 @@ export function getGdkMonitors(): GdkMonitors {
const geometry = curMonitor.get_geometry();
const scaleFactor = curMonitor.get_scale_factor();
// We can only use the scaleFactor for a scale variable in the key
// GDK3 doesn't support the fractional "scale" attribute (available in GDK4)
const key = `${model}_${geometry.width}x${geometry.height}_${scaleFactor}`;
gdkMonitors[i] = { key, model, used: false };
}
@@ -69,6 +73,51 @@ export function getGdkMonitors(): GdkMonitors {
return gdkMonitors;
}
export function matchMonitorKey(hypMon: AstalHyprland.Monitor, gdkMonitor: GdkMonitor): boolean {
const isRotated90 = hypMon.transform % 2 !== 0;
// Needed for the key regardless of scaling below because GDK3 only has the scale factor for the key
const gdkScaleFactor = Math.ceil(hypMon.scale);
// When gdk is scaled with the scale factor, the hyprland width/height will be the same as the base monitor resolution
// The GDK width/height will NOT flip regardless of transformation (e.g. 90 degrees will NOT swap the GDK width/height)
const scaleFactorWidth = Math.trunc(hypMon.width / gdkScaleFactor);
const scaleFactorHeight = Math.trunc(hypMon.height / gdkScaleFactor);
const scaleFactorKey = `${hypMon.model}_${scaleFactorWidth}x${scaleFactorHeight}_${gdkScaleFactor}`;
// When gdk geometry is scaled with the fractional scale, we need to scale the hyprland geometry to match it
// However a 90 degree transformation WILL flip the GDK width/height
const transWidth = isRotated90 ? hypMon.height : hypMon.width;
const transHeight = isRotated90 ? hypMon.width : hypMon.height;
const scaleWidth = Math.trunc(transWidth / hypMon.scale);
const scaleHeight = Math.trunc(transHeight / hypMon.scale);
const scaleKey = `${hypMon.model}_${scaleWidth}x${scaleHeight}_${gdkScaleFactor}`;
// In GDK3 the GdkMonitor geometry can change depending on how the compositor handles scaling surface framebuffers
// We try to match against two different possibilities:
// 1) The geometry is scaled by the correct fractional scale
// 2) The geometry is scaled by the scaleFactor (the fractional scale rounded up)
const keyMatch = gdkMonitor.key === scaleFactorKey || gdkMonitor.key === scaleKey;
// Monitor matching debug logging, use if your workspaces are appearing on the wrong screen
// To use, kill any running HyprPanel instances and then start a terminal, then run:
// G_MESSAGES_DEBUG=all hyprpanel | grep "hyprpanel-DEBUG"
// Create an issue in HyprPanel github and post these logs
console.debug('Attempting gdk key match');
console.debug(`GDK key: ${gdkMonitor.key}`);
console.debug(`HypMon.width: ${hypMon.width}`);
console.debug(`HypMon.height: ${hypMon.height}`);
console.debug(`HypMon.scale: ${hypMon.scale}`);
console.debug(`HypMon.transform: ${hypMon.transform}`);
console.debug(`isRotated90: ${isRotated90}`);
console.debug(`scaleFactor: ${gdkScaleFactor}`);
console.debug(`scaleFactorKey: ${scaleFactorKey}`);
console.debug(`scaleKey: ${scaleKey}`);
console.debug(`match?: ${keyMatch}`);
return keyMatch;
}
/**
* NOTE: Some more funky stuff being done by GDK.
* We render windows/bar based on the monitor ID. So if you have 3 monitors, then your
@@ -119,13 +168,7 @@ export const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors:
// First pass: Strict matching including the monitor index (i.e., hypMon.id === monitor + resolution+scale criteria)
const directMatch = hyprlandService.get_monitors().find((hypMon) => {
const isVertical = hypMon?.transform !== undefined ? hypMon.transform % 2 !== 0 : false;
const width = isVertical ? hypMon.height : hypMon.width;
const height = isVertical ? hypMon.width : hypMon.height;
const hyprlandKey = `${hypMon.model}_${width}x${height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
return matchMonitorKey(hypMon, gdkMonitor) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
});
if (directMatch) {
@@ -135,13 +178,7 @@ export const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors:
// Second pass: Relaxed matching without considering the monitor index
const hyprlandMonitor = hyprlandService.get_monitors().find((hypMon) => {
const isVertical = hypMon?.transform !== undefined ? hypMon.transform % 2 !== 0 : false;
const width = isVertical ? hypMon.height : hypMon.width;
const height = isVertical ? hypMon.width : hypMon.height;
const hyprlandKey = `${hypMon.model}_${width}x${height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id);
return matchMonitorKey(hypMon, gdkMonitor) && !usedHyprlandMonitors.has(hypMon.id);
});
if (hyprlandMonitor) {