fix: cancel recording works in wakeword mode + tray Cancel item (v2.03.26)
- Cancel hotkey now fires even when wakeword triggered the recording (ModifierScheme state was "idle" so the key was silently ignored) - Tray menu: "✕ Cancel recording" — always visible, enabled while recording, grayed out at idle; works for both wakeword and manual recordings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
599eb08c5a
commit
834cc20553
@ -9,6 +9,21 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.03.26] - 2026-06-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Cancel key now works during wakeword-triggered recording.** Previously the
|
||||||
|
`ModifierScheme` state machine stayed in "idle" when the wakeword fired
|
||||||
|
(it bypasses the key-press path), so the cancel hotkey was silently ignored.
|
||||||
|
It now checks `daemon.is_recording` as a fallback so it fires regardless of
|
||||||
|
how recording started.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **"✕ Cancel recording" in the tray menu.** Always visible; grayed out when
|
||||||
|
idle, enabled as soon as recording starts (wakeword or manual). The primary
|
||||||
|
escape hatch when the wakeword fires on audiobook / TV audio and spoken
|
||||||
|
cancel words can't be heard over the background audio.
|
||||||
|
|
||||||
## [2.03.25] - 2026-06-10
|
## [2.03.25] - 2026-06-10
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host
|
|||||||
(not in a container) so it can type into any application via xdotool.
|
(not in a container) so it can type into any application via xdotool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "2.03.25"
|
__version__ = "2.03.26"
|
||||||
|
|||||||
@ -74,18 +74,21 @@ class ModifierScheme:
|
|||||||
return
|
return
|
||||||
self._pressed.add(token)
|
self._pressed.add(token)
|
||||||
|
|
||||||
|
# Cancel works while arming/armed AND when the daemon is recording via
|
||||||
|
# wakeword (state stays "idle" in the scheme because wakeword bypasses
|
||||||
|
# the key-press path entirely).
|
||||||
|
if self._is(token, self.cancel) and (
|
||||||
|
self._state != "idle" or self.daemon.is_recording):
|
||||||
|
self._state = "idle"
|
||||||
|
self.daemon.cancel_dictation()
|
||||||
|
return
|
||||||
|
|
||||||
if self._state == "idle":
|
if self._state == "idle":
|
||||||
if self.start.issubset(self._pressed):
|
if self.start.issubset(self._pressed):
|
||||||
self._state = "arming"
|
self._state = "arming"
|
||||||
self.daemon.start_dictation()
|
self.daemon.start_dictation()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Cancel works while arming or armed.
|
|
||||||
if self._is(token, self.cancel):
|
|
||||||
self._state = "idle"
|
|
||||||
self.daemon.cancel_dictation()
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._state == "armed":
|
if self._state == "armed":
|
||||||
if self._is(token, self.send):
|
if self._is(token, self.send):
|
||||||
self._state = "idle"
|
self._state = "idle"
|
||||||
|
|||||||
@ -88,6 +88,15 @@ class Tray:
|
|||||||
|
|
||||||
menu.append(Gtk.SeparatorMenuItem())
|
menu.append(Gtk.SeparatorMenuItem())
|
||||||
|
|
||||||
|
# Cancel recording — always visible, only sensitive while recording.
|
||||||
|
# Primary escape hatch when wakeword fires on audiobook/TV audio.
|
||||||
|
self.cancel_item = Gtk.MenuItem(label="✕ Cancel recording")
|
||||||
|
self.cancel_item.set_sensitive(False)
|
||||||
|
self.cancel_item.connect(
|
||||||
|
"activate", lambda _i: self.app.daemon.cancel_dictation())
|
||||||
|
menu.append(self.cancel_item)
|
||||||
|
menu.append(Gtk.SeparatorMenuItem())
|
||||||
|
|
||||||
# Hands-free wakeword: a reversible pause toggle. Without this, a stale
|
# Hands-free wakeword: a reversible pause toggle. Without this, a stale
|
||||||
# /tmp/wake_muted flag would silently disable detection with no way back.
|
# /tmp/wake_muted flag would silently disable detection with no way back.
|
||||||
if getattr(self.app.cfg, "wakeword_enabled", False):
|
if getattr(self.app.cfg, "wakeword_enabled", False):
|
||||||
@ -120,6 +129,8 @@ class Tray:
|
|||||||
def update_status(self, state: str, message: str) -> None:
|
def update_status(self, state: str, message: str) -> None:
|
||||||
self.indicator.set_icon_full(ICONS.get(state, ICONS["idle"]), state)
|
self.indicator.set_icon_full(ICONS.get(state, ICONS["idle"]), state)
|
||||||
self.status_item.set_label(f"● {message or state.title()}")
|
self.status_item.set_label(f"● {message or state.title()}")
|
||||||
|
if hasattr(self, "cancel_item"):
|
||||||
|
self.cancel_item.set_sensitive(state in ("recording", "armed", "busy"))
|
||||||
|
|
||||||
def pump(self) -> None:
|
def pump(self) -> None:
|
||||||
"""Service pending GLib/GTK events; called from tkinter's loop."""
|
"""Service pending GLib/GTK events; called from tkinter's loop."""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user