diff --git a/linux/CHANGELOG.md b/linux/CHANGELOG.md index 0662836..a960661 100644 --- a/linux/CHANGELOG.md +++ b/linux/CHANGELOG.md @@ -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 diff --git a/linux/blitztext/tray.py b/linux/blitztext/tray.py index c430ec7..0ce1de8 100644 --- a/linux/blitztext/tray.py +++ b/linux/blitztext/tray.py @@ -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), diff --git a/linux/blitztext/wakeword.py b/linux/blitztext/wakeword.py index 037769c..b1283f3 100644 --- a/linux/blitztext/wakeword.py +++ b/linux/blitztext/wakeword.py @@ -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}'!")