From 61b21f389f90c2d6c7ed9cd1b206b6a31ae93d86 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sun, 21 Sep 2025 17:33:28 +1000 Subject: feat: add battery warnings Closes #73 Closes #117 --- modules/BatteryMonitor.qml | 54 ++++++++++++++++++++++++++++++++++ modules/utilities/toasts/ToastItem.qml | 3 ++ modules/utilities/toasts/Toasts.qml | 7 +++++ 3 files changed, 64 insertions(+) create mode 100644 modules/BatteryMonitor.qml (limited to 'modules') diff --git a/modules/BatteryMonitor.qml b/modules/BatteryMonitor.qml new file mode 100644 index 0000000..f407edf --- /dev/null +++ b/modules/BatteryMonitor.qml @@ -0,0 +1,54 @@ +import qs.config +import Caelestia +import Quickshell +import Quickshell.Services.UPower +import QtQuick + +Scope { + id: root + + readonly property list warnLevels: [...Config.general.battery.warnLevels].sort((a, b) => b.level - a.level) + + Connections { + target: UPower + + function onOnBatteryChanged(): void { + if (UPower.onBattery) { + Toaster.toast(qsTr("Charger unplugged"), qsTr("Battery is now on AC"), "power_off"); + } else { + Toaster.toast(qsTr("Charger plugged in"), qsTr("Battery is charging"), "battery_android_frame_bolt"); + for (const level of root.warnLevels) + level.warned = false; + } + } + } + + Connections { + target: UPower.displayDevice + + function onPercentageChanged(): void { + if (!UPower.onBattery) + return; + + const p = UPower.displayDevice.percentage; + for (const level of root.warnLevels) { + if (p <= level.level && !level.warned) { + level.warned = true; + Toaster.toast(level.title ?? qsTr("Battery warning"), level.message ?? qsTr("Battery level is low"), level.icon ?? "battery_android_alert", level.critical ? Toast.Error : Toast.Warning); + } + } + + if (!hibernateTimer.running && p <= Config.general.battery.criticalLevel) { + Toaster.toast(qsTr("Hibernating in 5 seconds"), qsTr("Hibernating to prevent data loss"), "battery_android_alert", Toast.Error); + hibernateTimer.start(); + } + } + } + + Timer { + id: hibernateTimer + + interval: 5000 + onTriggered: Quickshell.execDetached(["systemctl", "hibernate"]) + } +} diff --git a/modules/utilities/toasts/ToastItem.qml b/modules/utilities/toasts/ToastItem.qml index fa3aa18..481b831 100644 --- a/modules/utilities/toasts/ToastItem.qml +++ b/modules/utilities/toasts/ToastItem.qml @@ -107,10 +107,12 @@ StyledRect { return Colours.palette.m3onSurface; } font.pointSize: Appearance.font.size.normal + elide: Text.ElideRight } StyledText { Layout.fillWidth: true + textFormat: Text.StyledText text: root.modelData.message color: { if (root.modelData.type === Toast.Success) @@ -122,6 +124,7 @@ StyledRect { return Colours.palette.m3onSurface; } opacity: 0.8 + elide: Text.ElideRight } } } diff --git a/modules/utilities/toasts/Toasts.qml b/modules/utilities/toasts/Toasts.qml index c23790d..c9a8d4d 100644 --- a/modules/utilities/toasts/Toasts.qml +++ b/modules/utilities/toasts/Toasts.qml @@ -58,6 +58,11 @@ Item { return index >= Config.utilities.maxToasts + extraHidden; } + onPreviewHiddenChanged: { + if (initAnim.running && previewHidden) + initAnim.stop(); + } + opacity: modelData.closed || previewHidden ? 0 : 1 scale: modelData.closed || previewHidden ? 0.7 : 1 @@ -83,6 +88,8 @@ Item { Component.onCompleted: modelData.lock(this) Anim { + id: initAnim + Component.onCompleted: running = !toast.previewHidden target: toast -- cgit v1.2.3-freya