Files
community-plugins/battery-widget/widget.luau
T
Yocraft-2000andGitHub b75eb1cb33 Update Battery widget plugin (#346)
* fix: don't notify on warnings, use same default warning_threshold as noctalia

* chore: dump version
2026-08-12 22:44:40 -04:00

278 lines
7.5 KiB
Luau

local glyph
local color
local data = { percent = 0, status = "unknown" }
local label_content = noctalia.getConfig("label_content")
local warning_threshold = 10
local path = "/sys/class/power_supply/"
local batName
local stateMap = {
[0] = "unknown",
[1] = "charging",
[2] = "discharging",
[3] = "empty",
[4] = "fully-charged",
[5] = "pending-charge",
[6] = "pending-discharge",
}
local function shellEscape(raw)
return "'" .. raw:gsub("'", "'\\''") .. "'"
end
local function notifyError()
noctalia.notifyError("Battery widget", noctalia.tr("notify.error"))
end
if not noctalia.commandExists("gdbus") then
noctalia.log("battery-widget: gdbus not found.")
notifyError()
return
end
local function parse(output)
data = data or {}
local percent = output:match("'Percentage':%s*<([%d%.]+)>")
if percent then
data.percent = tonumber(percent) or 0
end
local state = output:match("'State':%s*<uint32%s+(%d+)>")
if state then
data.status = stateMap[tonumber(state)] or "unknown"
end
if label_content == "time" then
local timeToEmpty = output:match("'TimeToEmpty':%s*<int64%s+(%-?%d+)>")
if timeToEmpty then
data.timeToEmpty = tonumber(timeToEmpty)
end
local timeToFull = output:match("'TimeToFull':%s*<int64%s+(%-?%d+)>")
if timeToFull then
data.timeToFull = tonumber(timeToFull)
end
elseif label_content == "rate" then
local rate = output:match("'EnergyRate':%s*<([%-%d%.]+)>")
if rate then
data.rate = tonumber(rate)
end
end
return data
end
local function getBatName()
local config = noctalia.getConfig("battery")
if config == "bat0" then
return "BAT0"
elseif config == "custom" then
return noctalia.getConfig("battery_name")
end
local folders = noctalia.listDir(path)
if folders then
for _, name in ipairs(folders) do
if name:match("^BAT") then
return name
end
end
end
return nil
end
local function getGlyph()
if data.status == "charging" then
glyph = "battery-charging"
elseif data.status == "fully-charged" or data.status == "pending-charge" then
glyph = "battery-plugged"
elseif data.status == "unknown" then
glyph = "battery-exclamation"
else
glyph = data.percent >= 85 and "battery-4"
or data.percent >= 55 and "battery-3"
or data.percent >= 30 and "battery-2"
or data.percent >= 10 and "battery-1"
or "battery-0"
end
end
local function getWarningThreshold()
noctalia.runAsync(
"grep warning_threshold ~/.local/state/noctalia/settings.toml | awk '{print $3}'",
function(result)
if result.exitCode ~= 0 then
noctalia.log("battery-widget: failed to get warning_threshold: " .. result.stderr)
return
end
local res = result.stdout
if not res then
noctalia.log("battery-widget: invalid warning_threshold, keeping default")
return
end
local parsed = tonumber(noctalia.string.trim(res))
if parsed then
warning_threshold = parsed
else
noctalia.log("battery-widget: invalid warning_threshold, keeping default")
end
end
)
end
local function getColor()
if data.status == "charging" or data.status == "fully-charged" or data.status == "pending-charge" then
color = noctalia.getConfig("charging_color")
return
end
if data.percent <= warning_threshold then
color = noctalia.getConfig("warning_color")
return
end
color = noctalia.getConfig("color")
end
local function formatTime(seconds)
if not seconds or seconds <= 0 then
return nil
end
local hours = math.floor(seconds / 3600)
local minutes = math.floor((seconds % 3600) / 60)
if hours > 0 and minutes > 0 then
return string.format("%dh %dm", hours, minutes)
elseif hours > 0 then
return string.format("%dh", hours)
else
return string.format("%dm", minutes)
end
end
local function formatRate(rate)
if not rate or rate <= 0 then
return nil
end
return string.format("%.1f W", rate)
end
local function formatPercent()
return math.floor((data.percent or 0) + 0.5) .. "%"
end
local function getLabelText()
if label_content == "time" then
local connected = data.status == "charging" or data.status == "fully-charged" or data.status == "pending-charge"
local seconds = connected and data.timeToFull or data.timeToEmpty
return formatTime(seconds) or formatPercent()
elseif label_content == "rate" then
return formatRate(data.rate) or formatPercent()
end
return formatPercent()
end
local function render()
if (noctalia.getConfig("hide_plugged") and (data.status == "charging" or data.status == "pending-charge"))
or (noctalia.getConfig("hide_full") and data.status == "fully-charged") then
desktopWidget.render(ui.row())
return
end
local content = {}
if noctalia.getConfig("show_glyph") then
table.insert(content, ui.glyph({ name = glyph, color = color }))
end
if noctalia.getConfig("show_label") then
table.insert(content, ui.label({ text = getLabelText(), color = color }))
end
local vertical = noctalia.getConfig("layout") == "vertical"
local layout = vertical and ui.column or ui.row
desktopWidget.render(
layout({
gap = vertical and 0 or 4,
align = "center"
}, content)
)
end
local function refreshBattery(output)
parse(output)
getGlyph()
getColor()
render()
end
local function initData()
local cmd = string.format(
"gdbus call --system --dest org.freedesktop.UPower --object-path %s --method org.freedesktop.DBus.Properties.GetAll org.freedesktop.UPower.Device",
shellEscape("/org/freedesktop/UPower/devices/battery_" .. batName)
)
noctalia.runAsync(cmd,
function(result)
if result.exitCode ~= 0 then
noctalia.log("battery-widget: failed to read battery: " .. result.stderr)
notifyError()
return
end
if not result.stdout then
noctalia.log("battery-widget: invalid battery output")
notifyError()
return
end
refreshBattery(result.stdout)
end
)
end
local function watchBattery()
local patterns = { "Percentage", "State" }
if label_content == "time" then
table.insert(patterns, "TimeToEmpty")
table.insert(patterns, "TimeToFull")
elseif label_content == "rate" then
table.insert(patterns, "EnergyRate")
end
local cmd = string.format(
"gdbus monitor --system --dest org.freedesktop.UPower --object-path %s",
shellEscape("/org/freedesktop/UPower/devices/battery_" .. batName)
)
noctalia.runStream(cmd,
function(line)
for _, pattern in ipairs(patterns) do
if line:find(pattern) then
refreshBattery(line)
break
end
end
end
)
end
batName = getBatName()
if not batName then
noctalia.log("battery-widget: battery not found")
notifyError()
return
end
getWarningThreshold()
initData()
watchBattery()