diff --git a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss index 664f24ca8..82d88b4b3 100644 --- a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss +++ b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss @@ -83,3 +83,7 @@ .quick-toggle-menu-container { padding: 2 * $base_padding $base_padding 0; } + +.quick-settings-system-item { + & > StBoxLayout { spacing: 2 * $base_padding; } +} diff --git a/js/ui/panel.js b/js/ui/panel.js index e1f4f65a6..f43955781 100644 --- a/js/ui/panel.js +++ b/js/ui/panel.js @@ -378,18 +378,11 @@ class AggregateMenu extends PanelMenu.Button { else this._network = null; - this._system = new imports.ui.status.system.Indicator(); - if (this._network) this._indicators.add_child(this._network); if (this._network) this.menu.addMenuItem(this._network.menu); - - this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this.menu.addMenuItem(this._system.menu); - - menuLayout.addSizeChild(this._system.menu.actor); } }); @@ -410,6 +403,7 @@ class QuickSettings extends PanelMenu.Button { else this._bluetooth = null; + this._system = new imports.ui.status.system.Indicator(); this._volume = new imports.ui.status.volume.Indicator(); this._brightness = new imports.ui.status.brightness.Indicator(); this._remoteAccess = new imports.ui.status.remoteAccess.RemoteAccessApplet(); @@ -437,7 +431,9 @@ class QuickSettings extends PanelMenu.Button { this._indicators.add_child(this._volume); this._indicators.add_child(this._unsafeMode); this._indicators.add_child(this._power); + this._indicators.add_child(this._system); + this._addItems(this._system.quickSettingsItems, N_QUICK_SETTINGS_COLUMNS); this._addItems(this._volume.quickSettingsItems, N_QUICK_SETTINGS_COLUMNS); this._addItems(this._brightness.quickSettingsItems, N_QUICK_SETTINGS_COLUMNS); diff --git a/js/ui/status/system.js b/js/ui/status/system.js index b218e3e54..ed9e3cf7e 100644 --- a/js/ui/status/system.js +++ b/js/ui/status/system.js @@ -1,157 +1,181 @@ // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- /* exported Indicator */ -const { GObject, Shell, St } = imports.gi; +const {Clutter, GObject, Shell, St} = imports.gi; -const BoxPointer = imports.ui.boxpointer; const SystemActions = imports.misc.systemActions; const Main = imports.ui.main; -const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; +const {QuickSettingsItem, SystemIndicator} = imports.ui.quickSettings; -var Indicator = GObject.registerClass( -class Indicator extends PanelMenu.SystemIndicator { +const SettingsItem = GObject.registerClass( +class SettingsItem extends QuickSettingsItem { _init() { - super._init(); + super._init({ + style_class: 'icon-button', + can_focus: true, + child: new St.Icon(), + }); + + this._settingsApp = Shell.AppSystem.get_default().lookup_app( + 'org.gnome.Settings.desktop'); + + if (!this._settingsApp) + console.warn('Missing required core component Settings, expect trouble…'); + + this.child.gicon = this._settingsApp?.get_icon() ?? null; + this.accessible_name = this._settingsApp?.get_name() ?? null; + + this.connect('clicked', () => { + Main.overview.hide(); + Main.panel.closeQuickSettings(); + this._settingsApp.activate(); + }); + + Main.sessionMode.connectObject('updated', () => this._sync(), this); + this._sync(); + } + + _sync() { + this.visible = + this._settingsApp != null && Main.sessionMode.allowSettings; + } +}); + +const ShutdownItem = GObject.registerClass( +class ShutdownItem extends QuickSettingsItem { + _init() { + super._init({ + style_class: 'icon-button', + hasMenu: true, + canFocus: true, + child: new St.Icon({ + icon_name: 'system-shutdown-symbolic', + }), + accessible_name: _('Power Off Menu'), + }); this._systemActions = new SystemActions.getDefault(); + this._items = []; - this._createSubMenu(); + this.menu.setHeader('system-shutdown-symbolic', 'Power Off'); + + this._addSystemAction(_('Suspend'), 'can-suspend', () => { + this._systemActions.activateSuspend(); + Main.panel.closeQuickSettings(); + }); + + this._addSystemAction(_('Restart…'), 'can-restart', () => { + this._systemActions.activateRestart(); + Main.panel.closeQuickSettings(); + }); + + this._addSystemAction(_('Power Off…'), 'can-power-off', () => { + this._systemActions.activatePowerOff(); + Main.panel.closeQuickSettings(); + }); + + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + + this._addSystemAction(_('Log Out'), 'can-logout', () => { + this._systemActions.activateLogout(); + Main.panel.closeQuickSettings(); + }); + + this._addSystemAction(_('Switch User…'), 'can-switch-user', () => { + this._systemActions.activateSwitchUser(); + Main.panel.closeQuickSettings(); + }); - this._loginScreenItem.connect('notify::visible', - () => this._updateSessionSubMenu()); - this._logoutItem.connect('notify::visible', - () => this._updateSessionSubMenu()); - this._suspendItem.connect('notify::visible', - () => this._updateSessionSubMenu()); - this._powerOffItem.connect('notify::visible', - () => this._updateSessionSubMenu()); - this._restartItem.connect('notify::visible', - () => this._updateSessionSubMenu()); // Whether shutdown is available or not depends on both lockdown // settings (disable-log-out) and Polkit policy - the latter doesn't - // notify, so we update the menu item each time the menu opens or + // notify, so we update the item each time we become visible or // the lockdown setting changes, which should be close enough. - this.menu.connect('open-state-changed', (menu, open) => { - if (!open) + this.connect('notify::mapped', () => { + if (!this.mapped) return; this._systemActions.forceUpdate(); }); - this._updateSessionSubMenu(); - Main.sessionMode.connect('updated', this._sessionUpdated.bind(this)); - this._sessionUpdated(); + this.connect('clicked', () => this.menu.open()); + this.connect('popup-menu', () => this.menu.open()); } - _sessionUpdated() { - this._settingsItem.visible = Main.sessionMode.allowSettings; + _addSystemAction(label, propName, callback) { + const item = this.menu.addAction(label, callback); + this._items.push(item); + + this._systemActions.bind_property(propName, + item, 'visible', + GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE); + item.connect('notify::visible', () => this._sync()); } - _updateSessionSubMenu() { - this._sessionSubMenu.visible = - this._loginScreenItem.visible || - this._logoutItem.visible || - this._suspendItem.visible || - this._restartItem.visible || - this._powerOffItem.visible; - } - - _createSubMenu() { - let bindFlags = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE; - let item; - - let app = this._settingsApp = Shell.AppSystem.get_default().lookup_app( - 'org.gnome.Settings.desktop'); - if (app) { - const [icon] = app.app_info.get_icon().names; - const name = app.app_info.get_name(); - item = new PopupMenu.PopupImageMenuItem(name, icon); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - Main.overview.hide(); - this._settingsApp.activate(); - }); - this.menu.addMenuItem(item); - this._settingsItem = item; - } else { - log('Missing required core component Settings, expect trouble…'); - this._settingsItem = new St.Widget(); - } - - item = new PopupMenu.PopupImageMenuItem(_('Lock'), 'changes-prevent-symbolic'); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - this._systemActions.activateLockScreen(); - }); - this.menu.addMenuItem(item); - this._lockScreenItem = item; - this._systemActions.bind_property('can-lock-screen', - this._lockScreenItem, 'visible', - bindFlags); - - this._sessionSubMenu = new PopupMenu.PopupSubMenuMenuItem( - _('Power Off / Log Out'), true); - this._sessionSubMenu.icon.icon_name = 'system-shutdown-symbolic'; - - item = new PopupMenu.PopupMenuItem(_('Suspend')); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - this._systemActions.activateSuspend(); - }); - this._sessionSubMenu.menu.addMenuItem(item); - this._suspendItem = item; - this._systemActions.bind_property('can-suspend', - this._suspendItem, 'visible', - bindFlags); - - item = new PopupMenu.PopupMenuItem(_('Restart…')); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - this._systemActions.activateRestart(); - }); - this._sessionSubMenu.menu.addMenuItem(item); - this._restartItem = item; - this._systemActions.bind_property('can-restart', - this._restartItem, 'visible', - bindFlags); - - item = new PopupMenu.PopupMenuItem(_('Power Off…')); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - this._systemActions.activatePowerOff(); - }); - this._sessionSubMenu.menu.addMenuItem(item); - this._powerOffItem = item; - this._systemActions.bind_property('can-power-off', - this._powerOffItem, 'visible', - bindFlags); - - this._sessionSubMenu.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - - item = new PopupMenu.PopupMenuItem(_('Log Out')); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - this._systemActions.activateLogout(); - }); - this._sessionSubMenu.menu.addMenuItem(item); - this._logoutItem = item; - this._systemActions.bind_property('can-logout', - this._logoutItem, 'visible', - bindFlags); - - item = new PopupMenu.PopupMenuItem(_('Switch User…')); - item.connect('activate', () => { - this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); - this._systemActions.activateSwitchUser(); - }); - this._sessionSubMenu.menu.addMenuItem(item); - this._loginScreenItem = item; - this._systemActions.bind_property('can-switch-user', - this._loginScreenItem, 'visible', - bindFlags); - - this.menu.addMenuItem(this._sessionSubMenu); + _sync() { + this.visible = this._items.some(i => i.visible); + } +}); + +const LockItem = GObject.registerClass( +class LockItem extends QuickSettingsItem { + _init() { + this._systemActions = new SystemActions.getDefault(); + + super._init({ + style_class: 'icon-button', + can_focus: true, + child: new St.Icon({ + icon_name: 'system-lock-screen-symbolic', + }), + accessible_name: _('Lock Screen'), + }); + + this._systemActions.bind_property('can-lock-screen', + this, 'visible', + GObject.BindingFlags.DEFAULT | + GObject.BindingFlags.SYNC_CREATE); + + this.connect('clicked', + () => this._systemActions.activateLockScreen()); + } +}); + + +const SystemItem = GObject.registerClass( +class SystemItem extends QuickSettingsItem { + _init() { + super._init({ + style_class: 'quick-settings-system-item', + reactive: false, + }); + + this.child = new St.BoxLayout(); + + // spacer + this.child.add_child(new Clutter.Actor({x_expand: true})); + + const settingsItem = new SettingsItem(); + this.child.add_child(settingsItem); + + const lockItem = new LockItem(); + this.child.add_child(lockItem); + + const shutdownItem = new ShutdownItem(); + this.child.add_child(shutdownItem); + + this.menu = shutdownItem.menu; + } +}); + +var Indicator = GObject.registerClass( +class Indicator extends SystemIndicator { + _init() { + super._init(); + + const item = new SystemItem(); + + this.quickSettingsItems.push(item); } });