python-voiceio 0.3.8__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.
Files changed (104) hide show
  1. {python_voiceio-0.3.8/python_voiceio.egg-info → python_voiceio-0.3.10}/PKG-INFO +1 -1
  2. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/pyproject.toml +1 -1
  3. {python_voiceio-0.3.8 → python_voiceio-0.3.10/python_voiceio.egg-info}/PKG-INFO +1 -1
  4. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_ibus_typer.py +2 -1
  5. python_voiceio-0.3.10/voiceio/__init__.py +1 -0
  6. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/app.py +42 -4
  7. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/ibus.py +24 -7
  8. python_voiceio-0.3.8/voiceio/__init__.py +0 -1
  9. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/LICENSE +0 -0
  10. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/README.md +0 -0
  11. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/python_voiceio.egg-info/SOURCES.txt +0 -0
  12. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/python_voiceio.egg-info/dependency_links.txt +0 -0
  13. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/python_voiceio.egg-info/entry_points.txt +0 -0
  14. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/python_voiceio.egg-info/requires.txt +0 -0
  15. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/python_voiceio.egg-info/top_level.txt +0 -0
  16. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/setup.cfg +0 -0
  17. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_app_wiring.py +0 -0
  18. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_autocorrect.py +0 -0
  19. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_backend_probes.py +0 -0
  20. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_clipboard_read.py +0 -0
  21. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_commands.py +0 -0
  22. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_config.py +0 -0
  23. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_corrections.py +0 -0
  24. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_fallback.py +0 -0
  25. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_health.py +0 -0
  26. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_hints.py +0 -0
  27. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_history.py +0 -0
  28. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_llm.py +0 -0
  29. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_llm_api.py +0 -0
  30. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_numbers.py +0 -0
  31. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_platform.py +0 -0
  32. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_postprocess.py +0 -0
  33. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_prebuffer.py +0 -0
  34. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_prompt.py +0 -0
  35. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_recorder_integration.py +0 -0
  36. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_robustness.py +0 -0
  37. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_streaming.py +0 -0
  38. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_transcriber.py +0 -0
  39. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_tts.py +0 -0
  40. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_vad.py +0 -0
  41. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_vocabulary.py +0 -0
  42. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/tests/test_wordfreq.py +0 -0
  43. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/__main__.py +0 -0
  44. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/autocorrect.py +0 -0
  45. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/backends.py +0 -0
  46. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/cli.py +0 -0
  47. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/clipboard_read.py +0 -0
  48. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/commands.py +0 -0
  49. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/config.py +0 -0
  50. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/corrections.py +0 -0
  51. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/demo.py +0 -0
  52. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/feedback.py +0 -0
  53. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/health.py +0 -0
  54. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hints.py +0 -0
  55. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/history.py +0 -0
  56. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hotkeys/__init__.py +0 -0
  57. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hotkeys/base.py +0 -0
  58. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hotkeys/chain.py +0 -0
  59. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hotkeys/evdev.py +0 -0
  60. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hotkeys/pynput_backend.py +0 -0
  61. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/hotkeys/socket_backend.py +0 -0
  62. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/ibus/__init__.py +0 -0
  63. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/ibus/engine.py +0 -0
  64. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/llm.py +0 -0
  65. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/llm_api.py +0 -0
  66. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/models/__init__.py +0 -0
  67. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/models/silero_vad.onnx +0 -0
  68. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/numbers.py +0 -0
  69. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/pidlock.py +0 -0
  70. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/platform.py +0 -0
  71. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/postprocess.py +0 -0
  72. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/prompt.py +0 -0
  73. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/recorder.py +0 -0
  74. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/service.py +0 -0
  75. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/sounds/__init__.py +0 -0
  76. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/sounds/commit.wav +0 -0
  77. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/sounds/start.wav +0 -0
  78. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/sounds/stop.wav +0 -0
  79. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/streaming.py +0 -0
  80. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/transcriber.py +0 -0
  81. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tray/__init__.py +0 -0
  82. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tray/_icons.py +0 -0
  83. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tray/_indicator.py +0 -0
  84. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tray/_pystray.py +0 -0
  85. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/__init__.py +0 -0
  86. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/base.py +0 -0
  87. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/chain.py +0 -0
  88. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/edge_engine.py +0 -0
  89. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/espeak.py +0 -0
  90. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/piper_engine.py +0 -0
  91. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/tts/player.py +0 -0
  92. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/__init__.py +0 -0
  93. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/base.py +0 -0
  94. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/chain.py +0 -0
  95. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/clipboard.py +0 -0
  96. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/pynput_type.py +0 -0
  97. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/wtype.py +0 -0
  98. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/xdotool.py +0 -0
  99. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/typers/ydotool.py +0 -0
  100. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/vad.py +0 -0
  101. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/vocabulary.py +0 -0
  102. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/wizard.py +0 -0
  103. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/wordfreq.py +0 -0
  104. {python_voiceio-0.3.8 → python_voiceio-0.3.10}/voiceio/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-voiceio"
7
- version = "0.3.8"
7
+ version = "0.3.10"
8
8
  description = "Speak → text, locally, instantly."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.3.8
3
+ Version: 0.3.10
4
4
  Summary: Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -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, re-activate if stale after resume)
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 but may have lost IBus registration after
949
- # hibernate/suspend. Re-activate so preedit works next time.
950
- self._reactivate_ibus_if_stale()
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.3)
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
- return data == b"focused"
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
- return True # assume focused on error (safe default: no extra paste)
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.debug("No paste tool available (ydotoold not running, wtype not found)")
431
+ log.warning("No paste tool available (ydotoold not running, wtype not found)")
415
432
  except (subprocess.TimeoutExpired, OSError) as e:
416
- log.debug("Paste simulation failed: %s", e)
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.8"
File without changes