* 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
150 lines
3.1 KiB
Luau
150 lines
3.1 KiB
Luau
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
|