Files
dms-gamebar/services/TimerService.qml
T

201 lines
6.5 KiB
QML

import QtQuick
import Quickshell
import qs.Common
import qs.Services
Item {
id: service
property var daemon: null
readonly property var pluginData: (daemon && daemon.pluginData) ? daemon.pluginData : ({})
readonly property string alarmSound: String(pluginData.timerSound || "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga").trim()
readonly property bool notifyOnFinish: pluginData.timerNotify !== false
readonly property bool soundOnFinish: pluginData.timerPlaySound !== false
// ── Countdown ────────────────────────────────────────────────────────────
property int durationMs: 5 * 60 * 1000
property int remainingMs: durationMs
property double deadline: 0
property bool timerRunning: false
property bool timerFinished: false
property string timerLabel: ""
readonly property real timerProgress: durationMs > 0 ? Math.max(0, Math.min(1, 1 - remainingMs / durationMs)) : 0
// ── Stopwatch ────────────────────────────────────────────────────────────
property double stopwatchStartedAt: 0
property int stopwatchBaseMs: 0
property int stopwatchElapsedMs: 0
property bool stopwatchRunning: false
property var laps: []
signal finished(string label)
function formatDuration(ms) {
const total = Math.max(0, Math.round(ms / 1000));
const hours = Math.floor(total / 3600);
const mins = Math.floor((total % 3600) / 60);
const secs = total % 60;
const pad = value => (value < 10 ? "0" : "") + value;
return hours > 0 ? hours + ":" + pad(mins) + ":" + pad(secs) : pad(mins) + ":" + pad(secs);
}
function formatStopwatch(ms) {
const hundredths = Math.floor((Math.max(0, ms) % 1000) / 10);
return service.formatDuration(ms) + "." + (hundredths < 10 ? "0" : "") + hundredths;
}
function setDuration(ms) {
service.durationMs = Math.max(1000, Math.round(ms));
if (!service.timerRunning) {
service.remainingMs = service.durationMs;
service.timerFinished = false;
}
}
function addTime(ms) {
if (service.timerRunning) {
service.deadline += ms;
service.durationMs = Math.max(1000, service.durationMs + ms);
service._tick();
return;
}
service.setDuration(service.durationMs + ms);
}
function startTimer(ms, label) {
if (ms !== undefined && ms !== null)
service.setDuration(ms);
if (label !== undefined && label !== null)
service.timerLabel = String(label);
if (service.remainingMs <= 0)
service.remainingMs = service.durationMs;
service.timerFinished = false;
service.deadline = Date.now() + service.remainingMs;
service.timerRunning = true;
}
function pauseTimer() {
if (!service.timerRunning)
return;
service.remainingMs = Math.max(0, service.deadline - Date.now());
service.timerRunning = false;
}
function toggleTimer() {
if (service.timerRunning)
service.pauseTimer();
else
service.startTimer();
}
function resetTimer() {
service.timerRunning = false;
service.timerFinished = false;
service.remainingMs = service.durationMs;
}
function _tick() {
if (!service.timerRunning)
return;
const left = service.deadline - Date.now();
if (left <= 0) {
service.remainingMs = 0;
service.timerRunning = false;
service.timerFinished = true;
service._announce();
return;
}
service.remainingMs = left;
}
// Fires whether or not the overlay is open - the service lives in the daemon.
function _announce() {
const label = service.timerLabel.trim();
const body = label !== "" ? label : "Timer finished after " + service.formatDuration(service.durationMs);
if (service.soundOnFinish && service.alarmSound !== "")
Quickshell.execDetached(["sh", "-c", '[ -r "$1" ] && exec paplay "$1"', "gameBar", service.alarmSound]);
if (service.notifyOnFinish)
Quickshell.execDetached(["notify-send", "-a", "Game Bar", "-u", "critical", "-i", "timer", "Timer finished", body]);
if (typeof ToastService !== "undefined" && ToastService)
ToastService.showInfo("Timer finished", body);
service.finished(label);
}
function dismissFinished() {
service.timerFinished = false;
service.remainingMs = service.durationMs;
}
// ── Stopwatch control ────────────────────────────────────────────────────
function startStopwatch() {
if (service.stopwatchRunning)
return;
service.stopwatchStartedAt = Date.now();
service.stopwatchRunning = true;
}
function pauseStopwatch() {
if (!service.stopwatchRunning)
return;
service.stopwatchBaseMs = service.stopwatchElapsedMs;
service.stopwatchRunning = false;
}
function toggleStopwatch() {
if (service.stopwatchRunning)
service.pauseStopwatch();
else
service.startStopwatch();
}
function resetStopwatch() {
service.stopwatchRunning = false;
service.stopwatchBaseMs = 0;
service.stopwatchElapsedMs = 0;
service.laps = [];
}
function lap() {
if (!service.stopwatchRunning && service.stopwatchElapsedMs === 0)
return;
const previous = service.laps.length > 0 ? service.laps[0].total : 0;
service.laps = [
{
"index": service.laps.length + 1,
"total": service.stopwatchElapsedMs,
"split": service.stopwatchElapsedMs - previous
}
].concat(service.laps);
}
Timer {
running: service.timerRunning
interval: 200
repeat: true
onTriggered: service._tick()
}
Timer {
running: service.stopwatchRunning
interval: 50
repeat: true
onTriggered: service.stopwatchElapsedMs = service.stopwatchBaseMs + (Date.now() - service.stopwatchStartedAt)
}
}