14d7897a93
Braces are optional for single-line arrow functions, but there's a subtle difference: Without braces, the expression is implicitly used as return value; with braces, the function returns nothing unless there's an explicit return. We currently reflect that in our style by only omitting braces when the function is expected to have a return value, but that's not very obvious, not an important differentiation to make, and not easy to express in an automatic rule. So just omit braces consistently as mandated by gjs' coding style. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/608
321 lines
13 KiB
JavaScript
321 lines
13 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const { AccountsService, Clutter, GLib, GObject, Shell, St } = imports.gi;
|
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
|
const SystemActions = imports.misc.systemActions;
|
|
const Main = imports.ui.main;
|
|
const PanelMenu = imports.ui.panelMenu;
|
|
const PopupMenu = imports.ui.popupMenu;
|
|
|
|
|
|
var AltSwitcher = class {
|
|
constructor(standard, alternate) {
|
|
this._standard = standard;
|
|
this._standard.connect('notify::visible', this._sync.bind(this));
|
|
if (this._standard instanceof St.Button)
|
|
this._standard.connect('clicked',
|
|
() => this._clickAction.release());
|
|
|
|
this._alternate = alternate;
|
|
this._alternate.connect('notify::visible', this._sync.bind(this));
|
|
if (this._alternate instanceof St.Button)
|
|
this._alternate.connect('clicked',
|
|
() => this._clickAction.release());
|
|
|
|
this._capturedEventId = global.stage.connect('captured-event', this._onCapturedEvent.bind(this));
|
|
|
|
this._flipped = false;
|
|
|
|
this._clickAction = new Clutter.ClickAction();
|
|
this._clickAction.connect('long-press', this._onLongPress.bind(this));
|
|
|
|
this.actor = new St.Bin();
|
|
this.actor.connect('destroy', this._onDestroy.bind(this));
|
|
this.actor.connect('notify::mapped', () => this._flipped = false);
|
|
}
|
|
|
|
_sync() {
|
|
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;
|
|
} else {
|
|
this.actor.hide();
|
|
return;
|
|
}
|
|
|
|
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.show();
|
|
}
|
|
|
|
_onDestroy() {
|
|
if (this._capturedEventId > 0) {
|
|
global.stage.disconnect(this._capturedEventId);
|
|
this._capturedEventId = 0;
|
|
}
|
|
}
|
|
|
|
_onCapturedEvent(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(action, actor, state) {
|
|
if (state == Clutter.LongPressState.QUERY ||
|
|
state == Clutter.LongPressState.CANCEL)
|
|
return true;
|
|
|
|
this._flipped = !this._flipped;
|
|
this._sync();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
var Indicator = class extends PanelMenu.SystemIndicator {
|
|
constructor() {
|
|
super();
|
|
|
|
let userManager = AccountsService.UserManager.get_default();
|
|
this._user = userManager.get_user(GLib.get_user_name());
|
|
|
|
this._systemActions = new SystemActions.getDefault();
|
|
|
|
this._createSubMenu();
|
|
|
|
this._loginScreenItem.connect('notify::visible',
|
|
() => this._updateMultiUser());
|
|
this._logoutItem.connect('notify::visible',
|
|
() => this._updateMultiUser());
|
|
// 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', (menu, open) => {
|
|
if (!open)
|
|
return;
|
|
|
|
this._systemActions.forceUpdate();
|
|
});
|
|
this._updateMultiUser();
|
|
|
|
Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
|
|
this._sessionUpdated();
|
|
}
|
|
|
|
_updateActionsVisibility() {
|
|
let visible = (this._settingsAction.visible ||
|
|
this._orientationLockAction.visible ||
|
|
this._lockScreenAction.visible ||
|
|
this._altSwitcher.actor.visible);
|
|
|
|
this.buttonGroup.visible = visible;
|
|
}
|
|
|
|
_sessionUpdated() {
|
|
this._settingsAction.visible = Main.sessionMode.allowSettings;
|
|
}
|
|
|
|
_updateMultiUser() {
|
|
let hasSwitchUser = this._loginScreenItem.visible;
|
|
let hasLogout = this._logoutItem.visible;
|
|
|
|
this._switchUserSubMenu.visible = hasSwitchUser || hasLogout;
|
|
}
|
|
|
|
_updateSwitchUserSubMenu() {
|
|
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();
|
|
}
|
|
|
|
_createActionButton(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() {
|
|
let bindFlags = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE;
|
|
let item;
|
|
|
|
this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true);
|
|
this._switchUserSubMenu.icon.icon_name = 'avatar-default-symbolic';
|
|
|
|
// 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', (menu, isOpen) => {
|
|
if (isOpen)
|
|
this._updateSwitchUserSubMenu();
|
|
});
|
|
|
|
item = new PopupMenu.PopupMenuItem(_("Switch User"));
|
|
item.connect('activate', () => {
|
|
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|
this._systemActions.activateSwitchUser();
|
|
});
|
|
this._switchUserSubMenu.menu.addMenuItem(item);
|
|
this._loginScreenItem = item;
|
|
this._systemActions.bind_property('can-switch-user',
|
|
this._loginScreenItem,
|
|
'visible',
|
|
bindFlags);
|
|
|
|
item = new PopupMenu.PopupMenuItem(_("Log Out"));
|
|
item.connect('activate', () => {
|
|
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|
this._systemActions.activateLogout();
|
|
});
|
|
this._switchUserSubMenu.menu.addMenuItem(item);
|
|
this._logoutItem = item;
|
|
this._systemActions.bind_property('can-logout',
|
|
this._logoutItem,
|
|
'visible',
|
|
bindFlags);
|
|
|
|
this._switchUserSubMenu.menu.addSettingsAction(_("Account Settings"),
|
|
'gnome-user-accounts-panel.desktop');
|
|
|
|
this._user.connect('notify::is-loaded', this._updateSwitchUserSubMenu.bind(this));
|
|
this._user.connect('changed', this._updateSwitchUserSubMenu.bind(this));
|
|
|
|
this.menu.addMenuItem(this._switchUserSubMenu);
|
|
|
|
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
|
|
|
item = new PopupMenu.PopupBaseMenuItem({ reactive: false,
|
|
can_focus: false });
|
|
this.buttonGroup = item;
|
|
|
|
let app = this._settingsApp = Shell.AppSystem.get_default().lookup_app(
|
|
'gnome-control-center.desktop'
|
|
);
|
|
if (app) {
|
|
let [icon, name] = [app.app_info.get_icon().names[0],
|
|
app.get_name()];
|
|
this._settingsAction = this._createActionButton(icon, name);
|
|
this._settingsAction.connect('clicked',
|
|
this._onSettingsClicked.bind(this));
|
|
} else {
|
|
log('Missing required core component Settings, expect trouble…');
|
|
this._settingsAction = new St.Widget();
|
|
}
|
|
item.add(this._settingsAction, { expand: true, x_fill: false });
|
|
|
|
this._orientationLockAction = this._createActionButton('', _("Orientation Lock"));
|
|
this._orientationLockAction.connect('clicked', () => {
|
|
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE),
|
|
this._systemActions.activateLockOrientation();
|
|
});
|
|
item.add(this._orientationLockAction, { expand: true, x_fill: false });
|
|
this._systemActions.bind_property('can-lock-orientation',
|
|
this._orientationLockAction,
|
|
'visible',
|
|
bindFlags);
|
|
this._systemActions.bind_property('orientation-lock-icon',
|
|
this._orientationLockAction.child,
|
|
'icon-name',
|
|
bindFlags);
|
|
|
|
this._lockScreenAction = this._createActionButton('changes-prevent', _("Lock"));
|
|
this._lockScreenAction.connect('clicked', () => {
|
|
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|
this._systemActions.activateLockScreen();
|
|
});
|
|
item.add(this._lockScreenAction, { expand: true, x_fill: false });
|
|
this._systemActions.bind_property('can-lock-screen',
|
|
this._lockScreenAction,
|
|
'visible',
|
|
bindFlags);
|
|
|
|
this._suspendAction = this._createActionButton('media-playback-pause', _("Suspend"));
|
|
this._suspendAction.connect('clicked', () => {
|
|
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|
this._systemActions.activateSuspend();
|
|
});
|
|
this._systemActions.bind_property('can-suspend',
|
|
this._suspendAction,
|
|
'visible',
|
|
bindFlags);
|
|
|
|
this._powerOffAction = this._createActionButton('system-shutdown', _("Power Off"));
|
|
this._powerOffAction.connect('clicked', () => {
|
|
this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
|
|
this._systemActions.activatePowerOff();
|
|
});
|
|
this._systemActions.bind_property('can-power-off',
|
|
this._powerOffAction,
|
|
'visible',
|
|
bindFlags);
|
|
|
|
this._altSwitcher = new AltSwitcher(this._powerOffAction, this._suspendAction);
|
|
item.add(this._altSwitcher.actor, { expand: true, x_fill: false });
|
|
|
|
this.menu.addMenuItem(item);
|
|
|
|
|
|
this._settingsAction.connect('notify::visible',
|
|
() => this._updateActionsVisibility());
|
|
this._orientationLockAction.connect('notify::visible',
|
|
() => this._updateActionsVisibility());
|
|
this._lockScreenAction.connect('notify::visible',
|
|
() => this._updateActionsVisibility());
|
|
this._altSwitcher.actor.connect('notify::visible',
|
|
() => this._updateActionsVisibility());
|
|
}
|
|
|
|
_onSettingsClicked() {
|
|
this.menu.itemActivated();
|
|
Main.overview.hide();
|
|
this._settingsApp.activate();
|
|
}
|
|
};
|