b2d6c11ec3
We split the search string into words using whitespace, while GLib.tokenize_and_fold() splits on any non-alphanumeric characters. That is, a valid search term like ',' will be tokenized as [], so the original non-empty terms may get mapped to an empty array. And as [].every() returns true for any condition[0], we end up matching *all* system actions in that case. We want the exact opposite and not return any results, so handle that case explicitly. [0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/3169
458 lines
17 KiB
JavaScript
458 lines
17 KiB
JavaScript
/* 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 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';
|
|
|
|
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._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._monitorManager.connect('notify::panel-orientation-managed',
|
|
() => 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_restart() {
|
|
return this._actions.get(RESTART_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() {
|
|
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;
|
|
}
|
|
}
|
|
|
|
_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');
|
|
|
|
this._actions.get(RESTART_ACTION_ID).available = this._canHavePowerOff && !disabled;
|
|
this.notify('can-restart');
|
|
}
|
|
|
|
_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);
|
|
}
|
|
|
|
activateRestart() {
|
|
if (!this._actions.get(RESTART_ACTION_ID).available)
|
|
throw new Error('The restart action is not available!');
|
|
|
|
this._session.RebootRemote();
|
|
}
|
|
|
|
activateSuspend() {
|
|
if (!this._actions.get(SUSPEND_ACTION_ID).available)
|
|
throw new Error('The suspend action is not available!');
|
|
|
|
this._loginManager.suspend();
|
|
}
|
|
});
|