// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const AccountsService = imports.gi.AccountsService; const Clutter = imports.gi.Clutter; const Gdm = imports.gi.Gdm; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; const BoxPointer = imports.ui.boxpointer; const GnomeSession = imports.misc.gnomeSession; const LoginManager = imports.misc.loginManager; const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown'; const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen'; const DISABLE_USER_SWITCH_KEY = 'disable-user-switching'; const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen'; const DISABLE_LOG_OUT_KEY = 'disable-log-out'; const DISABLE_RESTART_KEY = 'disable-restart-buttons'; const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out'; const SENSOR_BUS_NAME = 'net.hadess.SensorProxy'; const SENSOR_OBJECT_PATH = '/net/hadess/SensorProxy'; const SensorProxyInterface = '<node> \ <interface name="net.hadess.SensorProxy"> \ <property name="HasAccelerometer" type="b" access="read"/> \ </interface> \ </node>'; const SensorProxy = Gio.DBusProxy.makeProxyWrapper(SensorProxyInterface); const AltSwitcher = new Lang.Class({ Name: 'AltSwitcher', _init: function(standard, alternate) { this._standard = standard; this._standard.connect('notify::visible', Lang.bind(this, this._sync)); if (this._standard instanceof St.Button) this._standard.connect('clicked', () => { this._clickAction.release(); }); this._alternate = alternate; this._alternate.connect('notify::visible', Lang.bind(this, this._sync)); if (this._alternate instanceof St.Button) this._alternate.connect('clicked', () => { this._clickAction.release(); }); this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent)); this._flipped = false; this._clickAction = new Clutter.ClickAction(); this._clickAction.connect('long-press', Lang.bind(this, this._onLongPress)); this.actor = new St.Bin(); this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); this.actor.connect('notify::mapped', () => { this._flipped = false; }); }, _sync: function() { let childToShow = null; if (this._standard.visible && this._alternate.visible) { let [x, y, mods] = global.get_pointer(); let altPressed = (mods & Clutter.ModifierType.MOD1_MASK) != 0; if (this._flipped) childToShow = altPressed ? this._standard : this._alternate; else childToShow = altPressed ? this._alternate : this._standard; } else if (this._standard.visible) { childToShow = this._standard; } else if (this._alternate.visible) { childToShow = this._alternate; } let childShown = this.actor.get_child(); if (childShown != childToShow) { if (childShown) { if (childShown.fake_release) childShown.fake_release(); childShown.remove_action(this._clickAction); } childToShow.add_action(this._clickAction); let hasFocus = this.actor.contains(global.stage.get_key_focus()); this.actor.set_child(childToShow); if (hasFocus) childToShow.grab_key_focus(); // The actors might respond to hover, so // sync the pointer to make sure they update. global.sync_pointer(); } this.actor.visible = (childToShow != null); }, _onDestroy: function() { if (this._capturedEventId > 0) { global.stage.disconnect(this._capturedEventId); this._capturedEventId = 0; } }, _onCapturedEvent: function(actor, event) { let type = event.type(); if (type == Clutter.EventType.KEY_PRESS || type == Clutter.EventType.KEY_RELEASE) { let key = event.get_key_symbol(); if (key == Clutter.KEY_Alt_L || key == Clutter.KEY_Alt_R) this._sync(); } return Clutter.EVENT_PROPAGATE; }, _onLongPress: function(action, actor, state) { if (state == Clutter.LongPressState.QUERY || state == Clutter.LongPressState.CANCEL) return true; this._flipped = !this._flipped; this._sync(); return true; } }); const Indicator = new Lang.Class({ Name: 'SystemIndicator', Extends: PanelMenu.SystemIndicator, _init: function() { this.parent(); this._loginScreenSettings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA }); this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA }); this._orientationSettings = new Gio.Settings({ schema_id: 'org.gnome.settings-daemon.peripherals.touchscreen' }); this._session = new GnomeSession.SessionManager(); this._loginManager = LoginManager.getLoginManager(); this._monitorManager = Meta.MonitorManager.get(); this._haveShutdown = true; this._haveSuspend = true; this._userManager = AccountsService.UserManager.get_default(); this._user = this._userManager.get_user(GLib.get_user_name()); this._createSubMenu(); this._userManager.connect('notify::is-loaded', Lang.bind(this, this._updateMultiUser)); this._userManager.connect('notify::has-multiple-users', Lang.bind(this, this._updateMultiUser)); this._userManager.connect('user-added', Lang.bind(this, this._updateMultiUser)); this._userManager.connect('user-removed', Lang.bind(this, this._updateMultiUser)); this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY, Lang.bind(this, this._updateMultiUser)); this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY, Lang.bind(this, this._updateMultiUser)); this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY, Lang.bind(this, this._updateLockScreen)); global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY, Lang.bind(this, this._updateMultiUser)); this._updateSwitchUser(); this._updateMultiUser(); this._updateLockScreen(); // 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 // the lockdown setting changes, which should be close enough. this.menu.connect('open-state-changed', Lang.bind(this, function(menu, open) { if (!open) return; this._updateHaveShutdown(); this._updateHaveSuspend(); })); this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY, Lang.bind(this, this._updateHaveShutdown)); this._orientationSettings.connect('changed::orientation-lock', Lang.bind(this, this._updateOrientationLock)); Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateOrientationLock)); Gio.DBus.system.watch_name(SENSOR_BUS_NAME, Gio.BusNameWatcherFlags.NONE, Lang.bind(this, this._sensorProxyAppeared), Lang.bind(this, function() { this._sensorProxy = null; this._updateOrientationLock(); })); this._updateOrientationLock(); Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated)); this._sessionUpdated(); }, _sensorProxyAppeared: function() { this._sensorProxy = new SensorProxy(Gio.DBus.system, SENSOR_BUS_NAME, SENSOR_OBJECT_PATH, Lang.bind(this, function(proxy, error) { if (error) { log(error.message); return; } this._sensorProxy.connect('g-properties-changed', Lang.bind(this, this._updateOrientationLock)); this._updateOrientationLock(); })); }, _updateActionsVisibility: function() { let visible = (this._settingsAction.visible || this._orientationLockAction.visible || this._lockScreenAction.visible || this._altSwitcher.actor.visible); this._actionsItem.actor.visible = visible; }, _sessionUpdated: function() { this._updateLockScreen(); this._updatePowerOff(); this._updateSuspend(); this._updateMultiUser(); this._settingsAction.visible = Main.sessionMode.allowSettings; this._updateActionsVisibility(); }, _updateMultiUser: function() { let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; let hasSwitchUser = this._updateSwitchUser(); let hasLogout = this._updateLogout(); this._switchUserSubMenu.actor.visible = shouldShowInMode && (hasSwitchUser || hasLogout); }, _updateSwitchUser: function() { let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY); let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users; let visible = allowSwitch && multiUser; this._loginScreenItem.actor.visible = visible; return visible; }, _updateLogout: function() { let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY); let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY); let systemAccount = this._user.system_account; let localAccount = this._user.local_account; let multiUser = this._userManager.has_multiple_users; let multiSession = Gdm.get_session_ids().length > 1; let visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount); this._logoutItem.actor.visible = visible; return visible; }, _updateSwitchUserSubMenu: function() { this._switchUserSubMenu.label.text = this._user.get_real_name(); let clutterText = this._switchUserSubMenu.label.clutter_text; // XXX -- for some reason, the ClutterText's width changes // rapidly unless we force a relayout of the actor. Probably // a size cache issue or something. Moving this to be a layout // manager would be a much better idea. clutterText.get_allocation_box(); let layout = clutterText.get_layout(); if (layout.is_ellipsized()) this._switchUserSubMenu.label.text = this._user.get_user_name(); let iconFile = this._user.get_icon_file(); if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS)) iconFile = null; if (iconFile) { let file = Gio.File.new_for_path(iconFile); let gicon = new Gio.FileIcon({ file: file }); this._switchUserSubMenu.icon.gicon = gicon; this._switchUserSubMenu.icon.add_style_class_name('user-icon'); this._switchUserSubMenu.icon.remove_style_class_name('default-icon'); } else { this._switchUserSubMenu.icon.icon_name = 'avatar-default-symbolic'; this._switchUserSubMenu.icon.add_style_class_name('default-icon'); this._switchUserSubMenu.icon.remove_style_class_name('user-icon'); } }, _updateOrientationLock: function() { if (this._sensorProxy) this._orientationLockAction.visible = this._sensorProxy.HasAccelerometer && this._monitorManager.get_is_builtin_display_on(); else this._orientationLockAction.visible = false; let locked = this._orientationSettings.get_boolean('orientation-lock'); let icon = this._orientationLockAction.child; icon.icon_name = locked ? 'rotation-locked-symbolic' : 'rotation-allowed-symbolic'; this._updateActionsVisibility(); }, _updateLockScreen: function() { let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); this._lockScreenAction.visible = showLock && allowLockScreen && LoginManager.canLock(); this._updateActionsVisibility(); }, _updateHaveShutdown: function() { this._session.CanShutdownRemote(Lang.bind(this, function(result, error) { if (error) return; this._haveShutdown = result[0]; this._updatePowerOff(); })); }, _updatePowerOff: function() { let disabled = Main.sessionMode.isLocked || (Main.sessionMode.isGreeter && this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); this._powerOffAction.visible = this._haveShutdown && !disabled; this._updateActionsVisibility(); }, _updateHaveSuspend: function() { this._loginManager.canSuspend(Lang.bind(this, function(canSuspend, needsAuth) { this._haveSuspend = canSuspend; this._suspendNeedsAuth = needsAuth; this._updateSuspend(); })); }, _updateSuspend: function() { let disabled = (Main.sessionMode.isLocked && this._suspendNeedsAuth) || (Main.sessionMode.isGreeter && this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); this._suspendAction.visible = this._haveSuspend && !disabled; this._updateActionsVisibility(); }, _createActionButton: function(iconName, accessibleName) { let icon = new St.Button({ reactive: true, can_focus: true, track_hover: true, accessible_name: accessibleName, style_class: 'system-menu-action' }); icon.child = new St.Icon({ icon_name: iconName }); return icon; }, _createSubMenu: function() { let item; this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true); this._switchUserSubMenu.icon.style_class = 'system-switch-user-submenu-icon'; // Since the label of the switch user submenu depends on the width of // the popup menu, and we can't easily connect on allocation-changed // or notify::width without creating layout cycles, simply update the // label whenever the menu is opened. this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { if (isOpen) this._updateSwitchUserSubMenu(); })); item = new PopupMenu.PopupMenuItem(_("Switch User")); item.connect('activate', Lang.bind(this, this._onLoginScreenActivate)); this._switchUserSubMenu.menu.addMenuItem(item); this._loginScreenItem = item; item = new PopupMenu.PopupMenuItem(_("Log Out")); item.connect('activate', Lang.bind(this, this._onQuitSessionActivate)); this._switchUserSubMenu.menu.addMenuItem(item); this._logoutItem = item; this._switchUserSubMenu.menu.addSettingsAction(_("Account Settings"), 'gnome-user-accounts-panel.desktop'); this._user.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUserSubMenu)); this._user.connect('changed', Lang.bind(this, this._updateSwitchUserSubMenu)); this.menu.addMenuItem(this._switchUserSubMenu); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); item = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false }); this._settingsAction = this._createActionButton('preferences-system-symbolic', _("Settings")); this._settingsAction.connect('clicked', Lang.bind(this, this._onSettingsClicked)); item.actor.add(this._settingsAction, { expand: true, x_fill: false }); this._orientationLockAction = this._createActionButton('', _("Orientation Lock")); this._orientationLockAction.connect('clicked', Lang.bind(this, this._onOrientationLockClicked)); item.actor.add(this._orientationLockAction, { expand: true, x_fill: false }); this._lockScreenAction = this._createActionButton('changes-prevent-symbolic', _("Lock")); this._lockScreenAction.connect('clicked', Lang.bind(this, this._onLockScreenClicked)); item.actor.add(this._lockScreenAction, { expand: true, x_fill: false }); this._suspendAction = this._createActionButton('media-playback-pause-symbolic', _("Suspend")); this._suspendAction.connect('clicked', Lang.bind(this, this._onSuspendClicked)); this._powerOffAction = this._createActionButton('system-shutdown-symbolic', _("Power Off")); this._powerOffAction.connect('clicked', Lang.bind(this, this._onPowerOffClicked)); this._altSwitcher = new AltSwitcher(this._powerOffAction, this._suspendAction); item.actor.add(this._altSwitcher.actor, { expand: true, x_fill: false }); this._actionsItem = item; this.menu.addMenuItem(item); }, _onSettingsClicked: function() { this.menu.itemActivated(); let app = Shell.AppSystem.get_default().lookup_app('gnome-control-center.desktop'); Main.overview.hide(); app.activate(); }, _onOrientationLockClicked: function() { this.menu.itemActivated(); let locked = this._orientationSettings.get_boolean('orientation-lock'); this._orientationSettings.set_boolean('orientation-lock', !locked); this._updateOrientationLock(); }, _onLockScreenClicked: function() { this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); Main.overview.hide(); Main.screenShield.lock(true); }, _onLoginScreenActivate: function() { this.menu.itemActivated(BoxPointer.PopupAnimation.NONE); Main.overview.hide(); if (Main.screenShield) Main.screenShield.lock(false); Clutter.threads_add_repaint_func_full(Clutter.RepaintFlags.POST_PAINT, function() { Gdm.goto_login_session_sync(null); return false; }); }, _onQuitSessionActivate: function() { Main.overview.hide(); this._session.LogoutRemote(0); }, _onPowerOffClicked: function() { this.menu.itemActivated(); Main.overview.hide(); this._session.ShutdownRemote(0); }, _onSuspendClicked: function() { this.menu.itemActivated(); this._loginManager.suspend(); }, });