bacfdbbb03
ES6 finally adds standard class syntax to the language, so we can replace our custom Lang.Class framework with the new syntax. Any classes that inherit from GObject will need special treatment, so limit the port to regular javascript classes for now. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/361
345 lines
14 KiB
JavaScript
345 lines
14 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const AccountsService = imports.gi.AccountsService;
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gio = imports.gi.Gio;
|
|
const GLib = imports.gi.GLib;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
const GObject = imports.gi.GObject;
|
|
|
|
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.actor.connect('notify::visible',
|
|
() => { this._updateMultiUser(); });
|
|
this._logoutItem.actor.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._actionsItem.actor.visible = visible;
|
|
}
|
|
|
|
_sessionUpdated() {
|
|
this._settingsAction.visible = Main.sessionMode.allowSettings;
|
|
}
|
|
|
|
_updateMultiUser() {
|
|
let hasSwitchUser = this._loginScreenItem.actor.visible;
|
|
let hasLogout = this._logoutItem.actor.visible;
|
|
|
|
this._switchUserSubMenu.actor.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();
|
|
|
|
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');
|
|
}
|
|
}
|
|
|
|
_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.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', (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.actor,
|
|
'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.actor,
|
|
'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 });
|
|
|
|
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.actor.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.actor.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.actor.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.actor.add(this._altSwitcher.actor, { expand: true, x_fill: false });
|
|
|
|
this._actionsItem = item;
|
|
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();
|
|
}
|
|
};
|