aboutsummaryrefslogtreecommitdiffstats
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/Status.qml74
1 files changed, 74 insertions, 0 deletions
diff --git a/shared/Status.qml b/shared/Status.qml
index be090b3..5d799e2 100644
--- a/shared/Status.qml
+++ b/shared/Status.qml
@@ -36,6 +36,23 @@ Singleton {
// the wanted default of locking on. The row inverts it for the label.
readonly property bool nolock: nolockFile.value
+ // Breaktimer, read only. The daemon publishes these three files and owns
+ // them; the shell calls verbs and never writes them, because the daemon
+ // loop rewrites state and phase on every transition.
+ //
+ // A stopped daemon is read from the state file rather than probed: both
+ // paths that end it, stop_daemon and the cleanup trap, write "stopped"
+ // there. A daemon lost to SIGKILL leaves a stale "running" and the drawer
+ // shows a frozen countdown, which is a visible wrong answer the start
+ // button resolves. That is cheaper than a liveness probe per repaint.
+ readonly property string btState: btStateFile.value
+ readonly property string btPhase: btPhaseFile.value
+ readonly property int btRemain: btRemainFile.value
+
+ readonly property bool btRunning: root.btState === "running"
+ || root.btState === "paused"
+ readonly property bool btPaused: root.btState === "paused"
+
readonly property int activeCount: (root.dnd ? 1 : 0) + (root.presentation ? 1 : 0)
+ (root.nolock ? 1 : 0)
@@ -121,6 +138,17 @@ Singleton {
breakProc.running = true;
}
+ // Seconds as m:ss. The daemon rewrites the remaining seconds every five
+ // seconds, its tick, so this counts down in five second steps and does
+ // not interpolate: a local one second timer would be a second clock
+ // drifting against the first, correcting itself with a visible jump, and
+ // it would keep ticking while the daemon is frozen outside the work
+ // window or paused.
+ function btCountdown() {
+ const s = Math.max(0, root.btRemain);
+ return Math.floor(s / 60) + ":" + String(s % 60).padStart(2, "0");
+ }
+
// breaktimer owns its own state file; this only calls its verbs. Two
// writers on that file would race with its daemon loop, which rewrites it
// on every phase change.
@@ -175,7 +203,53 @@ Singleton {
onLoadFailed: mf.value = false
}
+ // A daemon file holding a word. Same watch and same missing-file rule as
+ // ModeFile, without a write path: this side only reads.
+ component WordFile: FileView {
+ id: wf
+
+ property string value: "stopped"
+
+ function reparse() {
+ const t = wf.text().trim();
+ if (t !== wf.value) wf.value = t;
+ }
+
+ watchChanges: true
+ printErrors: false
+ onFileChanged: wf.reload()
+ onLoaded: wf.reparse()
+ // No file means no daemon, which is the stopped state, not an error.
+ onLoadFailed: wf.value = "stopped"
+ }
+
+ // The remaining seconds. Validated as an integer rather than trusted:
+ // the file is written every tick and a read can catch it mid-write, and
+ // an empty string coerces to 0 silently while NaN would propagate into
+ // the countdown as "NaN:aN".
+ component SecondsFile: FileView {
+ id: sf
+
+ property int value: 0
+
+ function reparse() {
+ const n = parseInt(sf.text().trim(), 10);
+ const v = (isNaN(n) || n < 0) ? 0 : n;
+ if (v !== sf.value) sf.value = v;
+ }
+
+ watchChanges: true
+ printErrors: false
+ onFileChanged: sf.reload()
+ onLoaded: sf.reparse()
+ onLoadFailed: sf.value = 0
+ }
+
ModeFile { id: dndFile; path: root.dir + "/status.dnd" }
ModeFile { id: presFile; path: root.dir + "/status.presentation" }
ModeFile { id: nolockFile; path: root.dir + "/status.nolock" }
+
+ WordFile { id: btStateFile; path: root.dir + "/breaktimer.state" }
+ WordFile { id: btPhaseFile; path: root.dir + "/breaktimer.phase" }
+ SecondsFile { id: btRemainFile; path: root.dir + "/breaktimer.remain" }
}