aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-30 17:34:33 +0100
committerDanilo M. <danix@danix.xyz>2026-10-30 17:34:33 +0100
commit412dec827b5d87c4138491faf49a0babb229f7e2 (patch)
tree3916ccf499c09853e16d2a2c6973fea3a453b877
parent9b9271c304cc7654de0320760f875b666294f591 (diff)
downloadquickshell-412dec827b5d87c4138491faf49a0babb229f7e2.tar.gz
quickshell-412dec827b5d87c4138491faf49a0babb229f7e2.zip
Drop breaktimer and the archived AI apps
breaktimer is parked and imggen and fanfictioner are archived and uninstalled, so the shell was reading files nothing writes and offering buttons that call commands that no longer exist. Status: remove the bt* properties, the file watchers behind them, the verb runner and the presentation-mode pause/resume. The status page loses BreakRow and the tile falls back to "All clear". AI: remove the imggen and fanfictioner rows from ai-state.sh, their labels and controls, and the pid-based fanfictioner stop. The sd-cli engine row stays, since the wallpaper upscaler uses sd-cli too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-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