From 7ed525a679aaf46e1bbff7c28c8e7ec3b31c36d3 Mon Sep 17 00:00:00 2001 From: Matt Jiang <67533680+matthewyjiang@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:59:56 -0700 Subject: [PATCH] fix(powermenu): show inactive EFI boot entries in reboot-to-OS picker (#3969) port 2.0 (cherry picked from commit fdebf6d9e66fa4270784dc8009a3aacc283a90ac) --- quickshell/Services/BootEntries.js | 10 ++++++---- quickshell/Services/BootEntryService.qml | 2 +- quickshell/tests/boot-entries.test.mjs | 10 +++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/quickshell/Services/BootEntries.js b/quickshell/Services/BootEntries.js index 9be841323..9073f1fe5 100644 --- a/quickshell/Services/BootEntries.js +++ b/quickshell/Services/BootEntries.js @@ -1,7 +1,9 @@ .pragma library -// Reads efibootmgr output into the current boot id and the active entries as [{id, label}], +// Reads efibootmgr output into the current boot id and the entries as [{id, label}], // where id is the upper-case hex Boot#### number. +// Inactive entries stay: dual-booters often deactivate Windows so it stops rewriting +// BootOrder, and BootNext still boots them. function parseEntries(text) { const result = { currentId: "", @@ -14,12 +16,12 @@ function parseEntries(text) { continue; } // efibootmgr 18+ appends a tab and the device path after the label - const entry = line.match(/^Boot([0-9A-Fa-f]{4})(\*?)\s+([^\t]+)/); - if (!entry || entry[2] !== "*") + const entry = line.match(/^Boot([0-9A-Fa-f]{4})\*?\s+([^\t]+)/); + if (!entry) continue; result.entries.push({ id: entry[1].toUpperCase(), - label: entry[3].trim() + label: entry[2].trim() }); } return result; diff --git a/quickshell/Services/BootEntryService.qml b/quickshell/Services/BootEntryService.qml index c2c5c2a10..8f5bc5f6c 100644 --- a/quickshell/Services/BootEntryService.qml +++ b/quickshell/Services/BootEntryService.qml @@ -20,7 +20,7 @@ Singleton { property string status: "unknown" // efibootmgr's stderr when status is "error" property string errorText: "" - // Active entries as [{id, label}], where id is the hex Boot#### number + // Entries as [{id, label}], where id is the hex Boot#### number property var entries: [] property string currentId: "" diff --git a/quickshell/tests/boot-entries.test.mjs b/quickshell/tests/boot-entries.test.mjs index 34340bb03..cfd65050b 100644 --- a/quickshell/tests/boot-entries.test.mjs +++ b/quickshell/tests/boot-entries.test.mjs @@ -7,8 +7,9 @@ const bootEntries = vm.createContext({}); vm.runInContext(readFileSync(new URL("../Services/BootEntries.js", import.meta.url), "utf8").replace(/^\.pragma.*$/m, ""), bootEntries); const plain = value => JSON.parse(JSON.stringify(value)); -test("parses active entries and the current boot from efibootmgr output", () => { - // efibootmgr 18 prints the device path after a tab, older releases print only the label +test("parses active and inactive entries and the current boot from efibootmgr output", () => { + // efibootmgr 18 prints the device path after a tab, older releases print only the label. + // Inactive entries have no "*" and stay listed, since BootNext still boots them. const output = [ "BootCurrent: 0001", "Timeout: 1 seconds", @@ -17,6 +18,7 @@ test("parses active entries and the current boot from efibootmgr output", () => "Boot0001* Limine\tHD(1,GPT,c28482eb,0x1000,0x830000)/\\EFI\\LIMINE\\LIMINE_X64.EFI", "Boot0010 UEFI OS\tHD(1,GPT,c28482eb,0x1000,0x830000)/\\EFI\\BOOT\\BOOTX64.EFI", "Boot001a* Fedora", + "Boot001B Ubuntu", "" ].join("\n"); @@ -26,7 +28,9 @@ test("parses active entries and the current boot from efibootmgr output", () => assert.deepEqual(plain(parsed.entries), [ { id: "0000", label: "Windows Boot Manager" }, { id: "0001", label: "Limine" }, - { id: "001A", label: "Fedora" } + { id: "0010", label: "UEFI OS" }, + { id: "001A", label: "Fedora" }, + { id: "001B", label: "Ubuntu" } ]); });