pragma Singleton import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.UPower import "../../../../config" Item { id: root // show widget for laptop battery *or* UPS (your main PC), hide otherwise // displayDevice is aggregate - for UPS it is type Ups, isLaptopBattery is false property bool available: { const d = UPower.displayDevice if (d && d.ready && d.isPresent && (d.isLaptopBattery || d.type === UPowerDeviceType.Ups)) return true // fallback: scan all devices (some systems don't set DisplayDevice to UPS) const devs = UPower.devices.values for (let i = 0; i < devs.length; i++) { const dev = devs[i] if (dev.isPresent && (dev.isLaptopBattery || dev.type === UPowerDeviceType.Ups)) return true } return false } property real percentage: UPower.displayDevice.percentage*100 // 'state' shadows Item.state (qmllint) -> use chargeState property string chargeState: UPowerDeviceState.toString(UPower.displayDevice.state) // keep alias via getter would still shadow, so expose via function if needed; prefer chargeState property bool charging: chargeState == "Charging" property bool full: chargeState == "FullyCharged" property real energy: UPower.displayDevice.energy property real energy_formated: Math.round(energy*10)/10 property real time_to_empty: UPower.displayDevice.timeToEmpty property real time_to_full: UPower.displayDevice.timeToFull property real hour_to_empty: Math.floor(time_to_empty/3600) property real min_to_empty: Math.floor((time_to_empty%3600)/60) property real hour_to_full: Math.floor(time_to_full/3600) property real min_to_full: Math.floor((time_to_full%3600)/60) property real energy_rate: UPower.displayDevice.changeRate property real energy_rate_formated: Math.round(energy_rate*10)/10 property bool healthSupported: UPower.displayDevice.healthSupported property real health: healthSupported ? UPower.displayDevice.healthPercentage : NaN property int _maxHistory: 60*60 // 1h window property var percentageHistory: [{time: Date.now() - _maxHistory*1000, value: 0}, {time: Date.now(), value: 0}] property var percentageHistoryExists: false property var energyHistory: [{time: Date.now() - _maxHistory*1000, value: 0}, {time: Date.now(), value: 0}] property var energyHistoryExists: false property var usageHistory: [{time: Date.now() - _maxHistory*1000, value: 0}, {time: Date.now(), value: 0}] property var usageHistoryExists: false // ---- persistence: survives reload AND close (FileView -> disk) ---- // use Config.configDir (base dir) so path is stable and visible in repo readonly property string _historyPath: Config.configDir + "/modules/bar/states/Battery/battery_history.json" FileView { id: historyFile path: _historyPath printErrors: true // adapter holds json, synced to disk via writeAdapter() adapter: JsonAdapter { property var percentageHistory: [] property var energyHistory: [] property var usageHistory: [] } onLoaded: { function prune(arr) { if (!arr || typeof arr.length !== "number" || arr.length===0) return null const f = arr.filter(p => p && typeof p.time==="number" && typeof p.value==="number") if (f.length < 2) return null return f.length > root._maxHistory ? f.slice(-root._maxHistory) : f } const p = prune(adapter.percentageHistory) if (p) { root.percentageHistory = p; root.percentageHistoryExists = p.some(i=>i.value!==0) } const e = prune(adapter.energyHistory) if (e) { root.energyHistory = e; root.energyHistoryExists = e.some(i=>i.value!==0) } const u = prune(adapter.usageHistory) if (u) { root.usageHistory = u; root.usageHistoryExists = u.some(i=>i.value!==0) } } onLoadFailed: {} // first run, file missing -> keep defaults } // periodic save every 10s (debounce-restart would never fire while statsTimer pushes every 1s) Timer { id: saveTimer interval: 10000 repeat: true running: true onTriggered: { historyFile.adapter.percentageHistory = root.percentageHistory historyFile.adapter.energyHistory = root.energyHistory historyFile.adapter.usageHistory = root.usageHistory historyFile.writeAdapter() } } Timer { id: statsTimer interval: 1000 repeat: true running: true onTriggered: { const now = Date.now() // Creating percentage History let pHist = root.percentageHistory.slice() pHist.push({time: now, value: percentage}) if (pHist.length > root._maxHistory) pHist.shift() root.percentageHistoryExists = pHist.some(item => item.value !== 0 && item.value !== undefined) root.percentageHistory = pHist // Creating energy History let eHist = root.energyHistory.slice() eHist.push({time: now, value: energy_formated}) if (eHist.length > root._maxHistory) eHist.shift() root.energyHistoryExists = eHist.some(item => item.value !== 0 && item.value !== undefined) root.energyHistory = eHist // Creating usage History let uHist = root.usageHistory.slice() uHist.push({time: now, value: energy_rate_formated}) if (uHist.length > root._maxHistory) uHist.shift() root.usageHistoryExists = uHist.some(item => item.value !== 0 && item.value !== undefined) root.usageHistory = uHist } } // ensure save on clean close / reload (best-effort, reload preserves via FileView anyway) Component.onDestruction: { if (historyFile.loaded) { historyFile.adapter.percentageHistory = root.percentageHistory historyFile.adapter.energyHistory = root.energyHistory historyFile.adapter.usageHistory = root.usageHistory historyFile.writeAdapter() } } }