diff options
Diffstat (limited to 'extensions/47/hibernate-status/extension.js')
-rw-r--r-- | extensions/47/hibernate-status/extension.js | 590 |
1 files changed, 590 insertions, 0 deletions
diff --git a/extensions/47/hibernate-status/extension.js b/extensions/47/hibernate-status/extension.js new file mode 100644 index 0000000..b0a26c7 --- /dev/null +++ b/extensions/47/hibernate-status/extension.js @@ -0,0 +1,590 @@ +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; + |