Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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/
Expand Down
11 changes: 11 additions & 0 deletions customreboot/42_custom_reboot
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions customreboot/bootloader.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
102 changes: 102 additions & 0 deletions customreboot/efibootmgr.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
205 changes: 205 additions & 0 deletions customreboot/grub.js
Original file line number Diff line number Diff line change
@@ -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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update-grub command has been replaced by grub-mkconfig: https://git.savannah.gnu.org/cgit/grub.git/tree/NEWS#n537

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change this, thanks.

]);

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;
}
}
}
Loading