aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--desktop/modules/status/README.md9
-rwxr-xr-xdesktop/modules/status/statusctl45
-rwxr-xr-xdesktop/modules/status/test-statusctl.sh41
3 files changed, 92 insertions, 3 deletions
diff --git a/desktop/modules/status/README.md b/desktop/modules/status/README.md
index 4d18342..7ac6b93 100644
--- a/desktop/modules/status/README.md
+++ b/desktop/modules/status/README.md
@@ -130,6 +130,15 @@ waybar's built-in `idle_inhibitor`, which cannot be kept alongside it: that
module owns its own inhibitor object, so both would have to be released
before the screen could lock.
+The watch emits three classes, not two. `status.gaming` turns the mode on
+without touching the manual half, so a game would otherwise leave the widget
+reading `deactivated` while the effects were asserted. `gaming` is its own
+class with its own icon, and `toggle` is refused while it holds: the detector
+reasserts the file within its poll, so the write would not stick. `set` is
+still allowed, since the manual half is worth setting for when the game exits.
+The watch therefore follows `status.gaming` as well as its own file, and emits
+only on a real change so an unchanged rewrite draws nothing.
+
## The check
./test-statusctl.sh
diff --git a/desktop/modules/status/statusctl b/desktop/modules/status/statusctl
index 571beef..531951b 100755
--- a/desktop/modules/status/statusctl
+++ b/desktop/modules/status/statusctl
@@ -82,15 +82,34 @@ tooltip() {
case "$1" in
activated) printf '%s: on' "$mode" ;;
deactivated) printf '%s: off' "$mode" ;;
+ gaming) printf '%s: on (game running)' "$mode" ;;
esac
}
+# The shell's game detector owns status.gaming and presentation is the OR of
+# the two, so the CLI has to read it to agree with the shell. It is not a mode
+# in MODES: nothing here may write it, and only presentation is affected.
+gaming_on() {
+ local v=""
+ [[ -e "$DIR/status.gaming" ]] && v="$(tr -d '[:space:]' < "$DIR/status.gaming" 2>/dev/null)"
+ [[ "$v" == "1" ]]
+}
+
# A missing file is the off state, not a distinct condition: read_mode already
# reads it as 0, and this is the same path `get` uses. There is no "down": a
# file cannot report whether a watcher is alive, and a missing one is exactly
# what a fresh session looks like.
+#
+# A running game reports its own state rather than plain activated, so the
+# widget can show why the mode is on and that a click will not turn it off.
state_now() {
- [[ "$(read_mode)" == "1" ]] && printf 'activated' || printf 'deactivated'
+ if [[ "$mode" == "presentation" ]] && gaming_on; then
+ printf 'gaming'
+ elif [[ "$(read_mode)" == "1" ]]; then
+ printf 'activated'
+ else
+ printf 'deactivated'
+ fi
}
case "$action" in
@@ -106,6 +125,15 @@ case "$action" in
esac
;;
toggle)
+ # A running game pins presentation on and the detector reasserts it
+ # within its poll, so a toggle here would flip back on its own. Refuse
+ # it instead of writing a value that does not stick. `set` still works:
+ # it is the explicit verb, and the manual half is worth setting for
+ # when the game exits.
+ if [[ "$mode" == "presentation" ]] && gaming_on; then
+ printf '%s: presentation held on by a running game\n' "${0##*/}" >&2
+ exit 1
+ fi
[[ "$(read_mode)" == "1" ]] && write_mode 0 || write_mode 1
;;
watch)
@@ -123,9 +151,20 @@ case "$action" in
exec 3< <(inotifywait -q -m -e close_write,moved_to,delete --format '%f' "$DIR" 2>/dev/null)
watcher=$!
trap 'kill "$watcher" 2>/dev/null' EXIT TERM INT
+ # Presentation also follows status.gaming, so a game starting or
+ # ending redraws the widget. Emitting only on a real change keeps a
+ # write of an unchanged value from firing a duplicate line.
+ last="$(state_now)"
while read -r changed <&3; do
- [[ "$changed" == "status.$mode" ]] || continue
- emit "$(state_now)"
+ case "$changed" in
+ "status.$mode") ;;
+ status.gaming) [[ "$mode" == "presentation" ]] || continue ;;
+ *) continue ;;
+ esac
+ now="$(state_now)"
+ [[ "$now" == "$last" ]] && continue
+ last="$now"
+ emit "$now"
done
;;
*)
diff --git a/desktop/modules/status/test-statusctl.sh b/desktop/modules/status/test-statusctl.sh
index 7b1b188..38da27c 100755
--- a/desktop/modules/status/test-statusctl.sh
+++ b/desktop/modules/status/test-statusctl.sh
@@ -92,5 +92,46 @@ kill "$watcher2" 2>/dev/null
wait "$watcher2" 2>/dev/null
check "watch reports deactivated when absent" "1" "$(grep -c '"class": *"deactivated"' "$out2")"
+# A running game is reported as its own state, not as plain activated: the
+# widget draws a different icon and the tooltip says why the mode is on.
+rm -f "$tmp/status.presentation"
+printf '1\n' > "$tmp/status.gaming"
+out3="$tmp/watch3.out"
+"$ctl" presentation watch > "$out3" 2>/dev/null &
+watcher3=$!
+sleep 0.5
+kill "$watcher3" 2>/dev/null
+wait "$watcher3" 2>/dev/null
+check "watch reports gaming" "1" "$(grep -c '"class": *"gaming"' "$out3")"
+
+# The gaming file is the shell's, so presentation's own file stays off and
+# `get` keeps reporting the manual half rather than the OR.
+check "get reads the manual half" "0" "$("$ctl" presentation get)"
+
+# Toggling while a game runs is refused: the detector would reassert it, so a
+# write here would not stick.
+"$ctl" presentation toggle 2>/dev/null
+check "toggle refused while gaming" "1" "$?"
+check "toggle wrote nothing" "0" "$("$ctl" presentation get)"
+
+# The refusal is specific to presentation; another mode toggles normally with
+# a game running.
+"$ctl" dnd set 0
+"$ctl" dnd toggle
+check "dnd toggles while gaming" "1" "$("$ctl" dnd get)"
+
+# A game ending redraws the widget, which is why the watch follows
+# status.gaming as well as its own file.
+out4="$tmp/watch4.out"
+"$ctl" presentation watch > "$out4" 2>/dev/null &
+watcher4=$!
+sleep 0.3
+printf '0\n' > "$tmp/status.gaming.tmp"
+mv -f "$tmp/status.gaming.tmp" "$tmp/status.gaming"
+sleep 0.5
+kill "$watcher4" 2>/dev/null
+wait "$watcher4" 2>/dev/null
+check "game ending redraws" "1" "$(grep -c '"class": *"deactivated"' "$out4")"
+
printf '\n%d passed, %d failed\n' "$pass" "$fail"
[[ "$fail" -eq 0 ]]