summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2026-07-24 13:17:51 +0200
committerYuval Adam <_@yuv.al>2026-07-24 13:17:51 +0200
commitb52ab0c70af12b80257b45ac91e5ea71b01549de (patch)
treeb60122dd66bcdd43eba21908f957fe4e111314cf /src
parentdf780f6aa210f0916323dd3eb925d762dbb0769d (diff)
Show an IBus preedit spinner while transcribing
Diffstat (limited to 'src')
-rw-r--r--src/parley/daemon.py6
-rw-r--r--src/parley/ibus_engine.py52
2 files changed, 58 insertions, 0 deletions
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: