diff --git a/data/theme/cinnamon-sass/widgets/_menus.scss b/data/theme/cinnamon-sass/widgets/_menus.scss index 107ddf19a9..40ad1157db 100644 --- a/data/theme/cinnamon-sass/widgets/_menus.scss +++ b/data/theme/cinnamon-sass/widgets/_menus.scss @@ -52,6 +52,10 @@ $menuitem_border_radius: $base_border_radius * 1; icon-size: $scalable_icon_size; } +.popup-inhibitor-menu-item .popup-menu-icon { + icon-size: to_em(24px); +} + // popup submenus .popup-sub-menu { border-radius: $base_border_radius + 1; diff --git a/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/applet.js index c300e085d0..acf50cfcbb 100644 --- a/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/applet.js @@ -1,16 +1,36 @@ const Applet = imports.ui.applet; +const Cinnamon = imports.gi.Cinnamon; const Gio = imports.gi.Gio; -const Lang = imports.lang; const Main = imports.ui.main; const St = imports.gi.St; const Tooltips = imports.ui.tooltips; const PopupMenu = imports.ui.popupMenu; -const GnomeSession = imports.misc.gnomeSession; +const Inhibitor = imports.misc.inhibitor; const Settings = imports.ui.settings; const Util = imports.misc.util; -const INHIBIT_IDLE_FLAG = 8; -const INHIBIT_SLEEP_FLAG = 4; + +function _resolveAppInfo(appId) { + let appSys = Cinnamon.AppSystem.get_default(); + + let app = appSys.lookup_app(`${appId}.desktop`) + || appSys.lookup_app(appId) + || appSys.lookup_flatpak_app_id(appId); + + // Try the last segment of a dotted app ID (e.g. "org.x.hypnotix" -> "hypnotix") + if (!app) { + let parts = appId.split('.'); + if (parts.length > 1) + app = appSys.lookup_app(`${parts[parts.length - 1]}.desktop`); + } + + if (app) { + let appInfo = app.get_app_info(); + return { name: app.get_name(), gicon: appInfo ? appInfo.get_icon() : null }; + } + + return { name: appId, gicon: null }; +} class InhibitAppletIcon { constructor(applet, notificationStatus, inhibitStatus) { @@ -47,9 +67,8 @@ class InhibitAppletIcon { } class InhibitSwitch extends PopupMenu.PopupBaseMenuItem { - constructor(applet) { + constructor() { super(); - this._applet = applet; this.label = new St.Label({ text: _("Power management") }); @@ -71,170 +90,41 @@ class InhibitSwitch extends PopupMenu.PopupBaseMenuItem { this.addActor(this._statusBin, { expand: true, span: -1, align: St.Align.END }); this._statusBin.child = this._switch.actor; - this.actor.hide(); this.tooltip = new Tooltips.Tooltip(this._statusIcon, ""); - - this.sessionProxy = null; - this.sessionCookie = null; - this.sigAddedId = 0; - this.sigRemovedId = 0; - - GnomeSession.SessionManager(Lang.bind(this, function(proxy, error) { - if (error) - return; - - this.sessionProxy = proxy; - this.actor.show(); - this.updateStatus(); - - this.sigAddedId = this.sessionProxy.connectSignal( - "InhibitorAdded", - Lang.bind(this, this.updateStatus) - ); - - this.sigRemovedId = this.sessionProxy.connectSignal( - "InhibitorRemoved", - Lang.bind(this, this.updateStatus) - ); - })); } activate(event) { - if (this._switch.actor.mapped) { - this._switch.toggle(); - } - - this.toggled(this._switch.state); - + this.toggle(); PopupMenu.PopupBaseMenuItem.prototype.activate.call(this, event, true); } - updateStatus(o) { - let current_state = this.sessionProxy.InhibitedActions; + get state() { + return this._switch.state; + } - if (current_state & INHIBIT_IDLE_FLAG || - current_state & INHIBIT_SLEEP_FLAG) { - this._applet.icon.toggleInhibitStatus(true); - this._applet.set_applet_tooltip(_("Power management: Inhibited")); - } else { - this._applet.icon.toggleInhibitStatus(false); - this._applet.set_applet_tooltip(_("Power management: Active")); - } + toggle() { + this._switch.toggle(); + this.emit('toggled', this._switch.state); + } - if (current_state >= INHIBIT_SLEEP_FLAG && !this.sessionCookie) { + updateState(active, showWarning) { + this._switch.setToggleState(active); + + if (showWarning) { this.tooltip.set_text(_("Power management is already inhibited by another program")); - this._applet.set_applet_tooltip(_("Power management: inhibited by another program")); this._statusIcon.set_opacity(255); - this._applet.inhibitors.updateInhibitors(this.sessionProxy); } else { this.tooltip.set_text(""); this._statusIcon.set_opacity(0); - this._applet.inhibitors.resetInhibitors(); - } - } - - toggled(active) { - if (!active && !this.sessionCookie) { - this.sessionProxy.InhibitRemote("inhibit@cinnamon.org", - 0, - "prevent idle functions like screen blanking and dimming", - INHIBIT_IDLE_FLAG, - Lang.bind(this, function(cookie) { - this.sessionCookie = cookie; - this.updateStatus(); - })); - } else if (active && this.sessionCookie) { - this.sessionProxy.UninhibitRemote(this.sessionCookie, Lang.bind(this, this.updateStatus)); - this.sessionCookie = null; - } - } - - kill() { - if (!this.sessionProxy) - return; - - if (this.sessionCookie) { - this.sessionProxy.UninhibitRemote(this.sessionCookie); - this.sessionCookie = null; - } - - if (this.sigAddedId) { - this.sessionProxy.disconnectSignal(this.sigAddedId); - } - - if (this.sigRemovedId) { - this.sessionProxy.disconnectSignal(this.sigRemovedId); - } - } -} - -class InhibitingAppMenuItem extends PopupMenu.PopupIconMenuItem { - constructor(appId) { - super( - appId, - "xsi-dialog-information-symbolic", - St.IconType.SYMBOLIC, - { activate: false, hover: false } - ); - - this.appId = appId; - this._reasonsByObjPath = {}; - this._inhibitorCount = 0; - this._tooltip = new Tooltips.Tooltip(this.actor, ""); - } - - addInhibitor(objectPath, reason) { - if (!(objectPath in this._reasonsByObjPath)) { - this._reasonsByObjPath[objectPath] = reason; - this._inhibitorCount++; - this._updateTooltip(); - } - } - - updateInhibitor(objectPath, reason) { - if (objectPath in this._reasonsByObjPath) { - this._reasonsByObjPath[objectPath] = reason; - this._updateTooltip(); } } - - removeInhibitor(objectPath) { - if (objectPath in this._reasonsByObjPath) { - delete this._reasonsByObjPath[objectPath]; - this._inhibitorCount--; - this._updateTooltip(); - } - } - - hasInhibitor() { - return !!this._inhibitorCount; - } - - _updateTooltip() { - let reasons = Object.values(this._reasonsByObjPath) - .map(r => r && r.trim()) // Remove extraneous whitespace. - .filter(Boolean); // Discard null/empty reasons. - - reasons = Array.from(new Set(reasons)); // Keep only unique reasons. - - this._tooltip.set_text(reasons.join("\n")); - } } class InhibitorMenuSection extends PopupMenu.PopupMenuSection { constructor() { super(); - // Menu items indexed by app ID e.g. "org.gnome.Rhythmbox3". - // Each menu item is associated with exactly one app ID and vice versa. - this._itemsByAppId = {}; - - // Menu items indexed by object path e.g. "/org/gnome/SessionManager/Inhibitor42". - // Multiple paths may point to the same item if an app creates multiple inhibitors. - this._itemsByObjPath = {}; - - this._itemCount = 0; - this._updateId = 0; // light-weight way to abort an in-progress update (by incrementing) + this._items = []; this._createHeading(); @@ -243,139 +133,42 @@ class InhibitorMenuSection extends PopupMenu.PopupMenuSection { _createHeading() { let headingText = _("Apps inhibiting power management:"); - let heading = new PopupMenu.PopupMenuItem(headingText, { reactive: false }); - this.addMenuItem(heading); + this._heading = new PopupMenu.PopupMenuItem(headingText, { reactive: false }); + this.addMenuItem(this._heading); } - resetInhibitors() { - // Abort any in-progress update or else it may continue to add menu items - // even after we've cleared them. - this._updateId++; - - if (this._itemCount) { - this._itemsByAppId = {}; - this._itemsByObjPath = {}; - this._itemCount = 0; + update(inhibitors) { + for (let item of this._items) + item.destroy(); - // Clear all, but make sure we still have a heading for next time we're shown. - this.removeAll(); - this._createHeading(); + this._items = []; + if (!inhibitors || inhibitors.length === 0) { this.actor.hide(); + return; } - } - - updateInhibitors(sessionProxy) { - // Grab a new ID for this update while at the same time aborting any other in-progress - // update. We don't want to end up with duplicate menu items! - let updateId = ++this._updateId; - - sessionProxy.GetInhibitorsRemote(Lang.bind(this, function(objectPaths) { - if (updateId != this._updateId) { - return; - } - - objectPaths = String(objectPaths).split(','); // Given object, convert to string[]. - - // Add menu items for any paths we haven't seen before, and keep track of the paths - // iterated so we can figure out which of our existing paths are no longer present. - - let pathsPresent = {}; - for (let objectPath of objectPaths) { - if (objectPath) { - pathsPresent[objectPath] = true; - - if (!(objectPath in this._itemsByObjPath)) { - this._addInhibitor(objectPath, updateId); - } - } - } - - // Remove menu items for those paths no longer present. - for (let objectPath in this._itemsByObjPath) { - if (!(objectPath in pathsPresent)) { - this._removeInhibitor(objectPath); - } - } - })); - } + for (let { appId, reasons } of inhibitors) { + let { name, gicon } = _resolveAppInfo(appId); + let label = name; + if (reasons.length > 0) + label += `: ${reasons.join(", ")}`; + + let item = new PopupMenu.PopupIconMenuItem( + label, + "application-x-executable", + St.IconType.FULLCOLOR, + { activate: false, hover: false, style_class: 'popup-inhibitor-menu-item' } + ); - // Precondition: objectPath not already in _itemsByObjPath - _addInhibitor(objectPath, updateId) { - GnomeSession.Inhibitor(objectPath, Lang.bind(this, function(inhibitorProxy, error) { - if (error || updateId != this._updateId) { - return; - } - - inhibitorProxy.GetFlagsRemote(Lang.bind(this, function(flags) { - if (updateId != this._updateId) { - return; - } - - flags = parseInt(flags, 10); // Given object, convert to integer. - - // Only include those inhibiting sleep, idle, or both. - if (flags < INHIBIT_SLEEP_FLAG) { - return; - } - - inhibitorProxy.GetAppIdRemote(Lang.bind(this, function(appId) { - if (updateId != this._updateId) { - return; - } - - appId = String(appId); // Given object, convert to string. - - // Get/create the menu item for this app. - let menuItem; - if (appId in this._itemsByAppId) { - menuItem = this._itemsByAppId[appId]; - } else { - menuItem = new InhibitingAppMenuItem(appId); - this._itemsByAppId[appId] = menuItem; - this.addMenuItem(menuItem); - - // Show the menu section upon adding the first menu item. - if (!(this._itemCount++)) { - this.actor.show(); - } - } - - this._itemsByObjPath[objectPath] = menuItem; - - // Go ahead and add the inhibitor to the item now and fill in the reason later. - menuItem.addInhibitor(objectPath); - - inhibitorProxy.GetReasonRemote(Lang.bind(this, function(reason) { - if (updateId != this._updateId) { - return; - } - - reason = String(reason); // Given object, convert to string. - menuItem.updateInhibitor(objectPath, reason); - })); - })); - })); - })); - } + if (gicon) + item.setGIcon(gicon); - // Precondition: objectPath already in _itemsByObjPath - _removeInhibitor(objectPath) { - let menuItem = this._itemsByObjPath[objectPath]; - delete this._itemsByObjPath[objectPath]; - menuItem.removeInhibitor(objectPath); - - // Remove the menu item if the last inhibitor for the app has been removed. - if (!menuItem.hasInhibitor()) { - delete this._itemsByAppId[menuItem.appId]; - menuItem.destroy(); - - // Hide the menu section upon removing the last menu item. - if (!(--this._itemCount)) { - this.actor.hide(); - } + this.addMenuItem(item); + this._items.push(item); } + + this.actor.show(); } } @@ -384,12 +177,20 @@ class CinnamonInhibitApplet extends Applet.IconApplet { super(orientation, panel_height, instanceId); this.metadata = metadata; + this._controller = Inhibitor.getController(); + this._controller.register(); this.menuManager = new PopupMenu.PopupMenuManager(this); this.menu = new Applet.AppletPopupMenu(this, orientation); this.menuManager.addMenu(this.menu); - this.inhibitSwitch = new InhibitSwitch(this); + this.inhibitSwitch = new InhibitSwitch(); + this.inhibitSwitch.connect('toggled', (sw, state) => { + if (state) + this._controller.uninhibit(); + else + this._controller.inhibit(); + }); this.menu.addMenuItem(this.inhibitSwitch); this.set_applet_tooltip(_("Inhibit applet")); @@ -397,16 +198,19 @@ class CinnamonInhibitApplet extends Applet.IconApplet { this.notif_settings = new Gio.Settings({ schema_id: "org.cinnamon.desktop.notifications" }); this.notificationsSwitch = new PopupMenu.PopupSwitchMenuItem(_("Notifications"), this.notif_settings.get_boolean("display-notifications")); - this.icon = new InhibitAppletIcon(this, !this.notificationsSwitch.state, !this.inhibitSwitch.state); + this.icon = new InhibitAppletIcon(this, !this.notificationsSwitch.state, false); this.icon.setAppletIcon(); - this.notif_settings.connect('changed::display-notifications', Lang.bind(this, function() { - this.notificationsSwitch.setToggleState(this.notif_settings.get_boolean("display-notifications")); - })); - this.notificationsSwitch.connect('toggled', Lang.bind(this, function() { + this.notif_settings.connectObject('changed::display-notifications', () => { + let enabled = this.notif_settings.get_boolean("display-notifications"); + this.notificationsSwitch.setToggleState(enabled); + this.icon.notificationStatus = !enabled; + this.icon.setAppletIcon(); + }, this); + this.notificationsSwitch.connect('toggled', () => { this.notif_settings.set_boolean("display-notifications", this.notificationsSwitch.state); this.icon.toggleNotificationStatus(); - })); + }); this.menu.addMenuItem(this.notificationsSwitch); @@ -416,6 +220,50 @@ class CinnamonInhibitApplet extends Applet.IconApplet { this._setKeybinding(); this._createInhibitorMenuSection(orientation); + + this._controller.connectObject( + 'status-changed', () => this._onStatusChanged(), this + ); + + this._onStatusChanged(); + } + + _onStatusChanged() { + if (!this._controller.ready) { + this.inhibitSwitch.actor.hide(); + return; + } + + this.inhibitSwitch.actor.show(); + + let currentState = this._controller.inhibitedActions; + let isInhibited = this._controller.isInhibited; + + if (currentState & Inhibitor.INHIBIT_IDLE_FLAG || + currentState & Inhibitor.INHIBIT_SLEEP_FLAG) { + this.icon.toggleInhibitStatus(true); + this.set_applet_tooltip(_("Power management: Inhibited")); + } else { + this.icon.toggleInhibitStatus(false); + this.set_applet_tooltip(_("Power management: Active")); + } + + // Only show external inhibitors if we're not doing the inhibiting ourselves. + // The list is mainly to explain why power management might be inhibited even + // though the user isn't actively disabling it themselves. + + let hasExternal = !isInhibited && this._controller.hasExternalInhibitors; + + this.inhibitSwitch.updateState(!isInhibited, hasExternal); + + if (hasExternal) { + this.set_applet_tooltip(_("Power management: inhibited by another program")); + this._inhibitorMenuSection.update(this._controller.externalInhibitors); + this._inhibitorSeparator.actor.show(); + } else { + this._inhibitorMenuSection.update(null); + this._inhibitorSeparator.actor.hide(); + } } _createInhibitorMenuSection(orientation) { @@ -436,10 +284,10 @@ class CinnamonInhibitApplet extends Applet.IconApplet { _setKeybinding() { Main.keybindingManager.addXletHotKey(this, "inhibit-power", this.keyPower, - Lang.bind(this, this.toggle_inhibit_power)); + () => this._toggleInhibitPower()); Main.keybindingManager.addXletHotKey(this, "inhibit-notifications", this.keyNotifications, - Lang.bind(this, this.toggle_inhibit_notifications)); + () => this._toggleInhibitNotifications()); } on_applet_clicked(event) { @@ -457,7 +305,11 @@ class CinnamonInhibitApplet extends Applet.IconApplet { on_applet_removed_from_panel() { Main.keybindingManager.removeXletHotKey(this, "inhibit-power"); Main.keybindingManager.removeXletHotKey(this, "inhibit-notifications"); - this.inhibitSwitch.kill(); + + this.notif_settings.disconnectObject(this); + this._controller.disconnectObject(this); + this._controller.unregister(); + this.settings.finalize(); } on_orientation_changed(orientation) { @@ -466,41 +318,37 @@ class CinnamonInhibitApplet extends Applet.IconApplet { this._createInhibitorMenuSection(orientation); - // Will put the inhibitor menu section into the correct state. - this.inhibitSwitch.updateStatus(); + this._onStatusChanged(); } - toggle_inhibit_power() { - this.inhibitSwitch._switch.toggle(); - this.inhibitSwitch.toggled(this.inhibitSwitch._switch.state); + _toggleInhibitPower() { + this.inhibitSwitch.toggle(); - let _symbol = this.inhibitSwitch._switch.state ? - "inhibit-symbolic" : - "inhibit-active-symbolic"; + let willBeInhibited = !this.inhibitSwitch.state; - let _text = this.inhibitSwitch._switch.state ? - _("Power management: Active") : - _("Power management: Inhibited"); + let symbol = willBeInhibited ? + "inhibit-active-symbolic" : + "inhibit-symbolic"; - Main.osdWindowManager.show(-1, Gio.ThemedIcon.new(_symbol), _text); + let text = willBeInhibited ? + _("Power management: Inhibited") : + _("Power management: Active"); + + Main.osdWindowManager.show(-1, Gio.ThemedIcon.new(symbol), text); } - toggle_inhibit_notifications() { + _toggleInhibitNotifications() { this.notificationsSwitch.toggle(); - let _symbol = this.notificationsSwitch._switch.state ? + let symbol = this.notificationsSwitch._switch.state ? "inhibit-notification-symbolic" : "inhibit-notification-active-symbolic"; - let _text = this.notificationsSwitch._switch.state ? + let text = this.notificationsSwitch._switch.state ? _("Notifications: Active") : _("Notifications: Inhibited"); - Main.osdWindowManager.show(-1, Gio.ThemedIcon.new(_symbol), _text); - } - - get inhibitors() { - return this._inhibitorMenuSection; + Main.osdWindowManager.show(-1, Gio.ThemedIcon.new(symbol), text); } } diff --git a/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/metadata.json b/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/metadata.json index 33ecf2bb88..c1c1dda185 100644 --- a/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/metadata.json +++ b/files/usr/share/cinnamon/applets/inhibit@cinnamon.org/metadata.json @@ -2,5 +2,6 @@ "uuid": "inhibit@cinnamon.org", "name": "Inhibit Applet", "icon": "preferences-desktop-screensaver", - "description": "This applet is used to disable notifications and power management (including the screensaver and the session from going idle)" + "description": "This applet is used to disable notifications and power management (including the screensaver and the session from going idle)", + "max-instances": -1 } diff --git a/js/misc/inhibitor.js b/js/misc/inhibitor.js new file mode 100644 index 0000000000..e2a9dee581 --- /dev/null +++ b/js/misc/inhibitor.js @@ -0,0 +1,318 @@ +const GObject = imports.gi.GObject; +const GnomeSession = imports.misc.gnomeSession; + +var INHIBIT_IDLE_FLAG = 8; +var INHIBIT_SLEEP_FLAG = 4; +const OWN_APP_ID = "inhibit@cinnamon.org"; + +// This is not a general-purpose inhibitor api, it is to allow the +// user to disable power management via something like the inhibit +// applet, and also gathers information on apps that have also +// created inhibitors. + +const InhibitController = GObject.registerClass({ + Signals: { + 'status-changed': {}, + }, +}, class InhibitController extends GObject.Object { + _init() { + super._init(); + + this._sessionProxy = null; + this._cookie = null; + this._inhibited = false; + this._syncing = false; + this._sigAddedId = 0; + this._sigRemovedId = 0; + this._updating = false; + this._refreshPending = false; + this._consumers = 0; + + // External inhibitors grouped by appId. + // Key: appId string, Value: { reasons: Map } + this._externalInhibitors = {}; + + GnomeSession.SessionManager((proxy, error) => { + if (error) { + global.logWarning(`InhibitController: Failed to connect to SessionManager: ${error}`); + return; + } + + this._sessionProxy = proxy; + + this._sigAddedId = this._sessionProxy.connectSignal( + "InhibitorAdded", () => this._refresh() + ); + + this._sigRemovedId = this._sessionProxy.connectSignal( + "InhibitorRemoved", () => this._refresh() + ); + + this._syncState(); + this._refresh(); + }); + } + + get ready() { + return this._sessionProxy !== null; + } + + get isInhibited() { + return this._inhibited; + } + + get inhibitedActions() { + if (this._sessionProxy === null) + return 0; + + return this._sessionProxy.InhibitedActions; + } + + get externalInhibitors() { + let result = []; + + for (let appId in this._externalInhibitors) { + let entry = this._externalInhibitors[appId]; + let reasons = Array.from(entry.reasons.values()) + .map(r => r && r.trim()) + .filter(Boolean); + reasons = Array.from(new Set(reasons)); + + result.push({ appId, reasons }); + } + + return result; + } + + get hasExternalInhibitors() { + return Object.keys(this._externalInhibitors).length > 0; + } + + register() { + this._consumers++; + } + + unregister() { + if (this._consumers <= 0) { + global.logWarning("InhibitController: unregister() called with no consumers"); + return; + } + + this._consumers--; + + if (this._consumers === 0) { + this._inhibited = false; + this._syncState(); + } + } + + inhibit() { + if (this._inhibited) + return; + + this._inhibited = true; + this._syncState(); + } + + uninhibit() { + if (!this._inhibited) + return; + + this._inhibited = false; + this._syncState(); + } + + _syncState() { + if (this._syncing || this._sessionProxy === null) + return; + + if (this._inhibited && this._cookie === null) { + this._syncing = true; + + this._sessionProxy.InhibitRemote( + OWN_APP_ID, + 0, + "prevent idle functions like screen blanking and dimming", + INHIBIT_IDLE_FLAG, + (result, error) => { + this._syncing = false; + + if (error) { + global.logWarning(`InhibitController: InhibitRemote failed: ${error}`); + this._inhibited = false; + } else { + this._cookie = result[0]; + } + + this.emit('status-changed'); + this._syncState(); + } + ); + } else if (!this._inhibited && this._cookie !== null) { + this._syncing = true; + let cookie = this._cookie; + + this._sessionProxy.UninhibitRemote(cookie, (result, error) => { + this._syncing = false; + + if (error) + global.logWarning(`InhibitController: UninhibitRemote failed: ${error}`); + // The only likely reason for failure would be the inhibitor is gone already, + // so clear the cookie regardless of error and assume we're no longer inhibited. + this._cookie = null; + + this.emit('status-changed'); + this._syncState(); + }); + } + } + + _refresh() { + if (this._sessionProxy === null) + return; + + if (this._updating) { + this._refreshPending = true; + return; + } + + this._updating = true; + this._refreshPending = false; + + this._sessionProxy.GetInhibitorsRemote((result, error) => { + if (error) { + global.logWarning(`InhibitController: GetInhibitorsRemote failed: ${error}`); + this._refreshDone(); + return; + } + + let objectPaths = result[0]; + + this._externalInhibitors = {}; + + let pending = 0; + + for (let objectPath of objectPaths) { + if (!objectPath) + continue; + + pending++; + + try { + this._fetchInhibitor(objectPath, () => { + pending--; + if (pending === 0) + this._refreshDone(); + }); + } catch (e) { + global.logWarning(`InhibitController: Exception fetching inhibitor ${objectPath}: ${e}`); + pending--; + if (pending === 0) + this._refreshDone(); + } + } + + if (pending === 0) + this._refreshDone(); + }); + } + + _refreshDone() { + this._updating = false; + this.emit('status-changed'); + + if (this._refreshPending) + this._refresh(); + } + + vfunc_dispose() { + this._inhibited = false; + + if (this._sessionProxy) { + if (this._cookie !== null) { + this._sessionProxy.UninhibitRemote(this._cookie); + this._cookie = null; + } + + if (this._sigAddedId) { + this._sessionProxy.disconnectSignal(this._sigAddedId); + this._sigAddedId = 0; + } + + if (this._sigRemovedId) { + this._sessionProxy.disconnectSignal(this._sigRemovedId); + this._sigRemovedId = 0; + } + + this._sessionProxy = null; + } + + this._externalInhibitors = {}; + this._updating = false; + this._refreshPending = false; + + super.vfunc_dispose(); + } + + _fetchInhibitor(objectPath, done) { + GnomeSession.Inhibitor(objectPath, (inhibitorProxy, error) => { + if (error) { + global.logWarning(`InhibitController: Failed to create Inhibitor proxy for ${objectPath}: ${error}`); + done(); + return; + } + + inhibitorProxy.GetFlagsRemote((result, error) => { + if (error) { + global.logWarning(`InhibitController: GetFlags failed for ${objectPath}: ${error}`); + done(); + return; + } + + let flags = result[0]; + + if (!(flags & (INHIBIT_SLEEP_FLAG | INHIBIT_IDLE_FLAG))) { + done(); + return; + } + + inhibitorProxy.GetAppIdRemote((result, error) => { + if (error) { + global.logWarning(`InhibitController: GetAppId failed for ${objectPath}: ${error}`); + done(); + return; + } + + let appId = result[0]; + + if (appId === OWN_APP_ID) { + done(); + return; + } + + if (!(appId in this._externalInhibitors)) + this._externalInhibitors[appId] = { reasons: new Map() }; + + inhibitorProxy.GetReasonRemote((result, error) => { + if (!error) { + let reason = result[0]; + this._externalInhibitors[appId].reasons.set(objectPath, reason); + } else { + global.logWarning(`InhibitController: GetReason failed for ${objectPath}: ${error}`); + } + done(); + }); + }); + }); + }); + } +}); + +let _controller = null; + +function getController() { + if (_controller === null) + _controller = new InhibitController(); + + return _controller; +} diff --git a/js/ui/popupMenu.js b/js/ui/popupMenu.js index 85a03f8cac..6482a04629 100644 --- a/js/ui/popupMenu.js +++ b/js/ui/popupMenu.js @@ -1060,6 +1060,16 @@ var PopupIconMenuItem = class PopupIconMenuItem extends PopupBaseMenuItem { this._icon.set_icon_name(iconName); this._icon.set_icon_type(St.IconType.FULLCOLOR); } + + /** + * setGIcon: + * @gicon (Gio.Icon): the icon to set + * + * Changes the icon to the #Gio.Icon @gicon. + */ + setGIcon (gicon) { + this._icon.set_gicon(gicon); + } } // Deprecated. Do not use