07dfcf4eff
Prior to commit 7bd98f3f5f
animation
onComplete handlers always occured at least after one main loop
iteration.
Now, if animations are disabled, they can get invoked immediately.
That breaks the endSessionDialog button handler, which calls
close before setting up the "closed" signal handler.
This commit fixes the handler to get set up first.
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2677>
799 lines
28 KiB
JavaScript
799 lines
28 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported init, EndSessionDialog */
|
|
/*
|
|
* Copyright 2010-2016 Red Hat, Inc
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
const {
|
|
AccountsService, Clutter, Gio, GLib, GObject,
|
|
Pango, Polkit, Shell, St, UPowerGlib: UPower,
|
|
} = imports.gi;
|
|
|
|
const CheckBox = imports.ui.checkBox;
|
|
const Dialog = imports.ui.dialog;
|
|
const GnomeSession = imports.misc.gnomeSession;
|
|
const LoginManager = imports.misc.loginManager;
|
|
const ModalDialog = imports.ui.modalDialog;
|
|
const UserWidget = imports.ui.userWidget;
|
|
|
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
|
|
|
const _ITEM_ICON_SIZE = 64;
|
|
|
|
const LOW_BATTERY_THRESHOLD = 30;
|
|
|
|
const EndSessionDialogIface = loadInterfaceXML('org.gnome.SessionManager.EndSessionDialog');
|
|
|
|
const logoutDialogContent = {
|
|
subjectWithUser: C_("title", "Log Out %s"),
|
|
subject: C_("title", "Log Out"),
|
|
descriptionWithUser(user, seconds) {
|
|
return ngettext(
|
|
'%s will be logged out automatically in %d second.',
|
|
'%s will be logged out automatically in %d seconds.',
|
|
seconds).format(user, seconds);
|
|
},
|
|
description(seconds) {
|
|
return ngettext(
|
|
'You will be logged out automatically in %d second.',
|
|
'You will be logged out automatically in %d seconds.',
|
|
seconds).format(seconds);
|
|
},
|
|
showBatteryWarning: false,
|
|
confirmButtons: [{
|
|
signal: 'ConfirmedLogout',
|
|
label: C_('button', 'Log Out'),
|
|
}],
|
|
showOtherSessions: false,
|
|
};
|
|
|
|
const shutdownDialogContent = {
|
|
subject: C_("title", "Power Off"),
|
|
subjectWithUpdates: C_("title", "Install Updates & Power Off"),
|
|
description(seconds) {
|
|
return ngettext(
|
|
'The system will power off automatically in %d second.',
|
|
'The system will power off automatically in %d seconds.',
|
|
seconds).format(seconds);
|
|
},
|
|
checkBoxText: C_("checkbox", "Install pending software updates"),
|
|
showBatteryWarning: true,
|
|
confirmButtons: [{
|
|
signal: 'ConfirmedShutdown',
|
|
label: C_('button', 'Power Off'),
|
|
}],
|
|
iconName: 'system-shutdown-symbolic',
|
|
showOtherSessions: true,
|
|
};
|
|
|
|
const restartDialogContent = {
|
|
subject: C_("title", "Restart"),
|
|
subjectWithUpdates: C_('title', 'Install Updates & Restart'),
|
|
description(seconds) {
|
|
return ngettext(
|
|
'The system will restart automatically in %d second.',
|
|
'The system will restart automatically in %d seconds.',
|
|
seconds).format(seconds);
|
|
},
|
|
checkBoxText: C_('checkbox', 'Install pending software updates'),
|
|
showBatteryWarning: true,
|
|
confirmButtons: [{
|
|
signal: 'ConfirmedReboot',
|
|
label: C_('button', 'Restart'),
|
|
}],
|
|
iconName: 'view-refresh-symbolic',
|
|
showOtherSessions: true,
|
|
};
|
|
|
|
const restartUpdateDialogContent = {
|
|
|
|
subject: C_("title", "Restart & Install Updates"),
|
|
description(seconds) {
|
|
return ngettext(
|
|
'The system will automatically restart and install updates in %d second.',
|
|
'The system will automatically restart and install updates in %d seconds.',
|
|
seconds).format(seconds);
|
|
},
|
|
showBatteryWarning: true,
|
|
confirmButtons: [{
|
|
signal: 'ConfirmedReboot',
|
|
label: C_('button', 'Restart & Install'),
|
|
}],
|
|
unusedFutureButtonForTranslation: C_("button", "Install & Power Off"),
|
|
unusedFutureCheckBoxForTranslation: C_("checkbox", "Power off after updates are installed"),
|
|
iconName: 'view-refresh-symbolic',
|
|
showOtherSessions: true,
|
|
};
|
|
|
|
const restartUpgradeDialogContent = {
|
|
|
|
subject: C_("title", "Restart & Install Upgrade"),
|
|
upgradeDescription(distroName, distroVersion) {
|
|
/* Translators: This is the text displayed for system upgrades in the
|
|
shut down dialog. First %s gets replaced with the distro name and
|
|
second %s with the distro version to upgrade to */
|
|
return _("%s %s will be installed after restart. Upgrade installation can take a long time: ensure that you have backed up and that the computer is plugged in.").format(distroName, distroVersion);
|
|
},
|
|
disableTimer: true,
|
|
showBatteryWarning: false,
|
|
confirmButtons: [{
|
|
signal: 'ConfirmedReboot',
|
|
label: C_('button', 'Restart & Install'),
|
|
}],
|
|
iconName: 'view-refresh-symbolic',
|
|
showOtherSessions: true,
|
|
};
|
|
|
|
const DialogType = {
|
|
LOGOUT: 0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */,
|
|
SHUTDOWN: 1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */,
|
|
RESTART: 2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */,
|
|
UPDATE_RESTART: 3,
|
|
UPGRADE_RESTART: 4,
|
|
};
|
|
|
|
const DialogContent = {
|
|
0 /* DialogType.LOGOUT */: logoutDialogContent,
|
|
1 /* DialogType.SHUTDOWN */: shutdownDialogContent,
|
|
2 /* DialogType.RESTART */: restartDialogContent,
|
|
3 /* DialogType.UPDATE_RESTART */: restartUpdateDialogContent,
|
|
4 /* DialogType.UPGRADE_RESTART */: restartUpgradeDialogContent,
|
|
};
|
|
|
|
var MAX_USERS_IN_SESSION_DIALOG = 5;
|
|
|
|
const LogindSessionIface = loadInterfaceXML('org.freedesktop.login1.Session');
|
|
const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface);
|
|
|
|
const PkOfflineIface = loadInterfaceXML('org.freedesktop.PackageKit.Offline');
|
|
const PkOfflineProxy = Gio.DBusProxy.makeProxyWrapper(PkOfflineIface);
|
|
|
|
const UPowerIface = loadInterfaceXML('org.freedesktop.UPower.Device');
|
|
const UPowerProxy = Gio.DBusProxy.makeProxyWrapper(UPowerIface);
|
|
|
|
function findAppFromInhibitor(inhibitor) {
|
|
let desktopFile;
|
|
try {
|
|
[desktopFile] = inhibitor.GetAppIdSync();
|
|
} catch (e) {
|
|
// XXX -- sometimes JIT inhibitors generated by gnome-session
|
|
// get removed too soon. Don't fail in this case.
|
|
log(`gnome-session gave us a dead inhibitor: ${inhibitor.get_object_path()}`);
|
|
return null;
|
|
}
|
|
|
|
if (!GLib.str_has_suffix(desktopFile, '.desktop'))
|
|
desktopFile += '.desktop';
|
|
|
|
return Shell.AppSystem.get_default().lookup_heuristic_basename(desktopFile);
|
|
}
|
|
|
|
// The logout timer only shows updates every 10 seconds
|
|
// until the last 10 seconds, then it shows updates every
|
|
// second. This function takes a given time and returns
|
|
// what we should show to the user for that time.
|
|
function _roundSecondsToInterval(totalSeconds, secondsLeft, interval) {
|
|
let time;
|
|
|
|
time = Math.ceil(secondsLeft);
|
|
|
|
// Final count down is in decrements of 1
|
|
if (time <= interval)
|
|
return time;
|
|
|
|
// Round up higher than last displayable time interval
|
|
time += interval - 1;
|
|
|
|
// Then round down to that time interval
|
|
if (time > totalSeconds)
|
|
time = Math.ceil(totalSeconds);
|
|
else
|
|
time -= time % interval;
|
|
|
|
return time;
|
|
}
|
|
|
|
function _setCheckBoxLabel(checkBox, text) {
|
|
let label = checkBox.getLabelActor();
|
|
|
|
if (text) {
|
|
label.set_text(text);
|
|
checkBox.show();
|
|
} else {
|
|
label.set_text('');
|
|
checkBox.hide();
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
// This always returns the same singleton object
|
|
// By instantiating it initially, we register the
|
|
// bus object, etc.
|
|
new EndSessionDialog();
|
|
}
|
|
|
|
var EndSessionDialog = GObject.registerClass(
|
|
class EndSessionDialog extends ModalDialog.ModalDialog {
|
|
_init() {
|
|
super._init({
|
|
styleClass: 'end-session-dialog',
|
|
destroyOnClose: false,
|
|
});
|
|
|
|
this._loginManager = LoginManager.getLoginManager();
|
|
this._canRebootToBootLoaderMenu = false;
|
|
this._getCanRebootToBootLoaderMenu();
|
|
|
|
this._userManager = AccountsService.UserManager.get_default();
|
|
this._user = this._userManager.get_user(GLib.get_user_name());
|
|
this._updatesPermission = null;
|
|
|
|
this._pkOfflineProxy = new PkOfflineProxy(Gio.DBus.system,
|
|
'org.freedesktop.PackageKit',
|
|
'/org/freedesktop/PackageKit',
|
|
this._onPkOfflineProxyCreated.bind(this));
|
|
|
|
this._powerProxy = new UPowerProxy(Gio.DBus.system,
|
|
'org.freedesktop.UPower',
|
|
'/org/freedesktop/UPower/devices/DisplayDevice',
|
|
(proxy, error) => {
|
|
if (error) {
|
|
log(error.message);
|
|
return;
|
|
}
|
|
this._powerProxy.connect('g-properties-changed',
|
|
this._sync.bind(this));
|
|
this._sync();
|
|
});
|
|
|
|
this._secondsLeft = 0;
|
|
this._totalSecondsToStayOpen = 0;
|
|
this._applications = [];
|
|
this._sessions = [];
|
|
this._capturedEventId = 0;
|
|
this._rebootButton = null;
|
|
this._rebootButtonAlt = null;
|
|
|
|
this.connect('opened',
|
|
this._onOpened.bind(this));
|
|
|
|
this._user.connectObject(
|
|
'notify::is-loaded', this._sync.bind(this),
|
|
'changed', this._sync.bind(this), this);
|
|
|
|
this._messageDialogContent = new Dialog.MessageDialogContent();
|
|
|
|
this._checkBox = new CheckBox.CheckBox();
|
|
this._checkBox.connect('clicked', this._sync.bind(this));
|
|
this._messageDialogContent.add_child(this._checkBox);
|
|
|
|
this._batteryWarning = new St.Label({
|
|
style_class: 'end-session-dialog-battery-warning',
|
|
text: _('Low battery power: please plug in before installing updates.'),
|
|
});
|
|
this._batteryWarning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
this._batteryWarning.clutter_text.line_wrap = true;
|
|
this._messageDialogContent.add_child(this._batteryWarning);
|
|
|
|
this.contentLayout.add_child(this._messageDialogContent);
|
|
|
|
this._applicationSection = new Dialog.ListSection({
|
|
title: _('Some applications are busy or have unsaved work'),
|
|
});
|
|
this.contentLayout.add_child(this._applicationSection);
|
|
|
|
this._sessionSection = new Dialog.ListSection({
|
|
title: _('Other users are logged in'),
|
|
});
|
|
this.contentLayout.add_child(this._sessionSection);
|
|
|
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this);
|
|
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog');
|
|
}
|
|
|
|
async _getCanRebootToBootLoaderMenu() {
|
|
const {canRebootToBootLoaderMenu} = await this._loginManager.canRebootToBootLoaderMenu();
|
|
this._canRebootToBootLoaderMenu = canRebootToBootLoaderMenu;
|
|
}
|
|
|
|
async _onPkOfflineProxyCreated(proxy, error) {
|
|
if (error) {
|
|
log(error.message);
|
|
return;
|
|
}
|
|
|
|
// Creating a D-Bus proxy won't propagate SERVICE_UNKNOWN or NAME_HAS_NO_OWNER
|
|
// errors if PackageKit is not available, but the GIO implementation will make
|
|
// sure in that case that the proxy's g-name-owner is set to null, so check that.
|
|
if (this._pkOfflineProxy.g_name_owner === null) {
|
|
this._pkOfflineProxy = null;
|
|
return;
|
|
}
|
|
|
|
// It only makes sense to check for this permission if PackageKit is available.
|
|
try {
|
|
this._updatesPermission = await Polkit.Permission.new(
|
|
'org.freedesktop.packagekit.trigger-offline-update', null, null);
|
|
} catch (e) {
|
|
log(`No permission to trigger offline updates: ${e}`);
|
|
}
|
|
}
|
|
|
|
_isDischargingBattery() {
|
|
return this._powerProxy.IsPresent &&
|
|
this._powerProxy.State !== UPower.DeviceState.CHARGING &&
|
|
this._powerProxy.State !== UPower.DeviceState.FULLY_CHARGED;
|
|
}
|
|
|
|
_isBatteryLow() {
|
|
return this._isDischargingBattery() && this._powerProxy.Percentage < LOW_BATTERY_THRESHOLD;
|
|
}
|
|
|
|
_shouldShowLowBatteryWarning(dialogContent) {
|
|
if (!dialogContent.showBatteryWarning)
|
|
return false;
|
|
|
|
if (!this._isBatteryLow())
|
|
return false;
|
|
|
|
if (this._checkBox.checked)
|
|
return true;
|
|
|
|
// Show the warning if updates have already been triggered, but
|
|
// the user doesn't have enough permissions to cancel them.
|
|
let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;
|
|
return this._updateInfo.UpdatePrepared && this._updateInfo.UpdateTriggered && !updatesAllowed;
|
|
}
|
|
|
|
_sync() {
|
|
let open = this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED;
|
|
if (!open)
|
|
return;
|
|
|
|
let dialogContent = DialogContent[this._type];
|
|
|
|
let subject = dialogContent.subject;
|
|
|
|
// Use different title when we are installing updates
|
|
if (dialogContent.subjectWithUpdates && this._checkBox.checked)
|
|
subject = dialogContent.subjectWithUpdates;
|
|
|
|
this._batteryWarning.visible = this._shouldShowLowBatteryWarning(dialogContent);
|
|
|
|
let description;
|
|
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
|
|
this._secondsLeft,
|
|
10);
|
|
|
|
if (this._user.is_loaded) {
|
|
let realName = this._user.get_real_name();
|
|
|
|
if (realName != null) {
|
|
if (dialogContent.subjectWithUser)
|
|
subject = dialogContent.subjectWithUser.format(realName);
|
|
|
|
if (dialogContent.descriptionWithUser)
|
|
description = dialogContent.descriptionWithUser(realName, displayTime);
|
|
}
|
|
}
|
|
|
|
// Use a different description when we are installing a system upgrade
|
|
// if the PackageKit proxy is available (i.e. PackageKit is available).
|
|
if (dialogContent.upgradeDescription) {
|
|
const { name, version } = this._updateInfo.PreparedUpgrade;
|
|
if (name != null && version != null)
|
|
description = dialogContent.upgradeDescription(name, version);
|
|
}
|
|
|
|
// Fall back to regular description
|
|
if (!description)
|
|
description = dialogContent.description(displayTime);
|
|
|
|
this._messageDialogContent.title = subject;
|
|
this._messageDialogContent.description = description;
|
|
|
|
let hasApplications = this._applications.length > 0;
|
|
let hasSessions = this._sessions.length > 0;
|
|
|
|
this._applicationSection.visible = hasApplications;
|
|
this._sessionSection.visible = hasSessions;
|
|
}
|
|
|
|
_onCapturedEvent(actor, event) {
|
|
let altEnabled = false;
|
|
|
|
let type = event.type();
|
|
if (type !== Clutter.EventType.KEY_PRESS && type !== Clutter.EventType.KEY_RELEASE)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
let key = event.get_key_symbol();
|
|
if (key !== Clutter.KEY_Alt_L && key !== Clutter.KEY_Alt_R)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
if (type === Clutter.EventType.KEY_PRESS)
|
|
altEnabled = true;
|
|
|
|
this._rebootButton.visible = !altEnabled;
|
|
this._rebootButtonAlt.visible = altEnabled;
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
}
|
|
|
|
_updateButtons() {
|
|
this.clearButtons();
|
|
|
|
this.addButton({
|
|
action: this.cancel.bind(this),
|
|
label: _('Cancel'),
|
|
key: Clutter.KEY_Escape,
|
|
});
|
|
|
|
let dialogContent = DialogContent[this._type];
|
|
for (let i = 0; i < dialogContent.confirmButtons.length; i++) {
|
|
let signal = dialogContent.confirmButtons[i].signal;
|
|
let label = dialogContent.confirmButtons[i].label;
|
|
let button = this.addButton({
|
|
action: () => {
|
|
let signalId = this.connect('closed', () => {
|
|
this.disconnect(signalId);
|
|
this._confirm(signal);
|
|
});
|
|
this.close(true);
|
|
},
|
|
label,
|
|
});
|
|
|
|
// Add Alt "Boot Options" option to the Reboot button
|
|
if (this._canRebootToBootLoaderMenu && signal === 'ConfirmedReboot') {
|
|
this._rebootButton = button;
|
|
this._rebootButtonAlt = this.addButton({
|
|
action: () => {
|
|
this.close(true);
|
|
let signalId = this.connect('closed', () => {
|
|
this.disconnect(signalId);
|
|
this._confirmRebootToBootLoaderMenu();
|
|
});
|
|
},
|
|
label: C_('button', 'Boot Options'),
|
|
});
|
|
this._rebootButtonAlt.visible = false;
|
|
this._capturedEventId = this.connect('captured-event',
|
|
this._onCapturedEvent.bind(this));
|
|
}
|
|
}
|
|
}
|
|
|
|
_stopAltCapture() {
|
|
if (this._capturedEventId > 0) {
|
|
global.stage.disconnect(this._capturedEventId);
|
|
this._capturedEventId = 0;
|
|
}
|
|
this._rebootButton = null;
|
|
this._rebootButtonAlt = null;
|
|
}
|
|
|
|
close(skipSignal) {
|
|
super.close();
|
|
|
|
if (!skipSignal)
|
|
this._dbusImpl.emit_signal('Closed', null);
|
|
}
|
|
|
|
cancel() {
|
|
this._stopTimer();
|
|
this._stopAltCapture();
|
|
this._dbusImpl.emit_signal('Canceled', null);
|
|
this.close();
|
|
}
|
|
|
|
_confirmRebootToBootLoaderMenu() {
|
|
this._loginManager.setRebootToBootLoaderMenu();
|
|
this._confirm('ConfirmedReboot');
|
|
}
|
|
|
|
async _confirm(signal) {
|
|
if (this._checkBox.visible) {
|
|
// Trigger the offline update as requested
|
|
if (this._checkBox.checked) {
|
|
switch (signal) {
|
|
case 'ConfirmedReboot':
|
|
await this._triggerOfflineUpdateReboot();
|
|
break;
|
|
case 'ConfirmedShutdown':
|
|
// To actually trigger the offline update, we need to
|
|
// reboot to do the upgrade. When the upgrade is complete,
|
|
// the computer will shut down automatically.
|
|
signal = 'ConfirmedReboot';
|
|
await this._triggerOfflineUpdateShutdown();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
await this._triggerOfflineUpdateCancel();
|
|
}
|
|
}
|
|
|
|
this._fadeOutDialog();
|
|
this._stopTimer();
|
|
this._stopAltCapture();
|
|
this._dbusImpl.emit_signal(signal, null);
|
|
}
|
|
|
|
_onOpened() {
|
|
this._sync();
|
|
}
|
|
|
|
async _triggerOfflineUpdateReboot() {
|
|
// Handle this gracefully if PackageKit is not available.
|
|
if (!this._pkOfflineProxy)
|
|
return;
|
|
|
|
try {
|
|
await this._pkOfflineProxy.TriggerAsync('reboot');
|
|
} catch (error) {
|
|
log(error.message);
|
|
}
|
|
}
|
|
|
|
async _triggerOfflineUpdateShutdown() {
|
|
// Handle this gracefully if PackageKit is not available.
|
|
if (!this._pkOfflineProxy)
|
|
return;
|
|
|
|
try {
|
|
await this._pkOfflineProxy.TriggerAsync('power-off');
|
|
} catch (error) {
|
|
log(error.message);
|
|
}
|
|
}
|
|
|
|
async _triggerOfflineUpdateCancel() {
|
|
// Handle this gracefully if PackageKit is not available.
|
|
if (!this._pkOfflineProxy)
|
|
return;
|
|
|
|
try {
|
|
await this._pkOfflineProxy.CancelAsync();
|
|
} catch (error) {
|
|
log(error.message);
|
|
}
|
|
}
|
|
|
|
_startTimer() {
|
|
let startTime = GLib.get_monotonic_time();
|
|
this._secondsLeft = this._totalSecondsToStayOpen;
|
|
|
|
this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
|
|
let currentTime = GLib.get_monotonic_time();
|
|
let secondsElapsed = (currentTime - startTime) / 1000000;
|
|
|
|
this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed;
|
|
if (this._secondsLeft > 0) {
|
|
this._sync();
|
|
return GLib.SOURCE_CONTINUE;
|
|
}
|
|
|
|
let dialogContent = DialogContent[this._type];
|
|
let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];
|
|
this._confirm(button.signal);
|
|
this._timerId = 0;
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
GLib.Source.set_name_by_id(this._timerId, '[gnome-shell] this._confirm');
|
|
}
|
|
|
|
_stopTimer() {
|
|
if (this._timerId > 0) {
|
|
GLib.source_remove(this._timerId);
|
|
this._timerId = 0;
|
|
}
|
|
|
|
this._secondsLeft = 0;
|
|
}
|
|
|
|
_onInhibitorLoaded(inhibitor) {
|
|
if (!this._applications.includes(inhibitor)) {
|
|
// Stale inhibitor
|
|
return;
|
|
}
|
|
|
|
let app = findAppFromInhibitor(inhibitor);
|
|
const [flags] = app ? inhibitor.GetFlagsSync() : [0];
|
|
|
|
if (app && flags & GnomeSession.InhibitFlags.LOGOUT) {
|
|
let [description] = inhibitor.GetReasonSync();
|
|
let listItem = new Dialog.ListSectionItem({
|
|
icon_actor: app.create_icon_texture(_ITEM_ICON_SIZE),
|
|
title: app.get_name(),
|
|
description,
|
|
});
|
|
this._applicationSection.list.add_child(listItem);
|
|
} else {
|
|
// inhibiting app is a service (not an application) or is not
|
|
// inhibiting logout/shutdown
|
|
this._applications.splice(this._applications.indexOf(inhibitor), 1);
|
|
}
|
|
|
|
this._sync();
|
|
}
|
|
|
|
async _loadSessions() {
|
|
let sessionId = GLib.getenv('XDG_SESSION_ID');
|
|
if (!sessionId) {
|
|
const currentSessionProxy = await this._loginManager.getCurrentSessionProxy();
|
|
sessionId = currentSessionProxy.Id;
|
|
log(`endSessionDialog: No XDG_SESSION_ID, fetched from logind: ${sessionId}`);
|
|
}
|
|
|
|
const sessions = await this._loginManager.listSessions();
|
|
for (const [id_, uid_, userName, seat_, sessionPath] of sessions) {
|
|
let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath);
|
|
|
|
if (proxy.Class !== 'user')
|
|
continue;
|
|
|
|
if (proxy.State === 'closing')
|
|
continue;
|
|
|
|
if (proxy.Id === sessionId)
|
|
continue;
|
|
|
|
const session = {
|
|
user: this._userManager.get_user(userName),
|
|
username: userName,
|
|
type: proxy.Type,
|
|
remote: proxy.Remote,
|
|
};
|
|
const nSessions = this._sessions.push(session);
|
|
|
|
let userAvatar = new UserWidget.Avatar(session.user, {
|
|
iconSize: _ITEM_ICON_SIZE,
|
|
});
|
|
userAvatar.update();
|
|
|
|
const displayUserName =
|
|
session.user.get_real_name() ?? session.username;
|
|
|
|
let userLabelText;
|
|
if (session.remote)
|
|
/* Translators: Remote here refers to a remote session, like a ssh login */
|
|
userLabelText = _('%s (remote)').format(displayUserName);
|
|
else if (session.type === 'tty')
|
|
/* Translators: Console here refers to a tty like a VT console */
|
|
userLabelText = _('%s (console)').format(displayUserName);
|
|
else
|
|
userLabelText = userName;
|
|
|
|
let listItem = new Dialog.ListSectionItem({
|
|
icon_actor: userAvatar,
|
|
title: userLabelText,
|
|
});
|
|
this._sessionSection.list.add_child(listItem);
|
|
|
|
// limit the number of entries
|
|
if (nSessions === MAX_USERS_IN_SESSION_DIALOG)
|
|
break;
|
|
}
|
|
|
|
this._sync();
|
|
}
|
|
|
|
async _getUpdateInfo() {
|
|
const connection = this._pkOfflineProxy.get_connection();
|
|
const reply = await connection.call(
|
|
this._pkOfflineProxy.g_name,
|
|
this._pkOfflineProxy.g_object_path,
|
|
'org.freedesktop.DBus.Properties',
|
|
'GetAll',
|
|
new GLib.Variant('(s)', [this._pkOfflineProxy.g_interface_name]),
|
|
null,
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null);
|
|
const [info] = reply.recursiveUnpack();
|
|
return info;
|
|
}
|
|
|
|
async OpenAsync(parameters, invocation) {
|
|
let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters;
|
|
this._totalSecondsToStayOpen = totalSecondsToStayOpen;
|
|
this._type = type;
|
|
|
|
try {
|
|
this._updateInfo = await this._getUpdateInfo();
|
|
} catch (e) {
|
|
if (this._pkOfflineProxy !== null)
|
|
log(`Failed to get update info from PackageKit: ${e.message}`);
|
|
|
|
this._updateInfo = {
|
|
UpdateTriggered: false,
|
|
UpdatePrepared: false,
|
|
UpgradeTriggered: false,
|
|
PreparedUpgrade: {},
|
|
};
|
|
}
|
|
|
|
// Only consider updates and upgrades if PackageKit is available.
|
|
if (this._pkOfflineProxy && this._type == DialogType.RESTART) {
|
|
if (this._updateInfo.UpdateTriggered)
|
|
this._type = DialogType.UPDATE_RESTART;
|
|
else if (this._updateInfo.UpgradeTriggered)
|
|
this._type = DialogType.UPGRADE_RESTART;
|
|
}
|
|
|
|
this._applications = [];
|
|
this._applicationSection.list.destroy_all_children();
|
|
|
|
this._sessions = [];
|
|
this._sessionSection.list.destroy_all_children();
|
|
|
|
if (!(this._type in DialogContent)) {
|
|
invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError',
|
|
"Unknown dialog type requested");
|
|
return;
|
|
}
|
|
|
|
let dialogContent = DialogContent[this._type];
|
|
|
|
for (let i = 0; i < inhibitorObjectPaths.length; i++) {
|
|
let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], proxy => {
|
|
this._onInhibitorLoaded(proxy);
|
|
});
|
|
|
|
this._applications.push(inhibitor);
|
|
}
|
|
|
|
if (dialogContent.showOtherSessions)
|
|
this._loadSessions();
|
|
|
|
let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;
|
|
|
|
_setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText || '');
|
|
this._checkBox.visible = dialogContent.checkBoxText && this._updateInfo.UpdatePrepared && updatesAllowed;
|
|
|
|
if (this._type === DialogType.UPGRADE_RESTART)
|
|
this._checkBox.checked = this._checkBox.visible && this._updateInfo.UpdateTriggered && !this._isDischargingBattery();
|
|
else
|
|
this._checkBox.checked = this._checkBox.visible && !this._isBatteryLow();
|
|
|
|
this._batteryWarning.visible = this._shouldShowLowBatteryWarning(dialogContent);
|
|
|
|
this._updateButtons();
|
|
|
|
if (!this.open(timestamp)) {
|
|
invocation.return_dbus_error('org.gnome.Shell.ModalDialog.GrabError',
|
|
"Cannot grab pointer and keyboard");
|
|
return;
|
|
}
|
|
|
|
if (!dialogContent.disableTimer)
|
|
this._startTimer();
|
|
|
|
this._sync();
|
|
|
|
let signalId = this.connect('opened', () => {
|
|
invocation.return_value(null);
|
|
this.disconnect(signalId);
|
|
});
|
|
}
|
|
|
|
Close(_parameters, _invocation) {
|
|
this.close();
|
|
}
|
|
});
|