From b52ab0c70af12b80257b45ac91e5ea71b01549de Mon Sep 17 00:00:00 2001 From: Yuval Adam <_@yuv.al> Date: Fri, 24 Jul 2026 13:17:51 +0200 Subject: Show an IBus preedit spinner while transcribing --- src/parley/daemon.py | 6 ++++++ src/parley/ibus_engine.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) (limited to 'src') diff --git a/src/parley/daemon.py b/src/parley/daemon.py index 4330cc6..de76bcd 100644 --- a/src/parley/daemon.py +++ b/src/parley/daemon.py @@ -175,6 +175,12 @@ class Daemon: open_folder(directory) def _on_state(self, state: State) -> None: + config = self.controller.config + self.ibus.set_processing( + state is State.TRANSCRIBING + and config.auto_insert + and config.insertion_mode == "ibus" + ) self._emit("StateChanged", GLib.Variant("(s)", (state.value,))) self._properties_changed("State", GLib.Variant("s", state.value)) diff --git a/src/parley/ibus_engine.py b/src/parley/ibus_engine.py index a14a139..1f80868 100644 --- a/src/parley/ibus_engine.py +++ b/src/parley/ibus_engine.py @@ -17,15 +17,19 @@ class ParleyIBusEngine(IBus.Engine): """Pass ordinary keys through and accept explicit semantic text commits.""" focused: "ParleyIBusEngine | None" = None + integration: "IBusIntegration | None" = None def do_process_key_event(self, keyval: int, keycode: int, state: int) -> bool: return False def do_focus_in(self) -> None: ParleyIBusEngine.focused = self + if ParleyIBusEngine.integration is not None: + ParleyIBusEngine.integration.refresh_preedit() def do_focus_out(self) -> None: if ParleyIBusEngine.focused is self: + self.hide_preedit_text() ParleyIBusEngine.focused = None def commit(self, text: str) -> str: @@ -36,9 +40,15 @@ class ParleyIBusEngine(IBus.Engine): class IBusIntegration: """Register Parley's engine and reconnect after a rare IBus restart.""" + SPINNER_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏") + def __init__(self, *, activate_on_connect: bool = False) -> None: IBus.init() self.activate_on_connect = activate_on_connect + self._processing = False + self._spinner_frame = 0 + self._spinner_source = 0 + ParleyIBusEngine.integration = self self.bus: IBus.Bus | None = None self.factory: IBus.Factory | None = None self.component: IBus.Component | None = None @@ -53,6 +63,7 @@ class IBusIntegration: return self.bus is not None and self.bus.is_connected() def commit(self, text: str) -> str: + self.set_processing(False) if not self.available: raise IBusInsertionError("IBus is unavailable") engine = ParleyIBusEngine.focused @@ -60,8 +71,48 @@ class IBusIntegration: raise IBusInsertionError("Parley does not own the focused input context") return engine.commit(text) + def set_processing(self, processing: bool) -> None: + if processing == self._processing: + return + self._processing = processing + if processing: + self._spinner_frame = 0 + self.refresh_preedit() + if not self._spinner_source: + self._spinner_source = GLib.timeout_add(120, self._advance_spinner) + else: + if self._spinner_source: + GLib.Source.remove(self._spinner_source) + self._spinner_source = 0 + engine = ParleyIBusEngine.focused + if engine is not None: + engine.hide_preedit_text() + + def refresh_preedit(self) -> None: + engine = ParleyIBusEngine.focused + if not self._processing or engine is None: + return + message = f"{self.SPINNER_FRAMES[self._spinner_frame]} Transcribing…" + engine.update_preedit_text_with_mode( + IBus.Text.new_from_string(message), + len(message), + True, + IBus.PreeditFocusMode.CLEAR, + ) + + def _advance_spinner(self) -> bool: + if not self._processing: + self._spinner_source = 0 + return GLib.SOURCE_REMOVE + self._spinner_frame = (self._spinner_frame + 1) % len(self.SPINNER_FRAMES) + self.refresh_preedit() + return GLib.SOURCE_CONTINUE + def close(self) -> None: + self.set_processing(False) self._closed = True + if ParleyIBusEngine.integration is self: + ParleyIBusEngine.integration = None if self._reconnect_source: GLib.Source.remove(self._reconnect_source) self._reconnect_source = 0 @@ -141,6 +192,7 @@ class IBusIntegration: self._reconnect_source = GLib.timeout_add_seconds(2, self._connect) def _clear_connection(self) -> None: + self.set_processing(False) ParleyIBusEngine.focused = None bus, self.bus = self.bus, None if bus is not None and self._disconnect_signal: -- cgit v1.3.1