From b5130c5943de6d0f415493d39dd32e9d4210e20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 23 Feb 2017 22:50:04 +0100 Subject: [PATCH] util: Add AppSettingsMonitor When integrating with optional components like Clocks, it is not safe to access their GSettings right after the application became visible to the AppSystem: Installation is usually not atomic, so the .desktop file may appear before the settings schema, in which case Gio will abort due to an "invalid" schema ID. To address this, add a small helper class that wraps the settings access in a safe way. https://bugzilla.gnome.org/show_bug.cgi?id=766410 --- js/misc/util.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/js/misc/util.js b/js/misc/util.js index 7c6d11124..0221e15af 100644 --- a/js/misc/util.js +++ b/js/misc/util.js @@ -4,6 +4,8 @@ const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; +const Mainloop = imports.mainloop; +const Signals = imports.signals; const Shell = imports.gi.Shell; const St = imports.gi.St; @@ -398,3 +400,94 @@ function ensureActorVisibleInScrollView(scrollView, actor) { time: SCROLL_TIME, transition: 'easeOutQuad' }); } + +const AppSettingsMonitor = new Lang.Class({ + Name: 'AppSettingsMonitor', + + _init: function(appId, schemaId) { + this._appId = appId; + this._schemaId = schemaId; + + this._app = null; + this._settings = null; + this._handlers = []; + + this._schemaSource = Gio.SettingsSchemaSource.get_default(); + + this._appSystem = Shell.AppSystem.get_default(); + this._appSystem.connect('installed-changed', + Lang.bind(this, this._onInstalledChanged)); + this._onInstalledChanged(); + }, + + get available() { + return this._app != null && this._settings != null; + }, + + activateApp: function() { + if (this._app) + this._app.activate(); + }, + + watchSetting: function(key, callback) { + let handler = { id: 0, key: key, callback: callback }; + this._handlers.push(handler); + + this._connectHandler(handler); + }, + + _connectHandler: function(handler) { + if (!this._settings || handler.id > 0) + return; + + handler.id = this._settings.connect('changed::' + handler.key, + handler.callback); + handler.callback(this._settings, handler.key); + }, + + _disconnectHandler: function(handler) { + if (this._settings && handler.id > 0) + this._settings.disconnect(handler.id); + handler.id = 0; + }, + + _onInstalledChanged: function() { + let hadApp = (this._app != null); + this._app = this._appSystem.lookup_app(this._appId); + let haveApp = (this._app != null); + + if (hadApp == haveApp) + return; + + if (haveApp) + this._checkSettings(); + else + this._setSettings(null); + }, + + _setSettings(settings) { + this._handlers.forEach((handler) => { this._disconnectHandler(handler); }); + + let hadSettings = (this._settings != null); + this._settings = settings; + let haveSettings = (this._settings != null); + + this._handlers.forEach((handler) => { this._connectHandler(handler); }); + + if (hadSettings != haveSettings) + this.emit('available-changed'); + }, + + _checkSettings: function() { + let schema = this._schemaSource.lookup(this._schemaId, true); + if (schema) { + this._setSettings(new Gio.Settings({ settings_schema: schema })); + } else if (this._app) { + Mainloop.timeout_add_seconds(1, () => { + this._checkSettings(); + return GLib.SOURCE_REMOVE; + }); + } + } +}); +Signals.addSignalMethods(AppSettingsMonitor.prototype);