diff options
Diffstat (limited to '.config/noctalia/plugins/timer/Main.qml')
| -rw-r--r-- | .config/noctalia/plugins/timer/Main.qml | 239 |
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(); + } + } + } + ); + } +} |