From ba5d99f3a94d918d029aec97b5218437c774f0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 3 Jun 2026 14:54:16 +0200 Subject: [PATCH] auto-move-windows: Optionally restrict to startup Resolves: https://redhat.atlassian.net/browse/RHEL-80282 --- ...ndows-Optionally-restrict-to-startup.patch | 194 ++++++++++++++++++ gnome-shell-extensions.spec | 2 + 2 files changed, 196 insertions(+) create mode 100644 0001-auto-move-windows-Optionally-restrict-to-startup.patch diff --git a/0001-auto-move-windows-Optionally-restrict-to-startup.patch b/0001-auto-move-windows-Optionally-restrict-to-startup.patch new file mode 100644 index 0000000..de3782b --- /dev/null +++ b/0001-auto-move-windows-Optionally-restrict-to-startup.patch @@ -0,0 +1,194 @@ +From 9272f1ecf3750d1385ea627ebda41edfce3b37eb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 27 May 2026 19:35:04 +0200 +Subject: [PATCH] auto-move-windows: Optionally restrict to startup + +It is not uncommon to use a combination of autostart and auto-move-windows +to work around missing session saving/restore support. In that case, only +autostarted windows should be moved to their configured workspace, while +windows that are opened later should behave normally. + +While we cannot detect that a window is autostarted, we can approximate +the behavior by only enabling moves for 20 seconds during startup. + +In theory we can improve accuracy further by only moving windows without +a startup ID (as gnome-session uses a plain GDesktopAppLaunchContext), +but in practice its usefulness is limited given that the activation token +of wayland windows is applied too late and not exposed to introspection. + +Closes: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/work_items/322 +--- + extensions/auto-move-windows/extension.js | 54 +++++++++++++++++-- + ...l.extensions.auto-move-windows.gschema.xml | 4 ++ + extensions/auto-move-windows/prefs.js | 43 ++++++++++++++- + 3 files changed, 97 insertions(+), 4 deletions(-) + +diff --git a/extensions/auto-move-windows/extension.js b/extensions/auto-move-windows/extension.js +index 34decce7..67381e48 100644 +--- a/extensions/auto-move-windows/extension.js ++++ b/extensions/auto-move-windows/extension.js +@@ -4,11 +4,15 @@ + // + // SPDX-License-Identifier: GPL-2.0-or-later + ++import GLib from 'gi://GLib'; + import Shell from 'gi://Shell'; + + import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; + import * as Main from 'resource:///org/gnome/shell/ui/main.js'; + ++// time during which windows are moved on startup if startup-only is true ++const SESSION_AUTOSTART_TIMEOUT_MS = 20 * 1000; ++ + class WindowMover { + constructor(settings) { + this._settings = settings; +@@ -19,16 +23,50 @@ class WindowMover { + this._appSystem.connectObject('installed-changed', + () => this._updateAppData(), this); + +- this._settings.connectObject('changed', ++ this._settings.connectObject('changed::application-list', + this._updateAppConfigs.bind(this), this); ++ this._settings.connectObject('changed::startup-only', ++ () => this._updateStartupOnly()); ++ this._updateStartupOnly(); ++ } ++ ++ _updateStartupOnly() { ++ const startupOnly = this._settings.get_boolean('startup-only'); ++ if (this._startupOnly === startupOnly) ++ return; ++ ++ this._startupOnly = startupOnly; ++ ++ if (startupOnly) { ++ this._movesEnabled = Main.layoutManager._startingUp; ++ ++ if (this._movesEnabled) { ++ this._startupTimeoutId = GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, ++ SESSION_AUTOSTART_TIMEOUT_MS, ++ () => { ++ this._movesEnabled = false; ++ this._updateAppConfigs(); ++ ++ delete this._startupTimeoutId; ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ } else { ++ this._movesEnabled = true; ++ } ++ + this._updateAppConfigs(); + } + + _updateAppConfigs() { + this._appConfigs.clear(); + +- this._settings.get_strv('application-list').forEach(v => { +- let [appId, num] = v.split(':'); ++ const appList = this._movesEnabled ++ ? this._settings.get_strv('application-list') ++ : []; ++ appList.forEach(v => { ++ const [appId, num] = v.split(':'); + this._appConfigs.set(appId, parseInt(num) - 1); + }); + +@@ -61,6 +99,10 @@ class WindowMover { + + this._appConfigs.clear(); + this._updateAppData(); ++ ++ if (this._startupTimeoutId) ++ GLib.source_remove(this._startupTimeoutId); ++ delete this._startupTimeoutId; + } + + _moveWindow(window, workspaceNum) { +@@ -89,6 +131,12 @@ class WindowMover { + return !windows.includes(w) && w.get_compositor_private() !== null; + })); + ++ // In startup-only mode, we only want to move auto-started apps; ++ // we can't filter for that, but at least we know that windows with ++ // a startup ID were launched by the user ++ if (this._startupOnly) ++ windows = windows.filter(w => w.get_startup_id() === null); ++ + let workspaceNum = this._appConfigs.get(app.id); + windows.filter(w => !data.windows.includes(w)).forEach(window => { + this._moveWindow(window, workspaceNum); +diff --git a/extensions/auto-move-windows/org.gnome.shell.extensions.auto-move-windows.gschema.xml b/extensions/auto-move-windows/org.gnome.shell.extensions.auto-move-windows.gschema.xml +index 563f7ee6..a7d5ebb5 100644 +--- a/extensions/auto-move-windows/org.gnome.shell.extensions.auto-move-windows.gschema.xml ++++ b/extensions/auto-move-windows/org.gnome.shell.extensions.auto-move-windows.gschema.xml +@@ -12,5 +12,9 @@ SPDX-License-Identifier: GPL-2.0-or-later + Application and workspace list + A list of strings, each containing an application id (desktop file name), followed by a colon and the workspace number + ++ ++ false ++ Only move windows during startup ++ + + +diff --git a/extensions/auto-move-windows/prefs.js b/extensions/auto-move-windows/prefs.js +index 98e424e0..4e1fa6c5 100644 +--- a/extensions/auto-move-windows/prefs.js ++++ b/extensions/auto-move-windows/prefs.js +@@ -140,7 +140,48 @@ class RulesList extends GObject.Object { + } + } + +-class AutoMoveSettingsWidget extends Adw.PreferencesGroup { ++class AutoMoveSettingsWidget extends Adw.PreferencesPage { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(settings) { ++ super(); ++ ++ this._settings = settings; ++ ++ const rulesGroup = new WorkspaceRulesGroup(settings); ++ this.add(rulesGroup); ++ ++ const optionsGroup = new OptionsGroup(settings); ++ this.add(optionsGroup); ++ } ++} ++ ++class OptionsGroup extends Adw.PreferencesGroup { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(settings) { ++ super(); ++ ++ this._settings = settings; ++ ++ const actionGroup = new Gio.SimpleActionGroup(); ++ actionGroup.add_action(settings.create_action('startup-only')); ++ this.insert_action_group('options', actionGroup); ++ ++ const row = new Adw.SwitchRow({ ++ title: _('Startup Only'), ++ subtitle: _('Only apply rules while the session is starting'), ++ action_name: 'options.startup-only', ++ }); ++ this.add(row); ++ } ++} ++ ++class WorkspaceRulesGroup extends Adw.PreferencesGroup { + static { + GObject.registerClass(this); + +-- +2.54.0 + diff --git a/gnome-shell-extensions.spec b/gnome-shell-extensions.spec index 27cb4d0..52a73b9 100644 --- a/gnome-shell-extensions.spec +++ b/gnome-shell-extensions.spec @@ -40,6 +40,8 @@ Patch: heads-up-display-from-file.patch Patch: scrollable-workspace-menu.patch +Patch: 0001-auto-move-windows-Optionally-restrict-to-startup.patch + %description GNOME Shell Extensions is a collection of extensions providing additional and optional functionality to GNOME Shell.