Update Battery widget plugin (#311)
* fix: invalid check in getWarningThreshold * fix: invalid status * feat: use dbus events instead of polling * fix: invalid status again * fix: dump version and set valid plugin API * fix: add commandExists check * fix: getColor function (warning color when not charging) * feat: use upower instead of cat /sys * feat: notify on error * fix: cleaner code * fix: use noctalia.tr for notification (not logs, intended) * feat: migrate from dbus-monitor to gdbus (everything in one line) * feat: use gdbus to init and update, parse only when needed * feat: add label_content and optimisations * fix: remove multiple commands check not needed anymore * fix: update README and dump version * fix: default data, warning_threshold check, floor percent
This commit is contained in:
@@ -9,6 +9,10 @@ Desktop/lockscreen widget that displays the current battery level and charging s
|
|||||||
| ID | `yocraft/battery-widget` |
|
| ID | `yocraft/battery-widget` |
|
||||||
| Entries | Desktop widget: `widget` |
|
| Entries | Desktop widget: `widget` |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
The plugin requires `gdbus` on `PATH` and `upower`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Add it to your desktop/lockscreen widgets using the widget editor. You can configure it in the widget settings.
|
Add it to your desktop/lockscreen widgets using the widget editor. You can configure it in the widget settings.
|
||||||
@@ -20,6 +24,7 @@ Add it to your desktop/lockscreen widgets using the widget editor. You can confi
|
|||||||
| `layout` | `select` | `horizontal` | Layout of the widget. |
|
| `layout` | `select` | `horizontal` | Layout of the widget. |
|
||||||
| `show_glyph` | `bool` | `true` | Show the battery icon. |
|
| `show_glyph` | `bool` | `true` | Show the battery icon. |
|
||||||
| `show_label` | `bool` | `true` | Show text next to the icon. |
|
| `show_label` | `bool` | `true` | Show text next to the icon. |
|
||||||
|
| `label_content` | `select` | `percent` | What the battery label shows: charge percentage, time remaining, or power draw. |
|
||||||
| `hide_plugged` | `bool` | `false` | Hide the battery widget when connected to AC power. |
|
| `hide_plugged` | `bool` | `false` | Hide the battery widget when connected to AC power. |
|
||||||
| `hide_full` | `bool` | `false` | Hide the battery widget when fully charged. |
|
| `hide_full` | `bool` | `false` | Hide the battery widget when fully charged. |
|
||||||
| `color` | `color` | `on_surface` | Color role for this widget's icon and label; fixed hex colors are also supported. |
|
| `color` | `color` | `on_surface` | Color role for this widget's icon and label; fixed hex colors are also supported. |
|
||||||
@@ -27,7 +32,6 @@ Add it to your desktop/lockscreen widgets using the widget editor. You can confi
|
|||||||
| `charging_color` | `color` | `on_surface` | Color applied when connected to AC power. |
|
| `charging_color` | `color` | `on_surface` | Color applied when connected to AC power. |
|
||||||
| `battery` | `select` | `auto` | Detect the battery automatically or set a custom name. |
|
| `battery` | `select` | `auto` | Detect the battery automatically or set a custom name. |
|
||||||
| `battery_name` | `string` | `BAT0` | Set a custom name for the battery. |
|
| `battery_name` | `string` | `BAT0` | Set a custom name for the battery. |
|
||||||
| `refresh_interval` | `int` | `60` | Controls how frequently the widget updates its content. (in seconds) |
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
+15
-12
@@ -1,14 +1,14 @@
|
|||||||
id = "yocraft/battery-widget"
|
id = "yocraft/battery-widget"
|
||||||
name = "Battery Widget"
|
name = "Battery Widget"
|
||||||
version = "1.0.1"
|
version = "2.0.0"
|
||||||
plugin_api = 19
|
plugin_api = 3
|
||||||
author = "yocraft"
|
author = "yocraft"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
deprecated = false
|
deprecated = false
|
||||||
icon = "battery"
|
icon = "battery"
|
||||||
description = "Desktop/Lockscreen widget to show battery."
|
description = "Desktop/Lockscreen widget to show battery."
|
||||||
tags = [ "desktop", "hardware", "system", "utility" ]
|
tags = [ "desktop", "hardware", "system", "utility" ]
|
||||||
dependencies = []
|
dependencies = [ "upower", "gdbus" ]
|
||||||
|
|
||||||
[[desktop_widget]]
|
[[desktop_widget]]
|
||||||
id = "widget"
|
id = "widget"
|
||||||
@@ -39,6 +39,18 @@ entry = "widget.luau"
|
|||||||
description_key = "settings.show_label.description"
|
description_key = "settings.show_label.description"
|
||||||
default = true
|
default = true
|
||||||
|
|
||||||
|
[[desktop_widget.setting]]
|
||||||
|
key = "label_content"
|
||||||
|
type = "select"
|
||||||
|
label_key = "settings.label_content.label"
|
||||||
|
description_key = "settings.label_content.description"
|
||||||
|
default = "percent"
|
||||||
|
options = [
|
||||||
|
{ value = "percent", label_key = "settings.label_content.percent" },
|
||||||
|
{ value = "time", label_key = "settings.label_content.time" },
|
||||||
|
{ value = "rate", label_key = "settings.label_content.rate" },
|
||||||
|
]
|
||||||
|
|
||||||
[[desktop_widget.setting]]
|
[[desktop_widget.setting]]
|
||||||
key = "hide_plugged"
|
key = "hide_plugged"
|
||||||
type = "bool"
|
type = "bool"
|
||||||
@@ -98,12 +110,3 @@ entry = "widget.luau"
|
|||||||
description_key = "settings.battery_name.description"
|
description_key = "settings.battery_name.description"
|
||||||
default = "BAT0"
|
default = "BAT0"
|
||||||
visible_when = { key = "battery", values = ["custom"] }
|
visible_when = { key = "battery", values = ["custom"] }
|
||||||
|
|
||||||
[[desktop_widget.setting]]
|
|
||||||
key = "refresh_interval"
|
|
||||||
type = "int"
|
|
||||||
label_key = "settings.refresh_interval.label"
|
|
||||||
description_key = "settings.refresh_interval.description"
|
|
||||||
default = 60
|
|
||||||
min = 1
|
|
||||||
max = 600
|
|
||||||
|
|||||||
@@ -48,6 +48,16 @@
|
|||||||
"warning_color": {
|
"warning_color": {
|
||||||
"description": "Color applied when charge is at or below noctalia's warning threshold.",
|
"description": "Color applied when charge is at or below noctalia's warning threshold.",
|
||||||
"label": "Warning Color"
|
"label": "Warning Color"
|
||||||
|
},
|
||||||
|
"label_content": {
|
||||||
|
"label": "Label Content",
|
||||||
|
"description": "What the battery label shows: charge percentage, time remaining, or power draw.",
|
||||||
|
"percent": "Percent",
|
||||||
|
"time": "Time",
|
||||||
|
"rate": "Rate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"error": "Something went wrong. Check the Noctalia log for details."
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+166
-43
@@ -1,13 +1,70 @@
|
|||||||
local glyph
|
local glyph
|
||||||
local color
|
local color
|
||||||
local percent
|
local data = { percent = 0, status = "unknown" }
|
||||||
local status
|
|
||||||
|
|
||||||
|
local label_content = noctalia.getConfig("label_content")
|
||||||
local warning_threshold = 20
|
local warning_threshold = 20
|
||||||
|
|
||||||
local path = "/sys/class/power_supply/"
|
local path = "/sys/class/power_supply/"
|
||||||
local batName
|
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 function getBatName()
|
||||||
local config = noctalia.getConfig("battery")
|
local config = noctalia.getConfig("battery")
|
||||||
|
|
||||||
@@ -30,17 +87,17 @@ local function getBatName()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function getGlyph()
|
local function getGlyph()
|
||||||
if status == "Charging" then
|
if data.status == "charging" then
|
||||||
glyph = "battery-charging"
|
glyph = "battery-charging"
|
||||||
elseif status == "Full" or status == "Not Charging" then
|
elseif data.status == "fully-charged" or data.status == "pending-charge" then
|
||||||
glyph = "battery-plugged"
|
glyph = "battery-plugged"
|
||||||
elseif status == "Unknown" then
|
elseif data.status == "unknown" then
|
||||||
glyph = "battery-exclamation"
|
glyph = "battery-exclamation"
|
||||||
else
|
else
|
||||||
glyph = percent >= 85 and "battery-4"
|
glyph = data.percent >= 85 and "battery-4"
|
||||||
or percent >= 55 and "battery-3"
|
or data.percent >= 55 and "battery-3"
|
||||||
or percent >= 30 and "battery-2"
|
or data.percent >= 30 and "battery-2"
|
||||||
or percent >= 10 and "battery-1"
|
or data.percent >= 10 and "battery-1"
|
||||||
or "battery-0"
|
or "battery-0"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -51,36 +108,85 @@ local function getWarningThreshold()
|
|||||||
function(result)
|
function(result)
|
||||||
if result.exitCode ~= 0 then
|
if result.exitCode ~= 0 then
|
||||||
noctalia.log("battery-widget: failed to get warning_threshold: " .. result.stderr)
|
noctalia.log("battery-widget: failed to get warning_threshold: " .. result.stderr)
|
||||||
|
notifyError()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local res = result.stdout
|
local res = result.stdout
|
||||||
|
|
||||||
if not warning_threshold then
|
if not res then
|
||||||
noctalia.log("battery-widget: invalid warning_threshold: " .. res)
|
noctalia.log("battery-widget: invalid warning_threshold, keeping default")
|
||||||
|
notifyError()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
warning_threshold = tonumber(noctalia.string.trim(res))
|
local parsed = tonumber(noctalia.string.trim(res))
|
||||||
|
if parsed then
|
||||||
|
warning_threshold = parsed
|
||||||
|
else
|
||||||
|
noctalia.log("battery-widget: invalid warning_threshold, keeping default")
|
||||||
|
notifyError()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function getColor()
|
local function getColor()
|
||||||
if status ~= "Charging" and percent <= warning_threshold then
|
if data.status == "charging" or data.status == "fully-charged" or data.status == "pending-charge" then
|
||||||
color = noctalia.getConfig("warning_color")
|
color = noctalia.getConfig("charging_color")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if status == "Charging" or status == "Full" or status == "Not Charging" then
|
if data.percent <= warning_threshold then
|
||||||
color = noctalia.getConfig("charging_color")
|
color = noctalia.getConfig("warning_color")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
color = noctalia.getConfig("color")
|
color = noctalia.getConfig("color")
|
||||||
end
|
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()
|
local function render()
|
||||||
if (noctalia.getConfig("hide_plugged") and (status == "Charging" or status == "Not Charging"))
|
if (noctalia.getConfig("hide_plugged") and (data.status == "charging" or data.status == "pending-charge"))
|
||||||
or (noctalia.getConfig("hide_full") and status == "Full") then
|
or (noctalia.getConfig("hide_full") and data.status == "fully-charged") then
|
||||||
desktopWidget.render(ui.row())
|
desktopWidget.render(ui.row())
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -90,7 +196,7 @@ local function render()
|
|||||||
table.insert(content, ui.glyph({ name = glyph, color = color }))
|
table.insert(content, ui.glyph({ name = glyph, color = color }))
|
||||||
end
|
end
|
||||||
if noctalia.getConfig("show_label") then
|
if noctalia.getConfig("show_label") then
|
||||||
table.insert(content, ui.label({ text = percent .. "%", color = color }))
|
table.insert(content, ui.label({ text = getLabelText(), color = color }))
|
||||||
end
|
end
|
||||||
|
|
||||||
local vertical = noctalia.getConfig("layout") == "vertical"
|
local vertical = noctalia.getConfig("layout") == "vertical"
|
||||||
@@ -105,38 +211,56 @@ local function render()
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function shellEscape(raw)
|
local function refreshBattery(output)
|
||||||
return "'" .. raw:gsub("'", "'\\''") .. "'"
|
parse(output)
|
||||||
|
getGlyph()
|
||||||
|
getColor()
|
||||||
|
render()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function refreshBattery()
|
local function initData()
|
||||||
noctalia.runAsync(
|
local cmd = string.format(
|
||||||
"cat "
|
"gdbus call --system --dest org.freedesktop.UPower --object-path %s --method org.freedesktop.DBus.Properties.GetAll org.freedesktop.UPower.Device",
|
||||||
.. shellEscape(path .. batName .. "/capacity")
|
shellEscape("/org/freedesktop/UPower/devices/battery_" .. batName)
|
||||||
.. " "
|
)
|
||||||
.. shellEscape(path .. batName .. "/status"),
|
noctalia.runAsync(cmd,
|
||||||
function(result)
|
function(result)
|
||||||
if result.exitCode ~= 0 then
|
if result.exitCode ~= 0 then
|
||||||
noctalia.log("battery-widget: failed to read battery: " .. result.stderr)
|
noctalia.log("battery-widget: failed to read battery: " .. result.stderr)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local lines = {}
|
if not result.stdout then
|
||||||
for line in result.stdout:gmatch("[^\r\n]+") do
|
|
||||||
table.insert(lines, line)
|
|
||||||
end
|
|
||||||
|
|
||||||
if not lines[1] or not lines[2] then
|
|
||||||
noctalia.log("battery-widget: invalid battery output")
|
noctalia.log("battery-widget: invalid battery output")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
percent = tonumber(noctalia.string.trim(lines[1]))
|
refreshBattery(result.stdout)
|
||||||
status = noctalia.string.trim(lines[2])
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
getGlyph()
|
local function watchBattery()
|
||||||
getColor()
|
local patterns = { "Percentage", "State" }
|
||||||
render()
|
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
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -144,12 +268,11 @@ end
|
|||||||
batName = getBatName()
|
batName = getBatName()
|
||||||
if not batName then
|
if not batName then
|
||||||
noctalia.log("battery-widget: battery not found")
|
noctalia.log("battery-widget: battery not found")
|
||||||
|
notifyError()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
getWarningThreshold()
|
getWarningThreshold()
|
||||||
|
|
||||||
function update()
|
initData()
|
||||||
noctalia.setUpdateInterval(noctalia.getConfig("refresh_interval") * 1000)
|
watchBattery()
|
||||||
refreshBattery()
|
|
||||||
end
|
|
||||||
|
|||||||
Reference in New Issue
Block a user