Skip to content

Commit e451399

Browse files
authored
Merge pull request #834 from krteke/feat/battery-monitor
2 parents bf4e9f7 + 662d6b4 commit e451399

10 files changed

Lines changed: 1907 additions & 0 deletions

File tree

battery-monitor-plus/BarWidget.qml

Lines changed: 481 additions & 0 deletions
Large diffs are not rendered by default.

battery-monitor-plus/Main.qml

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import QtQuick
2+
import Quickshell.Io
3+
import qs.Commons
4+
import qs.Services.Hardware
5+
6+
Item {
7+
id: root
8+
9+
property var pluginApi: null
10+
11+
property bool sysfsOk: false
12+
property string sysfsStatus: ""
13+
property real sysfsPowerWatts: -1
14+
property int sysfsTimeToEmpty: 0
15+
property int sysfsTimeToFull: 0
16+
property int sysfsCapacity: -1
17+
property string sysfsSource: ""
18+
property string sysfsError: ""
19+
property int refreshNonce: 0
20+
21+
readonly property var cfg: pluginApi?.pluginSettings || ({})
22+
readonly property var defaults: pluginApi?.manifest?.metadata?.defaultSettings || ({})
23+
readonly property int refreshIntervalSeconds: Math.max(1, cfg.refreshIntervalSeconds ?? defaults.refreshIntervalSeconds ?? 5)
24+
readonly property string deviceNativePath: cfg.deviceNativePath ?? defaults.deviceNativePath ?? "__default__"
25+
readonly property bool isChargingHardware: sysfsStatus === "Charging"
26+
readonly property bool isDischargingHardware: sysfsStatus === "Discharging"
27+
28+
Component.onCompleted: scanSysfs()
29+
30+
Timer {
31+
interval: root.refreshIntervalSeconds * 1000
32+
repeat: true
33+
running: true
34+
triggeredOnStart: false
35+
onTriggered: root.scanSysfs()
36+
}
37+
38+
Process {
39+
id: sysfsScanProcess
40+
running: false
41+
stdout: StdioCollector {
42+
id: sysfsStdout
43+
}
44+
stderr: StdioCollector {
45+
id: sysfsStderr
46+
}
47+
48+
onExited: function (exitCode, exitStatus) {
49+
root.applySysfsSnapshot(sysfsStdout.text || "", sysfsStderr.text || "");
50+
}
51+
}
52+
53+
function scanSysfs() {
54+
if (!pluginApi || !pluginApi.pluginDir || sysfsScanProcess.running) {
55+
return;
56+
}
57+
58+
sysfsScanProcess.command = ["sh", `${pluginApi.pluginDir}/scripts/read_battery_sysfs.sh`, root.deviceNativePath];
59+
sysfsScanProcess.running = true;
60+
}
61+
62+
function applySysfsSnapshot(stdoutText, stderrText) {
63+
let parsed = null;
64+
try {
65+
parsed = JSON.parse((stdoutText || "").trim());
66+
} catch (e) {
67+
root.sysfsOk = false;
68+
root.sysfsError = stderrText || String(e);
69+
root.refreshNonce++;
70+
Logger.w("BatteryMonitorPlus", "Failed to parse sysfs battery data:", root.sysfsError);
71+
return;
72+
}
73+
74+
root.sysfsOk = parsed.ok === true;
75+
root.sysfsStatus = parsed.status || "";
76+
root.sysfsPowerWatts = numberOr(parsed.powerWatts, -1);
77+
root.sysfsTimeToEmpty = Math.round(numberOr(parsed.timeToEmpty, 0));
78+
root.sysfsTimeToFull = Math.round(numberOr(parsed.timeToFull, 0));
79+
root.sysfsCapacity = Math.round(numberOr(parsed.capacity, -1));
80+
root.sysfsSource = parsed.source || "";
81+
root.sysfsError = parsed.error || "";
82+
root.refreshNonce++;
83+
}
84+
85+
function numberOr(value, fallback) {
86+
const parsed = Number(value);
87+
return isNaN(parsed) ? fallback : parsed;
88+
}
89+
90+
function statusText() {
91+
if (!sysfsOk) {
92+
return pluginApi?.tr("status.noBattery");
93+
}
94+
95+
if (sysfsStatus === "Charging") {
96+
return pluginApi?.tr("status.charging");
97+
}
98+
if (sysfsStatus === "Discharging") {
99+
return pluginApi?.tr("status.discharging");
100+
}
101+
if (sysfsStatus === "Full") {
102+
return pluginApi?.tr("status.fullyCharged");
103+
}
104+
if (sysfsStatus === "Not charging" || sysfsStatus === "Unknown") {
105+
return pluginApi?.tr("status.pluggedIn");
106+
}
107+
108+
return sysfsStatus || pluginApi?.tr("status.noBattery");
109+
}
110+
111+
function formatPower(compact, showUnavailable) {
112+
if (!sysfsOk || sysfsPowerWatts <= 0) {
113+
return showUnavailable ? pluginApi?.tr("common.unavailable") : "";
114+
}
115+
116+
return `${sysfsPowerWatts.toFixed(1)}${compact ? "W" : " W"}`;
117+
}
118+
119+
function formatRelevantTime(showUnavailable) {
120+
const seconds = sysfsStatus === "Charging" ? sysfsTimeToFull : sysfsTimeToEmpty;
121+
const text = formatDurationCompact(seconds);
122+
return text || (showUnavailable ? pluginApi?.tr("common.unavailable") : "");
123+
}
124+
125+
function formatDurationCompact(seconds) {
126+
if (seconds <= 0) {
127+
return "";
128+
}
129+
130+
const totalMinutes = Math.max(1, Math.round(seconds / 60));
131+
const hours = Math.floor(totalMinutes / 60);
132+
const minutes = totalMinutes % 60;
133+
134+
if (hours > 0) {
135+
return minutes > 0 ? `${hours}h${minutes}m` : `${hours}h`;
136+
}
137+
138+
return `${minutes}m`;
139+
}
140+
}

0 commit comments

Comments
 (0)