wakeword: reversible tray pause toggle; document quiet-session fixes

Bug: a stale /tmp/wake_muted flag silently disabled wakeword detection
with no in-app way to clear it (the flag was read but never written by
any code or UI).

- wakeword.py: expose MUTE_FILE + is_muted()/set_muted() helpers
- tray.py: add a "Pause wakeword" CheckMenuItem (shown when wakeword is
  enabled) that toggles the flag and reflects current state
- config.py: document the tray toggle instead of the bare /tmp file
- CHANGELOG: record the wakeword pause toggle and the existing notify
  hygiene fixes (transient/single-bubble notifications, silent
  hands-free sessions)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-06-06 22:17:50 +02:00
parent 73ab26c90b
commit 93de455b01
3 changed files with 50 additions and 3 deletions

View File

@ -9,6 +9,22 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py).
## [Unreleased]
### Added
- **Pause wakeword (tray)**: a reversible "Pause wakeword" toggle appears in the
system-tray menu when the wakeword is enabled. It pauses/resumes hands-free
detection by toggling the `/tmp/wake_muted` flag (external scripts may toggle
the same file).
### Fixed
- **Wakeword stuck muted**: a leftover `/tmp/wake_muted` flag silently disabled
detection with no in-app way to clear it. The state is now exposed and
reversible from the tray, so a stale flag no longer kills hands-free use.
- **Notification storm / lock-screen pile-up**: desktop notifications are now
sent as transient with a short expiry and reuse a single bubble, so they no
longer stack in the notification log or persist on the lock screen.
- **Quiet hands-free sessions**: per-dictation notifications are suppressed for
wakeword-triggered sessions (audio cues are used instead).
## [1.2.0] - 2026-06-05
### Added

View File

@ -87,6 +87,20 @@ class Tray:
menu.append(item)
menu.append(Gtk.SeparatorMenuItem())
# Hands-free wakeword: a reversible pause toggle. Without this, a stale
# /tmp/wake_muted flag would silently disable detection with no way back.
if getattr(self.app.cfg, "wakeword_enabled", False):
from . import wakeword
self.mute_item = Gtk.CheckMenuItem(label="Pause wakeword")
self.mute_item.set_active(wakeword.is_muted())
self.mute_item.connect(
"toggled", lambda i: wakeword.set_muted(i.get_active())
)
menu.append(self.mute_item)
menu.append(Gtk.SeparatorMenuItem())
for label, cb in (
("Show panel", self.app.show_panel),
("Settings…", self.app.open_settings),

View File

@ -19,7 +19,24 @@ from urllib.parse import urlparse
from . import logbuffer
_MUTE_FILE = "/tmp/wake_muted"
MUTE_FILE = "/tmp/wake_muted"
def is_muted() -> bool:
"""True if wakeword detections are currently paused via the mute flag."""
return os.path.exists(MUTE_FILE)
def set_muted(muted: bool) -> None:
"""Pause (create flag) or resume (remove flag) wakeword detection."""
try:
if muted:
open(MUTE_FILE, "a").close()
elif os.path.exists(MUTE_FILE):
os.remove(MUTE_FILE)
except OSError as e: # noqa: BLE001 - mute is best-effort, never crash
logbuffer.log(f"[wakeword] Could not update mute flag: {e}")
class WakewordListener:
def __init__(self, uri: str, model: str, mic: str, on_detect):
@ -142,8 +159,8 @@ class WakewordListener:
if time.time() < self._cooldown_until:
return
if os.path.exists(_MUTE_FILE):
logbuffer.log("[wakeword] Detected, but muted via /tmp/wake_muted")
if is_muted():
logbuffer.log("[wakeword] Detected, but paused (resume via tray)")
return
logbuffer.log(f"[wakeword] Detected '{self.model}'!")