summaryrefslogtreecommitdiffstats
path: root/js/ui/status/system.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/ui/status/system.js')
-rw-r--r--js/ui/status/system.js348
1 files changed, 348 insertions, 0 deletions
diff --git a/js/ui/status/system.js b/js/ui/status/system.js
new file mode 100644
index 0000000..5a2d92c
--- /dev/null
+++ b/js/ui/status/system.js
@@ -0,0 +1,348 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported Indicator */
+
+const {Atk, Clutter, Gio, GLib, GObject, Meta, Shell, St, UPowerGlib: UPower} = imports.gi;
+
+const SystemActions = imports.misc.systemActions;
+const Main = imports.ui.main;
+const PopupMenu = imports.ui.popupMenu;
+const {PopupAnimation} = imports.ui.boxpointer;
+
+const {QuickSettingsItem, QuickToggle, SystemIndicator} = imports.ui.quickSettings;
+const {loadInterfaceXML} = imports.misc.fileUtils;
+
+const BUS_NAME = 'org.freedesktop.UPower';
+const OBJECT_PATH = '/org/freedesktop/UPower/devices/DisplayDevice';
+
+const DisplayDeviceInterface = loadInterfaceXML('org.freedesktop.UPower.Device');
+const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(DisplayDeviceInterface);
+
+const SHOW_BATTERY_PERCENTAGE = 'show-battery-percentage';
+
+const PowerToggle = GObject.registerClass({
+ Properties: {
+ 'fallback-icon-name': GObject.ParamSpec.string('fallback-icon-name', '', '',
+ GObject.ParamFlags.READWRITE,
+ ''),
+ },
+}, class PowerToggle extends QuickToggle {
+ _init() {
+ super._init({
+ accessible_role: Atk.Role.PUSH_BUTTON,
+ });
+
+ this.add_style_class_name('power-item');
+
+ this._proxy = new PowerManagerProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
+ (proxy, error) => {
+ if (error)
+ console.error(error.message);
+ else
+ this._proxy.connect('g-properties-changed', () => this._sync());
+ this._sync();
+ });
+
+ this.bind_property('fallback-icon-name',
+ this._icon, 'fallback-icon-name',
+ GObject.BindingFlags.SYNC_CREATE);
+
+ this.connect('clicked', () => {
+ const app = Shell.AppSystem.get_default().lookup_app('gnome-power-panel.desktop');
+ Main.overview.hide();
+ Main.panel.closeQuickSettings();
+ app.activate();
+ });
+
+ Main.sessionMode.connect('updated', () => this._sessionUpdated());
+ this._sessionUpdated();
+ this._sync();
+ }
+
+ _sessionUpdated() {
+ this.reactive = Main.sessionMode.allowSettings;
+ }
+
+ _sync() {
+ // Do we have batteries or a UPS?
+ this.visible = this._proxy.IsPresent;
+ if (!this.visible)
+ return;
+
+ // The icons
+ let chargingState = this._proxy.State === UPower.DeviceState.CHARGING
+ ? '-charging' : '';
+ let fillLevel = 10 * Math.floor(this._proxy.Percentage / 10);
+ const charged =
+ this._proxy.State === UPower.DeviceState.FULLY_CHARGED ||
+ (this._proxy.State === UPower.DeviceState.CHARGING && fillLevel === 100);
+ const icon = charged
+ ? 'battery-level-100-charged-symbolic'
+ : `battery-level-${fillLevel}${chargingState}-symbolic`;
+
+ // Make sure we fall back to fallback-icon-name and not GThemedIcon's
+ // default fallbacks
+ const gicon = new Gio.ThemedIcon({
+ name: icon,
+ use_default_fallbacks: false,
+ });
+
+ this.set({
+ label: _('%d\u2009%%').format(this._proxy.Percentage),
+ fallback_icon_name: this._proxy.IconName,
+ gicon,
+ });
+ }
+});
+
+const ScreenshotItem = GObject.registerClass(
+class ScreenshotItem extends QuickSettingsItem {
+ _init() {
+ super._init({
+ style_class: 'icon-button',
+ can_focus: true,
+ icon_name: 'camera-photo-symbolic',
+ visible: !Main.sessionMode.isGreeter,
+ accessible_name: _('Take Screenshot'),
+ });
+
+ this.connect('clicked', () => {
+ const topMenu = Main.panel.statusArea.quickSettings.menu;
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
+ Main.screenshotUI.open().catch(logError);
+ return GLib.SOURCE_REMOVE;
+ });
+ topMenu.close(PopupAnimation.NONE);
+ });
+ }
+});
+
+const SettingsItem = GObject.registerClass(
+class SettingsItem extends QuickSettingsItem {
+ _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,
+ icon_name: 'system-shutdown-symbolic',
+ accessible_name: _('Power Off Menu'),
+ });
+
+ this._systemActions = new SystemActions.getDefault();
+ this._items = [];
+
+ this.menu.setHeader('system-shutdown-symbolic', C_('title', '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();
+ });
+
+ // 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 item each time we become visible or
+ // the lockdown setting changes, which should be close enough.
+ this.connect('notify::mapped', () => {
+ if (!this.mapped)
+ return;
+
+ this._systemActions.forceUpdate();
+ });
+
+ this.connect('clicked', () => this.menu.open());
+ this.connect('popup-menu', () => this.menu.open());
+ }
+
+ _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());
+ }
+
+ _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,
+ icon_name: 'system-lock-screen-symbolic',
+ accessible_name: C_('action', '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();
+
+ this._powerToggle = new PowerToggle();
+ this.child.add_child(this._powerToggle);
+
+ this._laptopSpacer = new Clutter.Actor({x_expand: true});
+ this._powerToggle.bind_property('visible',
+ this._laptopSpacer, 'visible',
+ GObject.BindingFlags.SYNC_CREATE);
+ this.child.add_child(this._laptopSpacer);
+
+ const screenshotItem = new ScreenshotItem();
+ this.child.add_child(screenshotItem);
+
+ const settingsItem = new SettingsItem();
+ this.child.add_child(settingsItem);
+
+ this._desktopSpacer = new Clutter.Actor({x_expand: true});
+ this._powerToggle.bind_property('visible',
+ this._desktopSpacer, 'visible',
+ GObject.BindingFlags.INVERT_BOOLEAN |
+ GObject.BindingFlags.SYNC_CREATE);
+ this.child.add_child(this._desktopSpacer);
+
+ const lockItem = new LockItem();
+ this.child.add_child(lockItem);
+
+ const shutdownItem = new ShutdownItem();
+ this.child.add_child(shutdownItem);
+
+ this.menu = shutdownItem.menu;
+ }
+
+ get powerToggle() {
+ return this._powerToggle;
+ }
+});
+
+var Indicator = GObject.registerClass(
+class Indicator extends SystemIndicator {
+ _init() {
+ super._init();
+
+ this._desktopSettings = new Gio.Settings({
+ schema_id: 'org.gnome.desktop.interface',
+ });
+ this._desktopSettings.connectObject(
+ `changed::${SHOW_BATTERY_PERCENTAGE}`, () => this._sync(), this);
+
+ this._indicator = this._addIndicator();
+ this._percentageLabel = new St.Label({
+ y_expand: true,
+ y_align: Clutter.ActorAlign.CENTER,
+ });
+ this.add_child(this._percentageLabel);
+ this.add_style_class_name('power-status');
+
+ this._systemItem = new SystemItem();
+
+ const {powerToggle} = this._systemItem;
+
+ powerToggle.bind_property('label',
+ this._percentageLabel, 'text',
+ GObject.BindingFlags.SYNC_CREATE);
+
+ powerToggle.connectObject(
+ 'notify::visible', () => this._sync(),
+ 'notify::gicon', () => this._sync(),
+ 'notify::fallback-icon-name', () => this._sync(),
+ this);
+
+ this.quickSettingsItems.push(this._systemItem);
+
+ this._sync();
+ }
+
+ _sync() {
+ const {powerToggle} = this._systemItem;
+ if (powerToggle.visible) {
+ this._indicator.set({
+ gicon: powerToggle.gicon,
+ fallback_icon_name: powerToggle.fallback_icon_name,
+ });
+ this._percentageLabel.visible =
+ this._desktopSettings.get_boolean(SHOW_BATTERY_PERCENTAGE);
+ } else {
+ // If there's no battery, then we use the power icon.
+ this._indicator.icon_name = 'system-shutdown-symbolic';
+ this._percentageLabel.hide();
+ }
+ }
+});