summaryrefslogtreecommitdiffstats
path: root/js/misc/systemActions.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/misc/systemActions.js474
1 files changed, 474 insertions, 0 deletions
diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js
new file mode 100644
index 0000000..c57afe5
--- /dev/null
+++ b/js/misc/systemActions.js
@@ -0,0 +1,474 @@
+/* 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 Screenshot = imports.ui.screenshot;
+
+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 POWER_OFF_ACTION_ID = 'power-off';
+const RESTART_ACTION_ID = 'restart';
+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 SCREENSHOT_UI_ACTION_ID = 'open-screenshot-ui';
+
+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-restart': GObject.ParamSpec.boolean(
+ 'can-restart', 'can-restart', 'can-restart',
+ 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;halt;stop')),
+ available: false,
+ });
+ this._actions.set(RESTART_ACTION_ID, {
+ // Translators: The name of the restart action in search
+ name: C_('search-result', 'Restart'),
+ iconName: 'system-reboot-symbolic',
+ // Translators: A list of keywords that match the restart action, separated by semicolons
+ keywords: tokenizeKeywords(_('reboot;restart;')),
+ 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: 'system-log-out-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._actions.set(SCREENSHOT_UI_ACTION_ID, {
+ // Translators: The name of the screenshot UI action in search
+ name: C_('search-result', 'Take a Screenshot'),
+ iconName: 'record-screen-symbolic',
+ // Translators: A list of keywords that match the screenshot UI action, separated by semicolons
+ keywords: tokenizeKeywords(_('screenshot;screencast;snip;capture;record')),
+ available: true,
+ });
+
+ 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::${DISABLE_USER_SWITCH_KEY}`,
+ () => this._updateSwitchUser());
+ this._lockdownSettings.connect(`changed::${DISABLE_LOG_OUT_KEY}`,
+ () => this._updateLogout());
+ global.settings.connect(`changed::${ALWAYS_SHOW_LOG_OUT_KEY}`,
+ () => this._updateLogout());
+
+ this._lockdownSettings.connect(`changed::${DISABLE_LOCK_SCREEN_KEY}`,
+ () => this._updateLockScreen());
+
+ this._lockdownSettings.connect(`changed::${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._monitorManager.connect('notify::panel-orientation-managed',
+ () => this._updateOrientationLock());
+ this._updateOrientationLock();
+ this._updateOrientationLockStatus();
+
+ Main.sessionMode.connect('updated', () => this._sessionUpdated());
+ this._sessionUpdated();
+ }
+
+ get canPowerOff() {
+ return this._actions.get(POWER_OFF_ACTION_ID).available;
+ }
+
+ get canRestart() {
+ return this._actions.get(RESTART_ACTION_ID).available;
+ }
+
+ get canSuspend() {
+ return this._actions.get(SUSPEND_ACTION_ID).available;
+ }
+
+ get canLockScreen() {
+ return this._actions.get(LOCK_SCREEN_ACTION_ID).available;
+ }
+
+ get canSwitchUser() {
+ return this._actions.get(SWITCH_USER_ACTION_ID).available;
+ }
+
+ get canLogout() {
+ return this._actions.get(LOGOUT_ACTION_ID).available;
+ }
+
+ get canLockOrientation() {
+ return this._actions.get(LOCK_ORIENTATION_ACTION_ID).available;
+ }
+
+ get orientationLockIcon() {
+ return this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName;
+ }
+
+ _updateOrientationLock() {
+ const available = this._monitorManager.get_panel_orientation_managed();
+
+ 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]).flat(2);
+
+ // tokenizing may return an empty array
+ if (terms.length === 0)
+ return [];
+
+ 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 RESTART_ACTION_ID:
+ this.activateRestart();
+ 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;
+ case SCREENSHOT_UI_ACTION_ID:
+ this.activateScreenshotUI();
+ 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');
+ }
+
+ async _updateHaveShutdown() {
+ try {
+ const [canShutdown] = await this._session.CanShutdownAsync();
+ this._canHavePowerOff = canShutdown;
+ } catch (e) {
+ this._canHavePowerOff = false;
+ }
+ 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');
+
+ this._actions.get(RESTART_ACTION_ID).available = this._canHavePowerOff && !disabled;
+ this.notify('can-restart');
+ }
+
+ async _updateHaveSuspend() {
+ const {canSuspend, needsAuth} = await this._loginManager.canSuspend();
+ 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.LogoutAsync(0).catch(logError);
+ }
+
+ activatePowerOff() {
+ if (!this._actions.get(POWER_OFF_ACTION_ID).available)
+ throw new Error('The power-off action is not available!');
+
+ this._session.ShutdownAsync(0).catch(logError);
+ }
+
+ activateRestart() {
+ if (!this._actions.get(RESTART_ACTION_ID).available)
+ throw new Error('The restart action is not available!');
+
+ this._session.RebootAsync().catch(logError);
+ }
+
+ activateSuspend() {
+ if (!this._actions.get(SUSPEND_ACTION_ID).available)
+ throw new Error('The suspend action is not available!');
+
+ this._loginManager.suspend();
+ }
+
+ activateScreenshotUI() {
+ if (!this._actions.get(SCREENSHOT_UI_ACTION_ID).available)
+ throw new Error('The screenshot UI action is not available!');
+
+ if (this._overviewHiddenId)
+ return;
+
+ this._overviewHiddenId = Main.overview.connect('hidden', () => {
+ Main.overview.disconnect(this._overviewHiddenId);
+ delete this._overviewHiddenId;
+ Screenshot.showScreenshotUI();
+ });
+ }
+});