repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Power.qml
Rawimport QtQuick
import Quickshell
import Quickshell.Services.UPower
import "power.js" as Power
// Power service: battery/device status from UPower (native) + session actions.
// Pure shaping and the action routing table live in power.js; this file owns
// only what must touch native objects: reading UPower devices and executing
// the chosen action (lock / niri / systemctl). DI per shell.qml — no singletons
// declared here.
Item {
id: root
property Lock lockService
property Niri niriService
property string pendingConfirmation: ""
property var lastAction: ({})
// --- Battery / devices (native UPower) ---------------------------------
function nativeBattery() {
const device = UPower.displayDevice;
if (!device || !device.ready)
return {
"id": "display",
"percent": 0,
"state": "unknown",
"timeEstimate": "unknown",
"health": -1
};
const state = Power.cleanState(UPowerDeviceState.toString(device.state || UPowerDeviceState.Unknown));
return {
"id": String(device.nativePath || "display"),
"percent": Power.normalizePercent(device.percentage),
"state": state,
"timeEstimate": Power.timeEstimateLabel(state, device.timeToFull, device.timeToEmpty),
"health": device.healthSupported ? Math.round(Number(device.healthPercentage || 0)) : -1
};
}
function batteryObject() {
const b = nativeBattery();
b.present = Power.hasMainBattery(b);
b.role = Power.batteryRole(b.percent);
return b;
}
function externalDevices() {
const rows = [];
const devices = UPower.devices.values || [];
for (let i = 0; i < devices.length; i++) {
const device = devices[i];
if (!device || device.isLaptopBattery || !device.isPresent)
continue;
rows.push({
"id": String(device.nativePath || device.model || `device-${i}`),
"name": String(device.model || UPowerDeviceType.toString(device.type)),
"type": String(UPowerDeviceType.toString(device.type)).toLowerCase(),
"percent": Power.normalizePercent(device.percentage),
"state": Power.cleanState(UPowerDeviceState.toString(device.state || UPowerDeviceState.Unknown))
});
}
return rows;
}
// lowPowerMode() drives global animation throttling (shell.qml binds
// nuguTheme.lowPowerMode to it). Keep it lean — it is read every frame.
function lowPowerMode() {
return Power.isLowPower(nativeBattery().state);
}
// --- State (IPC contract) ----------------------------------------------
function stateObject() {
const battery = batteryObject();
return {
"battery": battery,
"externalDevices": externalDevices(),
"sessionActions": Power.SESSION_ACTIONS,
"destructiveActions": Power.DESTRUCTIVE_ACTIONS,
"compact": {
"label": "Power",
"role": battery.role,
"charging": battery.state === "charging"
},
"pendingConfirmation": pendingConfirmation,
"lastAction": lastAction
};
}
function stateJson() {
return JSON.stringify(stateObject());
}
// --- Session actions ----------------------------------------------------
// Dispatch a resolved action plan (from power.js) to the native sink.
function runPlan(plan) {
if (plan.via === "lock" && lockService)
lockService.request();
else if (plan.via === "niri" && niriService)
niriService.action(plan.args);
else if (plan.via === "exec")
Quickshell.execDetached(plan.argv);
}
function sessionAction(kind) {
const action = String(kind);
if (Power.requiresConfirmation(action)) {
pendingConfirmation = action;
lastAction = {
"kind": action,
"confirmed": false
};
} else {
pendingConfirmation = "";
runPlan(Power.actionPlan(action));
lastAction = {
"kind": action,
"confirmed": true
};
}
return stateJson();
}
function confirmAction(kind) {
const action = String(kind);
const confirmed = pendingConfirmation === action && Power.requiresConfirmation(action);
pendingConfirmation = "";
if (confirmed)
runPlan(Power.actionPlan(action));
lastAction = {
"kind": action,
"confirmed": confirmed
};
return stateJson();
}
function cancelAction() {
pendingConfirmation = "";
lastAction = {
"kind": "cancel"
};
return stateJson();
}
// selfTest() — exercises the pure routing/shaping without touching native
// state or executing anything. Gate-checks power.js via IPC.
function selfTest() {
const cases = [];
function check(name, got, want) {
cases.push({
"name": name,
"pass": JSON.stringify(got) === JSON.stringify(want),
"got": got
});
}
check("poweroff routes to systemctl", Power.actionPlan("poweroff"), {
"via": "exec",
"argv": ["systemctl", "poweroff"]
});
check("logout routes to niri quit", Power.actionPlan("logout"), {
"via": "niri",
"args": ["quit", "--skip-confirmation"]
});
check("lock routes to lockService", Power.actionPlan("lock"), {
"via": "lock"
});
check("unknown is no-op", Power.actionPlan("bogus"), {
"via": "none"
});
check("poweroff confirms", Power.requiresConfirmation("poweroff"), true);
check("suspend immediate", Power.requiresConfirmation("suspend"), false);
check("batteryRole critical", Power.batteryRole(8), "error");
check("normalizePercent fraction", Power.normalizePercent(0.5), 50);
check("low power on discharge", Power.isLowPower("Discharging"), true);
const failed = cases.filter(c => !c.pass);
return JSON.stringify({
"ok": failed.length === 0,
"total": cases.length,
"failed": failed
});
}
}