summaryrefslogtreecommitdiff
path: root/.config/noctalia/plugins/timer/Main.qml
diff options
context:
space:
mode:
Diffstat (limited to '.config/noctalia/plugins/timer/Main.qml')
-rw-r--r--.config/noctalia/plugins/timer/Main.qml239
1 files changed, 239 insertions, 0 deletions
diff --git a/.config/noctalia/plugins/timer/Main.qml b/.config/noctalia/plugins/timer/Main.qml
new file mode 100644
index 0000000..db2193d
--- /dev/null
+++ b/.config/noctalia/plugins/timer/Main.qml
@@ -0,0 +1,239 @@
+import QtQuick
+import Quickshell
+import qs.Commons
+import qs.Services.System
+import qs.Services.UI
+import Quickshell.Io
+
+Item {
+ id: root
+
+ property var pluginApi: null
+
+ IpcHandler {
+ target: "plugin:timer"
+
+ function toggle() {
+ if (pluginApi) {
+ pluginApi.withCurrentScreen(screen => {
+ pluginApi.togglePanel(screen);
+ });
+ }
+ }
+
+ function start(duration_str: string) {
+ if (duration_str && duration_str === "stopwatch") {
+ root.stopwatchReset();
+ root.timerStopwatchMode = true;
+ root.stopwatchStart();
+ } else if (duration_str && duration_str !== "") {
+ const seconds = root.parseDuration(duration_str);
+ if (seconds > 0) {
+ root.countdownReset();
+ root.cdRemainingSeconds = seconds;
+ root.timerStopwatchMode = false;
+ root.countdownStart();
+ }
+ } else {
+ root.timerStart();
+ }
+ }
+
+ function pause() {
+ root.timerPause();
+ }
+
+ function reset() {
+ root.timerReset();
+ }
+ }
+
+ // View mode (which tab is active in the panel)
+ property bool timerStopwatchMode: false
+
+ // Countdown state
+ property bool cdRunning: false
+ property int cdRemainingSeconds: 0
+ property int cdTotalSeconds: 0
+ property int cdStartTimestamp: 0
+ property int cdPausedAt: 0
+ property bool cdSoundPlaying: false
+
+ // Stopwatch state
+ property bool swRunning: false
+ property int swElapsedSeconds: 0
+ property int swStartTimestamp: 0
+ property int swPausedAt: 0
+
+ // Backward-compatible computed properties (used by bar/CC widgets)
+ readonly property bool timerRunning: timerStopwatchMode ? swRunning : cdRunning
+ readonly property int timerRemainingSeconds: cdRemainingSeconds
+ readonly property int timerTotalSeconds: cdTotalSeconds
+ readonly property int timerElapsedSeconds: swElapsedSeconds
+ readonly property bool timerSoundPlaying: cdSoundPlaying
+
+ // Current timestamp
+ property int timestamp: Math.floor(Date.now() / 1000)
+
+ // Main timer loop
+ Timer {
+ id: updateTimer
+ interval: 1000
+ repeat: true
+ running: true
+ triggeredOnStart: false
+ onTriggered: {
+ var now = new Date();
+ root.timestamp = Math.floor(now.getTime() / 1000);
+
+ // Update countdown if running
+ if (root.cdRunning && root.cdStartTimestamp > 0) {
+ const elapsed = root.timestamp - root.cdStartTimestamp;
+ root.cdRemainingSeconds = root.cdTotalSeconds - elapsed;
+ if (root.cdRemainingSeconds <= 0) {
+ root.countdownOnFinished();
+ }
+ }
+
+ // Update stopwatch if running
+ if (root.swRunning && root.swStartTimestamp > 0) {
+ const elapsed = root.timestamp - root.swStartTimestamp;
+ root.swElapsedSeconds = root.swPausedAt + elapsed;
+ }
+
+ // Sync to next second
+ var msIntoSecond = now.getMilliseconds();
+ if (msIntoSecond > 100) {
+ updateTimer.interval = 1000 - msIntoSecond + 10;
+ updateTimer.restart();
+ } else {
+ updateTimer.interval = 1000;
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ // Sync start
+ var now = new Date();
+ var msUntilNextSecond = 1000 - now.getMilliseconds();
+ updateTimer.interval = msUntilNextSecond + 10;
+ updateTimer.restart();
+ }
+
+ // Countdown logic
+ function countdownStart() {
+ if (root.cdRemainingSeconds <= 0) return;
+ root.cdTotalSeconds = root.cdRemainingSeconds;
+ root.cdStartTimestamp = root.timestamp;
+ root.cdPausedAt = 0;
+ root.cdRunning = true;
+ }
+
+ function countdownPause() {
+ if (root.cdRunning) {
+ const currentTimestamp = Math.floor(Date.now() / 1000);
+ const elapsed = currentTimestamp - root.cdStartTimestamp;
+ const remaining = root.cdTotalSeconds - elapsed;
+ root.cdPausedAt = Math.max(0, remaining);
+ root.cdRemainingSeconds = root.cdPausedAt;
+ }
+ root.cdRunning = false;
+ root.cdStartTimestamp = 0;
+ SoundService.stopSound("alarm-beep.wav");
+ root.cdSoundPlaying = false;
+ }
+
+ function countdownReset() {
+ root.cdRunning = false;
+ root.cdStartTimestamp = 0;
+ root.cdRemainingSeconds = 0;
+ root.cdTotalSeconds = 0;
+ root.cdPausedAt = 0;
+ SoundService.stopSound("alarm-beep.wav");
+ root.cdSoundPlaying = false;
+ }
+
+ // Stopwatch logic
+ function stopwatchStart() {
+ root.swStartTimestamp = root.timestamp;
+ root.swPausedAt = root.swElapsedSeconds;
+ root.swRunning = true;
+ }
+
+ function stopwatchPause() {
+ if (root.swRunning) {
+ root.swPausedAt = root.swElapsedSeconds;
+ }
+ root.swRunning = false;
+ root.swStartTimestamp = 0;
+ }
+
+ function stopwatchReset() {
+ root.swRunning = false;
+ root.swStartTimestamp = 0;
+ root.swElapsedSeconds = 0;
+ root.swPausedAt = 0;
+ }
+
+ // Convenience: operate on current mode
+ function timerStart() {
+ if (root.timerStopwatchMode) stopwatchStart();
+ else countdownStart();
+ }
+
+ function timerPause() {
+ if (root.timerStopwatchMode) stopwatchPause();
+ else countdownPause();
+ }
+
+ function timerReset() {
+ if (root.timerStopwatchMode) stopwatchReset();
+ else countdownReset();
+ }
+
+ function parseDuration(duration_str) {
+ if (!duration_str) return 0;
+
+ // Default to minutes if just a number
+ if (/^\d+$/.test(duration_str)) {
+ return parseInt(duration_str) * 60;
+ }
+
+ var totalSeconds = 0;
+ var regex = /(\d+)([hms])/g;
+ var match;
+
+ while ((match = regex.exec(duration_str)) !== null) {
+ var value = parseInt(match[1]);
+ var unit = match[2];
+
+ if (unit === 'h') totalSeconds += value * 3600;
+ else if (unit === 'm') totalSeconds += value * 60;
+ else if (unit === 's') totalSeconds += value;
+ }
+
+ return totalSeconds;
+ }
+
+ function countdownOnFinished() {
+ root.cdRunning = false;
+ root.cdRemainingSeconds = 0;
+ root.cdSoundPlaying = true;
+ SoundService.playSound("alarm-beep.wav", {
+ repeat: true,
+ volume: 0.3
+ });
+ ToastService.showNotice(
+ pluginApi?.tr("toast.title") || "Timer",
+ pluginApi?.tr("toast.finished") || "Timer finished!",
+ "hourglass",
+ {
+ onDismissed: () => {
+ if (root.cdSoundPlaying) {
+ root.countdownPause();
+ }
+ }
+ }
+ );
+ }
+}