How can I reliably resize a window via `metaWindow.move_resize_frame(boolean, x, y, width, height);` right after an app launched by `Shell.App.launch()`

Hi,

Question again …

After an application was launched by shell.App.launch(), I connect the signal of windows-changed, and call metaWindow.move_resize_frame(false, x, y, width, height);

But the problem is that it can’t move and resize the window. Looks the window manager just ignore this operation. Bu if a window has existed for a long time (say maybe more than two seconds after showing in the workspace), calling metaWindow.move_resize_frame(false, x, y, width, height); can move and resize the the window.

What’s wrong here? How can I know when an app’s window can be moved and resized by calling metaWindow.move_resize_frame(false, x, y, width, height); after it was launched? Any solution for this issue?


Updated:

Code is at https://github.com/nlpsuge/gnome-shell-extension-another-window-session-manager/pull/18 in case someone want to see the code. All relevant code is here: https://github.com/nlpsuge/gnome-shell-extension-another-window-session-manager/blob/bb0fbd0e4671809a8700a8ffb017932e01861773/moveSession.js#L73-L153.

Still need help.

Thank you.

GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
        metaWindow.move_resize_frame(false, x, y, width, height);
        return GLib.SOURCE_REMOVE;
});

The above code looks work on X11, but not work on Wayland.

On Wayland I see an error:
meta_window_set_stack_position_no_sync: assertion 'window->stack_position >= 0' failed

Any suggestion?

Just a thought, but maybe try a lower priority like GLib.PRIORITY_LOW + 1? It might be mutter is using it’s own idle source somewhere that’s being dispatched after yours.

Thanks for your reply.

After some search, especially these comments above https://github.com/paperwm/PaperWM/blob/10215f57e8b34a044e10b7407cac8fac4b93bbbc/tiling.js#L2120, I connect first-frame and in this signal I still use below code to resize the window:

GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
        metaWindow.move_resize_frame(false, x, y, width, height);
        return GLib.SOURCE_REMOVE;
});

I also found that by using meta_window.change_workspace_by_index(desktop_number, false); cannot move a window to another workspace right after a window opened on Wayland, it’s OK on X11. So I also have to put meta_window.change_workspace_by_index(desktop_number, false); in to the signal first-frame. The whole pseudocode is like

// Do below code only on Wayland

let metaWindowActor = meta_window.get_compositor_private();
const firstFrameId = metaWindowActor.connect('first-frame', () => {
	GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
			let frameRect = open_window.get_frame_rect();
			let current_x = frameRect.x;
			let current_y = frameRect.y;
			let current_width = frameRect.width;
			let current_height = frameRect.height;
			// All zero, may indicates this window is not rendered completely
			if (current_x === 0 &&
				current_y === 0 &&
				current_width === 0 &&
				current_height === 0) 
			{
				return GLib.SOURCE_CONTINUE;
			}

			// Resize and move
			metaWindow.move_resize_frame(false, x, y, width, height);
			
			// Change workspace if necessary
			if (not-in-desktop_number) {
			    open_window.change_workspace_by_index(desktop_number, false);
			}
			
			metaWindowActor.disconnect(firstFrameId);
			return GLib.SOURCE_REMOVE;
	});
});

But I’m not sure how reliable it is. The return value of meta_window.get_compositor_private(); can be null somehow?

More code than on X11. And metaWindow.move_resize_frame(false, x, y, width, height); is more stable and reliable on X11.


Just a thought, but maybe try a lower priority like GLib.PRIORITY_LOW + 1?

I tried this just now, don’t work.

The code looks like this:

GObject *
meta_window_get_compositor_private (MetaWindow *window)
{
  if (!window)
    return NULL;
  return window->compositor_private;
}

Since you’re holding a (toggle) reference in JavaScript and calling the method on it, I think it can not return null. It’s possible the window could be destroyed with you still holding a reference though.

You could do something like this, to be extra sure that doesn’t happen:

let windowReadyId = 0;
const firstFrameId = metaWindowActor.connect('first-frame', () => {
	windowReadyId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
        // stuff

        // before returning GLib.SOURCE_REMOVE, zero out the id
        windowReadyId = 0
        return GLib.SOURCE_REMOVE;
    });
});
meta_window.connect('unmanaging', () => {
    if (windowReadyId)
        GLib.Source.remove(windowReadyId);
});

OK, thanks, I’ll try it.

@andyholmes

Hi,

Is it possible that I can miss the first-frame signal? because there is a situation that before I connect first-frame, first-frame has been send out by metaWindowActor, is this possible?

If so, how can I make sure not miss a signal?

The documentation says you should connect to this when Meta.Display::window-created is emitted, so I guess you might want to track all windows so you can be sure of their current state.

Indeed. I don’t quite understand what the doc said until you told me this. Thanks for this.


And during recently tests, I found that I cannot disconnect signals from metaWindowActor in a destroy(), if I do this many errors occur:

gnome-shell[27918]: Object .MetaWindowActorWayland (0x5646e9bae460), has been already disposed — impossible to access it. This might be caused by the object having been destroyed from C code using something such as destroy(), dispose(), or remove() vfuncs.
gnome-shell[27918]: == Stack trace for context 0x5646e58be190 ==
gnome-shell[27918]: #0   5646ebb25cb8 i   resource:///org/gnome/shell/ui/environment.js:360 (10e78dfafdd0 @ 22)
gnome-shell[27918]: #1   7ffd1fe2f930 b   resource:///org/gnome/shell/ui/environment.js:299 (10e78dfbf100 @ 39)

But if I disconnect signals from metaWindowActor within the signal unmanaging on meta_window like this and no errors reported:

meta_window.connect('unmanaging', () => {
	if (firstFrameId) {
		obj.disconnect(firstFrameId);
	}
});

I don’t understand. Why I cannot disconnect signals from metaWindowActor, but can do this on meta_window? metaWindowActor is attached to meta_window, is part of meta_window? Aren’t they existing and disappearing together?

Do you mean you’re overriding the destroy() method? If so, you should definitely not do that in GJS. This should be safe:

// This is a Meta.WindowActor, which is a subclass of Clutter.Actor and has a ::destroy signal
const destroyId = metaWindowActor.connect('destroy', (actor) => {
    actor.disconnect(destroyId);
});

This is safe, because the object must not be finalized if it is emitting a signal. Note that Meta.Window is just a GObject and has no ::destroy() signal. Meta.Window::unmanaged seems to be the closest that object has.

Also note that if an object is destroyed any signal handlers will be removed as part of the finalization. Since a finalized object can not emit signals, it’s not necessary to disconnect signals if you know the object is being destroyed.

I added a destroy() method in the indicator, which is the subclass of PanelMenu.Button. And I call super.destroy() at the end of this destroy().

Yes, you can’t do that. Never override the destroy() method of a C class (Clutter.Actor in this case). Instead you should connect to the signal, otherwise your override may be called after JavaScript is permitted to run.