mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 23:09:26 -05:00
feat: sound alerts for battery and pomodoro (#2223)
This commit is contained in:
@@ -245,6 +245,7 @@ Singleton {
|
|||||||
property JsonObject battery: JsonObject {
|
property JsonObject battery: JsonObject {
|
||||||
property int low: 20
|
property int low: 20
|
||||||
property int critical: 5
|
property int critical: 5
|
||||||
|
property int full: 101
|
||||||
property bool automaticSuspend: true
|
property bool automaticSuspend: true
|
||||||
property int suspend: 3
|
property int suspend: 3
|
||||||
}
|
}
|
||||||
@@ -418,13 +419,18 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property JsonObject sounds: JsonObject {
|
||||||
|
property bool battery: false
|
||||||
|
property bool pomodoro: false
|
||||||
|
property string theme: "freedesktop"
|
||||||
|
}
|
||||||
|
|
||||||
property JsonObject time: JsonObject {
|
property JsonObject time: JsonObject {
|
||||||
// https://doc.qt.io/qt-6/qtime.html#toString
|
// https://doc.qt.io/qt-6/qtime.html#toString
|
||||||
property string format: "hh:mm"
|
property string format: "hh:mm"
|
||||||
property string shortDateFormat: "dd/MM"
|
property string shortDateFormat: "dd/MM"
|
||||||
property string dateFormat: "ddd, dd/MM"
|
property string dateFormat: "ddd, dd/MM"
|
||||||
property JsonObject pomodoro: JsonObject {
|
property JsonObject pomodoro: JsonObject {
|
||||||
property string alertSound: ""
|
|
||||||
property int breakTime: 300
|
property int breakTime: 300
|
||||||
property int cyclesBeforeLongBreak: 4
|
property int cyclesBeforeLongBreak: 4
|
||||||
property int focus: 1500
|
property int focus: 1500
|
||||||
|
|||||||
@@ -113,6 +113,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 {
|
ContentSection {
|
||||||
@@ -241,6 +255,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 {
|
ContentSection {
|
||||||
icon: "nest_clock_farsight_analog"
|
icon: "nest_clock_farsight_analog"
|
||||||
title: Translation.tr("Time")
|
title: Translation.tr("Time")
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Singleton {
|
|||||||
property PwNode sink: Pipewire.defaultAudioSink
|
property PwNode sink: Pipewire.defaultAudioSink
|
||||||
property PwNode source: Pipewire.defaultAudioSource
|
property PwNode source: Pipewire.defaultAudioSource
|
||||||
readonly property real hardMaxValue: 2.00 // People keep joking about setting volume to 5172% so...
|
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);
|
signal sinkProtectionTriggered(string reason);
|
||||||
|
|
||||||
@@ -49,7 +50,28 @@ Singleton {
|
|||||||
}
|
}
|
||||||
lastVolume = sink.audio.volume;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,49 +8,79 @@ import QtQuick
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
|
id: root
|
||||||
property bool available: UPower.displayDevice.isLaptopBattery
|
property bool available: UPower.displayDevice.isLaptopBattery
|
||||||
property var chargeState: UPower.displayDevice.state
|
property var chargeState: UPower.displayDevice.state
|
||||||
property bool isCharging: chargeState == UPowerDeviceState.Charging
|
property bool isCharging: chargeState == UPowerDeviceState.Charging
|
||||||
property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge
|
property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge
|
||||||
property real percentage: UPower.displayDevice?.percentage ?? 1
|
property real percentage: UPower.displayDevice?.percentage ?? 1
|
||||||
readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend
|
readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend
|
||||||
|
readonly property bool soundEnabled: Config.options.sounds.battery
|
||||||
|
|
||||||
property bool isLow: available && (percentage <= Config.options.battery.low / 100)
|
property bool isLow: available && (percentage <= Config.options.battery.low / 100)
|
||||||
property bool isCritical: available && (percentage <= Config.options.battery.critical / 100)
|
property bool isCritical: available && (percentage <= Config.options.battery.critical / 100)
|
||||||
property bool isSuspending: available && (percentage <= Config.options.battery.suspend / 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 isLowAndNotCharging: isLow && !isCharging
|
||||||
property bool isCriticalAndNotCharging: isCritical && !isCharging
|
property bool isCriticalAndNotCharging: isCritical && !isCharging
|
||||||
property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging
|
property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging
|
||||||
|
property bool isFullAndCharging: isFull && isCharging
|
||||||
|
|
||||||
property real energyRate: UPower.displayDevice.changeRate
|
property real energyRate: UPower.displayDevice.changeRate
|
||||||
property real timeToEmpty: UPower.displayDevice.timeToEmpty
|
property real timeToEmpty: UPower.displayDevice.timeToEmpty
|
||||||
property real timeToFull: UPower.displayDevice.timeToFull
|
property real timeToFull: UPower.displayDevice.timeToFull
|
||||||
|
|
||||||
onIsLowAndNotChargingChanged: {
|
onIsLowAndNotChargingChanged: {
|
||||||
if (available && isLowAndNotCharging) Quickshell.execDetached([
|
if (!root.available || !isLowAndNotCharging) return;
|
||||||
|
Quickshell.execDetached([
|
||||||
"notify-send",
|
"notify-send",
|
||||||
Translation.tr("Low battery"),
|
Translation.tr("Low battery"),
|
||||||
Translation.tr("Consider plugging in your device"),
|
Translation.tr("Consider plugging in your device"),
|
||||||
"-u", "critical",
|
"-u", "critical",
|
||||||
"-a", "Shell"
|
"-a", "Shell"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if (root.soundEnabled) Audio.playSystemSound("dialog-warning");
|
||||||
}
|
}
|
||||||
|
|
||||||
onIsCriticalAndNotChargingChanged: {
|
onIsCriticalAndNotChargingChanged: {
|
||||||
if (available && isCriticalAndNotCharging) Quickshell.execDetached([
|
if (!root.available || !isCriticalAndNotCharging) return;
|
||||||
|
Quickshell.execDetached([
|
||||||
"notify-send",
|
"notify-send",
|
||||||
Translation.tr("Critically low battery"),
|
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",
|
"-u", "critical",
|
||||||
"-a", "Shell"
|
"-a", "Shell"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (root.soundEnabled) Audio.playSystemSound("suspend-error");
|
||||||
}
|
}
|
||||||
|
|
||||||
onIsSuspendingAndNotChargingChanged: {
|
onIsSuspendingAndNotChargingChanged: {
|
||||||
if (available && isSuspendingAndNotCharging) {
|
if (root.available && isSuspendingAndNotCharging) {
|
||||||
Quickshell.execDetached(["bash", "-c", `systemctl suspend || loginctl suspend`]);
|
Quickshell.execDetached(["bash", "-c", `systemctl suspend || loginctl suspend`]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onIsFullAndChargingChanged: {
|
||||||
|
if (!root.available || !isFullAndCharging) return;
|
||||||
|
Quickshell.execDetached([
|
||||||
|
"notify-send",
|
||||||
|
Translation.tr("Battery full"),
|
||||||
|
Translation.tr("Please unplug the charger"),
|
||||||
|
"-a", "Shell"
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (root.soundEnabled) Audio.playSystemSound("complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
onIsPluggedInChanged: {
|
||||||
|
if (!root.available || !root.soundEnabled) return;
|
||||||
|
if (isPluggedIn) {
|
||||||
|
Audio.playSystemSound("power-plug")
|
||||||
|
} else {
|
||||||
|
Audio.playSystemSound("power-unplug")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import qs.services
|
||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
@@ -17,7 +18,6 @@ Singleton {
|
|||||||
property int breakTime: Config.options.time.pomodoro.breakTime
|
property int breakTime: Config.options.time.pomodoro.breakTime
|
||||||
property int longBreakTime: Config.options.time.pomodoro.longBreak
|
property int longBreakTime: Config.options.time.pomodoro.longBreak
|
||||||
property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak
|
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 pomodoroRunning: Persistent.states.timer.pomodoro.running
|
||||||
property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak
|
property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak
|
||||||
@@ -64,8 +64,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]);
|
Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]);
|
||||||
if (alertSound)
|
if (Config.options.sounds.pomodoro) {
|
||||||
Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]);
|
Audio.playSystemSound("alarm-clock-elapsed")
|
||||||
|
}
|
||||||
|
|
||||||
if (!pomodoroBreak) {
|
if (!pomodoroBreak) {
|
||||||
Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak;
|
Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak;
|
||||||
|
|||||||
Reference in New Issue
Block a user