diff --git a/Makefile b/Makefile index f74ed54..af03d09 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ UUID = hibernate-status@dromi BASE_MODULES = extension.js metadata.json LICENSE README.md EXTRA_MODULES = prefs.js +CUSTOM_REBOOT_MODULES = customreboot TOLOCALIZE = confirmDialog.js prefs.js PO_FILES := $(wildcard ./locale/*/*/*.po) MO_FILES := $(PO_FILES:.po=.mo) @@ -69,7 +70,7 @@ zip-file: _build _build: all -rm -fR ./_build mkdir -p _build - cp $(BASE_MODULES) $(EXTRA_MODULES) _build + cp -r $(BASE_MODULES) $(EXTRA_MODULES) $(CUSTOM_REBOOT_MODULES) _build mkdir -p _build/schemas cp schemas/*.xml _build/schemas/ cp schemas/gschemas.compiled _build/schemas/ diff --git a/customreboot/42_custom_reboot b/customreboot/42_custom_reboot new file mode 100644 index 0000000..638d768 --- /dev/null +++ b/customreboot/42_custom_reboot @@ -0,0 +1,11 @@ +#! /bin/sh +# This script allows the extension to skip grub's timeout when rebooting + +set -e + +cat << EOF +if [ ! -z "\${boot_once}" ] ; then + set timeout_style=hidden + set timeout=0 +fi +EOF \ No newline at end of file diff --git a/customreboot/bootloader.js b/customreboot/bootloader.js new file mode 100644 index 0000000..f371e23 --- /dev/null +++ b/customreboot/bootloader.js @@ -0,0 +1,41 @@ +import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; +import { EFIBootManager } from "./efibootmgr.js"; +import { SystemdBoot } from './systemdBoot.js'; +import { Grub } from './grub.js'; + +import * as Utils from "./utils.js"; +import GLib from 'gi://GLib'; +import Gio from "gi://Gio"; + +export const BootLoaders = { + EFI: "EFI Boot Manager", + GRUB: "Grub", + SYSD: "Systemd Boot", + UNKNOWN: "Unknown Boot Loader" +} + +export class Bootloader { + /** + * Gets the first available boot loader type on the current system + * @returns BootLoaders type. Can be "EFI", "SYSD", "GRUB", or "UNKNOWN" + */ + static async GetUseableType() { + const settings = Extension.lookupByUUID('hibernate-status@dromi').getSettings('org.gnome.shell.extensions.hibernate-status-button'); + + if (await EFIBootManager.IsUseable() && settings.get_boolean('use-efibootmgr')) return BootLoaders.EFI; + if (await Grub.IsUseable() && settings.get_boolean('use-grub')) return BootLoaders.GRUB; + if (await SystemdBoot.IsUseable() && settings.get_boolean('use-systemd-boot')) return BootLoaders.SYSD; + return BootLoaders.UNKNOWN; + } + + /** + * Gets a instance of the provided boot loader + * @returns A boot loader if one is found otherwise undefined + */ + static async GetUseable(type) { + if (type === BootLoaders.EFI) return EFIBootManager; + if (type === BootLoaders.SYSD) return SystemdBoot; + if (type === BootLoaders.GRUB) return Grub; + return undefined; + } +} diff --git a/customreboot/efibootmgr.js b/customreboot/efibootmgr.js new file mode 100644 index 0000000..1075770 --- /dev/null +++ b/customreboot/efibootmgr.js @@ -0,0 +1,102 @@ +import Gio from "gi://Gio"; +import { ExecCommand, Log, LogWarning } from './utils.js'; + +/** + * Represents efibootmgr + */ +export class EFIBootManager { + /** + * Get's all available boot options + * @returns {[Map, string]} Map(title, id), defaultOption + */ + static async GetBootOptions() { + const [status, stdout, stderr] = await ExecCommand(['efibootmgr'],); + const lines = stdout.split("\n"); + + let boot_first = "0000"; + + const boot_options = new Map(); + + for (let l = 0; l < lines.length; l++) { + const line = lines[l]; + if (line.startsWith("BootOrder:")) { + boot_first = line.split(" ")[1].split(",")[0]; + continue; + } + + const regex = /(Boot[0-9]{4})/; + const vLine = regex.exec(line) + if (vLine && vLine.length) { + const option = line.replace("Boot", "").replace("*"," ").split(" "); + var title = option[1]; + //title = title.split("\t")[0] + if (title.includes("HD") || title.includes("RC") || title.includes("PciRoot")) { + const trimed_title = title.replace(/(?<=[\S\s]*)(HD|RC|PciRoot)([\s\S()]*|$)/, "").trim(); + boot_options.set(trimed_title, option[0].trim()); + } + else { + boot_options.set(option[1].trim(), option[0].trim()); + } + } + } + + return [boot_options, boot_first]; + } + + /** + * Set's the next boot option + * @param {string} id + * @returns True if the boot option was set, otherwise false + */ + static async SetBootOption(id) { + if (!this.IsUseable()) return false; + const [status, stdout, stderr] = await ExecCommand(['/usr/bin/pkexec', 'efibootmgr', '-n', id],); + if (status === 0) { + Log(`Set boot option to ${id}`); + return true; + } + LogWarning("Unable to set boot option using efibootmgr"); + return false; + } + + /** + * Can we use this bootloader? + * @returns True if useable otherwise false + */ + static async IsUseable() { + return await this.GetBinary() !== ""; + } + + /** + * Get's efibootmgr binary path + * @returns A string containing the location of the binary, if none is found returns a blank string + */ + static async GetBinary() { + let paths = ["/usr/bin/efibootmgr"]; + + let file; + + for (let i = 0; i < paths.length; i++) { + file = Gio.file_new_for_path(paths[i]); + if (file.query_exists(null)) { + return paths[i]; + } + } + + return ""; + } + + /** + * This boot loader cannot be quick rebooted + */ + static async CanQuickReboot() { + return false; + } + + /** + * This boot loader cannot be quick rebooted + */ + static async QuickRebootEnabled() { + return false; + } +} diff --git a/customreboot/grub.js b/customreboot/grub.js new file mode 100644 index 0000000..2a05e84 --- /dev/null +++ b/customreboot/grub.js @@ -0,0 +1,205 @@ +import Gio from "gi://Gio"; +import { ExecCommand, Log, LogWarning } from './utils.js'; +import { Extension } from "resource:///org/gnome/shell/extensions/extension.js" +/** + * Represents grub + */ +export class Grub { + /** + * Get's all available boot options + * @returns {[Map, string]} Map(title, id), defaultOption + */ + static async GetBootOptions() { + try { + let cfgpath = await this.GetConfig(); + if (cfgpath == "") { + throw new String("Failed to find grub config"); + } + + let bootOptions = new Map(); + + let defualtEn = ""; + + let file = Gio.file_new_for_path(cfgpath); + let [suc, content] = file.load_contents(null); + if (!suc) { + throw new String("Failed to load grub config"); + } + + let lines; + if (content instanceof Uint8Array) { + lines = new TextDecoder().decode(content); + } + else { + lines = content.toString(); + } + + let entryRx = /^menuentry ['"]([^'"]+)/; + let defaultRx = /(?<=set default=\")([A-Za-z- ()/0-9]*)(?=\")/ + lines.split('\n').forEach(l => { + let res = entryRx.exec(l); + if (res && res.length) { + bootOptions.set(res[1], res[1]); + } + let def = defaultRx.exec(l); + if (def && def.length) { + defualtEn = def[0]; + } + }); + + bootOptions.forEach((v, k) => { + Log(`${k} = ${v}`); + }); + + if (defualtEn == "") defualtEn = bootOptions.keys().next().value; + + return [bootOptions, defualtEn]; + + } catch (e) { + LogWarning(e); + return undefined; + } + } + + /** + * Set's the next boot option + * @param {string} id + * @returns True if the boot option was set, otherwise false + */ + static async SetBootOption(id) { + try { + let [status, stdout, stderr] = await ExecCommand( + ['/usr/bin/pkexec', '/usr/sbin/grub-reboot', id], + ); + Log(`Set boot option to ${id}: ${status}\n${stdout}\n${stderr}`); + return true; + } catch (e) { + LogWarning(e); + return false; + } + } + + /** + * Can we use this bootloader? + * @returns True if useable otherwise false + */ + static async IsUseable() { + return await this.GetConfig() !== ""; + } + + /** + * Get's grub config file + * @returns A string containing the location of the config file, if none is found returns a blank string + */ + static async GetConfig() { + let paths = ["/boot/grub/grub.cfg", "/boot/grub2/grub.cfg"]; + + let file; + + for (let i = 0; i < paths.length; i++) { + file = Gio.file_new_for_path(paths[i]); + if (file.query_exists(null)) { + return paths[i]; + } + } + + return ""; + } + + /** + * Copies a custom grub script to allow the extension to quickly reboot into another OS + * If anyone reads this: Idk how to combine these into one pkexec call, if you do please leave a commit fixing it + */ + static async EnableQuickReboot(ext) { + try { + let [status, stdout, stderr] = await ExecCommand([ + 'pkexec', + 'sh', + '-c', + `/usr/bin/cp ${Extension.lookupByUUID('hibernate-status@dromi').path}/customreboot/42_custom_reboot /etc/grub.d/42_custom_reboot && /usr/bin/chmod 755 /etc/grub.d/42_custom_reboot && /usr/sbin/update-grub` + ]); + + if (status !== 0) { + return false; + } + + return true; + } + catch (e) { + LogWarning(e); + return false; + } + } + + + /** + * Removes the script used to allow the extension to quickly reboot into another OS without waiting for grub's timeout + * If anyone reads this: Idk how to combine these into one pkexec call, if you do please leave a commit fixing it + */ + static async DisableQuickReboot() { + try { + + let [status, stdout, stderr] = await ExecCommand([ + 'pkexec', + 'sh', + '-c', + '/usr/bin/rm /etc/grub.d/42_custom_reboot && /usr/sbin/update-grub' + ]); + + if (status !== 0) { + return false; + } + + return true; + } + catch (e) { + LogWarning(e); + return false; + } + } + + + /** + * This boot loader can be quick rebooted + */ + static async CanQuickReboot() { + return true; + } + + /** + * Checks if /etc/grub.d/42_custom_reboot exists + */ + static async QuickRebootEnabled() { + try { + let [status, stdout, stderr] = await ExecCommand(['/usr/bin/cat', '/etc/grub.d/42_custom_reboot'],); + if (status !== 0) { + LogWarning(`/etc/grub.d/42_custom_reboot not found`); + return false; + } + Log(`/etc/grub.d/42_custom_reboot found`); + + return true; + } + catch (e) { + LogWarning(e); + return false; + } + } + + static async SetReadable() { + try { + const config = GetConfig(); + let [status, stdout, stderr] = await ExecCommand(['/usr/bin/pkexec', '/usr/bin/chmod', '644', config],); + if (status !== 0) { + Log(`Failed to make ${config} readable`); + return false; + } + Log(`Made ${config} readable`); + return true; + } + catch (e) { + Log(e); + return false; + } + } +} diff --git a/customreboot/menu.js b/customreboot/menu.js new file mode 100644 index 0000000..65096e5 --- /dev/null +++ b/customreboot/menu.js @@ -0,0 +1,166 @@ +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; +import * as QuickSettings from 'resource:///org/gnome/shell/ui/quickSettings.js'; + + +import { Extension, gettext as __ } from "resource:///org/gnome/shell/extensions/extension.js"; +import { getDefault } from "resource:///org/gnome/shell/misc/systemActions.js"; +import Gio from "gi://Gio"; +import GObject from "gi://GObject"; +import St from 'gi://St'; +import Clutter from 'gi://Clutter'; +import { PACKAGE_VERSION } from "resource:///org/gnome/shell/misc/config.js"; + +// Import Utils class + +//import +import { SetDebug, LogWarning, Log } from './utils.js'; +import { BootLoaders, Bootloader } from "./bootloader.js"; + +export const RebootSubMenu = GObject.registerClass( +class RebootSubMenu extends PopupMenu.PopupSubMenuMenuItem { + + _init() { + super._init(__('Restart to…')); + + // Add boot options to menu + try { + this.createBootMenu(); + } + catch (e) { + LogWarning(e); + } + } + + async createBootMenu() { + // Get boot options + const type = await Bootloader.GetUseableType(); + console.log(`Using ${type}`) + + const loader = await Bootloader.GetUseable(type); + + this.section = new PopupMenu.PopupMenuSection(); + + this.menu.open = (animate) => { + const heightLimit = 250 + if (this.menu.isOpen) + return; + + if (this.menu.isEmpty()) + return; + + this.menu.isOpen = true; + this.menu.emit('open-state-changed') + + this.menu.actor.show() + + let targetAngle = this.menu.actor.text_direction === Clutter.TextDirection.RTL ? -90 : 90; + let [, naturalHeight] = this.section.actor.get_preferred_height(-1) + if (naturalHeight > heightLimit) { + animate = false + naturalHeight = heightLimit; + this.menu.actor.vscrollbar_policy = St.PolicyType.AUTOMATIC; + this.menu.actor.add_style_pseudo_class('scrolled') + } else { + this.menu.actor.vscrollbar_policy = St.PolicyType.NEVER; + this.menu.actor.remove_style_pseudo_class('scrolled') + } + const duration = animate ? 250 : 0; + this.menu.actor.height = 0 + this.menu.actor.ease({ + height: naturalHeight, + duration, + mode:Clutter.AnimationMode.EASE_OUT_EXPO, + onComplete: () => this.menu.actor.set_height(naturalHeight), + }); + this.menu._arrow.ease({ + rotation_angle_z: targetAngle, + duration, + mode: Clutter.AnimationMode.EASE_OUT_EXPO, + }); + } + + this.menu.addMenuItem(this.section) + + if (loader === undefined) { + // Add reload option, to refresh extension menu without reloading GNOME or the extension + this.section.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this.section.addAction('Reload', () => { + this.section.removeAll(); + this.createBootMenu(); + }); + + // Add button to open settings + this.section.addAction('Settings', () => { + Extension.lookupByUUID('hibernate-status@dromi').openPreferences(); + }); + + return; + } + + loader.GetBootOptions().then(([bootOps, defaultOpt]) => { + if (bootOps !== undefined) { + this._itemsSection = new PopupMenu.PopupMenuSection(); + + for (let [title, id] of bootOps) { + Log(`${title} - ${id}`); + this._itemsSection.addAction(String(title), () => { + // Set boot option + loader.SetBootOption(String(id)).then(result => { + if (result) { + // On success trigger restart dialog + new getDefault().activateRestart(); + } + }); + }, (title === defaultOpt || id === defaultOpt)? "pan-end-symbolic" : undefined); + } + + this.section.addMenuItem(this._itemsSection); + } + + // Add reload option, to refresh extension menu without reloading GNOME or the extension + this.section.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + this.section.addAction('Reload', () => { + this.section.removeAll(); + this.createBootMenu(); + }); + + // Add button to open settings + this.section.addAction('Settings', () => { + Extension.lookupByUUID('hibernate-status@dromi').openPreferences(); + }); + + loader.CanQuickReboot().then(async result => { + if (!result) return; + if (!await loader.QuickRebootEnabled()) { + this.section.addAction('Enable Quick Reboot', async () => { + await loader.EnableQuickReboot(Extension.lookupByUUID('hibernate-status@dromi')); + this.section.removeAll(); + this.createBootMenu(); + }); + } + else { + this.section.addAction('Disable Quick Reboot', async () => { + await loader.DisableQuickReboot(); + this.section.removeAll(); + this.createBootMenu(); + }); + } + }); + + }).catch((error) => { + LogWarning(error); + // Only do this if the current bootloader is grub + if (type === BootLoaders.GRUB) + { + // Only add this if all fails, giving user option to make the config readable + this.section.addMenuItem(new PopupSeparatorMenuItem()); + this.section.addAction('Fix grub.cfg Perms', async () => { + await loader.SetReadable(); + this.section.removeAll(); + this.createBootMenu(extension); + }); + } + }) + } +}); diff --git a/customreboot/systemdBoot.js b/customreboot/systemdBoot.js new file mode 100644 index 0000000..b7229d3 --- /dev/null +++ b/customreboot/systemdBoot.js @@ -0,0 +1,122 @@ +import Gio from "gi://Gio"; +import { ExecCommand, Log, LogWarning } from './utils.js'; + +/** + * Represents Systemdboot + */ +export class SystemdBoot { + /** + * Get's all available boot options + * @returns {[Map, string]} Map(title, id), defaultOption + */ + static async GetBootOptions() { + let bootctl = await this.GetBinary(); + if (bootctl == "") { + Log(`Failed to find bootctl binary`); + return undefined; + } + + try { + let [status, stdout, stderr] = await ExecCommand([bootctl, "list"]); + if (status !== 0) + throw new Error(`Failed to get list from bootctl: ${status}\n${stdout}\n${stderr}`); + Log(`bootctl list: ${status}\n${stdout}\n${stderr}`); + let lines = String(stdout).split('\n'); + let titleRx = /(?<=title:\s+).+/; + let idRx = /(?<=id:\s+).+/; + let defaultRx = /\(default\)/; + let titles = []; + let ids = [] + let defaultOpt; + lines.forEach(l => { + let title = titleRx.exec(l); + let id = idRx.exec(l); + if (title && title.length) { + titles.push(String(title)); + } else if (id && id.length) { + ids.push(String(id)); + } + }); + if (titles.length !== ids.length) + throw new Error("Number of titles and ids do not match!"); + let bootOptions = new Map(); + for (let i = 0; i < titles.length; i++) { + bootOptions.set(ids[i], titles[i]) + } + + bootOptions.forEach((title, id) => { + Log(`${id} = ${title}`); + + let defaultRes = defaultRx.exec(title); + + if (defaultRes) { + defaultOpt = id; + } + }) + + return [bootOptions, bootOptions.get(defaultOpt)]; + } catch (e) { + LogWarning(e); + return undefined; + } + } + + /** + * Set's the next boot option + * @param {string} id + * @returns True if the boot option was set, otherwise false + */ + static async SetBootOption(id) { + try { + let [status, stdout, stderr] = await ExecCommand( + ['/usr/bin/pkexec', '/usr/sbin/grub-reboot', id], + ); + Log(`Set boot option to ${id}: ${status}\n${stdout}\n${stderr}`); + return true; + } catch (e) { + LogWarning(e); + return false; + } + } + + /** + * Can we use this bootloader? + * @returns True if useable otherwise false + */ + static async IsUseable() { + return await this.GetBinary() !== ""; + } + + /** + * Get's bootctl binary path + * @returns A string containing the location of the binary, if none is found returns a blank string + */ + static async GetBinary() { + let paths = ["/usr/sbin/bootctl", "/usr/bin/bootctl"]; + + let file; + + for (let i = 0; i < paths.length; i++) { + file = Gio.file_new_for_path(paths[i]); + if (file.query_exists(null)) { + return paths[i]; + } + } + + return ""; + } + + /** + * This boot loader cannot be quick rebooted + */ + static async CanQuickReboot() { + return false; + } + + /** + * This boot loader cannot be quick rebooted + */ + static async QuickRebootEnabled() { + return false; + } +} \ No newline at end of file diff --git a/customreboot/utils.js b/customreboot/utils.js new file mode 100644 index 0000000..873a74c --- /dev/null +++ b/customreboot/utils.js @@ -0,0 +1,90 @@ +/* utils.js + * + * Copyright (C) 2020 + * Daniel Shchur (DocQuantum) + * + * 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 3 of the License, 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later +*/ + +import GLib from 'gi://GLib'; +import Gio from "gi://Gio"; + + +var DEBUG = false; + +/** + * @param {String[]} argv + * @param {String} input + * @param {Gio.Cancellable} cancellable + * @returns {Promise} Function execution + * => @returns {[int, String, String]} [StatusCode, STDOUT, STDERR] + * + * Executes a command asynchronously. + */ +export async function ExecCommand(argv, input = null, cancellable = null) { + let flags = Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE; + + if (input !== null) + flags |= Gio.SubprocessFlags.STDIN_PIPE; + + let proc = new Gio.Subprocess({ + argv: argv, + flags: flags + }); + proc.init(cancellable); + return new Promise((resolve,reject) => { + proc.communicate_utf8_async(input, cancellable, (proc, res) => { + try { + resolve([(function() { + if(!proc.get_if_exited()) + throw new Error("Subprocess failed to exit in time!"); + return proc.get_exit_status() + })()].concat(proc.communicate_utf8_finish(res).slice(1))); + } catch (e) { + reject(e); + } + }); + }); +} + +/** + * @param {bool} value + * + * Set's whether to debug or to not. + */ +export function SetDebug(value){ + DEBUG = value; +} + +/** + * @param {string} msg + * + * Logs general messages if debug is set to true. + */ +export function Log(msg) { + if(DEBUG) + console.log(`CustomReboot NOTE: ${msg}`); +} + +/** + * @param {string} msg + * + * Logs warning messages if debug is set to true. + */ +export function LogWarning(msg) { + if(DEBUG) + console.warn(`CustomReboot WARN: ${msg}`); +} diff --git a/extension.js b/extension.js index b0a26c7..337e9c4 100644 --- a/extension.js +++ b/extension.js @@ -18,6 +18,9 @@ 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'; + +import { RebootSubMenu } from "./customreboot/menu.js"; + export {__}; const N__ = function (e) { return e; @@ -404,8 +407,12 @@ export default class HibernateButtonExtension extends Extension { () => this._onSuspendThenHibernateClicked() ); + this._customRestartMenuItem = new RebootSubMenu() + let afterSuspendPosition = this.systemMenu._systemItem.menu.numMenuItems - 5; + let afterRestartPosition = + this.systemMenu._systemItem.menu.numMenuItems - 1; this.systemMenu._systemItem.menu.addMenuItem( this._hybridSleepMenuItem, @@ -419,6 +426,10 @@ export default class HibernateButtonExtension extends Extension { this._suspendThenHibernateMenuItem, afterSuspendPosition ); + this.systemMenu._systemItem.menu.addMenuItem( + this._customRestartMenuItem, + afterRestartPosition + ) this._menuOpenStateChangedId = this.systemMenu._systemItem.menu.connect( 'open-state-changed', @@ -489,6 +500,11 @@ export default class HibernateButtonExtension extends Extension { this._hibernateMenuItem = 0; } + if (this._customRestartMenuItem) { + this._customRestartMenuItem.destroy(); + this._customRestartMenuItem = 0; + } + if (this.sourceId) { GLib.Source.remove(this.sourceId); this.sourceId = null; @@ -588,3 +604,4 @@ var ConfirmDialog = GObject.registerClass( const _DIALOG_ICON_SIZE = 32; + diff --git a/prefs.js b/prefs.js index 3a04ca6..629135b 100644 --- a/prefs.js +++ b/prefs.js @@ -3,7 +3,9 @@ import Gtk from 'gi://Gtk'; import Adw from 'gi://Adw'; // Use __() and N__() for the extension gettext domain, and reuse // the shell domain with the default _() and N_() - +// import { EFIBootManager } from './customreboot/efibootmgr.js'; +// import { Grub } from './customreboot/grub.js'; +// import { SystemdBoot } from './customreboot/systemdBoot.js'; import { ExtensionPreferences, gettext as __, @@ -164,5 +166,93 @@ export default class Prefs extends ExtensionPreferences { Gio.SettingsBindFlags.DEFAULT); window._settings.bind('show-suspend-then-hibernate-dialog', suspend_then_hibernate_dialog_row, 'active', Gio.SettingsBindFlags.DEFAULT); + + const reboot_page = new Adw.PreferencesPage({ + title: __('Custom Reboot'), + icon_name: 'system-reboot-symbolic', + }); + const reboot_group = new Adw.PreferencesGroup(); + reboot_page.add(reboot_group); + + // Create row for efibootmgr + const efi = new Adw.ActionRow({ title: 'Use EFI Boot Manager (efibootmgr)' }); + + // Create row for grub + const grub = new Adw.ActionRow({ title: 'Use Grub'}); + + // Create row for systemd-boot + const sysd = new Adw.ActionRow({ title: 'Use Systemd Boot'}); + + // Add rows + reboot_group.add(efi); + reboot_group.add(grub); + reboot_group.add(sysd); + + let settings = window._settings + + // Create switch for efibootmgr + const efi_switch = new Gtk.Switch({ + active: settings.get_boolean('use-efibootmgr'), + valign: Gtk.Align.CENTER, + }); + + // Create switch for grub + const grub_switch = new Gtk.Switch({ + active: settings.get_boolean('use-grub'), + valign: Gtk.Align.CENTER, + }); + + // Create switch for systemd-boot + const sysd_switch = new Gtk.Switch({ + active: settings.get_boolean('use-systemd-boot'), + valign: Gtk.Align.CENTER, + }); + + // Bind settings for efibootmgr + settings.bind( + 'use-efibootmgr', + efi_switch, + 'active', + Gio.SettingsBindFlags.DEFAULT + ); + + // Bind settings for grub + settings.bind( + 'use-grub', + grub_switch, + 'active', + Gio.SettingsBindFlags.DEFAULT + ); + + // Bind settings for systemd-boot + settings.bind( + 'use-systemd-boot', + sysd_switch, + 'active', + Gio.SettingsBindFlags.DEFAULT + ); + + // Add the switch for efibootmgr + efi.add_suffix(efi_switch); + efi.activatable_widget = efi_switch; + + // Add the switch for grub + grub.add_suffix(grub_switch); + grub.activatable_widget = grub_switch; + + // Add the switch for systemd-boot + sysd.add_suffix(sysd_switch); + sysd.activatable_widget = sysd_switch; + + // Add our page to the window + window.add(reboot_page); + + //(async () => { + // Disable/enable switches in accordance to them being usable + + // efi_switch.set_sensitive(await EFIBootManager.IsUseable()); + // grub_switch.set_sensitive(await Grub.IsUseable()); + // sysd_switch.set_sensitive(await SystemdBoot.IsUseable()); + //})(); } } diff --git a/schemas/org.gnome.shell.extensions.hibernate-status-button.gschema.xml b/schemas/org.gnome.shell.extensions.hibernate-status-button.gschema.xml index 242d9cc..4cbee42 100644 --- a/schemas/org.gnome.shell.extensions.hibernate-status-button.gschema.xml +++ b/schemas/org.gnome.shell.extensions.hibernate-status-button.gschema.xml @@ -31,5 +31,14 @@ false + + true + + + false + + + false +