From 89a97a57a69ffa82aa0c92c2ad70b90749df8da0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 20:34:57 +0100 Subject: [PATCH 1/2] common: Add more getters and setters for settings Allow to get GVariant values and reset them --- tiling-assistant@leleat-on-github/src/common.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tiling-assistant@leleat-on-github/src/common.js b/tiling-assistant@leleat-on-github/src/common.js index 835f91f7..67d1ca2e 100644 --- a/tiling-assistant@leleat-on-github/src/common.js +++ b/tiling-assistant@leleat-on-github/src/common.js @@ -144,6 +144,14 @@ var Settings = class Settings { return this._settings.get_boolean(key); } + static getValue(key) { + return this._settings.get_value(key); + } + + static getUserValue(key) { + return this._settings.get_user_value(key); + } + /** * Setters */ @@ -167,6 +175,14 @@ var Settings = class Settings { static setBoolean(key, value) { this._settings.set_boolean(key, value); } + + static setValue(key, value) { + return this._settings.set_value(key, value); + } + + static reset(key) { + this._settings.reset(key); + } }; /** From a0ecbd51bdebc70fae60621017f2b6fb6a516c62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 15 Mar 2023 18:04:32 +0100 Subject: [PATCH 2/2] extension: Add SettingsOverrider to preserve user settings The extension is overriding the user settings by writing on them, sadly this implies various issues because even if we reset them on extension unloading, a shell crash or mis-behavior could always lead to affect user settings. Tiling assistant replaces the user settings when running, but not to loose the user settings, we need to save them around so that when the extension gets unloaded they can be restored. So add a SettingsOverrider class that: - Replaces the user settings with the extensions one - Remembers what has been changed and saves it locally and in our settings - When the extension is unloaded the settings are restored or reset Now, in case gnome-shell is stopped or the extension has crashed for some reason, once the extension is reloaded we check if previous settings were saved and in case they are used to restore the user settings on destruction. --- .../extension.js | 186 ++++++++++++------ ...ll.extensions.tiling-assistant.gschema.xml | 4 + 2 files changed, 135 insertions(+), 55 deletions(-) diff --git a/tiling-assistant@leleat-on-github/extension.js b/tiling-assistant@leleat-on-github/extension.js index ae28c245..9dd95f8e 100644 --- a/tiling-assistant@leleat-on-github/extension.js +++ b/tiling-assistant@leleat-on-github/extension.js @@ -36,6 +36,115 @@ const { Rect, Util } = Me.imports.src.extension.utility; * => resizeHandler.js (when resizing a window) */ +class SettingsOverrider { + constructor(settings) { + this._settings = settings; + this._overrides = new Map(); + this._originalSettings = new Map(); + this._maybeNullValue = GLib.Variant.new_maybe( + new GLib.VariantType('b'), null); + + const savedSettings = this._settings.getUserValue('overridden-settings'); + this._wasOverridden = savedSettings !== null; + } + + _maybeUpdateOverriden(schemaId, key, value) { + if (this._wasOverridden) + return undefined; + + const savedSettings = this._settings.getValue( + 'overridden-settings').deepUnpack(); + const prefKey = `${schemaId}.${key}`; + const oldValue = savedSettings[prefKey]; + + if (value !== undefined) + savedSettings[prefKey] = value ?? this._maybeNullValue; + else + delete savedSettings[prefKey]; + + this._settings.setValue('overridden-settings', + new GLib.Variant('a{sv}', savedSettings)); + + return oldValue; + } + + add(settings, key, value) { + this._originalSettings.set(settings.schemaId, settings); + const userValue = settings.get_user_value(key); + + const values = this._overrides.get(settings.schemaId) ?? new Map(); + if (!values.size) + this._overrides.set(settings.schemaId, values); + values.set(key, userValue); + + settings.set_value(key, value); + + this._maybeUpdateOverriden(settings.schemaId, key, + userValue ?? this._maybeNullValue); + } + + remove(schema, key) { + const settings = this._originalSettings.get(schema); + if (!settings) + return; + + const values = this._overrides.get(settings.schemaId); + const value = values?.get(key); + + if (value === undefined) + return; + + if (value) + settings.set_value(key, value); + else + settings.reset(key); + + values.delete(key); + this._maybeUpdateOverriden(settings.schemaId, key, undefined); + } + + _clear() { + if (this._wasOverridden) { + const savedSettings = this._settings.getValue( + 'overridden-settings').unpack(); + + Object.entries(savedSettings).forEach(([path, value]) => { + const splits = path.split('.'); + const key = splits.at(-1); + const schemaId = splits.slice(0, -1).join('.'); + + const settings = this._originalSettings.get(schemaId) ?? + ExtensionUtils.getSettings(schemaId); + + value = value.get_variant(); + if (value.equal(this._maybeNullValue)) + settings.reset(key); + else + settings.set_value(key, value); + }); + } else { + this._originalSettings.forEach(settings => { + this._overrides.get(settings.schemaId).forEach((value, key) => { + if (value) + settings.set_value(key, value); + else + settings.reset(key); + }); + }); + } + + this._settings.reset('overridden-settings'); + } + + destroy() { + this._clear(); + this._maybeNullValue = null; + this._originalSettings = null; + this._overrides = null; + this._settings = null; + } +} + function init() { ExtensionUtils.initTranslations(Me.metadata.uuid); } @@ -43,6 +152,7 @@ function init() { function enable() { this._settings = Me.imports.src.common.Settings; this._settings.initialize(); + this._settingsOverrider = new SettingsOverrider(this._settings); this._twm = Me.imports.src.extension.tilingWindowManager.TilingWindowManager; this._twm.initialize(); @@ -62,45 +172,36 @@ function enable() { this._altTabOverride = new AltTabOverride(); // Disable native tiling. - this._gnomeMutterSettings = ExtensionUtils.getSettings('org.gnome.mutter'); - this._gnomeMutterEdgeTilingUserValue = this._gnomeMutterSettings.get_user_value('edge-tiling'); - this._gnomeMutterSettings.set_boolean('edge-tiling', false); - - if (Gio.SettingsSchemaSource.get_default().lookup('org.gnome.shell.overrides', true)) { - this._gnomeShellSettings = ExtensionUtils.getSettings('org.gnome.shell.overrides'); - this._gnomeShellEdgeTilingUserValue = this._gnomeShellSettings.get_user_value('edge-tiling'); - this._gnomeShellSettings.set_boolean('edge-tiling', false); - } + this._settingsOverrider.add(ExtensionUtils.getSettings('org.gnome.mutter'), + 'edge-tiling', new GLib.Variant('b', false)); // Disable native keybindings for Super+Up/Down/Left/Right - this._gnomeMutterKeybindings = ExtensionUtils.getSettings('org.gnome.mutter.keybindings'); - this._gnomeDesktopKeybindings = ExtensionUtils.getSettings('org.gnome.desktop.wm.keybindings'); - this._nativeKeybindings = []; + const gnomeMutterKeybindings = ExtensionUtils.getSettings( + 'org.gnome.mutter.keybindings'); + const gnomeDesktopKeybindings = ExtensionUtils.getSettings( + 'org.gnome.desktop.wm.keybindings'); const sc = Me.imports.src.common.Shortcuts; + const emptyStrvVariant = new GLib.Variant('as', []); - if (this._gnomeDesktopKeybindings.get_strv('maximize').includes('Up') && + if (gnomeDesktopKeybindings.get_strv('maximize').includes('Up') && this._settings.getStrv(sc.MAXIMIZE).includes('Up')) { - const userValue = this._gnomeDesktopKeybindings.get_value('maximize'); - this._gnomeDesktopKeybindings.set_strv('maximize', []); - this._nativeKeybindings.push([this._gnomeDesktopKeybindings, 'maximize', userValue]); + this._settingsOverrider.add(gnomeDesktopKeybindings, + 'maximize', emptyStrvVariant); } - if (this._gnomeDesktopKeybindings.get_strv('unmaximize').includes('Down') && + if (gnomeDesktopKeybindings.get_strv('unmaximize').includes('Down') && this._settings.getStrv(sc.RESTORE_WINDOW).includes('Down')) { - const userValue = this._gnomeDesktopKeybindings.get_value('unmaximize'); - this._gnomeDesktopKeybindings.set_strv('unmaximize', []); - this._nativeKeybindings.push([this._gnomeDesktopKeybindings, 'unmaximize', userValue]); + this._settingsOverrider.add(gnomeDesktopKeybindings, + 'unmaximize', emptyStrvVariant); } - if (this._gnomeMutterKeybindings.get_strv('toggle-tiled-left').includes('Left') && + if (gnomeMutterKeybindings.get_strv('toggle-tiled-left').includes('Left') && this._settings.getStrv(sc.LEFT).includes('Left')) { - const userValue = this._gnomeMutterKeybindings.get_value('toggle-tiled-left'); - this._gnomeMutterKeybindings.set_strv('toggle-tiled-left', []); - this._nativeKeybindings.push([this._gnomeMutterKeybindings, 'toggle-tiled-left', userValue]); + this._settingsOverrider.add(gnomeMutterKeybindings, + 'toggle-tiled-left', emptyStrvVariant); } - if (this._gnomeMutterKeybindings.get_strv('toggle-tiled-right').includes('Right') && + if (gnomeMutterKeybindings.get_strv('toggle-tiled-right').includes('Right') && this._settings.getStrv(sc.RIGHT).includes('Right')) { - const userValue = this._gnomeMutterKeybindings.get_value('toggle-tiled-right'); - this._gnomeMutterKeybindings.set_strv('toggle-tiled-right', []); - this._nativeKeybindings.push([this._gnomeMutterKeybindings, 'toggle-tiled-right', userValue]); + this._settingsOverrider.add(gnomeMutterKeybindings, + 'toggle-tiled-right', emptyStrvVariant); } // Include tiled windows when dragging from the top panel. @@ -134,6 +235,8 @@ function disable() { // them after the session is unlocked again. _saveBeforeSessionLock(); + this._settingsOverrider.destroy(); + this._settingsOverrider = null; this._moveHandler.destroy(); this._moveHandler = null; this._resizeHandler.destroy(); @@ -154,33 +257,6 @@ function disable() { this._settings.destroy(); this._settings = null; - const restoreSetting = (gsettings, key, oldValue) => { - if (gsettings) { - if (oldValue) - gsettings.set_value(key, oldValue); - else - gsettings.reset(key); - } - }; - - // Re-enable native tiling. - restoreSetting(this._gnomeMutterSettings, - 'edge-tiling', this._gnomeMutterEdgeTilingUserValue); - this._gnomeMutterEdgeTilingUserValue = null; - this._gnomeMutterSettings = null; - - restoreSetting(this._gnomeShellSettings, - 'edge-tiling', this._gnomeShellEdgeTilingUserValue); - this._gnomeShellEdgeTilingUserValue = null; - this._gnomeShellSettings = null; - - // Restore native keybindings for Super+Up/Down/Left/Right - this._nativeKeybindings.forEach(([kbSetting, kbName, kbOldValue]) => - restoreSetting(kbSetting, kbName, kbOldValue)); - this._nativeKeybindings = []; - this._gnomeMutterKeybindings = null; - this._gnomeDesktopKeybindings = null; - // Restore old functions. Main.panel._getDraggableWindowForPosition = this._getDraggableWindowForPosition; this._getDraggableWindowForPosition = null; diff --git a/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml b/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml index 1ee19031..1764d5e9 100644 --- a/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml +++ b/tiling-assistant@leleat-on-github/schemas/org.gnome.shell.extensions.tiling-assistant.gschema.xml @@ -279,5 +279,9 @@ -1 + + + [] +