Monitor identification update. (#107)

* Add proper gtk to hyprland monitor mapping

* Updated the monitor identification logic to sync GDK monitor IDs properly with hyprland monitor IDs.

* Remove console statement.

* Revert this to how it was before since its exactly the same thing.
This commit is contained in:
Jas Singh
2024-08-10 18:34:55 -07:00
committed by GitHub
parent f3d287ad59
commit 0898c98f9c
3 changed files with 205 additions and 59 deletions

View File

@@ -54,8 +54,8 @@ export async function sh(cmd: string | string[]) {
}
export function forMonitors(widget: (monitor: number) => Gtk.Window) {
const n = Gdk.Display.get_default()?.get_n_monitors() || 1
return range(n, 0).flatMap(widget)
const n = Gdk.Display.get_default()?.get_n_monitors() || 1;
return range(n, 0).flatMap(widget);
}
/**
@@ -148,7 +148,7 @@ export const Notify = (notifPayload: NotificationArgs): void => {
Utils.execAsync(command)
}
export function getPosition (pos: NotificationAnchor | OSDAnchor): ("top" | "bottom" | "left" | "right")[] {
export function getPosition(pos: NotificationAnchor | OSDAnchor): ("top" | "bottom" | "left" | "right")[] {
const positionMap: { [key: string]: ("top" | "bottom" | "left" | "right")[] } = {
"top": ["top"],
"top right": ["top", "right"],

View File

@@ -13,6 +13,7 @@ const hyprland = await Service.import("hyprland");
import { BarItemBox as WidgetContainer } from "../shared/barItemBox.js";
import options from "options";
import Gdk from "gi://Gdk?version=3.0";
const { layouts } = options.bar;
@@ -85,9 +86,144 @@ const widget = {
systray: () => WidgetContainer(SysTray()),
};
export const Bar = (monitor: number) => {
type GdkMonitors = {
[key: string]: {
key: string,
model: string,
used: boolean
}
};
function getGdkMonitors(): GdkMonitors {
const display = Gdk.Display.get_default();
if (display === null) {
console.error("Failed to get Gdk display.");
return {};
}
const numGdkMonitors = display.get_n_monitors();
const gdkMonitors = {};
for (let i = 0; i < numGdkMonitors; i++) {
const curMonitor = display.get_monitor(i);
if (curMonitor === null) {
console.warn(`Monitor at index ${i} is null.`);
continue;
}
const model = curMonitor.get_model();
const geometry = curMonitor.get_geometry();
const scaleFactor = curMonitor.get_scale_factor();
const key = `${model}_${geometry.width}x${geometry.height}_${scaleFactor}`;
gdkMonitors[i] = { key, model, used: false };
}
return gdkMonitors;
}
/**
* 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
* monitor IDs will be [0, 1, 2]. Hyprland will NEVER change what ID belongs to what monitor.
*
* So if hyprland determines id 0 = DP-1, even after you unplug, shut off or restart your monitor,
* the id 0 will ALWAYS be DP-1.
*
* However, GDK (the righteous genius that it is) will change the order of ID anytime your monitor
* setup is changed. So if you unplug your monitor and plug it back it, it now becomes the last id.
* So if DP-1 was id 0 and you unplugged it, it will reconfigure to id 2. This sucks because now
* there's a mismtach between what GDK determines the monitor is at id 2 and what Hyprland determines
* is at id 2.
*
* So for that reason, we need to redirect the input `monitor` that the Bar module takes in, to the
* proper Hyprland monitor. So when monitor id 0 comes in, we need to find what the id of that monitor
* is being determined as by Hyprland so the bars show up on the right monitors.
*
* Since GTK3 doesn't contain connection names and only monitor models, we have to make the best guess
* in the case that there are multiple models in the same resolution with the same scale. We find the
* 'right' monitor by checking if the model matches along with the resolution and scale. If monitor at
* ID 0 for GDK is being reported as 'MSI MAG271CQR' we find the same model in the Hyprland monitor list
* and check if the resolution and scaling is the same... if it is then we determine it's a match.
*
* The edge-case that we just can't handle is if you have the same monitors in the same resolution at the same
* scale. So if you've got 2 'MSI MAG271CQR' monitors at 2560x1440 at scale 1, then we just match the first
* monitor in the list as the first match and then the second 'MSI MAG271CQR' as a match in the 2nd iteration.
* You may have the bar showing up on the wrong one in this case because we don't know what the connector id
* is of either of these monitors (DP-1, DP-2) which are unique values - as these are only in GTK4.
*
* Keep in mind though, this is ONLY an issue if you change your monitor setup by plugging in a new one, restarting
* an existing one or shutting it off.
*
* If your monitors aren't changed in the current session you're in then none of this safeguarding is relevant.
*
* Fun stuff really... :facepalm:
*/
const gdkMonitorIdToHyprlandId = (monitor: number, usedHyprlandMonitors: Set<number>): number => {
const gdkMonitors = getGdkMonitors();
if (Object.keys(gdkMonitors).length === 0) {
console.error("No GDK monitors were found.");
return monitor;
}
// Get the GDK monitor for the given monitor index
const gdkMonitor = gdkMonitors[monitor];
// First pass: Strict matching including the monitor index (i.e., hypMon.id === monitor + resolution+scale criteria)
const directMatch = hyprland.monitors.find(hypMon => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id) && hypMon.id === monitor;
});
if (directMatch) {
usedHyprlandMonitors.add(directMatch.id);
return directMatch.id;
}
// Second pass: Relaxed matching without considering the monitor index
const hyprlandMonitor = hyprland.monitors.find(hypMon => {
const hyprlandKey = `${hypMon.model}_${hypMon.width}x${hypMon.height}_${hypMon.scale}`;
return gdkMonitor.key.startsWith(hyprlandKey) && !usedHyprlandMonitors.has(hypMon.id);
});
if (hyprlandMonitor) {
usedHyprlandMonitors.add(hyprlandMonitor.id);
return hyprlandMonitor.id;
}
// Fallback: Find the first available monitor ID that hasn't been used
const fallbackMonitor = hyprland.monitors.find(hypMon => !usedHyprlandMonitors.has(hypMon.id));
if (fallbackMonitor) {
usedHyprlandMonitors.add(fallbackMonitor.id);
return fallbackMonitor.id;
}
// Ensure we return a valid monitor ID that actually exists
for (let i = 0; i < hyprland.monitors.length; i++) {
if (!usedHyprlandMonitors.has(i)) {
usedHyprlandMonitors.add(i);
return i;
}
}
// As a last resort, return the original monitor index if no unique monitor can be found
console.warn(`Returning original monitor index as a last resort: ${monitor}`);
return monitor;
};
export const Bar = (() => {
const usedHyprlandMonitors = new Set<number>();
return (monitor: number) => {
const hyprlandMonitor = gdkMonitorIdToHyprlandId(monitor, usedHyprlandMonitors);
return Widget.Window({
name: `bar-${monitor}`,
name: `bar-${hyprlandMonitor}`,
class_name: "bar",
monitor,
visible: true,
@@ -103,9 +239,9 @@ export const Bar = (monitor: number) => {
hexpand: true,
setup: self => {
self.hook(layouts, (self) => {
const foundLayout = getModulesForMonitor(monitor, layouts.value as BarLayout)
self.children = foundLayout.left.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](monitor));
})
const foundLayout = getModulesForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.left.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor));
});
},
}),
centerWidget: Widget.Box({
@@ -113,9 +249,9 @@ export const Bar = (monitor: number) => {
hpack: "center",
setup: self => {
self.hook(layouts, (self) => {
const foundLayout = getModulesForMonitor(monitor, layouts.value as BarLayout)
self.children = foundLayout.middle.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](monitor));
})
const foundLayout = getModulesForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.middle.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor));
});
},
}),
endWidget: Widget.Box({
@@ -123,12 +259,13 @@ export const Bar = (monitor: number) => {
hpack: "end",
setup: self => {
self.hook(layouts, (self) => {
const foundLayout = getModulesForMonitor(monitor, layouts.value as BarLayout)
self.children = foundLayout.right.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](monitor));
})
const foundLayout = getModulesForMonitor(hyprlandMonitor, layouts.value as BarLayout);
self.children = foundLayout.right.filter(mod => Object.keys(widget).includes(mod)).map(w => widget[w](hyprlandMonitor));
});
},
}),
})
})
}),
}),
});
};
};
})();

View File

@@ -17,11 +17,16 @@ const moveBoxToCursor = (self: any, fixed: boolean) => {
}
globalMousePos.connect("changed", ({ value }) => {
const hyprScaling = hyprland.monitors[hyprland.active.monitor.id].scale;
const currentWidth = self.child.get_allocation().width;
const curHyprlandMonitor = hyprland.monitors.find(m => m.id === hyprland.active.monitor.id);
const dropdownWidth = self.child.get_allocation().width;
let monWidth = hyprland.monitors[hyprland.active.monitor.id].width;
let monHeight = hyprland.monitors[hyprland.active.monitor.id].height;
const hyprScaling = curHyprlandMonitor?.scale;
let monWidth = curHyprlandMonitor?.width;
let monHeight = curHyprlandMonitor?.height;
if (monWidth === undefined || monHeight === undefined || hyprScaling === undefined) {
return;
}
// If GDK Scaling is applied, then get divide width by scaling
// to get the proper coordinates.
@@ -39,24 +44,28 @@ const moveBoxToCursor = (self: any, fixed: boolean) => {
}
// If monitor is vertical (transform = 1 || 3) swap height and width
if (hyprland.monitors[hyprland.active.monitor.id].transform % 2 !== 0) {
const isVertical = curHyprlandMonitor?.transform !== undefined
? curHyprlandMonitor.transform % 2 !== 0
: false;
if (isVertical) {
[monWidth, monHeight] = [monHeight, monWidth];
}
let marginRight = monWidth - currentWidth / 2;
let marginRight = monWidth - dropdownWidth / 2;
marginRight = fixed ? marginRight - monWidth / 2 : marginRight - value[0];
let marginLeft = monWidth - currentWidth - marginRight;
let marginLeft = monWidth - dropdownWidth - marginRight;
const minimumMargin = 0;
if (marginRight < minimumMargin) {
marginRight = minimumMargin;
marginLeft = monWidth - currentWidth - minimumMargin;
marginLeft = monWidth - dropdownWidth - minimumMargin;
}
if (marginLeft < minimumMargin) {
marginLeft = minimumMargin;
marginRight = monWidth - currentWidth - minimumMargin;
marginRight = monWidth - dropdownWidth - minimumMargin;
}
const marginTop = 45;