python-voiceio 0.3.9__tar.gz → 0.3.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_voiceio-0.3.9/python_voiceio.egg-info → python_voiceio-0.3.10}/PKG-INFO +1 -1
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/pyproject.toml +1 -1
- {python_voiceio-0.3.9 → python_voiceio-0.3.10/python_voiceio.egg-info}/PKG-INFO +1 -1
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_ibus_typer.py +2 -1
- python_voiceio-0.3.10/voiceio/__init__.py +1 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/app.py +42 -4
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/ibus.py +24 -7
- python_voiceio-0.3.9/voiceio/__init__.py +0 -1
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/LICENSE +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/README.md +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/python_voiceio.egg-info/SOURCES.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/setup.cfg +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_app_wiring.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_autocorrect.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_backend_probes.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_commands.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_config.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_corrections.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_fallback.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_health.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_hints.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_history.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_llm.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_llm_api.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_numbers.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_platform.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_postprocess.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_prebuffer.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_prompt.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_robustness.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_streaming.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_transcriber.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_tts.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_vad.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_vocabulary.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/tests/test_wordfreq.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/__main__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/autocorrect.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/backends.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/cli.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/commands.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/config.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/corrections.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/demo.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/feedback.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/health.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hints.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/history.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/llm.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/llm_api.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/models/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/numbers.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/pidlock.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/platform.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/postprocess.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/prompt.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/recorder.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/service.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/streaming.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/transcriber.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/base.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/chain.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/tts/player.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/base.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/chain.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/vad.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/vocabulary.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/wizard.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/wordfreq.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.10}/voiceio/worker.py +0 -0
|
@@ -108,7 +108,8 @@ class TestIBusTyper:
|
|
|
108
108
|
typer._wl_copy = "/usr/bin/wl-copy"
|
|
109
109
|
mock_proc = MagicMock()
|
|
110
110
|
mock_proc.stdin = MagicMock()
|
|
111
|
-
with patch("voiceio.typers.ibus.subprocess.Popen", return_value=mock_proc)
|
|
111
|
+
with patch("voiceio.typers.ibus.subprocess.Popen", return_value=mock_proc), \
|
|
112
|
+
patch("voiceio.typers.ibus.subprocess.run"):
|
|
112
113
|
typer.commit_text("Hello")
|
|
113
114
|
mock_proc.stdin.write.assert_called_once_with(b"Hello")
|
|
114
115
|
mock_proc.stdin.close.assert_called_once()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.10"
|
|
@@ -648,6 +648,26 @@ class VoiceIO:
|
|
|
648
648
|
self._set_gnome_input_source_index(0)
|
|
649
649
|
log.info("VoiceIO IBus engine ready (dormant until recording)")
|
|
650
650
|
|
|
651
|
+
def _ping_ibus_engine(self) -> bool:
|
|
652
|
+
"""Check if the IBus engine's socket listener is alive.
|
|
653
|
+
|
|
654
|
+
Returns True if the engine responds to a ping within 1 second.
|
|
655
|
+
A False result means the engine process is alive but its socket
|
|
656
|
+
listener / GLib loop is dead (zombie engine).
|
|
657
|
+
"""
|
|
658
|
+
import socket as _socket
|
|
659
|
+
from voiceio.ibus import SOCKET_PATH
|
|
660
|
+
try:
|
|
661
|
+
sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM)
|
|
662
|
+
sock.settimeout(1.0)
|
|
663
|
+
sock.bind("")
|
|
664
|
+
sock.sendto(b"ping", str(SOCKET_PATH))
|
|
665
|
+
data, _ = sock.recvfrom(64)
|
|
666
|
+
sock.close()
|
|
667
|
+
return data == b"pong"
|
|
668
|
+
except (OSError, _socket.timeout):
|
|
669
|
+
return False
|
|
670
|
+
|
|
651
671
|
def _reactivate_ibus_if_stale(self) -> None:
|
|
652
672
|
"""Re-activate IBus engine if it lost registration (e.g. after hibernate).
|
|
653
673
|
|
|
@@ -933,7 +953,7 @@ class VoiceIO:
|
|
|
933
953
|
self.platform = _redetect_platform()
|
|
934
954
|
self._try_upgrade_typer(reason="probe-failed")
|
|
935
955
|
|
|
936
|
-
# Check IBus engine (restart if died,
|
|
956
|
+
# Check IBus engine (restart if died, zombie, or stale after resume)
|
|
937
957
|
if self._typer.name == "ibus" and self._engine_proc is not None:
|
|
938
958
|
if self._engine_proc.poll() is not None:
|
|
939
959
|
log.warning("IBus engine process died (rc=%d), restarting",
|
|
@@ -945,9 +965,27 @@ class VoiceIO:
|
|
|
945
965
|
except Exception:
|
|
946
966
|
log.exception("IBus engine recovery failed")
|
|
947
967
|
elif self._state == _State.IDLE:
|
|
948
|
-
# Engine alive
|
|
949
|
-
#
|
|
950
|
-
|
|
968
|
+
# Engine process alive — check if its socket listener is
|
|
969
|
+
# actually responding. A zombie engine (process alive but
|
|
970
|
+
# GLib loop stuck / socket thread dead) silently drops all
|
|
971
|
+
# preedit and commit messages, causing the "transcription
|
|
972
|
+
# works but no text appears" symptom.
|
|
973
|
+
if not self._ping_ibus_engine():
|
|
974
|
+
log.warning("IBus engine not responding to ping, restarting")
|
|
975
|
+
self._engine_proc.kill()
|
|
976
|
+
try:
|
|
977
|
+
self._engine_proc.wait(timeout=3)
|
|
978
|
+
except subprocess.TimeoutExpired:
|
|
979
|
+
pass
|
|
980
|
+
self._engine_proc = None
|
|
981
|
+
try:
|
|
982
|
+
self._ensure_ibus_engine()
|
|
983
|
+
log.info("IBus engine recovered (was zombie)")
|
|
984
|
+
except Exception:
|
|
985
|
+
log.exception("IBus engine recovery failed")
|
|
986
|
+
else:
|
|
987
|
+
# Engine alive and responding — check IBus registration
|
|
988
|
+
self._reactivate_ibus_if_stale()
|
|
951
989
|
|
|
952
990
|
# ── Main loop ───────────────────────────────────────────────────────
|
|
953
991
|
|
|
@@ -375,14 +375,21 @@ class IBusTyper:
|
|
|
375
375
|
"""Ask the IBus engine whether it has input focus in the active window."""
|
|
376
376
|
try:
|
|
377
377
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
|
378
|
-
sock.settimeout(0.
|
|
378
|
+
sock.settimeout(0.5)
|
|
379
379
|
sock.bind("") # autobind for receiving reply
|
|
380
380
|
sock.sendto(b"focus?", str(SOCKET_PATH))
|
|
381
381
|
data, _ = sock.recvfrom(64)
|
|
382
382
|
sock.close()
|
|
383
|
-
|
|
383
|
+
focused = data == b"focused"
|
|
384
|
+
log.debug("IBus engine focus: %s", data.decode(errors="replace"))
|
|
385
|
+
return focused
|
|
384
386
|
except (OSError, socket.timeout):
|
|
385
|
-
|
|
387
|
+
# Engine not responding — assume NOT focused so we fall back
|
|
388
|
+
# to clipboard paste. Worst case: a redundant Ctrl+V that
|
|
389
|
+
# pastes over IBus-committed text. Better than silently losing
|
|
390
|
+
# the text entirely.
|
|
391
|
+
log.warning("IBus engine not responding to focus query, assuming unfocused")
|
|
392
|
+
return False
|
|
386
393
|
|
|
387
394
|
@staticmethod
|
|
388
395
|
def _ydotoold_running() -> bool:
|
|
@@ -401,19 +408,29 @@ class IBusTyper:
|
|
|
401
408
|
try:
|
|
402
409
|
if self._ydotool and self._ydotoold_running():
|
|
403
410
|
# ydotool key scancodes: 29=LCtrl, 47=V
|
|
404
|
-
subprocess.run(
|
|
411
|
+
result = subprocess.run(
|
|
405
412
|
[self._ydotool, "key", "29:1", "47:1", "47:0", "29:0"],
|
|
406
413
|
capture_output=True, timeout=3,
|
|
407
414
|
)
|
|
415
|
+
if result.returncode != 0:
|
|
416
|
+
log.warning("ydotool paste failed (rc=%d): %s",
|
|
417
|
+
result.returncode, result.stderr.decode(errors="replace").strip())
|
|
418
|
+
else:
|
|
419
|
+
log.debug("Pasted via ydotool")
|
|
408
420
|
elif self._wtype:
|
|
409
|
-
subprocess.run(
|
|
421
|
+
result = subprocess.run(
|
|
410
422
|
[self._wtype, "-M", "ctrl", "-k", "v", "-m", "ctrl"],
|
|
411
423
|
capture_output=True, timeout=3,
|
|
412
424
|
)
|
|
425
|
+
if result.returncode != 0:
|
|
426
|
+
log.warning("wtype paste failed (rc=%d): %s",
|
|
427
|
+
result.returncode, result.stderr.decode(errors="replace").strip())
|
|
428
|
+
else:
|
|
429
|
+
log.debug("Pasted via wtype")
|
|
413
430
|
else:
|
|
414
|
-
log.
|
|
431
|
+
log.warning("No paste tool available (ydotoold not running, wtype not found)")
|
|
415
432
|
except (subprocess.TimeoutExpired, OSError) as e:
|
|
416
|
-
log.
|
|
433
|
+
log.warning("Paste simulation failed: %s", e)
|
|
417
434
|
|
|
418
435
|
def _copy_to_clipboard(self, text: str) -> None:
|
|
419
436
|
"""Copy text to clipboard as backup for non-IBus apps (terminals).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|