590 lines
20 KiB
JavaScript
590 lines
20 KiB
JavaScript
import Gio from 'gi://Gio';
|
|
import GLib from 'gi://GLib';
|
|
import GObject from 'gi://GObject';
|
|
import St from 'gi://St';
|
|
import Clutter from 'gi://Clutter';
|
|
|
|
import * as LoginManager from 'resource:///org/gnome/shell/misc/loginManager.js';
|
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
import * as StatusSystem from 'resource:///org/gnome/shell/ui/status/system.js';
|
|
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
|
import * as ExtensionSystem from 'resource:///org/gnome/shell/ui/extensionSystem.js';
|
|
import * as ModalDialog from 'resource:///org/gnome/shell/ui/modalDialog.js';
|
|
import * as Dialog from 'resource:///org/gnome/shell/ui/dialog.js';
|
|
import * as CheckBoxImport from 'resource:///org/gnome/shell/ui/checkBox.js';
|
|
import {loadInterfaceXML} from 'resource:///org/gnome/shell/misc/fileUtils.js';
|
|
|
|
const CheckBox = CheckBoxImport.CheckBox;
|
|
// Use __ () and N__() for the extension gettext domain, and reuse
|
|
// the shell domain with the default _() and N_()
|
|
import {Extension, gettext as __} from 'resource:///org/gnome/shell/extensions/extension.js';
|
|
export {__};
|
|
const N__ = function (e) {
|
|
return e;
|
|
};
|
|
|
|
const HIBERNATE_CHECK_TIMEOUT = 20000;
|
|
|
|
export default class HibernateButtonExtension extends Extension {
|
|
_loginManagerCanHibernate(asyncCallback) {
|
|
if (this._loginManager._proxy) {
|
|
// systemd path
|
|
this._loginManager._proxy.call(
|
|
'CanHibernate',
|
|
null,
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null,
|
|
function (proxy, asyncResult) {
|
|
let result, error;
|
|
|
|
try {
|
|
result = proxy.call_finish(asyncResult).deep_unpack();
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
|
|
if (error) asyncCallback(false);
|
|
else asyncCallback(!['no', 'na'].includes(result[0]));
|
|
}
|
|
);
|
|
} else {
|
|
this.can_hibernate_sourceID = GLib.idle_add(() => {
|
|
asyncCallback(false);
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
_loginManagerHibernate() {
|
|
if (this._setting.get_boolean('hibernate-works-check')) {
|
|
this._hibernateStarted = new Date();
|
|
this.hibernate_sourceID = GLib.timeout_add(
|
|
GLib.PRIORITY_DEFAULT,
|
|
HIBERNATE_CHECK_TIMEOUT,
|
|
() => this._checkDidHibernate()
|
|
);
|
|
}
|
|
if (this._loginManager._proxy) {
|
|
// systemd path
|
|
this._loginManager._proxy.call(
|
|
'Hibernate',
|
|
GLib.Variant.new('(b)', [true]),
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null,
|
|
null
|
|
);
|
|
} else {
|
|
// Can't do in ConsoleKit
|
|
this._loginManager.emit('prepare-for-sleep', true);
|
|
this._loginManager.emit('prepare-for-sleep', false);
|
|
}
|
|
}
|
|
|
|
_loginManagerCanHybridSleep(asyncCallback) {
|
|
if (this._loginManager._proxy) {
|
|
// systemd path
|
|
this._loginManager._proxy.call(
|
|
'CanHybridSleep',
|
|
null,
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null,
|
|
function (proxy, asyncResult) {
|
|
let result, error;
|
|
|
|
try {
|
|
result = proxy.call_finish(asyncResult).deep_unpack();
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
|
|
if (error) asyncCallback(false);
|
|
else asyncCallback(!['no', 'na'].includes(result[0]));
|
|
}
|
|
);
|
|
} else {
|
|
this.can_hybrid_sleep_sourceID = GLib.idle_add(() => {
|
|
asyncCallback(false);
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
_loginManagerHybridSleep() {
|
|
if (this._loginManager._proxy) {
|
|
// systemd path
|
|
this._loginManager._proxy.call(
|
|
'HybridSleep',
|
|
GLib.Variant.new('(b)', [true]),
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null,
|
|
null
|
|
);
|
|
} else {
|
|
// Can't do in ConsoleKit
|
|
this._loginManager.emit('prepare-for-sleep', true);
|
|
this._loginManager.emit('prepare-for-sleep', false);
|
|
}
|
|
}
|
|
|
|
_loginManagerCanSuspendThenHibernate(asyncCallback) {
|
|
if (this._loginManager._proxy) {
|
|
// systemd path
|
|
this._loginManager._proxy.call(
|
|
'CanSuspendThenHibernate',
|
|
null,
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null,
|
|
function (proxy, asyncResult) {
|
|
let result, error;
|
|
|
|
try {
|
|
result = proxy.call_finish(asyncResult).deep_unpack();
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
|
|
if (error) asyncCallback(false);
|
|
else asyncCallback(!['no', 'na'].includes(result[0]));
|
|
}
|
|
);
|
|
} else {
|
|
this.can_suspend_then_hibernate_sourceID = GLib.idle_add(() => {
|
|
asyncCallback(false);
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
_loginManagerSuspendThenHibernate() {
|
|
if (this._loginManager._proxy) {
|
|
// systemd path
|
|
this._loginManager._proxy.call(
|
|
'SuspendThenHibernate',
|
|
GLib.Variant.new('(b)', [true]),
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null,
|
|
null
|
|
);
|
|
} else {
|
|
// Can't do in ConsoleKit
|
|
this._loginManager.emit('prepare-for-sleep', true);
|
|
this._loginManager.emit('prepare-for-sleep', false);
|
|
}
|
|
}
|
|
|
|
_updateHaveHibernate() {
|
|
this._loginManagerCanHibernate(result => {
|
|
log(`Able to hibernate: ${result}`);
|
|
this._haveHibernate = result;
|
|
this._updateHibernate();
|
|
});
|
|
}
|
|
|
|
_updateHibernate() {
|
|
this._hibernateMenuItem.visible =
|
|
this._haveHibernate && !Main.sessionMode.isLocked && this._setting.get_boolean('show-hibernate');
|
|
}
|
|
|
|
_updateHaveHybridSleep() {
|
|
this._loginManagerCanHybridSleep(result => {
|
|
log(`Able to hybrid-sleep: ${result}`);
|
|
this._haveHybridSleep = result;
|
|
this._updateHybridSleep();
|
|
});
|
|
}
|
|
|
|
_updateHybridSleep() {
|
|
this._hybridSleepMenuItem.visible =
|
|
this._haveHybridSleep && !Main.sessionMode.isLocked && this._setting.get_boolean('show-hybrid-sleep');
|
|
}
|
|
|
|
_updateHaveSuspendThenHibernate() {
|
|
this._loginManagerCanSuspendThenHibernate(result => {
|
|
log(`Able to suspend then hibernate: ${result}`);
|
|
this._haveSuspendThenHibernate = result;
|
|
this._updateSuspendThenHibernate();
|
|
});
|
|
}
|
|
|
|
_updateSuspendThenHibernate() {
|
|
this._suspendThenHibernateMenuItem.visible =
|
|
this._haveSuspendThenHibernate && !Main.sessionMode.isLocked && this._setting.get_boolean('show-suspend-then-hibernate');
|
|
}
|
|
|
|
_updateDefaults() {
|
|
console.log("Update defaults");
|
|
let menuItems = this.systemMenu._systemItem.menu._getMenuItems()
|
|
for (let menuItem of menuItems) {
|
|
console.log(menuItem.label.get_text())
|
|
if ( menuItem.label.get_text() === _('Suspend') ) {
|
|
console.log(`Show suspend button: ${this._setting.get_boolean('show-suspend')}`)
|
|
menuItem.visible = this._setting.get_boolean('show-suspend');
|
|
}
|
|
if ( menuItem.label.get_text() === _('Restart…') ) {
|
|
console.log(`Show restart button: ${this._setting.get_boolean('show-restart')}`)
|
|
menuItem.visible = this._setting.get_boolean('show-restart');
|
|
}
|
|
if ( menuItem.label.get_text() === _('Power Off…') ) {
|
|
console.log(`Show shutdown button: ${this._setting.get_boolean('show-shutdown')}`)
|
|
menuItem.visible = this._setting.get_boolean('show-shutdown');
|
|
}
|
|
}
|
|
}
|
|
|
|
_onHibernateClicked() {
|
|
this.systemMenu._systemItem.menu.itemActivated();
|
|
|
|
if (this._setting.get_boolean('show-hibernate-dialog')) {
|
|
let HibernateDialogContent = {
|
|
subject: C_('title', __('Hibernate')),
|
|
description: __('Do you really want to hibernate the system?'),
|
|
confirmButtons: [
|
|
{
|
|
signal: 'Cancel',
|
|
label: C_('button', __('Cancel')),
|
|
key: Clutter.Escape,
|
|
},
|
|
{
|
|
signal: 'ConfirmedHibernate',
|
|
label: C_('button', __('Hibernate')),
|
|
default: true,
|
|
},
|
|
],
|
|
};
|
|
|
|
this._dialog = new ConfirmDialog(
|
|
HibernateDialogContent
|
|
);
|
|
this._dialog.connect('ConfirmedHibernate', () =>
|
|
this._loginManagerHibernate()
|
|
);
|
|
this._dialog.open();
|
|
} else {
|
|
this._loginManagerHibernate()
|
|
}
|
|
}
|
|
|
|
_onHybridSleepClicked() {
|
|
this.systemMenu._systemItem.menu.itemActivated();
|
|
this._loginManagerHybridSleep();
|
|
}
|
|
|
|
_onSuspendThenHibernateClicked() {
|
|
this.systemMenu._systemItem.menu.itemActivated();
|
|
this._loginManagerSuspendThenHibernate();
|
|
}
|
|
|
|
_disableExtension() {
|
|
Main.extensionManager.disableExtension('hibernate-status@dromi')
|
|
console.log('Disabled')
|
|
}
|
|
|
|
_cancelDisableExtension(notAgain) {
|
|
if (notAgain) this.setHibernateWorksCheckEnabled(false);
|
|
}
|
|
|
|
_checkRequirements() {
|
|
if (GLib.access('/run/systemd/seats', 0) < 0) {
|
|
let SystemdMissingDialogContent = {
|
|
subject: C_('title', __('Hibernate button: Systemd Missing')),
|
|
description: __('Systemd seems to be missing and is required.'),
|
|
confirmButtons: [
|
|
{
|
|
signal: 'Cancel',
|
|
label: C_('button', __('Cancel')),
|
|
key: Clutter.Escape,
|
|
},
|
|
{
|
|
signal: 'DisableExtension',
|
|
label: C_('button', __('Disable Extension')),
|
|
default: true,
|
|
},
|
|
],
|
|
iconName: 'document-save-symbolic',
|
|
iconStyleClass: 'end-session-dialog-shutdown-icon',
|
|
};
|
|
|
|
this._dialog = new ConfirmDialog(
|
|
SystemdMissingDialogContent
|
|
);
|
|
this._dialog.connect('DisableExtension', this._disableExtension);
|
|
this._dialog.open();
|
|
}
|
|
}
|
|
|
|
_checkDidHibernate() {
|
|
/* This function is called HIBERNATE_CHECK_TIMEOUT ms after
|
|
* hibernate started. If it is successful, at that point the GS
|
|
* process is already frozen; so when this function is actually
|
|
* called, way more than HIBERNATE_CHECK_TIMEOUT ms are passed*/
|
|
if (
|
|
new Date() - this._hibernateStarted >
|
|
HIBERNATE_CHECK_TIMEOUT + 5000
|
|
) {
|
|
// hibernate succeeded
|
|
return;
|
|
}
|
|
// hibernate failed
|
|
|
|
let HibernateFailedDialogContent = {
|
|
subject: C_('title', __('Hibernate button: Hibernate failed')),
|
|
description: __(
|
|
'Looks like hibernation failed. On some linux distributions hibernation is disabled ' +
|
|
'because not all hardware supports it well; ' +
|
|
'please check your distribution documentation ' +
|
|
'on how to enable it.'
|
|
),
|
|
checkBox: __("You are wrong, don't check this anymore!"),
|
|
confirmButtons: [
|
|
{
|
|
signal: 'Cancel',
|
|
label: C_('button', __('Cancel')),
|
|
key: Clutter.Escape,
|
|
},
|
|
{
|
|
signal: 'DisableExtension',
|
|
label: C_('button', __('Disable Extension')),
|
|
default: true,
|
|
},
|
|
],
|
|
iconName: 'document-save-symbolic',
|
|
iconStyleClass: 'end-session-dialog-shutdown-icon',
|
|
}
|
|
this._dialog = new ConfirmDialog(
|
|
HibernateFailedDialogContent
|
|
);
|
|
this._dialog.connect('DisableExtension', this._disableExtension);
|
|
this._dialog.connect('Cancel', this._cancelDisableExtension);
|
|
this._dialog.open();
|
|
}
|
|
|
|
setHibernateWorksCheckEnabled(enabled) {
|
|
let key = 'hibernate-works-check';
|
|
if (this._setting.is_writable(key)) {
|
|
if (this._setting.set_boolean(key, enabled)) {
|
|
Gio.Settings.sync();
|
|
} else {
|
|
throw this._errorSet(key);
|
|
}
|
|
} else {
|
|
throw this._errorWritable(key);
|
|
}
|
|
}
|
|
|
|
_modifySystemItem() {
|
|
this._setting = this.getSettings()
|
|
this._checkRequirements();
|
|
this._loginManager = LoginManager.getLoginManager();
|
|
this.systemMenu = Main.panel.statusArea.quickSettings._system;
|
|
this._hibernateMenuItem = new PopupMenu.PopupMenuItem(__('Hibernate'));
|
|
this._hibernateMenuItemId = this._hibernateMenuItem.connect(
|
|
'activate',
|
|
() => this._onHibernateClicked()
|
|
);
|
|
|
|
this._hybridSleepMenuItem = new PopupMenu.PopupMenuItem(
|
|
__('Hybrid Sleep')
|
|
);
|
|
this._hybridSleepMenuItemId = this._hybridSleepMenuItem.connect(
|
|
'activate',
|
|
() => this._onHybridSleepClicked()
|
|
);
|
|
|
|
this._suspendThenHibernateMenuItem = new PopupMenu.PopupMenuItem(
|
|
__('Suspend then Hibernate')
|
|
);
|
|
this._suspendThenHibernateMenuItemId = this._suspendThenHibernateMenuItem.connect(
|
|
'activate',
|
|
() => this._onSuspendThenHibernateClicked()
|
|
);
|
|
|
|
let afterSuspendPosition =
|
|
this.systemMenu._systemItem.menu.numMenuItems - 5;
|
|
|
|
this.systemMenu._systemItem.menu.addMenuItem(
|
|
this._hybridSleepMenuItem,
|
|
afterSuspendPosition
|
|
);
|
|
this.systemMenu._systemItem.menu.addMenuItem(
|
|
this._hibernateMenuItem,
|
|
afterSuspendPosition
|
|
);
|
|
this.systemMenu._systemItem.menu.addMenuItem(
|
|
this._suspendThenHibernateMenuItem,
|
|
afterSuspendPosition
|
|
);
|
|
|
|
this._menuOpenStateChangedId = this.systemMenu._systemItem.menu.connect(
|
|
'open-state-changed',
|
|
(menu, open) => {
|
|
if (!open) return;
|
|
this._updateDefaults();
|
|
this._updateHaveHibernate();
|
|
this._updateHaveHybridSleep();
|
|
this._updateHaveSuspendThenHibernate();
|
|
}
|
|
);
|
|
}
|
|
|
|
_queueModifySystemItem() {
|
|
this.sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
|
|
if (!Main.panel.statusArea.quickSettings._system)
|
|
return GLib.SOURCE_CONTINUE;
|
|
|
|
this._modifySystemItem();
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
}
|
|
|
|
enable() {
|
|
if (!Main.panel.statusArea.quickSettings._system) {
|
|
this._queueModifySystemItem();
|
|
} else {
|
|
this._modifySystemItem();
|
|
}
|
|
}
|
|
|
|
disable() {
|
|
this._setting = null;
|
|
if (this._menuOpenStateChangedId) {
|
|
this.systemMenu._systemItem.menu.disconnect(
|
|
this._menuOpenStateChangedId
|
|
);
|
|
this._menuOpenStateChangedId = 0;
|
|
}
|
|
|
|
if (this._suspendThenHibernateMenuItemId) {
|
|
this._suspendThenHibernateMenuItem.disconnect(this._suspendThenHibernateMenuItemId);
|
|
this._suspendThenHibernateMenuItemId = 0;
|
|
}
|
|
|
|
if (this._hybridSleepMenuItemId) {
|
|
this._hybridSleepMenuItem.disconnect(this._hybridSleepMenuItemId);
|
|
this._hybridSleepMenuItemId = 0;
|
|
}
|
|
|
|
if (this._hibernateMenuItemId) {
|
|
this._hibernateMenuItem.disconnect(this._hibernateMenuItemId);
|
|
this._hibernateMenuItemId = 0;
|
|
}
|
|
|
|
if (this._suspendThenHibernateMenuItem) {
|
|
this._suspendThenHibernateMenuItem.destroy();
|
|
this._suspendThenHibernateMenuItem = 0;
|
|
}
|
|
|
|
if (this._hybridSleepMenuItem) {
|
|
this._hybridSleepMenuItem.destroy();
|
|
this._hybridSleepMenuItem = 0;
|
|
}
|
|
|
|
if (this._hibernateMenuItem) {
|
|
this._hibernateMenuItem.destroy();
|
|
this._hibernateMenuItem = 0;
|
|
}
|
|
|
|
if (this.sourceId) {
|
|
GLib.Source.remove(this.sourceId);
|
|
this.sourceId = null;
|
|
}
|
|
|
|
if (this.can_suspend_then_hibernate_sourceID) {
|
|
GLib.Source.remove(this.can_suspend_then_hibernate_sourceID);
|
|
this.can_suspend_then_hibernate_sourceID = null;
|
|
}
|
|
|
|
if (this.can_hybrid_sleep_sourceID) {
|
|
GLib.Source.remove(this.can_hybrid_sleep_sourceID);
|
|
this.can_hybrid_sleep_sourceID = null;
|
|
}
|
|
|
|
if (this.can_hibernate_sourceID) {
|
|
GLib.Source.remove(this.can_hibernate_sourceID);
|
|
this.can_hibernate_sourceID = null;
|
|
}
|
|
|
|
if (this.hibernate_sourceID) {
|
|
GLib.Source.remove(this.hibernate_sourceID);
|
|
this.hibernate_sourceID = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
var ConfirmDialog = GObject.registerClass(
|
|
{
|
|
Signals: {
|
|
ConfirmedHibernate: {param_types: [GObject.TYPE_BOOLEAN]},
|
|
DisableExtension: {param_types: [GObject.TYPE_BOOLEAN]},
|
|
Cancel: {param_types: [GObject.TYPE_BOOLEAN]},
|
|
},
|
|
},
|
|
class ConfirmDialog extends ModalDialog.ModalDialog {
|
|
_init(dialog) {
|
|
super._init({
|
|
styleClass: 'end-session-dialog',
|
|
destroyOnClose: true,
|
|
});
|
|
|
|
|
|
this._messageDialogContent = new Dialog.MessageDialogContent();
|
|
|
|
|
|
this._messageDialogContent.description = dialog.description;
|
|
this._messageDialogContent.title = dialog.subject;
|
|
|
|
if (dialog.iconName) {
|
|
this._icon = new St.Icon({
|
|
icon_name: dialog.iconName,
|
|
icon_size: _DIALOG_ICON_SIZE,
|
|
style_class: dialog.iconStyleClass,
|
|
});
|
|
}
|
|
|
|
if (dialog.checkBox) {
|
|
this._checkBox = new CheckBox(dialog.checkBox);
|
|
this._messageDialogContent.add(this._checkBox.actor);
|
|
}
|
|
|
|
this.contentLayout.add_child(this._messageDialogContent);
|
|
|
|
let buttons = [];
|
|
for (let i = 0; i < dialog.confirmButtons.length; i++) {
|
|
let signal = dialog.confirmButtons[i].signal;
|
|
let label = dialog.confirmButtons[i].label;
|
|
let keys = dialog.confirmButtons[i].key;
|
|
buttons.push({
|
|
action: () => {
|
|
let signalId = this.connect('closed', () => {
|
|
this.disconnect(signalId);
|
|
this._confirm(signal);
|
|
});
|
|
this.close();
|
|
},
|
|
label: label,
|
|
key: keys,
|
|
});
|
|
}
|
|
|
|
this.setButtons(buttons);
|
|
}
|
|
|
|
_confirm(signal) {
|
|
var checked;
|
|
if (this._checkBox) checked = this._checkBox.actor.get_checked();
|
|
this.emit(signal, checked);
|
|
}
|
|
|
|
cancel() {
|
|
this.close();
|
|
}
|
|
}
|
|
);
|
|
|
|
const _DIALOG_ICON_SIZE = 32;
|
|
|