From 37fd19fc9a6bccb6efc23bd0887db8c7403ed854 Mon Sep 17 00:00:00 2001 From: 0blivi0nis <182329535+0blivi0nis@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:03:14 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20sound=20alerts=20for=20batt?= =?UTF-8?q?ery=20and=20pomodoro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../quickshell/ii/modules/common/Config.qml | 8 +++- .../ii/modules/settings/GeneralConfig.qml | 38 +++++++++++++++++ dots/.config/quickshell/ii/services/Audio.qml | 24 ++++++++++- .../quickshell/ii/services/Battery.qml | 42 ++++++++++++++++++- .../quickshell/ii/services/TimerService.qml | 7 ++-- 5 files changed, 112 insertions(+), 7 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index a9289d83b..f06ca2a50 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -244,6 +244,7 @@ Singleton { property JsonObject battery: JsonObject { property int low: 20 property int critical: 5 + property int full: 101 property bool automaticSuspend: true property int suspend: 3 } @@ -395,13 +396,18 @@ Singleton { } } + property JsonObject sounds: JsonObject { + property bool battery: false + property bool pomodoro: false + property string theme: "freedesktop" + } + property JsonObject time: JsonObject { // https://doc.qt.io/qt-6/qtime.html#toString property string format: "hh:mm" property string shortDateFormat: "dd/MM" property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { - property string alertSound: "" property int breakTime: 300 property int cyclesBeforeLongBreak: 4 property int focus: 1500 diff --git a/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml b/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml index 565758468..2e53a757a 100644 --- a/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml @@ -112,6 +112,20 @@ ContentPage { } } } + ConfigRow { + uniform: true + ConfigSpinBox { + icon: "charger" + text: Translation.tr("Full warning") + value: Config.options.battery.full + from: 0 + to: 101 + stepSize: 5 + onValueChanged: { + Config.options.battery.full = value; + } + } + } } ContentSection { @@ -239,6 +253,30 @@ ContentPage { } } + ContentSection { + icon: "notification_sound" + title: Translation.tr("Sounds") + ConfigRow { + uniform: true + ConfigSwitch { + buttonIcon: "battery_android_full" + text: Translation.tr("Battery") + checked: Config.options.sounds.battery + onCheckedChanged: { + Config.options.sounds.battery = checked; + } + } + ConfigSwitch { + buttonIcon: "av_timer" + text: Translation.tr("Pomodoro") + checked: Config.options.sounds.pomodoro + onCheckedChanged: { + Config.options.sounds.pomodoro = checked; + } + } + } + } + ContentSection { icon: "nest_clock_farsight_analog" title: Translation.tr("Time") diff --git a/dots/.config/quickshell/ii/services/Audio.qml b/dots/.config/quickshell/ii/services/Audio.qml index 43bc61b4d..4ce6521c6 100644 --- a/dots/.config/quickshell/ii/services/Audio.qml +++ b/dots/.config/quickshell/ii/services/Audio.qml @@ -15,6 +15,7 @@ Singleton { property PwNode sink: Pipewire.defaultAudioSink property PwNode source: Pipewire.defaultAudioSource readonly property real hardMaxValue: 2.00 // People keep joking about setting volume to 5172% so... + property string audioTheme: Config.options.sounds.theme signal sinkProtectionTriggered(string reason); @@ -49,7 +50,28 @@ Singleton { } lastVolume = sink.audio.volume; } - } + function playSystemSound(soundName) { + const ogaPath = `/usr/share/sounds/${root.audioTheme}/stereo/${soundName}.oga`; + const oggPath = `/usr/share/sounds/${root.audioTheme}/stereo/${soundName}.ogg`; + + // Try playing .oga first + let command = [ + "ffplay", + "-nodisp", + "-autoexit", + ogaPath + ]; + Quickshell.execDetached(command); + + // Also try playing .ogg (ffplay will just fail silently if file doesn't exist) + command = [ + "ffplay", + "-nodisp", + "-autoexit", + oggPath + ]; + Quickshell.execDetached(command); + } } diff --git a/dots/.config/quickshell/ii/services/Battery.qml b/dots/.config/quickshell/ii/services/Battery.qml index 0a19b70f1..8bee59770 100644 --- a/dots/.config/quickshell/ii/services/Battery.qml +++ b/dots/.config/quickshell/ii/services/Battery.qml @@ -18,10 +18,12 @@ Singleton { property bool isLow: available && (percentage <= Config.options.battery.low / 100) property bool isCritical: available && (percentage <= Config.options.battery.critical / 100) property bool isSuspending: available && (percentage <= Config.options.battery.suspend / 100) + property bool isFull: available && (percentage >= Config.options.battery.full / 100) property bool isLowAndNotCharging: isLow && !isCharging property bool isCriticalAndNotCharging: isCritical && !isCharging property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging + property bool isFullAndCharging: isFull && isCharging property real energyRate: UPower.displayDevice.changeRate property real timeToEmpty: UPower.displayDevice.timeToEmpty @@ -35,17 +37,28 @@ Singleton { "-u", "critical", "-a", "Shell" ]) + + if (available && Config.options.sounds.battery) { + if (isLowAndNotCharging) { + Audio.playSystemSound("dialog-warning") + } + } } onIsCriticalAndNotChargingChanged: { if (available && isCriticalAndNotCharging) Quickshell.execDetached([ "notify-send", Translation.tr("Critically low battery"), - Translation.tr("Please charge!\nAutomatic suspend triggers at %1").arg(Config.options.battery.suspend), + Translation.tr("Please charge!\nAutomatic suspend triggers at %1%").arg(Config.options.battery.suspend), "-u", "critical", "-a", "Shell" ]); - + + if (available && Config.options.sounds.battery) { + if (isCriticalAndNotCharging) { + Audio.playSystemSound("suspend-error") + } + } } onIsSuspendingAndNotChargingChanged: { @@ -53,4 +66,29 @@ Singleton { Quickshell.execDetached(["bash", "-c", `systemctl suspend || loginctl suspend`]); } } + + onIsFullAndChargingChanged: { + if (available && isFullAndCharging) Quickshell.execDetached([ + "notify-send", + Translation.tr("Battery full"), + Translation.tr("Please unplug the charger"), + "-a", "Shell" + ]); + + if (available && Config.options.sounds.battery) { + if (isFullAndCharging) { + Audio.playSystemSound("complete") + } + } + } + + onIsPluggedInChanged: { + if (available && Config.options.sounds.battery) { + if (isPluggedIn) { + Audio.playSystemSound("power-plug") + } else { + Audio.playSystemSound("power-unplug") + } + } + } } diff --git a/dots/.config/quickshell/ii/services/TimerService.qml b/dots/.config/quickshell/ii/services/TimerService.qml index a75893681..69e5fe1c1 100644 --- a/dots/.config/quickshell/ii/services/TimerService.qml +++ b/dots/.config/quickshell/ii/services/TimerService.qml @@ -1,6 +1,7 @@ pragma Singleton pragma ComponentBehavior: Bound +import qs.services import qs.modules.common import Quickshell @@ -17,7 +18,6 @@ Singleton { property int breakTime: Config.options.time.pomodoro.breakTime property int longBreakTime: Config.options.time.pomodoro.longBreak property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak - property string alertSound: Config.options.time.pomodoro.alertSound property bool pomodoroRunning: Persistent.states.timer.pomodoro.running property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak @@ -64,8 +64,9 @@ Singleton { } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); - if (alertSound) - Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]); + if (Config.options.sounds.pomodoro) { + Audio.playSystemSound("alarm-clock-elapsed") + } if (!pomodoroBreak) { Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak;