-
-
Notifications
You must be signed in to change notification settings - Fork 117
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
Re-open windows for a monitor when it's re-connected #64
Comments
I can't figure out how to show the window on the correct monitor, no matter how I call |
Hi, I have a workaround for that. I noticed that when a monitor is powered off the windows are destroyed, so connect to the destroyed signal of that window and rebuild it. if you build your window with a function like in the example bar make this: win.connect('destroy', () => {
app.removeWindow(win);
app.addWindow(Bar(monitor));
}); Be careful because the old window is fully destroyed and a new one is created, it means that if you have functions that make use of widget instances of the old window, it will throws error each time its executed. For example: if you added: Hyprland.connect('workspace-added', (_, id) => {
// ...
someWorkspaceButtonContainer.add( build_a_button_for_workspace(id) );
// ...
}); the handler must be removed because someWorkspaceButtonContainer will be disposed on your window destruction, so get the handler id and remove it like this: let signal_id = Hyprland.connect('workspace-added', (_, id) => {
// ...
someWorkspaceButtonContainer.add( build_a_button_for_workspace(id) );
// ...
});
// ...
win.connect('destroy', () => {
if (signal_id) {
Hyprland.disconnect(signal_id);
signal_id = null;
}
app.removeWindow(win);
app.addWindow(Bar(monitor));
}); Same happens with interval, timeouts, and so on. if you use .bind(), make use of the last parameter so the callback can be removed on widget or service destruction. Hope be helpful. |
Thanks for the input, it didn't even occur to me that it emitted destroy, since App should have a reference of it, so I wonder why it does. Anyway will put it on the common issues page as a plausible solution. The disconnection issue shouldn't be a problem as long as you only use const Bar = (...params) => Window({
setup: win => win.on('destroy', () => {
App.removeWindow(win)
App.addWindow(Bar(...params))
})
}) |
i haven't tried to reproduce in sway, but i remember that when i came from sway to hyprland one thing that surprised me was that hyprland kill outputs when the monitor is powered off, but in sway i had not that issue. I suspect that hyprland send a destroy event on the output interface and then gdk issue a window destroy, so nothing to do for ags, its an external problem. the only thing that comes to mind is bind() to hyprland monitors and map between monitors and windows, then have a program logic to rebuild the windows when the monitor is reconnected. i think that the Hyprland service could fetch name and id from monitors for the sake of really binding windows to the correct screen. maybe a new feature could be adding a "monitor" parameter to the Widget.Window class and then monitoring monitor disconnections but that is tricky, that shouldn't be necessary if the compositor works properly. I just want to say that this problem comes from outside, your work is awesome and it works, thank you for that. |
My bad, it's implemented, sorry.
I found a way to do that: // This function returns the monitor number in the Gdk.Display monitor list
// given it's name in your wayland compositor
function getMonitorIndexFromName(name) {
let x = -1;
let y = -1;
// Suppose you are in Hyprland, find your way if you are in Sway
for (let m of Hyprland.monitors) if (m.name == name) {
x = m.x + (m.width / 2);
y = m.y + (m.height / 2);
break;
}
if (x == -1) return 0; // Monitor 0 if errors
let monitor = Gdk.Display.get_default()?.get_monitor_at_point(x, y);
for (let i=0; i<Gdk.Display.get_default()?.get_n_monitors(); i++) {
const m = Gdk.Display.get_default()?.get_monitor(i);
if (m === monitor) return i;
}
return 0; // Monitor 0 if errors
}; The Gdk docs says that they represents the display (compositor in our case) as a big and unique screen where monitors are just portions. Gdk.Display.get_monitor_at_point(x, y) returns the Gdk.Monitor that contains the (x,y) point, or the nearest, so i calculate the center of the monitor just for fun, any point should work, for example (monitor.x + 1, monitor.y + 1). I tested and found that Window.monitor can set the correct monitor, even dynamically, but Window.gdkmonitor don't.
@benthejack-vuw try this: // Find the imports
/*
Windows creation and bind to specific monitor
*/
const windowList = [
{ monitor: "HDMI-A-1", callback: Bar, data: "extra parameter if needed"},
{ monitor: "eDP-1", callback: OtherBar, data: "optional"},
{ monitor: "DP-2", callback: FancyWidget, data: "don't add if not used"},
];
function getMonitorIndex (name) {
let x = -1;
let y = -1;
for (let m of Hyprland.monitors) if (m.name == name) {
x = m.x + 1;
y = m.y + 1;
break;
}
if (x == -1) return 0;
let monitor = Gdk.Display.get_default()?.get_monitor_at_point(x, y);
for (let i=0; i<Gdk.Display.get_default()?.get_n_monitors(); i++) {
const m = Gdk.Display.get_default()?.get_monitor(i);
if (m === monitor) return i;
}
return 0;
};
function initWindows() {
if (Hyprland.monitors.length == 0) {
timeout(100, initWindows);
return;
}
for (let entry of windowList) {
const win = entry.callback(getMonitorIndex(entry.monitor), entry.data);
app.addWindow(win);
win.on("destroy", () => app.removeWindow(win.name) );
}
};
initWindows();
Hyprland.connect('monitor-added', (_hypr, monitor) => {
if (!monitor) return;
for (let entry of windowList) if (monitor == entry.monitor) {
const win = entry.callback(getMonitorIndex(entry.monitor), entry.data);
app.addWindow(win);
win.on("destroy", () => app.removeWindow(win.name) );
}
}); |
Share my simply method const createWindows = () =>
[
...Hyprland.monitors.map(m => Bar(m.id)),
Powermenu(),
].map(w => w.on("destroy", self => App.removeWindow(self)));
const recreateWindows = () => {
for (const win of App.windows) {
App.removeWindow(win);
}
App.config({ windows: createWindows() });
};
export default App.config({
style: css,
windows: createWindows(),
onConfigParsed: () => {
hyprland.connect("monitor-removed", recreateWindows);
hyprland.connect("monitor-added", recreateWindows);
},
}); use |
Beware, windows should not be created on monitor-removed event. Removing on window's destroy signal should be enough, and recreatewindows() needs to be called on monitor-added event only, and just for creation, removal is performed on destroy signal. A little improvement. Another problem is multimonitor setups, how to bind the correct window to the correct monitor as aylur mentioned? |
For my instance, currently, My windows have bar-0, bar-1, power_menu
Finally, my approach is actually like restart ags when monitor changed, so it should correct the window's position as its initial. |
I see, it's enough for some monitor-agnostic implementación, but note that issue is about binding a specific window to a specific monitor, it make sense what monitor is the correct, since some user, like the OP, maybe wants display their windows in that specific monitor and not in other one. Suppose someone have two monitors with a bar in each one, if one is disconnected, both bars will appear in the only available monitor, it may be annoying. Note that some HDMI monitors seem to go out when you turn it off, but some VGA monitors just forward an dmps state change. It's a hardware issue but in the former some window managers cant distinguish between a monitor being disconnected and just powering off. The OP wonders about reopen window without restarting ags. In your second case, it's a problem with GDK, it actually destroy the window on monitor disconnection without giving you the chance to reuse it, and the window's state is lost. Unfortunately, in your example, powermenu-like windows needs a more sofisticated mechanism for recreation. |
if a monitor is turned off ags currently has to be killed and re-started, it would be great if it could re-launch any windows for that monitor.
The text was updated successfully, but these errors were encountered: