import QtQuick import Quickshell.Io import Quickshell.Services.UPower import "." as M M.BarSection { id: root spacing: M.Theme.moduleSpacing opacity: M.Modules.battery.enable && (UPower.displayDevice?.isLaptopBattery ?? false) ? 1 : 0 visible: opacity > 0 tooltip: { const state = root.charging ? "Charging" : "Discharging"; const t = root.charging ? root.dev?.timeToFull : root.dev?.timeToEmpty; const mins = t ? Math.round(t / 60) : 0; const timeStr = mins > 0 ? "\n" + Math.floor(mins / 60) + "h " + (mins % 60) + "m " + (root.charging ? "until full" : "remaining") : ""; return state + " \u2014 " + Math.round(root.pct) + "%" + timeStr; } readonly property var dev: UPower.displayDevice readonly property real pct: (dev?.percentage ?? 0) * 100 readonly property bool charging: dev?.state === UPowerDeviceState.Charging readonly property int _critThresh: M.Modules.battery.critical || 15 readonly property int _warnThresh: M.Modules.battery.warning || 25 readonly property bool _critical: pct < _critThresh && !charging property color _stateColor: charging ? M.Theme.base0B : _critical ? M.Theme.base09 : pct < _warnThresh ? M.Theme.base0A : M.Theme.base08 property real _blinkOpacity: 1 SequentialAnimation { running: root._critical loops: Animation.Infinite NumberAnimation { target: root property: "_blinkOpacity" to: 0.2 duration: 400 easing.type: Easing.InOutQuad } NumberAnimation { target: root property: "_blinkOpacity" to: 1 duration: 400 easing.type: Easing.InOutQuad } onRunningChanged: if (!running) root._blinkOpacity = 1 } property bool _warnSent: false property bool _critSent: false onChargingChanged: { _warnSent = false; _critSent = false; } onPctChanged: { if (charging) return; if (pct < _critThresh && !_critSent) { _critSent = true; _warnSent = true; _notif.command = ["notify-send", "--urgency=critical", "--icon=battery-low", "--category=device", "Very Low Battery", "Connect to power now!"]; _notif.running = true; } else if (pct < _warnThresh && !_warnSent) { _warnSent = true; _notif.command = ["notify-send", "--icon=battery-caution", "--category=device", "Low Battery"]; _notif.running = true; } } Process { id: _notif } M.BarIcon { icon: { if (root.charging) return "\uDB80\uDC84"; const icons = ["\uDB80\uDC8E", "\uDB80\uDC7A", "\uDB80\uDC7B", "\uDB80\uDC7C", "\uDB80\uDC7D", "\uDB80\uDC7E", "\uDB80\uDC7F", "\uDB80\uDC80", "\uDB80\uDC81", "\uDB80\uDC82", "\uDB85\uDFE2"]; return icons[Math.min(10, Math.floor(root.pct / 10))]; } color: root._stateColor opacity: root._blinkOpacity font.pixelSize: M.Theme.fontSize + 2 anchors.verticalCenter: parent.verticalCenter } M.BarLabel { label: Math.round(root.pct) + "%" minText: "100%" color: root._stateColor opacity: root._blinkOpacity anchors.verticalCenter: parent.verticalCenter } }