diff options
| author | Yuval Adam <_@yuv.al> | 2026-07-24 13:14:36 +0200 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2026-07-24 13:14:36 +0200 |
| commit | df780f6aa210f0916323dd3eb925d762dbb0769d (patch) | |
| tree | 6a23d3845a14c1b61052e47841b846058420650e | |
| parent | f2197687e18eb129fcf25462943aa75972560579 (diff) | |
Modernize GLib and D-Bus integrations
| -rw-r--r-- | data/dbus/org.parley.Transcription1.service | 2 | ||||
| -rw-r--r-- | extension/extension.js | 1 | ||||
| -rw-r--r-- | pyproject.toml | 5 | ||||
| -rwxr-xr-x | scripts/install-dev | 1 | ||||
| -rw-r--r-- | src/parley/config.py | 8 | ||||
| -rw-r--r-- | src/parley/controller.py | 4 | ||||
| -rw-r--r-- | src/parley/daemon.py | 42 | ||||
| -rw-r--r-- | src/parley/desktop.py | 4 | ||||
| -rw-r--r-- | src/parley/ibus_engine.py | 8 | ||||
| -rw-r--r-- | src/parley/persistence.py | 4 | ||||
| -rw-r--r-- | src/parley/transcription.py | 10 | ||||
| -rw-r--r-- | tests/test_controller.py | 5 | ||||
| -rw-r--r-- | tests/test_persistence.py | 8 | ||||
| -rw-r--r-- | tests/test_transcription.py | 12 |
14 files changed, 59 insertions, 55 deletions
diff --git a/data/dbus/org.parley.Transcription1.service b/data/dbus/org.parley.Transcription1.service index cc5d2cf..ab3610c 100644 --- a/data/dbus/org.parley.Transcription1.service +++ b/data/dbus/org.parley.Transcription1.service @@ -1,4 +1,4 @@ [D-BUS Service] Name=org.parley.Transcription1 -Exec=/bin/false +Exec=/usr/bin/parleyd SystemdService=parley.service diff --git a/extension/extension.js b/extension/extension.js index 00ce517..465e7db 100644 --- a/extension/extension.js +++ b/extension/extension.js @@ -160,7 +160,6 @@ class Indicator extends PanelMenu.Button { this._proxy[`${method}Remote`]((_result, error) => { if (error) { console.error(`Parley ${method} failed: ${error.message}`); - Main.notifyError('Parley', error.message); } }); } diff --git a/pyproject.toml b/pyproject.toml index cf91954..f3455c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,3 +24,8 @@ where = ["src"] [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.ruff.lint.per-file-ignores] +"src/parley/ctl.py" = ["E402"] +"src/parley/daemon.py" = ["E402"] +"src/parley/ibus_engine.py" = ["E402"] diff --git a/scripts/install-dev b/scripts/install-dev index d21bc46..a2eb660 100755 --- a/scripts/install-dev +++ b/scripts/install-dev @@ -16,6 +16,7 @@ install -m644 data/dbus/org.parley.Transcription1.service \ systemctl --user daemon-reload systemctl --user enable parley.service +systemctl --user reset-failed parley.service systemctl --user restart parley.service echo "Installed local development service from $root" diff --git a/src/parley/config.py b/src/parley/config.py index 4ba1202..923812c 100644 --- a/src/parley/config.py +++ b/src/parley/config.py @@ -40,9 +40,7 @@ class Config: prototype = _prototype_root() data_home = _default_data_home() ffmpeg = os.environ.get("PARLEY_FFMPEG") or shutil.which("ffmpeg") or "ffmpeg" - installed_model = ( - data_home / "parley/models/parakeet-unified-en-0.6b-Q8_0.gguf" - ) + installed_model = data_home / "parley/models/parakeet-unified-en-0.6b-Q8_0.gguf" default_model = ( installed_model if installed_model.is_file() @@ -66,9 +64,7 @@ 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() + notifications_enabled=os.environ.get("PARLEY_NOTIFICATIONS", "0").lower() in {"1", "true", "yes", "on"}, ) diff --git a/src/parley/controller.py b/src/parley/controller.py index 58091cf..567544f 100644 --- a/src/parley/controller.py +++ b/src/parley/controller.py @@ -117,9 +117,7 @@ class Controller: lambda error=error: self._worker_failed(token, workspace, error) ) else: - self.dispatch( - lambda: self._worker_succeeded(token, workspace, text, path) - ) + self.dispatch(lambda: self._worker_succeeded(token, workspace, text, path)) def _worker_succeeded( self, diff --git a/src/parley/daemon.py b/src/parley/daemon.py index 121ff24..4330cc6 100644 --- a/src/parley/daemon.py +++ b/src/parley/daemon.py @@ -26,9 +26,11 @@ INTERFACE = BUS_NAME class Daemon: def __init__(self, config: Config | None = None) -> None: - xml = files("parley.interfaces").joinpath( - "org.parley.Transcription1.xml" - ).read_text(encoding="utf-8") + xml = ( + files("parley.interfaces") + .joinpath("org.parley.Transcription1.xml") + .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() @@ -70,7 +72,7 @@ class Daemon: def _on_bus_acquired(self, connection: Gio.DBusConnection, name: str) -> None: self.connection = connection - self.registration_id = connection.register_object( + self.registration_id = connection.register_object_with_closures2( OBJECT_PATH, self.interface_info, self._method_call, @@ -81,9 +83,7 @@ class Daemon: def _on_name_acquired(self, connection: Gio.DBusConnection, name: str) -> None: print(f"Parley daemon owns {name}", file=sys.stderr) - def _on_name_lost( - self, connection: Gio.DBusConnection | None, name: str - ) -> None: + def _on_name_lost(self, connection: Gio.DBusConnection | None, name: str) -> None: print(f"error: could not own D-Bus name {name}", file=sys.stderr) self.loop.quit() @@ -139,12 +139,8 @@ class Daemon: str(self.controller.last_transcript_path or ""), ), "LastError": GLib.Variant("s", self.controller.last_error), - "InsertionMode": GLib.Variant( - "s", self.controller.config.insertion_mode - ), - "AutoInsert": GLib.Variant( - "b", self.controller.config.auto_insert - ), + "InsertionMode": GLib.Variant("s", self.controller.config.insertion_mode), + "AutoInsert": GLib.Variant("b", self.controller.config.auto_insert), } return values[property_name] @@ -171,9 +167,7 @@ class Daemon: else: copy_text(text) message = "copied; press Ctrl+V to insert" - self._emit( - "InsertionFinished", GLib.Variant("(sbs)", (backend, True, message)) - ) + self._emit("InsertionFinished", GLib.Variant("(sbs)", (backend, True, message))) def _open_transcript_folder(self) -> None: directory = self.controller.config.transcript_dir @@ -187,8 +181,10 @@ class Daemon: def _on_transcript(self, text, path) -> None: self._emit("TranscriptReady", GLib.Variant("(ss)", (text, str(path)))) self._properties_changed( - "LastTranscript", GLib.Variant("s", text), - "LastTranscriptPath", GLib.Variant("s", str(path)), + "LastTranscript", + GLib.Variant("s", text), + "LastTranscriptPath", + GLib.Variant("s", str(path)), ) clipboard_error = None try: @@ -207,7 +203,11 @@ class Daemon: "InsertionFinished", GLib.Variant("(sbs)", ("ibus", False, str(error))), ) - fallback = "Copied; press Ctrl+V" if clipboard_error is None else "Transcript saved" + fallback = ( + "Copied; press Ctrl+V" + if clipboard_error is None + else "Transcript saved" + ) self._notify("Automatic insertion unavailable", fallback) else: self._emit( @@ -240,9 +240,7 @@ class Daemon: def _emit(self, name: str, parameters: GLib.Variant) -> None: if self.connection is not None: - self.connection.emit_signal( - None, OBJECT_PATH, INTERFACE, name, parameters - ) + self.connection.emit_signal(None, OBJECT_PATH, INTERFACE, name, parameters) def _quit(self) -> bool: self.loop.quit() diff --git a/src/parley/desktop.py b/src/parley/desktop.py index 37f8a7f..3802985 100644 --- a/src/parley/desktop.py +++ b/src/parley/desktop.py @@ -54,4 +54,6 @@ def open_folder(path: Path) -> None: stderr=subprocess.DEVNULL, ) except OSError as error: - raise DesktopIntegrationError(f"could not open transcript folder: {error}") from error + raise DesktopIntegrationError( + f"could not open transcript folder: {error}" + ) from error diff --git a/src/parley/ibus_engine.py b/src/parley/ibus_engine.py index 6f0e642..a14a139 100644 --- a/src/parley/ibus_engine.py +++ b/src/parley/ibus_engine.py @@ -57,18 +57,16 @@ class IBusIntegration: raise IBusInsertionError("IBus is unavailable") engine = ParleyIBusEngine.focused if engine is None: - raise IBusInsertionError( - "Parley does not own the focused input context" - ) + raise IBusInsertionError("Parley does not own the focused input context") return engine.commit(text) def close(self) -> None: self._closed = True if self._reconnect_source: - GLib.source_remove(self._reconnect_source) + GLib.Source.remove(self._reconnect_source) self._reconnect_source = 0 if self._activation_source: - GLib.source_remove(self._activation_source) + GLib.Source.remove(self._activation_source) self._activation_source = 0 self._clear_connection() diff --git a/src/parley/persistence.py b/src/parley/persistence.py index c5bab79..bd004d8 100644 --- a/src/parley/persistence.py +++ b/src/parley/persistence.py @@ -10,9 +10,7 @@ from typing import Callable Clock = Callable[[], datetime] -def save_transcript( - text: str, directory: Path, *, clock: Clock | None = None -) -> Path: +def save_transcript(text: str, directory: Path, *, clock: Clock | None = None) -> Path: """Atomically save UTF-8 text and return its collision-safe path.""" directory.mkdir(parents=True, exist_ok=True) now = (clock or (lambda: datetime.now().astimezone()))() diff --git a/src/parley/transcription.py b/src/parley/transcription.py index 87ce66a..0735bb2 100644 --- a/src/parley/transcription.py +++ b/src/parley/transcription.py @@ -14,7 +14,7 @@ def parse_jsonl(output: str) -> str: for line in output.splitlines(): try: row = json.loads(line) - except (json.JSONDecodeError, TypeError): + except json.JSONDecodeError, TypeError: continue if not isinstance(row, dict) or row.get("type") == "batch_header": continue @@ -54,9 +54,13 @@ class Transcriber: "--batch-jsonl", ] try: - process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + process = subprocess.Popen( + command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) except OSError as error: - raise TranscriptionError(f"could not start transcription: {error}") from error + raise TranscriptionError( + f"could not start transcription: {error}" + ) from error with self._lock: self._process = process try: diff --git a/tests/test_controller.py b/tests/test_controller.py index 61c291e..b57cb69 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -55,7 +55,10 @@ class ControllerTests(unittest.TestCase): received = [] controller = Controller( self.config, - on_transcript=lambda text, path: (received.append((text, path)), ready.set()), + on_transcript=lambda text, path: ( + received.append((text, path)), + ready.set(), + ), ) controller.recorder = FakeRecorder() controller.transcriber = FakeTranscriber() diff --git a/tests/test_persistence.py b/tests/test_persistence.py index eecb378..d8ae257 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -18,9 +18,7 @@ class PersistenceTests(unittest.TestCase): def test_writes_utf8_with_one_final_newline(self) -> None: path = save_transcript("héllo\n\n", self.directory, clock=lambda: self.now) self.assertEqual(path.read_text(encoding="utf-8"), "héllo\n") - self.assertEqual( - path.name, "transcript_2026-07-24_12-34-56_123456+0000.txt" - ) + self.assertEqual(path.name, "transcript_2026-07-24_12-34-56_123456+0000.txt") def test_uses_counter_on_collision(self) -> None: first = save_transcript("one", self.directory, clock=lambda: self.now) @@ -32,7 +30,9 @@ class PersistenceTests(unittest.TestCase): def test_leaves_no_temporary_file(self) -> None: save_transcript("text", self.directory, clock=lambda: self.now) - self.assertFalse(any(path.suffix == ".tmp" for path in self.directory.iterdir())) + self.assertFalse( + any(path.suffix == ".tmp" for path in self.directory.iterdir()) + ) if __name__ == "__main__": diff --git a/tests/test_transcription.py b/tests/test_transcription.py index 916aac2..e79bb2b 100644 --- a/tests/test_transcription.py +++ b/tests/test_transcription.py @@ -6,11 +6,13 @@ from parley.transcription import parse_jsonl class ParseJsonlTests(unittest.TestCase): def test_skips_header_and_malformed_lines(self) -> None: - output = '\n'.join([ - 'runtime diagnostic', - '{"type":"batch_header","count":1}', - '{"file":"recording.wav","text":"hello world"}', - ]) + output = "\n".join( + [ + "runtime diagnostic", + '{"type":"batch_header","count":1}', + '{"file":"recording.wav","text":"hello world"}', + ] + ) self.assertEqual(parse_jsonl(output), "hello world") def test_reports_error_row(self) -> None: |
