aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--desktop/modules/ai/AiModule.qml16
-rw-r--r--desktop/modules/ai/AiPage.qml2
-rw-r--r--desktop/modules/ai/README.md10
-rwxr-xr-xdesktop/modules/ai/ai-state.sh27
-rwxr-xr-xdesktop/modules/ai/test-ai-state.sh22
-rw-r--r--desktop/modules/notes/NotesModule.qml2
-rw-r--r--desktop/modules/status/BreakRow.qml84
-rw-r--r--desktop/modules/status/README.md46
-rw-r--r--desktop/modules/status/StatusPage.qml12
-rw-r--r--desktop/modules/status/StatusTile.qml9
-rw-r--r--games/__pycache__/games.cpython-312.pycbin0 -> 11370 bytes
-rw-r--r--shared/Notify.qml5
-rw-r--r--shared/Status.qml97
-rw-r--r--window-switcher/README.md2
14 files changed, 25 insertions, 309 deletions
diff --git a/desktop/modules/ai/AiModule.qml b/desktop/modules/ai/AiModule.qml
index d58ebfe..4c50224 100644
--- a/desktop/modules/ai/AiModule.qml
+++ b/desktop/modules/ai/AiModule.qml
@@ -41,16 +41,12 @@ Module {
sd: "stable-diffusion.cpp",
assistant: "Desktop assistant",
chat: "Local chat",
- imggen: "imggen",
- fanfic: "Fanfictioner",
})
readonly property string assistantSh:
Quickshell.env("HOME") + "/Programming/GIT/desktop-assistant/assistant.sh"
- // Each project's own control. Fanfictioner has no start, a run needs a
- // story file, and its stop needs the pid, see stop(). llamachat has no
- // restart, and --quit answers before the app is gone, so a --daemon
+ // Each project's own control. llamachat has no restart, and --quit answers before the app is gone, so a --daemon
// straight after would find it still running and do nothing: wait for
// --ping to fail first, up to 5s.
readonly property var controls: ({
@@ -59,7 +55,6 @@ Module {
chat: { start: ["llamachat", "--daemon"], stop: ["llamachat", "--quit"],
restart: ["sh", "-c", "llamachat --quit; for _ in $(seq 50); do "
+ "llamachat --ping || break; sleep 0.1; done; exec llamachat --daemon"] },
- imggen: { start: ["imggen", "start"], stop: ["imggen", "stop"] },
})
function canStart(app) { return controls[app.name] !== undefined; }
@@ -75,14 +70,7 @@ Module {
}
function stop(app) {
- if (app.name !== "fanfic") {
- exec(controls[app.name].stop);
- return;
- }
- // The sd-cli children first, then the script, by pid only: a name
- // match would take unrelated sd-cli and python processes with it.
- if (!/^[0-9]+$/.test(app.detail)) return;
- exec(["sh", "-c", 'pkill -TERM -P "$1"; kill -TERM "$1"', "sh", app.detail]);
+ exec(controls[app.name].stop);
}
// The router frees the model's VRAM; the next request for it loads it
diff --git a/desktop/modules/ai/AiPage.qml b/desktop/modules/ai/AiPage.qml
index 369c1b5..c1cb07c 100644
--- a/desktop/modules/ai/AiPage.qml
+++ b/desktop/modules/ai/AiPage.qml
@@ -68,7 +68,7 @@ Column {
detail: {
if (!modelData.running) return "Stopped";
if (modelData.detail === "") return "Running";
- return "Running · " + (modelData.name === "fanfic" ? "pid " : "") + modelData.detail;
+ return "Running · " + modelData.detail;
}
action: modelData.running ? "Stop" : page.ai.canStart(modelData) ? "Start" : ""
danger: modelData.running
diff --git a/desktop/modules/ai/README.md b/desktop/modules/ai/README.md
index e71159b..d87087a 100644
--- a/desktop/modules/ai/README.md
+++ b/desktop/modules/ai/README.md
@@ -18,10 +18,10 @@ llama's state comes from its API: `/models` for the loaded model, then
`autoload=false` matters: a plain `/slots?model=` on an unloaded model loads
it into VRAM, which is how this was found.
-The sd row covers two processes. A running `sd-cli`, the one-shot runs
-fanfictioner makes, reads up and generating with its model marked `(sd-cli)`,
-whatever the web UI is doing: it holds VRAM for as long as it lives. The first
-version watched only `sd-server` and showed the engine down mid-run.
+The sd row covers two processes. A running one-shot `sd-cli` reads up and
+generating with its model marked `(sd-cli)`, whatever the web UI is doing: it
+holds VRAM for as long as it lives. The first version watched only
+`sd-server` and showed the engine down mid-run.
sd-server has no progress endpoint. Its log, `~/.cache/sd-server.log`, streams
progress bars while it generates, so the row reads busy while the log was
@@ -33,8 +33,6 @@ written in the last 5s. A step slower than that reads idle.
| --- | --- | --- | --- | --- |
| desktop-assistant | `assistant.sh status` | `assistant.sh start` | `assistant.sh stop` | `assistant.sh restart` |
| local chat | `llamachat --ping` | `llamachat --daemon` | `llamachat --quit` | quit, wait for `--ping` to fail (5s at most), `--daemon` |
-| imggen | `imggen status` | `imggen start` (default model) | `imggen stop` | none |
-| fanfictioner | `pgrep` on `python3 .../fanfictioner` | none | TERM its children, then it, by pid | none |
Restart shows only while an app is running. llamachat's `--quit` answers
before the app has exited, so a `--daemon` sent straight after finds it still
diff --git a/desktop/modules/ai/ai-state.sh b/desktop/modules/ai/ai-state.sh
index 8b10368..a4406ce 100755
--- a/desktop/modules/ai/ai-state.sh
+++ b/desktop/modules/ai/ai-state.sh
@@ -12,19 +12,17 @@
# GNU General Public License for more details.
#
# One sweep of the local AI stack for the drawer's AI page. Always prints
-# these six lines, tab separated, and exits 0:
+# these four lines, tab separated, and exits 0:
#
# engine<TAB>llama<TAB>up|down<TAB>idle|busy<TAB>model
# engine<TAB>sd<TAB>up|down<TAB>idle|busy<TAB>model, "(sd-cli)" for a CLI run
# app<TAB>assistant<TAB>running|stopped<TAB>-
# app<TAB>chat<TAB>running|stopped<TAB>-
-# app<TAB>imggen<TAB>running|stopped<TAB>model
-# app<TAB>fanfic<TAB>running|stopped<TAB>pid
#
# An empty value is "-". A probe that fails reads as down or stopped, not as
# an error: every row is something that may simply not be running.
#
-# The test stubs curl, pgrep, llamachat and imggen on PATH and points
+# The test stubs curl, pgrep and llamachat on PATH and points
# AI_ASSISTANT and AI_SD_LOG at fixtures; see test-ai-state.sh.
set -u
@@ -65,7 +63,7 @@ model_of() {
sd() {
local line busy=idle mtime
- # A one-shot sd-cli (fanfictioner runs them) holds VRAM and generates for
+ # A one-shot sd-cli holds VRAM and generates for
# as long as it lives, so it outranks whatever the web UI is doing.
if line="$(pgrep -axo sd-cli)"; then
row engine sd up busy "$(model_of "$line") (sd-cli)"
@@ -81,8 +79,6 @@ sd() {
}
apps() {
- local out pid
-
if "$ASSISTANT" status >/dev/null 2>&1; then
row app assistant running -
else
@@ -94,23 +90,6 @@ apps() {
else
row app chat stopped -
fi
-
- # imggen status exits 0 either way: the daemon's JSON when it is up,
- # "daemon down" when it is not.
- out="$(imggen status 2>/dev/null)"
- if [[ "$out" == "{"* ]]; then
- row app imggen running "$(jq -r '.model // "-"' <<<"$out" 2>/dev/null || echo -)"
- else
- row app imggen stopped -
- fi
-
- # It runs through env, so the command line is python3 then the script,
- # by path from ~/bin or by ./fanfictioner from the repo.
- if pid="$(pgrep -u "$USER" -fo '^[^ ]*python3[^ ]* [^ ]*fanfictioner( |$)')"; then
- row app fanfic running "$pid"
- else
- row app fanfic stopped -
- fi
}
llama
diff --git a/desktop/modules/ai/test-ai-state.sh b/desktop/modules/ai/test-ai-state.sh
index e3e8ba0..aac83fb 100755
--- a/desktop/modules/ai/test-ai-state.sh
+++ b/desktop/modules/ai/test-ai-state.sh
@@ -11,8 +11,8 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
-# The one runnable check for ai-state.sh. It puts stub curl, pgrep,
-# llamachat and imggen on PATH and a stub assistant.sh beside them, then runs
+# The one runnable check for ai-state.sh. It puts stub curl, pgrep
+# and llamachat on PATH and a stub assistant.sh beside them, then runs
# the real script, so nothing here touches the live stack. T_* variables pick
# the state each stub reports.
#
@@ -57,9 +57,6 @@ case "$*" in
*sd-server*)
[[ -n "${T_SD:-}" ]] || exit 1
echo "4242 sd-server --listen-port 7860 --diffusion-model /data/SD/z_image_turbo-Q8_0.gguf --vae /data/SD/vae/flux1-ae.safetensors" ;;
- *fanfictioner*)
- [[ -n "${T_FANFIC:-}" ]] || exit 1
- echo 31337 ;;
*) exit 1 ;;
esac
STUB
@@ -69,13 +66,6 @@ cat > "$stub/llamachat" <<'STUB'
[[ "$1" == --ping && -n "${T_CHAT:-}" ]]
STUB
-# The real imggen exits 0 either way and says which on stdout.
-cat > "$stub/imggen" <<'STUB'
-#!/bin/bash
-[[ "$1" == status ]] || exit 1
-if [[ -n "${T_IMG:-}" ]]; then echo '{"model": "realvis", "ready": true}'; else echo "daemon down"; fi
-STUB
-
cat > "$stub/assistant.sh" <<'STUB'
#!/bin/bash
[[ "$1" == status && -n "${T_ASSIST:-}" ]]
@@ -106,7 +96,7 @@ run() {
bash "$here/ai-state.sh"
}
-apps_down="$(l app assistant stopped -; l app chat stopped -; l app imggen stopped -; l app fanfic stopped -)"
+apps_down="$(l app assistant stopped -; l app chat stopped -)"
want="$(l engine llama down idle -; l engine sd down idle -)
$apps_down"
@@ -131,7 +121,7 @@ touch -d '1 minute ago' "$log"
got="$(run T_SD=1 | sed -n 2p)"
check "sd running, log quiet" "$(l engine sd up idle z_image_turbo-Q8_0.gguf)" "$got"
-# A one-shot sd-cli (fanfictioner's) holds VRAM and is generating for as
+# A one-shot sd-cli holds VRAM and is generating for as
# long as it runs, whatever the web UI is doing. Its prompt is free text, so
# a "-m" inside it must not be read as the model.
got="$(run T_CLI=1 | sed -n 2p)"
@@ -140,8 +130,8 @@ check "sd-cli running" "$(l engine sd up busy "Krea-2-Turbo-Q6_K.gguf (sd-cli)")
got="$(run T_CLI=1 T_SD=1 | sed -n 2p)"
check "sd-cli wins over an idle web UI" "$(l engine sd up busy "Krea-2-Turbo-Q6_K.gguf (sd-cli)")" "$got"
-want="$(l app assistant running -; l app chat running -; l app imggen running realvis; l app fanfic running 31337)"
-got="$(run T_ASSIST=1 T_CHAT=1 T_IMG=1 T_FANFIC=1 | tail -n4)"
+want="$(l app assistant running -; l app chat running -)"
+got="$(run T_ASSIST=1 T_CHAT=1 | tail -n2)"
check "every app running" "$want" "$got"
printf '\n%d passed, %d failed\n' "$pass" "$fail"
diff --git a/desktop/modules/notes/NotesModule.qml b/desktop/modules/notes/NotesModule.qml
index 3066c8a..33e1cdf 100644
--- a/desktop/modules/notes/NotesModule.qml
+++ b/desktop/modules/notes/NotesModule.qml
@@ -238,7 +238,7 @@ Module {
mod.lastError = "";
mod.gitPull.command = ["sh", "-c", 'git -C "$1" pull --rebase --autostash 2>&1', "sh", mod.baseDir];
// Reassigning running on a running Process does nothing, so clear it
- // first (Status.qml does the same for its breaktimer).
+ // first (Status.qml does the same for its game poll).
mod.gitPull.running = false;
mod.gitPull.running = true;
}
diff --git a/desktop/modules/status/BreakRow.qml b/desktop/modules/status/BreakRow.qml
deleted file mode 100644
index 8b732b1..0000000
--- a/desktop/modules/status/BreakRow.qml
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 2 as
-// published by the Free Software Foundation.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-import QtQuick
-import "../.."
-
-// Breaktimer: the daemon's phase and countdown, a pause switch and a
-// start/stop button. Every control is a verb; nothing here writes the
-// daemon's files.
-Item {
- id: row
-
- implicitHeight: Math.max(texts.implicitHeight, controls.implicitHeight) + 16
-
- readonly property string phaseLabel: {
- if (!Status.btRunning) return "Stopped";
- if (Status.btPaused) return "Paused";
- if (Status.btPhase === "breaking") return "Micro-pause";
- if (Status.btPhase === "longbreak") return "Long pause";
- return "Working";
- }
-
- // A paused countdown is frozen, and a frozen number reads as a bug, so
- // the paused state says what it is instead of showing one.
- readonly property string detail: {
- if (!Status.btRunning) return "Break reminders are not running.";
- if (Status.btPaused) return "Countdown frozen until resumed.";
- if (Status.btPhase === "breaking" || Status.btPhase === "longbreak")
- return "Back to work in " + Status.btCountdown() + ".";
- return "Next break in " + Status.btCountdown() + ".";
- }
-
- Column {
- id: texts
- anchors {
- left: parent.left
- right: controls.left
- rightMargin: 12
- verticalCenter: parent.verticalCenter
- }
- spacing: 2
-
- Text {
- text: "Breaktimer · " + row.phaseLabel
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true }
- color: Theme.text
- }
-
- Text {
- width: parent.width
- wrapMode: Text.WordWrap
- text: row.detail
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
- color: Theme.subtext
- }
- }
-
- Row {
- id: controls
- anchors { right: parent.right; verticalCenter: parent.verticalCenter }
- spacing: 8
-
- Button {
- text: Status.btRunning ? "Stop" : "Start"
- danger: Status.btRunning
- onClicked: Status.runBreaktimer(Status.btRunning ? "stop" : "start")
- }
-
- Switch {
- id: sw
- enabled: Status.btRunning
- checked: Status.btRunning && !Status.btPaused
- onToggled: Status.runBreaktimer(Status.btPaused ? "resume" : "pause")
- }
- }
-}
diff --git a/desktop/modules/status/README.md b/desktop/modules/status/README.md
index d3d0cce..ea069d6 100644
--- a/desktop/modules/status/README.md
+++ b/desktop/modules/status/README.md
@@ -45,7 +45,7 @@ and reasserted when it returns.
`dnd` has none of its own. It is state the notification daemon reads.
`presentation` sets `dnd`, `nolock` and `quiet`, asserts a Wayland idle
-inhibitor, and pauses breaktimer. Turning it off restores the three to the
+inhibitor. Turning it off restores the three to the
values they had before rather than clearing them, so hand-set DND or a
hand-set disable survives a presentation.
@@ -71,52 +71,10 @@ The opencode notifier runs its command for every event, `session_started` and
are off in `opencode-notifier.json`, and it rereads that file on every event,
so neither change needs an opencode restart.
-breaktimer is paused and resumed by verb, never by writing its state file. See
-Breaktimer below, which is also the read direction.
-
-## Breaktimer
-
-The traffic runs both ways, and only one way writes.
-
-**Reading.** The daemon publishes three files the singleton watches:
-
- $XDG_RUNTIME_DIR/breaktimer.state running | paused | stopped
- $XDG_RUNTIME_DIR/breaktimer.phase working | breaking | longbreak | stopped
- $XDG_RUNTIME_DIR/breaktimer.remain seconds left in the phase
-
-exposed as `Status.btState`, `btPhase` and `btRemain`, with `btRunning` and
-`btPaused` derived from the first. The old bar script read the same three
-files; the two consumers never knew about each other.
-
-**Writing: never.** The daemon owns those files and rewrites state and phase on
-every transition, so a second writer would race its loop. Every control calls a
-verb through `runBreaktimer()`, which is why presentation mode has always
-called `pause` rather than writing `breaktimer.state`.
-
-**A stopped daemon** is read from the state file, not probed. Both paths that
-end the daemon write `stopped` there: `stop_daemon`, and the `cleanup` trap on
-`TERM`. A daemon lost to `KILL` leaves a stale `running` and the drawer shows a
-frozen countdown, a visible wrong answer the Start button resolves, which is
-cheaper than a liveness probe on every repaint. QML cannot send a signal, so
-the `kill -0` check the old bar module used is not available here anyway.
-
-**The countdown counts in five second steps**, because that is the daemon's
-tick and the shell does not interpolate between its writes. A local one second
-timer would be a second clock drifting against the first, correcting itself
-with a visible jump every five seconds, and it would keep counting while the
-daemon is frozen outside the work window or paused.
-
-**The tile** shows breaktimer below every mode, so an active mode still owns
-the line and breaktimer replaces only the idle `All clear`. It does not count
-toward `activeCount`: a running daemon is not a mode the user switched on.
-
-The daemon's own configuration lives in `~/.config/breaktimer.conf` and is not
-edited from here; `breaktimer.sh config` prints what is in effect.
-
## Game detection
Presentation is set automatically while a game runs, so a fullscreen game does
-not get locked or interrupted by the idle timer, DND or breaktimer. The shell
+not get locked or interrupted by the idle timer or DND. The shell
polls every five seconds for a `gamescope` process, a running Steam binary, or
the DuckStation or PCSX2 emulators, and any one is enough:
diff --git a/desktop/modules/status/StatusPage.qml b/desktop/modules/status/StatusPage.qml
index 8a21ad5..4741bcc 100644
--- a/desktop/modules/status/StatusPage.qml
+++ b/desktop/modules/status/StatusPage.qml
@@ -37,7 +37,7 @@ Column {
width: page.width
mode: "presentation"
label: "Presentation"
- description: "Do not disturb, quiet, no screen lock, breaktimer paused."
+ description: "Do not disturb, quiet, no screen lock."
value: Status.presentation
}
@@ -74,14 +74,4 @@ Column {
SnoozeRow {
width: page.width
}
-
- Rectangle {
- width: page.width
- height: 1
- color: Qt.alpha(Theme.text, 0.08)
- }
-
- BreakRow {
- width: page.width
- }
}
diff --git a/desktop/modules/status/StatusTile.qml b/desktop/modules/status/StatusTile.qml
index cec18b6..ab3a7af 100644
--- a/desktop/modules/status/StatusTile.qml
+++ b/desktop/modules/status/StatusTile.qml
@@ -17,15 +17,8 @@ Text {
elide: Text.ElideRight
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
color: Status.activeCount > 0 ? Theme.text : Theme.subtext
- // Breaktimer sits below every mode, so an active mode still owns the line
- // and breaktimer replaces only the idle "All clear". A stopped daemon
- // falls through to it, which is true: nothing is being tracked.
text: Status.presentation ? "Presenting"
: Status.dnd ? "Do not disturb"
: Status.nolock ? "No lock"
- : !Status.btRunning ? "All clear"
- : Status.btPaused ? "Paused"
- : (Status.btPhase === "breaking" || Status.btPhase === "longbreak")
- ? "Back in " + Status.btCountdown()
- : "Break in " + Status.btCountdown()
+ : "All clear"
}
diff --git a/games/__pycache__/games.cpython-312.pyc b/games/__pycache__/games.cpython-312.pyc
new file mode 100644
index 0000000..46a5521
--- /dev/null
+++ b/games/__pycache__/games.cpython-312.pyc
Binary files differ
diff --git a/shared/Notify.qml b/shared/Notify.qml
index a85ed6a..836dc21 100644
--- a/shared/Notify.qml
+++ b/shared/Notify.qml
@@ -42,9 +42,8 @@ Singleton {
// Read from the status registry's own file rather than from the Status
// singleton. The renderers only read DND; referencing Status here would
- // instantiate it, and its onPresentationChanged writes status.dnd and
- // shells out to breaktimer.sh, side effects a read-only consumer must not
- // trigger. The file is the interface, so reading it directly is the same
+ // instantiate it, and its onPresentationChanged writes status.dnd, a side
+ // effect a read-only consumer must not trigger. The file is the interface, so reading it directly is the same
// value with none of the write side.
property bool dnd: false
diff --git a/shared/Status.qml b/shared/Status.qml
index 523ea7d..f32cd35 100644
--- a/shared/Status.qml
+++ b/shared/Status.qml
@@ -44,23 +44,6 @@ Singleton {
// drawer's view of it.
readonly property bool quiet: quietFile.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) + (root.quiet ? 1 : 0)
@@ -144,49 +127,17 @@ Singleton {
root.setMode("dnd", true);
root.setMode("nolock", true);
root.setMode("quiet", true);
- // pause/resume write the state file whether or not a daemon
- // runs, so send the verb only when one exists.
- if (root.btRunning) root.runBreaktimer("pause");
} else {
root.setMode("dnd", root.dndBeforePresentation);
root.setMode("nolock", root.nolockBeforePresentation);
root.setMode("quiet", root.quietBeforePresentation);
- if (root.btRunning) root.runBreaktimer("resume");
- }
- }
-
- function runBreaktimer(verb) {
- breakProc.command = ["breaktimer.sh", verb];
- breakProc.running = false;
- 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.
- Process {
- id: breakProc
- onExited: code => {
- if (code !== 0)
- console.warn("status: breaktimer.sh " + breakProc.command[1] + " exited " + code);
}
}
// --- game detection ---------------------------------------------------
//
// A game is a fullscreen app that already holds the screen, and the idle
- // lock, DND and breaktimer are all unwanted while it runs, so presentation
+ // lock and DND are both unwanted while it runs, so presentation
// is set automatically. This machine's games come from Steam and launch
// either under gamescope or directly, and the DuckStation and PCSX2 emulators
// run standalone, so all four are tested and any one is enough.
@@ -269,56 +220,10 @@ 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" }
ModeFile { id: quietFile; path: root.dir + "/status.quiet" }
// Written only by the game detector above, never by statusctl.
ModeFile { id: gamingFile; path: root.dir + "/status.gaming" }
-
- WordFile { id: btStateFile; path: root.dir + "/breaktimer.state" }
- WordFile { id: btPhaseFile; path: root.dir + "/breaktimer.phase" }
- SecondsFile { id: btRemainFile; path: root.dir + "/breaktimer.remain" }
}
diff --git a/window-switcher/README.md b/window-switcher/README.md
index faefeb1..04e8d25 100644
--- a/window-switcher/README.md
+++ b/window-switcher/README.md
@@ -95,7 +95,7 @@ would reopen it 250ms later.
It is hidden while `status.gaming` is set. It reads that file from
`$XDG_RUNTIME_DIR` itself rather than linking `shared/Status.qml`, which is not
passive: a second instance would run its own game poll and react to
-presentation mode a second time, pausing breaktimer twice.
+presentation mode a second time.
## Hyprland dispatches from QML