Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reference Monitor IDs by Hyprland ID, not GDK ID #363

Open
cpinflux opened this issue Mar 21, 2024 · 9 comments · May be fixed by #400
Open

Reference Monitor IDs by Hyprland ID, not GDK ID #363

cpinflux opened this issue Mar 21, 2024 · 9 comments · May be fixed by #400

Comments

@cpinflux
Copy link

cpinflux commented Mar 21, 2024

The AGS Window widget should accept a Hyprland Monitor ID instead of a GDK monitor number.

I noticed that the gdkmonitor parameter as added, and especially given this, I think the "monitor" value of the window should be updated to actually relate the the Hyprland monitor ID.

There doesn't appear to be an ideally simple way to translate, however it's possible to get a correct translation by going via get_monitor_at_point().

I have got this working through a simple enough update to a copy of your dotfiles:

utils.js:

export function GdkMonitorFromHyprID(id) {
    const monitors = JSON.parse(Utils.exec('hyprctl -j monitors'));
    const monitor = Gdk.Display.get_default()?.get_monitor_at_point(monitors[id].x, monitors[id].y) || 1;
    return monitor;
}

TopBar.js:

export default monitor => Widget.Window({
    name: `bar${monitor}`,
    class_name: 'transparent',
    exclusivity: 'exclusive',
    gdkmonitor: GdkMonitorFromHyprID(monitor),
    [...]
    }),
});

But it shouldn't be necessary to reference GDK in AGS config. Instead, I presume something like this should work:

    get monitor(): number { return this._get('monitor'); }
    set monitor(monitor: number) {
        if (monitor < 0)
            return;

        const monitors = JSON.parse(Utils.exec('hyprctl -j monitors'));
        const m = Gdk.Display.get_default()?.get_monitor_at_point(monitors[monitor].x, monitors[monitor].y);
        if (m) {
            this.gdkmonitor = m;
            this._set('monitor', monitor);
            return;
        }

        console.error(`Could not find monitor with id: ${monitor}`);
    }

Notes:

  1. A this point in loading, Hyprland.monitors is still undefined, ideally this could be resolved as an issue in it's own write, but as a known workaround, we have the hyprctl call itself. Not great.

  2. An alternative approach could be to implement an equivalent "monitor_at_point" parameter and pass the x,y found by the user but I fail to see any reason this half way solution need be the stopping point.

  3. By default GDK monitor numbers do match the Hyprland ones, so no disruption is likely. The issues arise for me when my laptop is undocked and reconnected, or I disable the built in monitor in Hyprland. These cahnges cause the ID's to become unaligned.

@kotontrion
Copy link
Contributor

kotontrion commented Mar 21, 2024

The windows monitor property should stay as gdk monitor id so this is compositor independent. Otherwise you would get problems with the upcoming sway service. There could however be methods to map hyprland (or sway) monitor ids to gdk ids in utils or their respective services.
Maybe even add the gdk monitor id as an additional property to the monitor object?

@cpinflux
Copy link
Author

Oh, I was unaware of any work for Sway, interesting. I suppose that could be a motivation for a "monitor_at_point" parameter in point 2? That would technically count as a solution in my book if a more complete integration is not feasible. :)

There could be a reserve solution I guess, add something to the Hyprland service to deduce the GDK ID - get the correct monitor and then iterate through all GDK monitors to find the match then return the array index. But as elsewhere, the GDK display number is a pretty meaningless thing, it's just the position in an array, not a formally classified value, so is pretty... wishy washy, and still something that AGS users shouldn't ideally have to be exposed directly to.

@Aylur
Copy link
Owner

Aylur commented Mar 21, 2024

We are not tying AGS to Hyprland, its supposed to be compositor independent.
I agree that it would be better to not expose the user to gdk and the cleanest solution here is to add a getGdkMonitor(id: number): Gdk.Monitor to the Hyprland and Sway service

@Kondor777200
Copy link

Why not use a port name or monitor description for more consistency?

@shezdy shezdy linked a pull request Apr 29, 2024 that will close this issue
@wagyourtail
Copy link

wagyourtail commented May 3, 2024

Why not use a port name or monitor description for more consistency?

something like

export function monitorIdFromName(name) {
  const monitors = JSON.parse(Utils.exec('wlr-randr --json'));
  const m = monitors.find(m => m.name === name);
  if (!m) return 0;
  const mode = m.modes.find(m => m.current);
  const position = m.position;
  const gmonitors = range(Gdk.Display.get_default()?.get_n_monitors() ?? 0, 0).map(i => Gdk.Display.get_default()?.get_monitor(i));
  return gmonitors.findIndex(gmonitor => gmonitor?.geometry.x === position.x && gmonitor?.geometry.y === position.y && gmonitor?.geometry.width === mode.width && gmonitor?.geometry.height === mode.height);
}

@realSaltyFish
Copy link

GDK 3 does not provide a way to uniquely identify a monitor. GDK 4 provides a get_connector() function and a get_description() function that can be used to precisely map a GDK monitor to a Hyprland monitor. Is it possible to upgrade AGS to use GDK 4?

Note: The GDK display object I get by calling Gdk.Display.get_default()?.get_monitor(0) contains an empty workarea and an empty geometry, so no useful identifying information. Also, there is no way to extract the GDK monitor ID from a monitor object, while a monitor ID is required to create a window.

To summarize, the problem is:

  • There is no way to find the corresponding Hyprland monitor ID from a GDK monitor.
  • It is possible to get the corresponding GDK monitor from a Hyprland monitor (using get_monitor_at_point(), but not its ID.

Two ways to address the inconsistency between GDK and Hyprland:

  • Make Widget.Window accept a GDK monitor instead of an ID. This enables going from Hyprland to GDK.
  • Upgrade to GDK 4. This enables going from GDK to Hyprland.

@realSaltyFish
Copy link

In the meantime, I have written this ad-hoc code to extract the monitor ID from a GDK monitor:

const gMonitor = Gdk.Display.get_default()?.get_monitor_at_point(monitor.x, monitor.y)
let gMonitorID: number
for (gMonitorID = 0; true; gMonitorID++) {
  if (Gdk.Display.get_default()?.get_monitor(gMonitorID) === gMonitor) {
    break
  }
}

Here monitor is a Hyprland monitor object. I then pass both gMonitorID and monitor.id to my bar constructor so that it can show up on the correct GDK display with the correct Hyprland workspace list for that display.

Disclaimer: I am not sure whether this code is robust enough.

@kotontrion
Copy link
Contributor

kotontrion commented May 30, 2024

Upgrade to GDK 4. This enables going from GDK to Hyprland.

No. Gdk4 would require to also switch to GTK4. AGS will stay with gtk3, a gtk4 port was created (it's called astal), but it was halted for now because of instability issues with gtk4-layer-shell.

Make Widget.Window accept a GDK monitor instead of an ID. This enables going from Hyprland to GDK.

This is already possible. Windows do have a gdkmonitor property, which takes a gdk monitor object.

GDK 4 provides a get_connector() function

I have implemented this in my config. It does use a deprecated part of the gdk3 api and therefore it should not get merged into AGS, but it works for now.

const display = Gdk.Display.get_default();
function getMonitorName(gdkmonitor) {
  const screen = display.get_default_screen();
  for(let i = 0; i < display.get_n_monitors(); ++i) {
    if(gdkmonitor === display.get_monitor(i))
      return screen.get_monitor_plug_name(i);
  }
}

@realSaltyFish
Copy link

This is already possible. Windows do have a gdkmonitor property, which takes a gdk monitor object.

Thanks for the info. I somehow missed it in the docs.

const display = Gdk.Display.get_default();
function getMonitorName(gdkmonitor) {
  const screen = display.get_default_screen();
  for(let i = 0; i < display.get_n_monitors(); ++i) {
    if(gdkmonitor === display.get_monitor(i))
      return screen.get_monitor_plug_name(i);
  }
}

This works like a charm! I'll use this for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants