diff options
| author | Yuval Adam <_@yuv.al> | 2026-07-24 13:02:08 +0200 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2026-07-24 13:02:08 +0200 |
| commit | 213b7d931056e09d3b0f39c049f0fce9ead05911 (patch) | |
| tree | 472e269f1684d4c0e1358ee362b7d459289e3306 | |
| parent | 6e88c83227a94b331bbb3ac08152fee77611ddd3 (diff) | |
Disable desktop notifications by default
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | SPEC.md | 5 | ||||
| -rw-r--r-- | src/parley/config.py | 5 | ||||
| -rw-r--r-- | src/parley/daemon.py | 20 | ||||
| -rw-r--r-- | src/parley/ibus_engine.py | 25 |
5 files changed, 47 insertions, 10 deletions
@@ -28,7 +28,7 @@ With Parley installed and the shortcut configured: 4. Press `Super+Shift+D` again. 5. Wait for local transcription and insertion. -GNOME displays its microphone privacy indicator while recording. The Parley top-bar indicator shows idle, recording, transcribing, and error states and provides start/stop, cancel, copy-last, and open-folder actions. +GNOME displays its microphone privacy indicator while recording. The Parley top-bar indicator shows idle, recording, transcribing, and error states and provides start/stop, cancel, copy-last, and open-folder actions. Desktop notifications are disabled by default. Useful fallback commands: @@ -327,8 +327,9 @@ The extension shortcut replaces or disables the development custom shortcut to p ## 9. Notifications and clipboard - Copy every successful transcript to the clipboard. -- Notify on transcription completion when no visible UI feedback is available. -- Notify when insertion falls back to manual paste. +- Desktop notifications are disabled by default and can be explicitly enabled. +- When enabled, notify on transcription completion when no visible UI feedback is available. +- When enabled, notify when insertion falls back to manual paste. - Surface concise errors while retaining details in the journal. - Avoid repeated noisy notifications when the top-bar state already makes progress obvious. - Use a registered desktop application identity for production notifications; `notify-send` is acceptable during the prototype phase. diff --git a/src/parley/config.py b/src/parley/config.py index 89cf59c..4ba1202 100644 --- a/src/parley/config.py +++ b/src/parley/config.py @@ -33,6 +33,7 @@ class Config: sample_rate: int = 16_000 auto_insert: bool = False insertion_mode: str = "clipboard" + notifications_enabled: bool = False @classmethod def from_environment(cls) -> "Config": @@ -65,6 +66,10 @@ class Config: auto_insert=os.environ.get("PARLEY_AUTO_INSERT", "0").lower() in {"1", "true", "yes", "on"}, insertion_mode=os.environ.get("PARLEY_INSERTION_MODE", "clipboard"), + notifications_enabled=os.environ.get( + "PARLEY_NOTIFICATIONS", "0" + ).lower() + in {"1", "true", "yes", "on"}, ) def validate(self) -> None: diff --git a/src/parley/daemon.py b/src/parley/daemon.py index 63d4697..121ff24 100644 --- a/src/parley/daemon.py +++ b/src/parley/daemon.py @@ -31,12 +31,16 @@ class Daemon: ).read_text(encoding="utf-8") self.node_info = Gio.DBusNodeInfo.new_for_xml(xml) self.interface_info = self.node_info.interfaces[0] + resolved_config = config or Config.from_environment() self.loop = GLib.MainLoop() - self.ibus = IBusIntegration() + self.ibus = IBusIntegration( + activate_on_connect=resolved_config.auto_insert + and resolved_config.insertion_mode == "ibus" + ) self.connection: Gio.DBusConnection | None = None self.registration_id = 0 self.controller = Controller( - config or Config.from_environment(), + resolved_config, dispatch=lambda callback: GLib.idle_add(callback), on_state=self._on_state, on_transcript=self._on_transcript, @@ -204,20 +208,24 @@ class Daemon: GLib.Variant("(sbs)", ("ibus", False, str(error))), ) fallback = "Copied; press Ctrl+V" if clipboard_error is None else "Transcript saved" - notify("Automatic insertion unavailable", fallback) + self._notify("Automatic insertion unavailable", fallback) else: self._emit( "InsertionFinished", GLib.Variant("(sbs)", ("ibus", True, message)), ) - notify("Transcription ready", "Inserted through IBus") + self._notify("Transcription ready", "Inserted through IBus") elif clipboard_error is None: - notify("Transcription ready", "Copied to the clipboard") + self._notify("Transcription ready", "Copied to the clipboard") def _on_error(self, code: str, message: str) -> None: self._emit("Error", GLib.Variant("(ss)", (code, message))) self._properties_changed("LastError", GLib.Variant("s", message)) - notify("Parley error", message) + self._notify("Parley error", message) + + def _notify(self, summary: str, body: str) -> None: + if self.controller.config.notifications_enabled: + notify(summary, body) def _properties_changed(self, *items) -> None: changed = {items[index]: items[index + 1] for index in range(0, len(items), 2)} diff --git a/src/parley/ibus_engine.py b/src/parley/ibus_engine.py index 7f65a99..86c155c 100644 --- a/src/parley/ibus_engine.py +++ b/src/parley/ibus_engine.py @@ -49,13 +49,15 @@ class ParleyIBusEngine(IBus.Engine): class IBusIntegration: """Register Parley's engine and reconnect after a rare IBus restart.""" - def __init__(self) -> None: + def __init__(self, *, activate_on_connect: bool = False) -> None: IBus.init() + self.activate_on_connect = activate_on_connect self.bus: IBus.Bus | None = None self.factory: IBus.Factory | None = None self.component: IBus.Component | None = None self._disconnect_signal = 0 self._reconnect_source = 0 + self._activation_source = 0 self._closed = False self._connect() @@ -78,6 +80,9 @@ class IBusIntegration: if self._reconnect_source: GLib.source_remove(self._reconnect_source) self._reconnect_source = 0 + if self._activation_source: + GLib.source_remove(self._activation_source) + self._activation_source = 0 self._clear_connection() def _connect(self) -> bool: @@ -122,8 +127,26 @@ class IBusIntegration: self.factory = factory self.component = component self._disconnect_signal = bus.connect("disconnected", self._on_disconnected) + if self.activate_on_connect: + self._activation_source = GLib.timeout_add(100, self._activate) return GLib.SOURCE_REMOVE + def _activate(self) -> bool: + self._activation_source = 0 + if self.available: + self.bus.set_global_engine_async( + "parley", 2_000, None, self._activation_finished + ) + return GLib.SOURCE_REMOVE + + def _activation_finished(self, bus: IBus.Bus, result) -> None: + try: + bus.set_global_engine_async_finish(result) + except GLib.Error: + # Insertion remains available as a clipboard fallback. A later + # explicit selection can activate the engine. + pass + def _on_disconnected(self, bus: IBus.Bus) -> None: self._clear_connection() self._schedule_reconnect() |
