feat(nextboot-selector): add nextboot-selector plugin (#36)
* feat(nextboot-selector): add nextboot-selector plugin * fix(nextboot-selector): change readme * fix(nextboot-selector): change readme * fix(nextboot-selector): change en.json structure * fix(nextboot-selector): check command result before rebooting and notify if error * fix(nextboot-selector): add reboot and sudo as dependencies
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
# Next Boot Selector
|
||||||
|
|
||||||
|
Next Boot Selector selects which boot entry to boot for the next reboot only. Useful if you're dual-booting and goes back and forth to/from Windows.
|
||||||
|
|
||||||
|
## Plugin
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | `avivbintangaringga/nextboot-selector` |
|
||||||
|
| Entries | Bar widget: `nextboot-selector`; panel: `panel` |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
This plugin mainly requires `efibootmgr` but need to escalate using `pkexec` or `sudo`. Note that if you use `sudo`, you need to set the option `NOPASSWD` for the `efibootmgr` command so it doesn't requires password to run. `reboot` command is also required if reboot on select is enabled.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add the `nextboot-selector` widget to a bar. Click it to show a panel that lists all boot entries on your computer. Click one of the entry to change the next boot to it.
|
||||||
|
|
||||||
|
Open the panel directly with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
noctalia msg panel-toggle avivbintangaringga/nextboot-selector:panel
|
||||||
|
```
|
||||||
|
|
||||||
|
You can add more keywords to the exclusion list in the settings to hide unwanted entries. You also can make it automatically reboots after selecting an entry.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `excluded_keywords` | `string_list` | `["pxe", "uefi os", "uefi shell", "shell", "ipv4", "ipv6", "network", "diagnostics", "setup", "recovery"]` | Keywords to exclude unwanted boot entries |
|
||||||
|
| `privilege_command` | `string` | `pkexec` | Command to escalate the privilege of the `efibootmgr` command |
|
||||||
|
| `close_on_select` | `bool` | `false` | Whether to close the panel after selecting an entry |
|
||||||
|
| `reboot_on_select` | `bool` | `false` | Whether to automatically reboot after selecting an entry |
|
||||||
|
| `reboot_command` | `string` | `reboot` | Command to be executed when reboot on select is enabled |
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
noctalia.setUpdateInterval(1000)
|
||||||
|
|
||||||
|
function update()
|
||||||
|
barWidget.setGlyph("refresh-dot")
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick()
|
||||||
|
noctalia.togglePanel("avivbintangaringga/nextboot-selector:panel")
|
||||||
|
end
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
function tr(key)
|
||||||
|
return noctalia.tr(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
function cfg(key)
|
||||||
|
return noctalia.getConfig(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
local env = getfenv()
|
||||||
|
local excludedKeywords = cfg("excluded_keywords")
|
||||||
|
local privilegeCmd = cfg("privilege_command")
|
||||||
|
local closeOnSelect = cfg("close_on_select")
|
||||||
|
local rebootOnSelect = cfg("reboot_on_select")
|
||||||
|
local rebootCmd = cfg("reboot_command")
|
||||||
|
|
||||||
|
function parseEntries(output)
|
||||||
|
local entries = {}
|
||||||
|
|
||||||
|
local bootNext = output:match("BootNext:%s*(%x+)")
|
||||||
|
local bootCurrent = output:match("BootCurrent:%s*(%x+)")
|
||||||
|
|
||||||
|
local selectedId = bootNext or bootCurrent
|
||||||
|
if selectedId then
|
||||||
|
selectedId = selectedId:upper()
|
||||||
|
end
|
||||||
|
|
||||||
|
for line in output:gmatch("[^\r\n]+") do
|
||||||
|
local id, active, name = line:match("^Boot(%x%x%x%x)(%*?)%s+([^\t]+)")
|
||||||
|
if id then
|
||||||
|
id = id:upper()
|
||||||
|
table.insert(entries, {
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
isActive = active == "*",
|
||||||
|
isSelected = selectedId ~= nil and id == selectedId,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
function excludeEntries(entries, keywords)
|
||||||
|
local filtered = {}
|
||||||
|
for _, entry in ipairs(entries) do
|
||||||
|
local haystack = entry.name:lower()
|
||||||
|
local excluded = false
|
||||||
|
for _, word in ipairs(keywords) do
|
||||||
|
if haystack:find(word:lower(), 1, true) then
|
||||||
|
excluded = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not excluded then
|
||||||
|
table.insert(filtered, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return filtered
|
||||||
|
end
|
||||||
|
|
||||||
|
function resetBootNext()
|
||||||
|
noctalia.runAsync(`{privilegeCmd} efibootmgr --delete-bootnext`, function(result)
|
||||||
|
load()
|
||||||
|
end, 5000)
|
||||||
|
end
|
||||||
|
|
||||||
|
function render(entries)
|
||||||
|
local entriesButton = {}
|
||||||
|
for i, entry in ipairs(entries) do
|
||||||
|
local callBackFn = `setBootNext_{entry.id}`
|
||||||
|
env[callBackFn] = function()
|
||||||
|
noctalia.runAsync(`{privilegeCmd} efibootmgr --bootnext {entry.id}`, function(result)
|
||||||
|
if not result.timedOut then
|
||||||
|
if result.exitCode == 0 then
|
||||||
|
load()
|
||||||
|
|
||||||
|
if rebootOnSelect then
|
||||||
|
noctalia.runAsync(rebootCmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
if closeOnSelect then
|
||||||
|
panel.close()
|
||||||
|
end
|
||||||
|
else
|
||||||
|
noctalia.notify(tr("notification.failed_to_set_next_boot"), result.stderr)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
noctalia.notify(tr("notification.failed_to_set_next_boot"), tr("notification.timed_out"))
|
||||||
|
end
|
||||||
|
end, 60 * 1000)
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(
|
||||||
|
entriesButton,
|
||||||
|
ui.button({
|
||||||
|
text = entry.name,
|
||||||
|
selected = entry.isSelected,
|
||||||
|
enabled = entry.isActive,
|
||||||
|
onClick = callBackFn,
|
||||||
|
contentAlign = "start",
|
||||||
|
glyph = if entry.isSelected then "circle-check" else "circle",
|
||||||
|
glyphSize = 18,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
panel.render(ui.column({
|
||||||
|
flexGrow = 1,
|
||||||
|
}, {
|
||||||
|
ui.row({
|
||||||
|
justify = "space_between",
|
||||||
|
align = "center"
|
||||||
|
}, {
|
||||||
|
ui.label({
|
||||||
|
text = tr("panel.title"),
|
||||||
|
fontSize = 16
|
||||||
|
}),
|
||||||
|
ui.button({
|
||||||
|
text = tr("panel.reset"),
|
||||||
|
glyph = "reload",
|
||||||
|
variant = "ghost",
|
||||||
|
controlSize = "sm",
|
||||||
|
onClick = "resetBootNext"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
ui.separator({
|
||||||
|
thickness = 2,
|
||||||
|
orientation = "horizontal",
|
||||||
|
spacing = 8,
|
||||||
|
}),
|
||||||
|
ui.scroll({
|
||||||
|
flexGrow = 1,
|
||||||
|
gap = 8,
|
||||||
|
}, entriesButton),
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
function load()
|
||||||
|
noctalia.runAsync("efibootmgr", function(result)
|
||||||
|
if result.exitCode == 0 then
|
||||||
|
local entries = parseEntries(result.stdout)
|
||||||
|
render(excludeEntries(entries, excludedKeywords))
|
||||||
|
end
|
||||||
|
end, 5000)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onOpen()
|
||||||
|
load()
|
||||||
|
end
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
id = "avivbintangaringga/nextboot-selector"
|
||||||
|
name = "Next Boot Selector"
|
||||||
|
version = "1.0.0"
|
||||||
|
plugin_api = 3
|
||||||
|
icon = "refresh-dot"
|
||||||
|
author = "avivbintangaringga"
|
||||||
|
description = "Select boot entry for the next reboot"
|
||||||
|
tags = [ "utility" ]
|
||||||
|
dependencies = [ "efibootmgr", "pkexec", "reboot", "sudo" ]
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[[widget]]
|
||||||
|
id = "nextboot-selector"
|
||||||
|
entry = "nextboot_selector.luau"
|
||||||
|
|
||||||
|
[[panel]]
|
||||||
|
id = "panel"
|
||||||
|
entry = "panel.luau"
|
||||||
|
placement = "attached"
|
||||||
|
position = "auto"
|
||||||
|
open_near_click = true
|
||||||
|
width = 350
|
||||||
|
height = 250
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "excluded_keywords"
|
||||||
|
type = "string_list"
|
||||||
|
label_key = "setting.excluded_keywords.label"
|
||||||
|
description_key = "setting.excluded_keywords.description"
|
||||||
|
default = ["pxe", "uefi os", "uefi shell", "shell", "ipv4", "ipv6", "network", "diagnostics", "setup", "recovery"]
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "privilege_command"
|
||||||
|
type = "string"
|
||||||
|
label_key = "setting.privilege_command.label"
|
||||||
|
description_key = "setting.privilege_command.description"
|
||||||
|
default = "pkexec"
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "close_on_select"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "setting.close_on_select.label"
|
||||||
|
description_key = "setting.close_on_select.description"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "reboot_on_select"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "setting.reboot_on_select.label"
|
||||||
|
description_key = "setting.reboot_on_select.description"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "reboot_command"
|
||||||
|
type = "string"
|
||||||
|
label_key = "setting.reboot_command.label"
|
||||||
|
description_key = "setting.reboot_command.description"
|
||||||
|
default = "reboot"
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"panel.title": "Next Boot Selector",
|
||||||
|
"panel.reset": "Reset",
|
||||||
|
"setting.excluded_keywords.label": "Excluded keywords",
|
||||||
|
"setting.excluded_keywords.description": "Keywords to exclude entries (case insensitive)",
|
||||||
|
"setting.privilege_command.label": "Privilege command",
|
||||||
|
"setting.privilege_command.description": "Command to escalate the privilege of the efibootmgr command",
|
||||||
|
"setting.close_on_select.label": "Close on select",
|
||||||
|
"setting.close_on_select.description": "Whether to close the panel after selecting an entry",
|
||||||
|
"setting.reboot_on_select.label": "Reboot on select",
|
||||||
|
"setting.reboot_on_select.description": "Whether to automatically reboot after selecting an entry",
|
||||||
|
"setting.reboot_command.label": "Reboot command",
|
||||||
|
"setting.reboot_command.description": "Command to be executed when reboot on select is enabled",
|
||||||
|
"notification.failed_to_set_next_boot": "Failed to set next boot",
|
||||||
|
"notification.timed_out": "Timed out"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user