python-voiceio 1.15.0__tar.gz → 1.15.2__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 (233) hide show
  1. {python_voiceio-1.15.0/python_voiceio.egg-info → python_voiceio-1.15.2}/PKG-INFO +5 -1
  2. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/README.md +4 -0
  3. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/pyproject.toml +1 -1
  4. {python_voiceio-1.15.0 → python_voiceio-1.15.2/python_voiceio.egg-info}/PKG-INFO +5 -1
  5. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/SOURCES.txt +1 -0
  6. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_feedback_stream.py +4 -4
  7. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_health.py +26 -0
  8. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_idle_audio.py +8 -8
  9. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_postprocess.py +14 -0
  10. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_report.py +19 -1
  11. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_robustness.py +17 -0
  12. python_voiceio-1.15.2/tests/test_tray.py +58 -0
  13. python_voiceio-1.15.2/voiceio/__init__.py +1 -0
  14. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/feedback.py +3 -5
  15. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/health.py +13 -1
  16. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/platform.py +16 -0
  17. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/postprocess.py +3 -2
  18. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/recorder.py +2 -6
  19. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/report.py +11 -2
  20. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/steps.py +2 -1
  21. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/__init__.py +15 -1
  22. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/_pystray.py +3 -0
  23. python_voiceio-1.15.0/voiceio/__init__.py +0 -1
  24. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/LICENSE +0 -0
  25. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/dependency_links.txt +0 -0
  26. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/entry_points.txt +0 -0
  27. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/requires.txt +0 -0
  28. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/python_voiceio.egg-info/top_level.txt +0 -0
  29. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/setup.cfg +0 -0
  30. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_announce.py +0 -0
  31. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_app_wiring.py +0 -0
  32. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_audio_quality.py +0 -0
  33. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_audio_source.py +0 -0
  34. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_backend_probes.py +0 -0
  35. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_cancel.py +0 -0
  36. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_cli.py +0 -0
  37. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_clipboard_read.py +0 -0
  38. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_clipboard_typer.py +0 -0
  39. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_commands.py +0 -0
  40. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_concurrency_lockdown.py +0 -0
  41. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_config.py +0 -0
  42. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_context_terms.py +0 -0
  43. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_corrections.py +0 -0
  44. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_dead_mic.py +0 -0
  45. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_decoders.py +0 -0
  46. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_engine.py +0 -0
  47. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_evdev_uaccess.py +0 -0
  48. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_fallback.py +0 -0
  49. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_fcitx_bridge.py +0 -0
  50. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_focus.py +0 -0
  51. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_hints.py +0 -0
  52. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_history.py +0 -0
  53. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_hotkey_modes.py +0 -0
  54. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_engine_limits.py +0 -0
  55. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_engine_listener.py +0 -0
  56. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_pending.py +0 -0
  57. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_pids.py +0 -0
  58. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_ping.py +0 -0
  59. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_session.py +0 -0
  60. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_textlimit.py +0 -0
  61. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_ibus_typer.py +0 -0
  62. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_clean.py +0 -0
  63. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_import.py +0 -0
  64. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_language.py +0 -0
  65. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_metrics.py +0 -0
  66. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_pipeline.py +0 -0
  67. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_quiet.py +0 -0
  68. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_resume.py +0 -0
  69. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_review.py +0 -0
  70. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_learn_targets.py +0 -0
  71. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_llm.py +0 -0
  72. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_llm_api.py +0 -0
  73. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_mic_gain.py +0 -0
  74. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_modifier_guard.py +0 -0
  75. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_numbers.py +0 -0
  76. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_paste_last.py +0 -0
  77. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_pipeline.py +0 -0
  78. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_platform.py +0 -0
  79. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_postcorrect.py +0 -0
  80. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_prebuffer.py +0 -0
  81. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_presence.py +0 -0
  82. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_prompt.py +0 -0
  83. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_quiet_audio.py +0 -0
  84. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_recorder_integration.py +0 -0
  85. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_retention.py +0 -0
  86. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_security_hardening.py +0 -0
  87. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_server.py +0 -0
  88. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_service.py +0 -0
  89. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_setup.py +0 -0
  90. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_sherpa.py +0 -0
  91. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_spool.py +0 -0
  92. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_streaming.py +0 -0
  93. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_suggest.py +0 -0
  94. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_tokens.py +0 -0
  95. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_transcriber.py +0 -0
  96. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_tts.py +0 -0
  97. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_typer_manager.py +0 -0
  98. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_vad.py +0 -0
  99. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_vocabulary.py +0 -0
  100. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_wordfreq.py +0 -0
  101. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/tests/test_worker_decode.py +0 -0
  102. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/__main__.py +0 -0
  103. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/announce.py +0 -0
  104. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/app.py +0 -0
  105. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/audio_source.py +0 -0
  106. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/backends.py +0 -0
  107. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/__init__.py +0 -0
  108. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/configcmd.py +0 -0
  109. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/corrections.py +0 -0
  110. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/doctor.py +0 -0
  111. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/history.py +0 -0
  112. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/learn.py +0 -0
  113. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/models.py +0 -0
  114. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/servicecmd.py +0 -0
  115. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/uninstall.py +0 -0
  116. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/cli/vocab.py +0 -0
  117. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/clipboard_read.py +0 -0
  118. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/commands.py +0 -0
  119. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/config.py +0 -0
  120. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/consent.py +0 -0
  121. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/context_terms.py +0 -0
  122. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/__init__.py +0 -0
  123. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/bias.py +0 -0
  124. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/__init__.py +0 -0
  125. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/base.py +0 -0
  126. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/catalog.py +0 -0
  127. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/faster_whisper/__init__.py +0 -0
  128. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/faster_whisper/worker.py +0 -0
  129. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/fetch.py +0 -0
  130. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/lanes.py +0 -0
  131. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/sherpa.py +0 -0
  132. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/decoders/whispercpp.py +0 -0
  133. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/engine.py +0 -0
  134. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/core/pipeline.py +0 -0
  135. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/corrections.py +0 -0
  136. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/data/60-voiceio-uaccess.rules +0 -0
  137. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/data/__init__.py +0 -0
  138. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/demo.py +0 -0
  139. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/display.py +0 -0
  140. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/focus.py +0 -0
  141. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hints.py +0 -0
  142. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/history.py +0 -0
  143. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/__init__.py +0 -0
  144. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/base.py +0 -0
  145. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/chain.py +0 -0
  146. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/evdev.py +0 -0
  147. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/pynput_backend.py +0 -0
  148. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/hotkeys/socket_backend.py +0 -0
  149. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/__init__.py +0 -0
  150. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/engine.py +0 -0
  151. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/install.py +0 -0
  152. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/pending.py +0 -0
  153. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/session.py +0 -0
  154. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ibus/textlimit.py +0 -0
  155. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/idle_audio.py +0 -0
  156. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/__init__.py +0 -0
  157. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/augment.py +0 -0
  158. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/clean.py +0 -0
  159. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/evaluate.py +0 -0
  160. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/importer.py +0 -0
  161. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/metrics.py +0 -0
  162. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/quiet.py +0 -0
  163. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/review.py +0 -0
  164. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/store.py +0 -0
  165. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/teacher.py +0 -0
  166. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/learn/train.py +0 -0
  167. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/llm.py +0 -0
  168. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/llm_api.py +0 -0
  169. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/mic_gain.py +0 -0
  170. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/models/__init__.py +0 -0
  171. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/models/silero_vad.onnx +0 -0
  172. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/numbers.py +0 -0
  173. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/pidlock.py +0 -0
  174. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/postcorrect.py +0 -0
  175. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/power.py +0 -0
  176. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/presence.py +0 -0
  177. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/privacy.py +0 -0
  178. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/prompt.py +0 -0
  179. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/quiet_audio.py +0 -0
  180. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/retention.py +0 -0
  181. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/__init__.py +0 -0
  182. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/__main__.py +0 -0
  183. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/app.py +0 -0
  184. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/config.py +0 -0
  185. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/server/runtime.py +0 -0
  186. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/service.py +0 -0
  187. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/__init__.py +0 -0
  188. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/configfile.py +0 -0
  189. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/extras.py +0 -0
  190. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/hotkey.py +0 -0
  191. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/noninteractive.py +0 -0
  192. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/speech.py +0 -0
  193. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/system.py +0 -0
  194. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/setup/ui.py +0 -0
  195. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/__init__.py +0 -0
  196. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/alert.wav +0 -0
  197. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/commit.wav +0 -0
  198. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/start.wav +0 -0
  199. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/sounds/stop.wav +0 -0
  200. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/spool.py +0 -0
  201. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/streaming.py +0 -0
  202. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/suggest.py +0 -0
  203. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tokens.py +0 -0
  204. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/_icons.py +0 -0
  205. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tray/_indicator.py +0 -0
  206. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/__init__.py +0 -0
  207. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/base.py +0 -0
  208. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/chain.py +0 -0
  209. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/edge_engine.py +0 -0
  210. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/espeak.py +0 -0
  211. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/piper_engine.py +0 -0
  212. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/player.py +0 -0
  213. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/tts/supertonic_engine.py +0 -0
  214. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/__init__.py +0 -0
  215. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/base.py +0 -0
  216. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/chain.py +0 -0
  217. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/clipboard.py +0 -0
  218. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/collecting.py +0 -0
  219. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/fcitx_bridge.py +0 -0
  220. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/ibus.py +0 -0
  221. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/manager.py +0 -0
  222. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/modifier_guard.py +0 -0
  223. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/paste.py +0 -0
  224. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/pynput_type.py +0 -0
  225. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/wtype.py +0 -0
  226. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/xdotool.py +0 -0
  227. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/typers/ydotool.py +0 -0
  228. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/ui.py +0 -0
  229. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/vad.py +0 -0
  230. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/vocab_stats.py +0 -0
  231. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/vocabulary.py +0 -0
  232. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/watchdog.py +0 -0
  233. {python_voiceio-1.15.0 → python_voiceio-1.15.2}/voiceio/wordfreq.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 1.15.0
3
+ Version: 1.15.2
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -146,6 +146,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
146
146
 
147
147
  Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the architecture, conventions and the reasoning behind the model choice. Install reports and ideas are welcome on the [feedback board](https://voiceio.dev/#feedback).
148
148
 
149
+ ## How it's built
150
+
151
+ I write voiceio with AI coding assistants, mostly Claude Code. I decide what goes in and I use it every day, which is how most of its bugs get found.
152
+
149
153
  ## Credits
150
154
 
151
155
  - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
@@ -93,6 +93,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
93
93
 
94
94
  Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the architecture, conventions and the reasoning behind the model choice. Install reports and ideas are welcome on the [feedback board](https://voiceio.dev/#feedback).
95
95
 
96
+ ## How it's built
97
+
98
+ I write voiceio with AI coding assistants, mostly Claude Code. I decide what goes in and I use it every day, which is how most of its bugs get found.
99
+
96
100
  ## Credits
97
101
 
98
102
  - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-voiceio"
7
- version = "1.15.0"
7
+ version = "1.15.2"
8
8
  description = "Voice dictation for Linux. 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: 1.15.0
3
+ Version: 1.15.2
4
4
  Summary: Voice dictation for Linux. Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -146,6 +146,10 @@ Everything lives in `~/.config/voiceio/config.toml`; [config.example.toml](confi
146
146
 
147
147
  Open an issue before a large PR. [CONTRIBUTING.md](CONTRIBUTING.md) covers the architecture, conventions and the reasoning behind the model choice. Install reports and ideas are welcome on the [feedback board](https://voiceio.dev/#feedback).
148
148
 
149
+ ## How it's built
150
+
151
+ I write voiceio with AI coding assistants, mostly Claude Code. I decide what goes in and I use it every day, which is how most of its bugs get found.
152
+
149
153
  ## Credits
150
154
 
151
155
  - Speech recognition: [faster-whisper](https://github.com/SYSTRAN/faster-whisper) and [CTranslate2](https://github.com/OpenNMT/CTranslate2), with OpenAI's [Whisper](https://github.com/openai/whisper) models; [sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) with NVIDIA's [Parakeet-TDT](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v2). The in-browser demo runs [Moonshine](https://github.com/usefulsensors/moonshine) through [transformers.js](https://github.com/huggingface/transformers.js).
@@ -79,6 +79,7 @@ tests/test_streaming.py
79
79
  tests/test_suggest.py
80
80
  tests/test_tokens.py
81
81
  tests/test_transcriber.py
82
+ tests/test_tray.py
82
83
  tests/test_tts.py
83
84
  tests/test_typer_manager.py
84
85
  tests/test_vad.py
@@ -1,11 +1,11 @@
1
1
  """The persistent cue stream: buffer size and cue timing."""
2
2
  from __future__ import annotations
3
3
 
4
- import pytest
4
+ from types import SimpleNamespace
5
5
 
6
- pytest.importorskip("sounddevice")
6
+ import pytest
7
7
 
8
- from voiceio import feedback # noqa: E402
8
+ from voiceio import feedback
9
9
 
10
10
 
11
11
  class FakeStream:
@@ -24,7 +24,7 @@ class FakeStream:
24
24
  @pytest.fixture
25
25
  def fake_stream(monkeypatch):
26
26
  FakeStream.opened = []
27
- monkeypatch.setattr(feedback.sd, "OutputStream", FakeStream)
27
+ monkeypatch.setattr(feedback, "sd", SimpleNamespace(OutputStream=FakeStream))
28
28
  monkeypatch.setattr(feedback, "_stream", None)
29
29
  monkeypatch.setattr(feedback, "_sounds", {})
30
30
  return FakeStream
@@ -123,3 +123,29 @@ def test_learning_status_and_tips(monkeypatch):
123
123
  assert st["Voice model"] == "fine-tuned on you (v0001-small-x)"
124
124
  assert st["Recordings"].startswith("on (") and st["Scheduled learning"] == "off"
125
125
  assert any("learn schedule on" in t for t in _accuracy_tips(st))
126
+
127
+
128
+ def test_silent_ibus_engine_is_not_a_warning_while_voiceio_is_stopped():
129
+ from voiceio.health import HealthReport, IBusStatus
130
+ p = Platform(os="linux", display_server="wayland", desktop="kde")
131
+ ib = IBusStatus(component_installed=True, daemon_running=True, voiceio_running=False)
132
+ text = format_report(HealthReport(platform=p, ibus_checks=ib))
133
+ line = next(ln for ln in text.splitlines() if "Engine answers" in ln)
134
+ assert "⚠" not in line and "isn't running" in line
135
+ ib.voiceio_running = True
136
+ line = next(ln for ln in format_report(HealthReport(platform=p, ibus_checks=ib)).splitlines()
137
+ if "Engine answers" in ln)
138
+ assert "⚠" in line and "service restart" in line
139
+
140
+
141
+ def test_check_ibus_asks_about_voiceio_only_when_the_engine_is_silent():
142
+ from voiceio import health
143
+ with patch("voiceio.ibus.install.component_installed", return_value=True), \
144
+ patch("voiceio.ibus.install.daemon_running", return_value=True), \
145
+ patch("voiceio.ibus.ping_engine", return_value=False), \
146
+ patch("voiceio.pidlock.daemon_alive", return_value=False) as alive:
147
+ assert health._check_ibus().voiceio_running is False
148
+ with patch("voiceio.ibus.ping_engine", return_value=True):
149
+ alive.reset_mock()
150
+ assert health._check_ibus().voiceio_running is True
151
+ alive.assert_not_called()
@@ -3,17 +3,16 @@ from __future__ import annotations
3
3
 
4
4
  import threading
5
5
  import time
6
+ from types import SimpleNamespace
6
7
  from unittest.mock import MagicMock, patch
7
8
 
8
9
  import numpy as np
9
10
  import pytest
10
11
 
11
- pytest.importorskip("sounddevice")
12
-
13
- from voiceio import feedback, idle_audio # noqa: E402
14
- from voiceio.config import AudioConfig, Config # noqa: E402
15
- from voiceio.idle_audio import IdleAudio # noqa: E402
16
- from voiceio.recorder import AudioRecorder # noqa: E402
12
+ from voiceio import feedback, idle_audio
13
+ from voiceio.config import AudioConfig, Config
14
+ from voiceio.idle_audio import IdleAudio
15
+ from voiceio.recorder import AudioRecorder
17
16
 
18
17
 
19
18
  class FakeInput:
@@ -50,8 +49,9 @@ class FakeOutput(FakeInput):
50
49
  @pytest.fixture
51
50
  def streams(monkeypatch):
52
51
  FakeInput.opened = FakeOutput.opened = 0
53
- monkeypatch.setattr("voiceio.recorder.sd.InputStream", FakeInput)
54
- monkeypatch.setattr(feedback.sd, "OutputStream", FakeOutput)
52
+ # Whole fakes: touching the real module would initialize PortAudio.
53
+ monkeypatch.setattr("voiceio.recorder.sd", SimpleNamespace(InputStream=FakeInput))
54
+ monkeypatch.setattr(feedback, "sd", SimpleNamespace(OutputStream=FakeOutput))
55
55
  monkeypatch.setattr(feedback, "_stream", None)
56
56
 
57
57
 
@@ -88,6 +88,20 @@ class TestStripDisfluencies:
88
88
  assert strip_disfluencies("uh-huh right") == "right"
89
89
  assert strip_disfluencies("uh huh yes") == "yes"
90
90
 
91
+ def test_mm_hmm_goes_whole(self):
92
+ # Parakeet ends about 1 clip in 4 with an invented "Mm-hmm."; cutting
93
+ # only its "hmm" typed a stray "Mm-." after real dictation.
94
+ assert strip_disfluencies("I sent it. Mm-hmm.") == "I sent it."
95
+ assert strip_disfluencies("Are you sure? Mm-hmm.") == "Are you sure?"
96
+ assert strip_disfluencies("Mm-hmm.") == ""
97
+ assert strip_disfluencies("Fine. Mm-hmm. So it works.") == "Fine. So it works."
98
+ assert strip_disfluencies("mmhmm that works") == "that works"
99
+
100
+ def test_fillers_inside_hyphenated_words_survive(self):
101
+ assert strip_disfluencies("a hmm-hmm sound") == "a hmm-hmm sound"
102
+ assert strip_disfluencies("she said uh-uh twice") == "she said uh-uh twice"
103
+ assert strip_disfluencies("the 5 mm drill") == "the 5 mm drill"
104
+
91
105
  def test_preserves_emphatic_short_repeat(self):
92
106
  # Short repeats are emphasis, not a re-decode artifact — keep them.
93
107
  assert strip_disfluencies("No. No.") == "No. No."
@@ -9,7 +9,7 @@ import pytest
9
9
 
10
10
  from voiceio import report
11
11
  from voiceio.config import Config
12
- from voiceio.health import BackendStatus, HealthReport
12
+ from voiceio.health import BackendStatus, HealthReport, IBusStatus
13
13
  from voiceio.platform import Platform
14
14
 
15
15
  FAKE_HOME = "/home/zelda-tester"
@@ -104,6 +104,16 @@ def test_report_lists_failures_and_warnings():
104
104
  assert "insecure permissions on 2 file(s)" in text
105
105
 
106
106
 
107
+ def test_silent_ibus_engine_is_expected_while_voiceio_is_stopped():
108
+ # Two 1.15.0 reports warned "ibus engine ping" only because voiceio wasn't running.
109
+ ib = IBusStatus(component_installed=True, daemon_running=True, env_persisted=True)
110
+ stopped = report.build_report(_health(ibus_checks=IBusStatus(**{**vars(ib), "voiceio_running": False})),
111
+ cfg=Config())
112
+ assert "engine ping" not in stopped and "voiceio not running" in stopped
113
+ running = report.build_report(_health(ibus_checks=ib), cfg=Config())
114
+ assert "ibus engine ping" in running and "voiceio not running" not in running
115
+
116
+
107
117
  def test_comment_is_one_bounded_line():
108
118
  text = report.build_report(_health(), comment="a\nb\tc " + "x" * 1000, cfg=Config())
109
119
  line = next(ln for ln in text.splitlines() if ln.startswith("comment:"))
@@ -111,6 +121,14 @@ def test_comment_is_one_bounded_line():
111
121
  assert len(line) <= len("comment: ") + report.MAX_COMMENT
112
122
 
113
123
 
124
+ def test_comment_drops_terminal_escapes():
125
+ # A real 1.15.0 report arrived with "comment: \x1bv" (Alt+V at the prompt).
126
+ text = report.build_report(_health(), comment="\x1bv", cfg=Config())
127
+ assert "comment:" not in text and "\x1b" not in text
128
+ text = report.build_report(_health(), comment="works\x1b[D\x1b[C great\x07", cfg=Config())
129
+ assert "comment: works great" in text.splitlines()
130
+
131
+
114
132
  # ── posting ──
115
133
 
116
134
  class _Resp(io.BytesIO):
@@ -936,3 +936,20 @@ class TestZombieStreamRecovery:
936
936
  # any real signal → not a zombie
937
937
  rec._ring.append(np.full(rec._ring._max, 0.1, dtype=np.float32))
938
938
  assert rec.is_zombie() is False
939
+
940
+
941
+ def test_app_imports_when_portaudio_cannot_initialize(tmp_path):
942
+ # ubuntu-26.04 runners: libportaudio2 pulls libpulse0, and with no pulse
943
+ # server `import sounddevice` raises; importing voiceio.app must not.
944
+ import subprocess
945
+ import sys
946
+ (tmp_path / "sounddevice.py").write_text(
947
+ "class PortAudioError(Exception): pass\n"
948
+ "raise PortAudioError('PulseAudio_Initialize: Can\\'t connect to server')\n")
949
+ code = ("import voiceio.app, voiceio.recorder as r\n"
950
+ "try:\n r.sd.InputStream\nexcept Exception as e:\n"
951
+ " print(type(e).__name__)\n")
952
+ out = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True,
953
+ env={**__import__("os").environ, "PYTHONPATH": str(tmp_path)})
954
+ assert out.returncode == 0, out.stderr
955
+ assert out.stdout.strip() == "PortAudioError"
@@ -0,0 +1,58 @@
1
+ """Tray backend probing: what doctor/setup promise must match what start() does."""
2
+ from __future__ import annotations
3
+
4
+ import builtins
5
+ import sys
6
+ from pathlib import Path
7
+ from unittest.mock import patch
8
+
9
+ import pytest
10
+
11
+ from voiceio import tray
12
+ from voiceio.tray import _pystray
13
+
14
+ linux = pytest.mark.skipif(not sys.platform.startswith("linux"), reason="linux probe")
15
+
16
+
17
+ @linux
18
+ def test_appindicator_wins():
19
+ with patch.object(tray, "_find_system_python", return_value="/usr/bin/python3"):
20
+ assert tray.probe_availability() == (True, "AppIndicator3 available", "")
21
+
22
+
23
+ @linux
24
+ def test_pystray_xembed_fallback_counts_as_a_tray():
25
+ # KDE/LXQt users without python3-gi got "no tray" though start() falls back to pystray.
26
+ with patch.object(tray, "_find_system_python", return_value=None), \
27
+ patch.object(tray, "_pystray_backend", return_value="xorg"):
28
+ ok, reason, hint = tray.probe_availability()
29
+ assert ok and "no menu" in reason and "for the full menu" in hint
30
+
31
+
32
+ @linux
33
+ def test_no_backend_at_all():
34
+ with patch.object(tray, "_find_system_python", return_value=None), \
35
+ patch.object(tray, "_pystray_backend", return_value=None):
36
+ ok, reason, hint = tray.probe_availability()
37
+ assert not ok and "AppIndicator3" in reason and hint.startswith("install:")
38
+
39
+
40
+ def _import_raising(exc):
41
+ real = builtins.__import__
42
+
43
+ def fake(name, *a, **kw):
44
+ if name == "pystray":
45
+ raise exc
46
+ return real(name, *a, **kw)
47
+ return fake
48
+
49
+
50
+ class _DisplayNameError(Exception):
51
+ """Stands in for Xlib.error.DisplayNameError (no DISPLAY): not an ImportError."""
52
+
53
+
54
+ def test_pystray_without_x_display_does_not_raise(monkeypatch):
55
+ monkeypatch.delitem(sys.modules, "pystray", raising=False)
56
+ with patch("builtins.__import__", _import_raising(_DisplayNameError(":0"))):
57
+ assert tray._pystray_backend() is None
58
+ assert _pystray.start(lambda: None, Path("idle.png"), []) is False
@@ -0,0 +1 @@
1
+ __version__ = "1.15.2"
@@ -20,11 +20,9 @@ from pathlib import Path
20
20
 
21
21
  import numpy as np
22
22
 
23
- try:
24
- import sounddevice as sd
25
- except ImportError as _e:
26
- from voiceio.platform import desktop_dep_hint
27
- raise ImportError(desktop_dep_hint("sounddevice")) from _e
23
+ from voiceio.platform import LazySounddevice
24
+
25
+ sd = LazySounddevice()
28
26
 
29
27
  log = logging.getLogger(__name__)
30
28
 
@@ -25,6 +25,8 @@ class IBusStatus:
25
25
  component_installed: bool = False
26
26
  daemon_running: bool = False
27
27
  socket_reachable: bool = False
28
+ # The engine runs while voiceio does, so a silent one only matters then.
29
+ voiceio_running: bool = True
28
30
  gnome_source_configured: bool = False
29
31
  env_persisted: bool = False
30
32
 
@@ -301,6 +303,10 @@ def _check_ibus() -> IBusStatus:
301
303
  # Ping, not exists(): an engine can hold a socket file yet be deaf.
302
304
  from voiceio.ibus import ping_engine
303
305
  status.socket_reachable = ping_engine(timeout=1.0)
306
+ if not status.socket_reachable:
307
+ from voiceio.config import PID_PATH
308
+ from voiceio.pidlock import daemon_alive
309
+ status.voiceio_running = daemon_alive(PID_PATH)
304
310
 
305
311
  # Check GNOME input source
306
312
  try:
@@ -433,7 +439,9 @@ def format_report(report: HealthReport) -> str:
433
439
  lines.append(_check_line("Tray icon", report.tray_ok,
434
440
  report.tray_reason if not report.tray_ok else "",
435
441
  warn=not report.tray_ok))
436
- if report.tray_fix_hint and not report.tray_ok:
442
+ if report.tray_ok and report.tray_fix_hint: # works, but degraded
443
+ lines[-1] += f" {DIM}{report.tray_reason}{RESET}"
444
+ if report.tray_fix_hint:
437
445
  lines.append(f" {DIM}→ {report.tray_fix_hint}{RESET}")
438
446
 
439
447
  # IBus pipeline
@@ -448,6 +456,10 @@ def format_report(report: HealthReport) -> str:
448
456
  ("GNOME input source", ibus.gnome_source_configured, "voiceio setup"),
449
457
  ("Env persisted (reboot-safe)", ibus.env_persisted, "voiceio setup"),
450
458
  ]:
459
+ if label == "Engine answers" and not ok and not ibus.voiceio_running:
460
+ lines.append(f" {DIM}–{RESET} {label} "
461
+ f"{DIM}starts with voiceio, which isn't running{RESET}")
462
+ continue
451
463
  lines.append(_check_line(label, ok, hint if not ok else "",
452
464
  warn=not ok))
453
465
 
@@ -15,6 +15,22 @@ log = logging.getLogger(__name__)
15
15
  DESKTOP_EXTRA_INSTALL = "pipx install --force 'python-voiceio[desktop]'"
16
16
 
17
17
 
18
+ class LazySounddevice:
19
+ """`sounddevice`, imported on first use. Its import initializes PortAudio,
20
+ which raises when libpulse is installed but no sound server runs (CI,
21
+ headless boxes): imported eagerly, that broke `import voiceio.app` and
22
+ every command behind it."""
23
+
24
+ def __getattr__(self, name: str):
25
+ if name.startswith("_"): # introspection (mock, inspect) must not import it
26
+ raise AttributeError(name)
27
+ try:
28
+ import sounddevice
29
+ except ImportError as e:
30
+ raise ImportError(desktop_dep_hint("sounddevice")) from e
31
+ return getattr(sounddevice, name)
32
+
33
+
18
34
  def desktop_dep_hint(package: str) -> str:
19
35
  """One-line message for a desktop dependency that failed to import."""
20
36
  return (
@@ -19,8 +19,9 @@ _NO_CASE_LANGUAGES = frozenset({"zh", "ja", "ko", "ar", "he", "th", "hi", "bn",
19
19
  # own line doesn't swallow the paragraph/list break around it.
20
20
  # Word repetitions ("had had" is valid English) and filler "like"/"you know"
21
21
  # need judgment and are left to the LLM layer. Order matters: multi-token
22
- # "uh-huh" before "[Uu]h+" so it isn't clipped to a stray "-huh".
23
- _FILLER = r"\b(?:[Uu]h[-\s]?huh|[Mm]hm|[Uu]h+m*|[Uu]m+|[Hh]m{2,})\b"
22
+ # "uh-huh" and "mm-hmm" (Parakeet invents "Mm-hmm." on a clip's quiet tail)
23
+ # before the bare forms. Never a piece of a hyphenated word: that left "Mm-.".
24
+ _FILLER = r"(?<!-)\b(?:[Uu]h[-\s]?huh|[Mm]m-?[Hh]m+|[Mm]hm|[Uu]h+m*|[Uu]m+|[Hh]m{2,})\b(?!-)"
24
25
  # A run of fillers and the comma/space around it; the lowercase letter after it, when
25
26
  # it opened a sentence, gets capitalized ("those. Um so I" -> "those. So I"),
26
27
  # or the sentence end before it would read as a false stop and become a comma.
@@ -8,19 +8,15 @@ from typing import TYPE_CHECKING, Callable
8
8
 
9
9
  import numpy as np
10
10
 
11
- try:
12
- import sounddevice as sd
13
- except ImportError as _e:
14
- from voiceio.platform import desktop_dep_hint
15
- raise ImportError(desktop_dep_hint("sounddevice")) from _e
16
-
17
11
  from voiceio.audio_source import AudioBuffer
12
+ from voiceio.platform import LazySounddevice
18
13
 
19
14
  if TYPE_CHECKING:
20
15
  from voiceio.config import AudioConfig
21
16
  from voiceio.vad import VadBackend
22
17
 
23
18
  log = logging.getLogger(__name__)
19
+ sd = LazySounddevice()
24
20
 
25
21
  # Peak amplitude below this is treated as no signal at all (a muted mic emits
26
22
  # exact zeros; a working mic's "silence" still noises above ~1e-4). Matches the
@@ -30,6 +30,8 @@ TIMEOUT_SECS = 8.0
30
30
  MAX_COMMENT = 300
31
31
  MAX_DETAIL = 90
32
32
 
33
+ # A terminal escape: CSI sequences (arrow keys) or ESC plus one key (Alt+key).
34
+ _ESCAPE = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|.)?", re.S)
33
35
  _IPV4 = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
34
36
  _IPV6 = re.compile(r"\b(?:[0-9a-f]{1,4}:){3,7}[0-9a-f]{1,4}\b", re.I)
35
37
  _MAC = re.compile(r"\b(?:[0-9a-f]{2}[:-]){5}[0-9a-f]{2}\b", re.I)
@@ -155,7 +157,7 @@ def build_report(
155
157
  if health.ibus_checks is not None:
156
158
  ib = health.ibus_checks
157
159
  for label, ok in (("component", ib.component_installed), ("daemon", ib.daemon_running),
158
- ("engine ping", ib.socket_reachable),
160
+ ("engine ping", ib.socket_reachable or not ib.voiceio_running),
159
161
  ("env persisted", ib.env_persisted)):
160
162
  if not ok:
161
163
  warned.append(f"ibus {label}")
@@ -178,7 +180,9 @@ def build_report(
178
180
  f"python {_py_platform.python_version()}",
179
181
  f"backends: hotkey {hotkey.name if hotkey else 'none'} · "
180
182
  f"typer {typer.name if typer else 'none'} · audio {audio} "
181
- f"(vad {cfg.audio.vad_backend})",
183
+ f"(vad {cfg.audio.vad_backend})"
184
+ + (" · voiceio not running" if health.ibus_checks is not None
185
+ and not health.ibus_checks.voiceio_running else ""),
182
186
  f"model: {_model_label(cfg.model.name)}",
183
187
  ]
184
188
  if unavailable:
@@ -186,6 +190,7 @@ def build_report(
186
190
  lines.append("failed: " + "; ".join(failed) if failed else "failed: none")
187
191
  if warned:
188
192
  lines.append("warnings: " + "; ".join(warned))
193
+ comment = "".join(c if c.isprintable() else " " for c in _ESCAPE.sub("", comment))
189
194
  comment = " ".join(comment.split())[:MAX_COMMENT]
190
195
  if comment:
191
196
  lines.append(f"comment: {comment}")
@@ -233,6 +238,10 @@ def _interactive() -> bool:
233
238
 
234
239
 
235
240
  def _ask(prompt: str) -> str:
241
+ try:
242
+ import readline # noqa: F401 line editing, so arrow keys don't type escapes
243
+ except ImportError:
244
+ pass
236
245
  try:
237
246
  return input(prompt)
238
247
  except (EOFError, KeyboardInterrupt):
@@ -184,7 +184,8 @@ def _print_tray_check() -> bool:
184
184
  from voiceio.tray import probe_availability
185
185
  tray_ok, tray_reason, tray_fix_hint = probe_availability()
186
186
  if tray_ok:
187
- _print_check("Tray icon", True, "system tray indicator available")
187
+ _print_check("Tray icon", True, f"{tray_reason}; {tray_fix_hint}" if tray_fix_hint
188
+ else "system tray indicator available")
188
189
  else:
189
190
  _print_check("Tray icon", False,
190
191
  tray_fix_hint if tray_fix_hint else tray_reason,
@@ -59,11 +59,16 @@ def probe_availability() -> tuple[bool, str, str]:
59
59
  if py:
60
60
  return True, "AppIndicator3 available", ""
61
61
  from voiceio.platform import pkg_install
62
+ hint = f"install: {pkg_install('python3-gi', 'gir1.2-ayatanaappindicator3-0.1')}"
63
+ if _pystray_backend() == "xorg":
64
+ # What start() falls back to: an XEmbed icon (KDE and LXQt show
65
+ # it), animated, but without the menu.
66
+ return True, "basic icon (pystray, no menu)", f"for the full menu, {hint}"
62
67
  return (
63
68
  False,
64
69
  "no python3 with GTK3/AppIndicator3 bindings "
65
70
  "(set VOICEIO_SYSTEM_PYTHON to override)",
66
- f"install: {pkg_install('python3-gi', 'gir1.2-ayatanaappindicator3-0.1')}",
71
+ hint,
67
72
  )
68
73
  elif sys.platform == "darwin":
69
74
  try:
@@ -82,6 +87,15 @@ def probe_availability() -> tuple[bool, str, str]:
82
87
  return False, "unsupported platform", ""
83
88
 
84
89
 
90
+ def _pystray_backend() -> str | None:
91
+ """The backend pystray picks here ("xorg", "appindicator", ...), or None."""
92
+ try:
93
+ import pystray
94
+ except Exception: # ImportError, or Xlib errors when there's no X display
95
+ return None
96
+ return pystray.Icon.__module__.rsplit("._", 1)[-1]
97
+
98
+
85
99
  def _start_indicator(
86
100
  theme_dir: Path,
87
101
  icon_names: list[str],
@@ -35,6 +35,9 @@ def start(
35
35
  except ImportError:
36
36
  log.warning("pystray/Pillow not installed, tray icon disabled")
37
37
  return False
38
+ except Exception as e: # Linux import opens an X display: none means Xlib errors
39
+ log.warning("pystray unusable (%s), tray icon disabled", e)
40
+ return False
38
41
 
39
42
  _idle_img = Image.open(idle_path)
40
43
  _recording_frames = [Image.open(p) for p in frame_paths]
@@ -1 +0,0 @@
1
- __version__ = "1.15.0"
File without changes