/* exported getDefault */ const { AccountsService, Clutter, Gdm, Gio, GLib, GObject, Meta } = imports.gi; const GnomeSession = imports.misc.gnomeSession; const LoginManager = imports.misc.loginManager; const Main = imports.ui.main; const { loadInterfaceXML } = imports.misc.fileUtils; 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 = loadInterfaceXML('net.hadess.SensorProxy'); const POWER_OFF_ACTION_ID = 'power-off'; const LOCK_SCREEN_ACTION_ID = 'lock-screen'; const LOGOUT_ACTION_ID = 'logout'; const SUSPEND_ACTION_ID = 'suspend'; const SWITCH_USER_ACTION_ID = 'switch-user'; const LOCK_ORIENTATION_ACTION_ID = 'lock-orientation'; const SensorProxy = Gio.DBusProxy.makeProxyWrapper(SensorProxyInterface); let _singleton = null; function getDefault() { if (_singleton == null) _singleton = new SystemActions(); return _singleton; } const SystemActions = GObject.registerClass({ Properties: { 'can-power-off': GObject.ParamSpec.boolean('can-power-off', 'can-power-off', 'can-power-off', GObject.ParamFlags.READABLE, false), 'can-suspend': GObject.ParamSpec.boolean('can-suspend', 'can-suspend', 'can-suspend', GObject.ParamFlags.READABLE, false), 'can-lock-screen': GObject.ParamSpec.boolean('can-lock-screen', 'can-lock-screen', 'can-lock-screen', GObject.ParamFlags.READABLE, false), 'can-switch-user': GObject.ParamSpec.boolean('can-switch-user', 'can-switch-user', 'can-switch-user', GObject.ParamFlags.READABLE, false), 'can-logout': GObject.ParamSpec.boolean('can-logout', 'can-logout', 'can-logout', GObject.ParamFlags.READABLE, false), 'can-lock-orientation': GObject.ParamSpec.boolean('can-lock-orientation', 'can-lock-orientation', 'can-lock-orientation', GObject.ParamFlags.READABLE, false), 'orientation-lock-icon': GObject.ParamSpec.string('orientation-lock-icon', 'orientation-lock-icon', 'orientation-lock-icon', GObject.ParamFlags.READWRITE, null), }, }, class SystemActions extends GObject.Object { _init() { super._init(); this._canHavePowerOff = true; this._canHaveSuspend = true; function tokenizeKeywords(keywords) { return keywords.split(';').map(keyword => GLib.str_tokenize_and_fold(keyword, null)).flat(2); } this._actions = new Map(); this._actions.set(POWER_OFF_ACTION_ID, { // Translators: The name of the power-off action in search name: C_("search-result", "Power Off"), iconName: 'system-shutdown-symbolic', // Translators: A list of keywords that match the power-off action, separated by semicolons keywords: tokenizeKeywords(_('power off;shutdown;reboot;restart;halt;stop')), available: false, }); this._actions.set(LOCK_SCREEN_ACTION_ID, { // Translators: The name of the lock screen action in search name: C_("search-result", "Lock Screen"), iconName: 'system-lock-screen-symbolic', // Translators: A list of keywords that match the lock screen action, separated by semicolons keywords: tokenizeKeywords(_('lock screen')), available: false, }); this._actions.set(LOGOUT_ACTION_ID, { // Translators: The name of the logout action in search name: C_("search-result", "Log Out"), iconName: 'application-exit-symbolic', // Translators: A list of keywords that match the logout action, separated by semicolons keywords: tokenizeKeywords(_('logout;log out;sign off')), available: false, }); this._actions.set(SUSPEND_ACTION_ID, { // Translators: The name of the suspend action in search name: C_("search-result", "Suspend"), iconName: 'media-playback-pause-symbolic', // Translators: A list of keywords that match the suspend action, separated by semicolons keywords: tokenizeKeywords(_('suspend;sleep')), available: false, }); this._actions.set(SWITCH_USER_ACTION_ID, { // Translators: The name of the switch user action in search name: C_("search-result", "Switch User"), iconName: 'system-switch-user-symbolic', // Translators: A list of keywords that match the switch user action, separated by semicolons keywords: tokenizeKeywords(_('switch user')), available: false, }); this._actions.set(LOCK_ORIENTATION_ACTION_ID, { name: '', iconName: '', // Translators: A list of keywords that match the lock orientation action, separated by semicolons keywords: tokenizeKeywords(_('lock orientation;unlock orientation;screen;rotation')), available: false, }); 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._userManager = AccountsService.UserManager.get_default(); this._userManager.connect('notify::is-loaded', () => this._updateMultiUser()); this._userManager.connect('notify::has-multiple-users', () => this._updateMultiUser()); this._userManager.connect('user-added', () => this._updateMultiUser()); this._userManager.connect('user-removed', () => this._updateMultiUser()); this._lockdownSettings.connect('changed::%s'.format(DISABLE_USER_SWITCH_KEY), () => this._updateSwitchUser()); this._lockdownSettings.connect('changed::%s'.format(DISABLE_LOG_OUT_KEY), () => this._updateLogout()); global.settings.connect('changed::%s'.format(ALWAYS_SHOW_LOG_OUT_KEY), () => this._updateLogout()); this._lockdownSettings.connect('changed::%s'.format(DISABLE_LOCK_SCREEN_KEY), () => this._updateLockScreen()); this._lockdownSettings.connect('changed::%s'.format(DISABLE_LOG_OUT_KEY), () => this._updateHaveShutdown()); this.forceUpdate(); this._orientationSettings.connect('changed::orientation-lock', () => { this._updateOrientationLock(); this._updateOrientationLockStatus(); }); Main.layoutManager.connect('monitors-changed', () => this._updateOrientationLock()); this._sensorProxy = new SensorProxy(Gio.DBus.system, SENSOR_BUS_NAME, SENSOR_OBJECT_PATH, (proxy, error) => { if (error) log(error.message); }, null, Gio.DBusProxyFlags.DO_NOT_AUTO_START); this._sensorProxy.connect('g-properties-changed', () => { this._updateOrientationLock(); }); this._sensorProxy.connect('notify::g-name-owner', () => { this._updateOrientationLock(); }); this._updateOrientationLock(); this._updateOrientationLockStatus(); Main.sessionMode.connect('updated', () => this._sessionUpdated()); this._sessionUpdated(); } // eslint-disable-next-line camelcase get can_power_off() { return this._actions.get(POWER_OFF_ACTION_ID).available; } // eslint-disable-next-line camelcase get can_suspend() { return this._actions.get(SUSPEND_ACTION_ID).available; } // eslint-disable-next-line camelcase get can_lock_screen() { return this._actions.get(LOCK_SCREEN_ACTION_ID).available; } // eslint-disable-next-line camelcase get can_switch_user() { return this._actions.get(SWITCH_USER_ACTION_ID).available; } // eslint-disable-next-line camelcase get can_logout() { return this._actions.get(LOGOUT_ACTION_ID).available; } // eslint-disable-next-line camelcase get can_lock_orientation() { return this._actions.get(LOCK_ORIENTATION_ACTION_ID).available; } // eslint-disable-next-line camelcase get orientation_lock_icon() { return this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName; } _updateOrientationLock() { let available = false; if (this._sensorProxy.g_name_owner) { available = this._sensorProxy.HasAccelerometer && this._monitorManager.get_is_builtin_display_on(); } this._actions.get(LOCK_ORIENTATION_ACTION_ID).available = available; this.notify('can-lock-orientation'); } _updateOrientationLockStatus() { let locked = this._orientationSettings.get_boolean('orientation-lock'); let action = this._actions.get(LOCK_ORIENTATION_ACTION_ID); // Translators: The name of the lock orientation action in search // and in the system status menu let name = locked ? C_('search-result', 'Unlock Screen Rotation') : C_('search-result', 'Lock Screen Rotation'); let iconName = locked ? 'rotation-locked-symbolic' : 'rotation-allowed-symbolic'; action.name = name; action.iconName = iconName; this.notify('orientation-lock-icon'); } _sessionUpdated() { this._updateLockScreen(); this._updatePowerOff(); this._updateSuspend(); this._updateMultiUser(); } forceUpdate() { // Whether those actions are available or not depends on both lockdown // settings and Polkit policy - we don't get change notifications for the // latter, so their value may be outdated; force an update now this._updateHaveShutdown(); this._updateHaveSuspend(); } getMatchingActions(terms) { // terms is a list of strings terms = terms.map(term => GLib.str_tokenize_and_fold(term, null)[0]); let results = []; for (let [key, { available, keywords }] of this._actions) { if (available && terms.every(t => keywords.some(k => k.startsWith(t)))) results.push(key); } return results; } getName(id) { return this._actions.get(id).name; } getIconName(id) { return this._actions.get(id).iconName; } activateAction(id) { switch (id) { case POWER_OFF_ACTION_ID: this.activatePowerOff(); break; case LOCK_SCREEN_ACTION_ID: this.activateLockScreen(); break; case LOGOUT_ACTION_ID: this.activateLogout(); break; case SUSPEND_ACTION_ID: this.activateSuspend(); break; case SWITCH_USER_ACTION_ID: this.activateSwitchUser(); break; case LOCK_ORIENTATION_ACTION_ID: this.activateLockOrientation(); break; } } _updateLockScreen() { let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY); this._actions.get(LOCK_SCREEN_ACTION_ID).available = showLock && allowLockScreen && LoginManager.canLock(); this.notify('can-lock-screen'); } _updateHaveShutdown() { this._session.CanShutdownRemote((result, error) => { if (error) return; this._canHavePowerOff = result[0]; this._updatePowerOff(); }); } _updatePowerOff() { let disabled = Main.sessionMode.isLocked || (Main.sessionMode.isGreeter && this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); this._actions.get(POWER_OFF_ACTION_ID).available = this._canHavePowerOff && !disabled; this.notify('can-power-off'); } _updateHaveSuspend() { this._loginManager.canSuspend( (canSuspend, needsAuth) => { this._canHaveSuspend = canSuspend; this._suspendNeedsAuth = needsAuth; this._updateSuspend(); }); } _updateSuspend() { let disabled = (Main.sessionMode.isLocked && this._suspendNeedsAuth) || (Main.sessionMode.isGreeter && this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY)); this._actions.get(SUSPEND_ACTION_ID).available = this._canHaveSuspend && !disabled; this.notify('can-suspend'); } _updateMultiUser() { this._updateLogout(); this._updateSwitchUser(); } _updateSwitchUser() { let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY); let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users; let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; let visible = allowSwitch && multiUser && shouldShowInMode; this._actions.get(SWITCH_USER_ACTION_ID).available = visible; this.notify('can-switch-user'); return visible; } _updateLogout() { let user = this._userManager.get_user(GLib.get_user_name()); let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY); let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY); let systemAccount = user.system_account; let localAccount = user.local_account; let multiUser = this._userManager.has_multiple_users; let multiSession = Gdm.get_session_ids().length > 1; let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter; let visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount) && shouldShowInMode; this._actions.get(LOGOUT_ACTION_ID).available = visible; this.notify('can-logout'); return visible; } activateLockOrientation() { if (!this._actions.get(LOCK_ORIENTATION_ACTION_ID).available) throw new Error('The lock-orientation action is not available!'); let locked = this._orientationSettings.get_boolean('orientation-lock'); this._orientationSettings.set_boolean('orientation-lock', !locked); } activateLockScreen() { if (!this._actions.get(LOCK_SCREEN_ACTION_ID).available) throw new Error('The lock-screen action is not available!'); Main.screenShield.lock(true); } activateSwitchUser() { if (!this._actions.get(SWITCH_USER_ACTION_ID).available) throw new Error('The switch-user action is not available!'); if (Main.screenShield) Main.screenShield.lock(false); Clutter.threads_add_repaint_func_full(Clutter.RepaintFlags.POST_PAINT, () => { Gdm.goto_login_session_sync(null); return false; }); } activateLogout() { if (!this._actions.get(LOGOUT_ACTION_ID).available) throw new Error('The logout action is not available!'); Main.overview.hide(); this._session.LogoutRemote(0); } activatePowerOff() { if (!this._actions.get(POWER_OFF_ACTION_ID).available) throw new Error('The power-off action is not available!'); this._session.ShutdownRemote(0); } activateSuspend() { if (!this._actions.get(SUSPEND_ACTION_ID).available) throw new Error('The suspend action is not available!'); this._loginManager.suspend(); } });