* Track the power profile through Quickshell instead of polling busctl Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Recover the power profile when the daemon was down at shell start Addresses Codex review: Quickshell never attaches its PowerProfiles listener if the daemon failed at startup. One-off reads on startup, power source changes and an IPC refresh from omarchy-powerprofiles-set cover that, still with no timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Don't let a stale power profile read win over a change signal or drop a refresh Addresses Codex re-review: a busctl read in flight could overwrite a newer PowerProfiles signal, and refreshes requested while a read ran were dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test power profile transitions and stale reads as behavior, not source text Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
1.6 KiB
JavaScript
51 lines
1.6 KiB
JavaScript
function batteryPercentage(device) {
|
|
if (!device || !device.isPresent) return -1
|
|
return Math.round(Number(device.percentage || 0) * 100)
|
|
}
|
|
|
|
function isDischarging(device, onBattery, dischargingState) {
|
|
return !!(device && device.isPresent && onBattery && device.state === dischargingState)
|
|
}
|
|
|
|
function shouldWarnLowBattery(device, onBattery, dischargingState, threshold, alreadyNotified) {
|
|
var level = batteryPercentage(device)
|
|
if (level < 0) return { level: level, notify: false, notifiedLowBattery: false }
|
|
|
|
var low = isDischarging(device, onBattery, dischargingState) && level <= threshold
|
|
return {
|
|
level: level,
|
|
notify: low && !alreadyNotified,
|
|
notifiedLowBattery: low
|
|
}
|
|
}
|
|
|
|
function parseActiveProfile(text) {
|
|
// busctl --json=short prints {"type":"s","data":"balanced"}; an empty or
|
|
// malformed reply (daemon not running) reads as no active profile.
|
|
try {
|
|
return String(JSON.parse(text).data || "").trim()
|
|
} catch (e) {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
function profileAfterRead(current, replyText, signalsAtStart, signalsNow) {
|
|
// A change signal that arrived during the read is newer than its reply.
|
|
return signalsAtStart === signalsNow ? parseActiveProfile(replyText) : current
|
|
}
|
|
|
|
function isPowerSaverOnBattery(onBattery, profile) {
|
|
return !!onBattery && profile === "power-saver"
|
|
}
|
|
|
|
if (typeof module !== "undefined") {
|
|
module.exports = {
|
|
batteryPercentage: batteryPercentage,
|
|
isDischarging: isDischarging,
|
|
shouldWarnLowBattery: shouldWarnLowBattery,
|
|
parseActiveProfile: parseActiveProfile,
|
|
profileAfterRead: profileAfterRead,
|
|
isPowerSaverOnBattery: isPowerSaverOnBattery
|
|
}
|
|
}
|