python-voiceio 0.3.9__tar.gz → 0.3.11__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.9/python_voiceio.egg-info → python_voiceio-0.3.11}/PKG-INFO +16 -10
  2. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/README.md +14 -2
  3. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/pyproject.toml +3 -6
  4. {python_voiceio-0.3.9 → python_voiceio-0.3.11/python_voiceio.egg-info}/PKG-INFO +16 -10
  5. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/requires.txt +0 -10
  6. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_commands.py +21 -9
  7. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_ibus_typer.py +2 -1
  8. python_voiceio-0.3.11/voiceio/__init__.py +1 -0
  9. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/app.py +61 -5
  10. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/commands.py +5 -2
  11. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/config.py +1 -0
  12. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/platform.py +4 -0
  13. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/clipboard.py +1 -1
  14. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/ibus.py +24 -7
  15. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/wizard.py +15 -12
  16. python_voiceio-0.3.9/voiceio/__init__.py +0 -1
  17. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/LICENSE +0 -0
  18. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/SOURCES.txt +0 -0
  19. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/dependency_links.txt +0 -0
  20. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/entry_points.txt +0 -0
  21. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/top_level.txt +0 -0
  22. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/setup.cfg +0 -0
  23. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_app_wiring.py +0 -0
  24. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_autocorrect.py +0 -0
  25. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_backend_probes.py +0 -0
  26. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_clipboard_read.py +0 -0
  27. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_config.py +0 -0
  28. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_corrections.py +0 -0
  29. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_fallback.py +0 -0
  30. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_health.py +0 -0
  31. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_hints.py +0 -0
  32. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_history.py +0 -0
  33. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_llm.py +0 -0
  34. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_llm_api.py +0 -0
  35. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_numbers.py +0 -0
  36. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_platform.py +0 -0
  37. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_postprocess.py +0 -0
  38. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_prebuffer.py +0 -0
  39. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_prompt.py +0 -0
  40. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_recorder_integration.py +0 -0
  41. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_robustness.py +0 -0
  42. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_streaming.py +0 -0
  43. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_transcriber.py +0 -0
  44. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_tts.py +0 -0
  45. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_vad.py +0 -0
  46. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_vocabulary.py +0 -0
  47. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_wordfreq.py +0 -0
  48. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/__main__.py +0 -0
  49. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/autocorrect.py +0 -0
  50. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/backends.py +0 -0
  51. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/cli.py +0 -0
  52. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/clipboard_read.py +0 -0
  53. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/corrections.py +0 -0
  54. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/demo.py +0 -0
  55. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/feedback.py +0 -0
  56. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/health.py +0 -0
  57. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hints.py +0 -0
  58. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/history.py +0 -0
  59. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/__init__.py +0 -0
  60. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/base.py +0 -0
  61. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/chain.py +0 -0
  62. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/evdev.py +0 -0
  63. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/pynput_backend.py +0 -0
  64. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/socket_backend.py +0 -0
  65. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/ibus/__init__.py +0 -0
  66. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/ibus/engine.py +0 -0
  67. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/llm.py +0 -0
  68. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/llm_api.py +0 -0
  69. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/models/__init__.py +0 -0
  70. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/models/silero_vad.onnx +0 -0
  71. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/numbers.py +0 -0
  72. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/pidlock.py +0 -0
  73. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/postprocess.py +0 -0
  74. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/prompt.py +0 -0
  75. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/recorder.py +0 -0
  76. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/service.py +0 -0
  77. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/__init__.py +0 -0
  78. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/commit.wav +0 -0
  79. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/start.wav +0 -0
  80. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/stop.wav +0 -0
  81. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/streaming.py +0 -0
  82. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/transcriber.py +0 -0
  83. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/__init__.py +0 -0
  84. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/_icons.py +0 -0
  85. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/_indicator.py +0 -0
  86. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/_pystray.py +0 -0
  87. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/__init__.py +0 -0
  88. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/base.py +0 -0
  89. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/chain.py +0 -0
  90. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/edge_engine.py +0 -0
  91. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/espeak.py +0 -0
  92. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/piper_engine.py +0 -0
  93. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/player.py +0 -0
  94. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/__init__.py +0 -0
  95. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/base.py +0 -0
  96. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/chain.py +0 -0
  97. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/pynput_type.py +0 -0
  98. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/wtype.py +0 -0
  99. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/xdotool.py +0 -0
  100. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/ydotool.py +0 -0
  101. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/vad.py +0 -0
  102. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/vocabulary.py +0 -0
  103. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/wordfreq.py +0 -0
  104. {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.3.9
3
+ Version: 0.3.11
4
4
  Summary: Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -26,16 +26,10 @@ Requires-Dist: numpy>=1.24.0
26
26
  Requires-Dist: onnxruntime>=1.16.0
27
27
  Requires-Dist: wordfreq>=3.0
28
28
  Requires-Dist: evdev>=1.6.0; sys_platform == "linux"
29
- Requires-Dist: pynput>=1.7.6; sys_platform == "win32"
30
- Requires-Dist: pynput>=1.7.6; sys_platform == "darwin"
29
+ Requires-Dist: pynput>=1.7.6
31
30
  Requires-Dist: pyperclip>=1.8.0; sys_platform == "win32"
32
31
  Requires-Dist: win11toast>=0.36; sys_platform == "win32"
33
- Provides-Extra: x11
34
- Requires-Dist: pynput>=1.7.6; extra == "x11"
35
- Provides-Extra: mac
36
- Requires-Dist: pynput>=1.7.6; extra == "mac"
37
32
  Provides-Extra: win
38
- Requires-Dist: pynput>=1.7.6; extra == "win"
39
33
  Requires-Dist: pyperclip>=1.8.0; extra == "win"
40
34
  Requires-Dist: win11toast>=0.36; extra == "win"
41
35
  Provides-Extra: tray
@@ -52,6 +46,15 @@ Dynamic: license-file
52
46
 
53
47
  # voiceio
54
48
 
49
+ ```
50
+ ██╗ ██╗ ██████╗ ██╗ ██████╗███████╗██╗ ██████╗
51
+ ██║ ██║██╔═══██╗██║██╔════╝██╔════╝██║██╔═══██╗
52
+ ██║ ██║██║ ██║██║██║ █████╗ ██║██║ ██║
53
+ ╚██╗ ██╔╝██║ ██║██║██║ ██╔══╝ ██║██║ ██║
54
+ ╚████╔╝ ╚██████╔╝██║╚██████╗███████╗██║╚██████╔╝
55
+ ╚═══╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═════╝
56
+ ```
57
+
55
58
  [![CI](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml/badge.svg)](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml)
56
59
  [![PyPI](https://img.shields.io/pypi/v/python-voiceio)](https://pypi.org/project/python-voiceio/)
57
60
  [![Python](https://img.shields.io/pypi/pyversions/python-voiceio)](https://pypi.org/project/python-voiceio/)
@@ -60,11 +63,13 @@ Dynamic: license-file
60
63
 
61
64
  Speak → text, locally, instantly.
62
65
 
66
+ https://github.com/user-attachments/assets/9cf5d1ac-b4bb-4cf8-b775-7a66dc16b376
67
+
63
68
  ## Quick start
64
69
 
65
70
  ```bash
66
71
  # 1. Install system dependencies (Ubuntu/Debian)
67
- sudo apt install pipx ibus gir1.2-ibus-1.0 python3-gi portaudio19-dev
72
+ sudo apt install pipx ibus gir1.2-ibus-1.0 python3-gi python3-dev portaudio19-dev
68
73
 
69
74
  # 2. Install voiceio
70
75
  pipx install python-voiceio
@@ -79,7 +84,7 @@ That's it. Press **Ctrl+Alt+V** (or your chosen hotkey) to start dictating.
79
84
  <summary><strong>Fedora</strong></summary>
80
85
 
81
86
  ```bash
82
- sudo dnf install pipx ibus python3-gobject portaudio-devel
87
+ sudo dnf install pipx ibus python3-gobject python3-devel portaudio-devel
83
88
  pipx install python-voiceio
84
89
  voiceio setup
85
90
  ```
@@ -90,6 +95,7 @@ voiceio setup
90
95
 
91
96
  ```bash
92
97
  sudo pacman -S python-pipx ibus python-gobject portaudio
98
+ # Note: Arch includes Python headers by default with the python package
93
99
  pipx install python-voiceio
94
100
  voiceio setup
95
101
  ```
@@ -1,5 +1,14 @@
1
1
  # voiceio
2
2
 
3
+ ```
4
+ ██╗ ██╗ ██████╗ ██╗ ██████╗███████╗██╗ ██████╗
5
+ ██║ ██║██╔═══██╗██║██╔════╝██╔════╝██║██╔═══██╗
6
+ ██║ ██║██║ ██║██║██║ █████╗ ██║██║ ██║
7
+ ╚██╗ ██╔╝██║ ██║██║██║ ██╔══╝ ██║██║ ██║
8
+ ╚████╔╝ ╚██████╔╝██║╚██████╗███████╗██║╚██████╔╝
9
+ ╚═══╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═════╝
10
+ ```
11
+
3
12
  [![CI](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml/badge.svg)](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml)
4
13
  [![PyPI](https://img.shields.io/pypi/v/python-voiceio)](https://pypi.org/project/python-voiceio/)
5
14
  [![Python](https://img.shields.io/pypi/pyversions/python-voiceio)](https://pypi.org/project/python-voiceio/)
@@ -8,11 +17,13 @@
8
17
 
9
18
  Speak → text, locally, instantly.
10
19
 
20
+ https://github.com/user-attachments/assets/9cf5d1ac-b4bb-4cf8-b775-7a66dc16b376
21
+
11
22
  ## Quick start
12
23
 
13
24
  ```bash
14
25
  # 1. Install system dependencies (Ubuntu/Debian)
15
- sudo apt install pipx ibus gir1.2-ibus-1.0 python3-gi portaudio19-dev
26
+ sudo apt install pipx ibus gir1.2-ibus-1.0 python3-gi python3-dev portaudio19-dev
16
27
 
17
28
  # 2. Install voiceio
18
29
  pipx install python-voiceio
@@ -27,7 +38,7 @@ That's it. Press **Ctrl+Alt+V** (or your chosen hotkey) to start dictating.
27
38
  <summary><strong>Fedora</strong></summary>
28
39
 
29
40
  ```bash
30
- sudo dnf install pipx ibus python3-gobject portaudio-devel
41
+ sudo dnf install pipx ibus python3-gobject python3-devel portaudio-devel
31
42
  pipx install python-voiceio
32
43
  voiceio setup
33
44
  ```
@@ -38,6 +49,7 @@ voiceio setup
38
49
 
39
50
  ```bash
40
51
  sudo pacman -S python-pipx ibus python-gobject portaudio
52
+ # Note: Arch includes Python headers by default with the python package
41
53
  pipx install python-voiceio
42
54
  voiceio setup
43
55
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-voiceio"
7
- version = "0.3.9"
7
+ version = "0.3.11"
8
8
  description = "Speak → text, locally, instantly."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -28,16 +28,13 @@ dependencies = [
28
28
  "onnxruntime>=1.16.0",
29
29
  "wordfreq>=3.0",
30
30
  "evdev>=1.6.0; sys_platform == 'linux'",
31
- "pynput>=1.7.6; sys_platform == 'win32'",
32
- "pynput>=1.7.6; sys_platform == 'darwin'",
31
+ "pynput>=1.7.6",
33
32
  "pyperclip>=1.8.0; sys_platform == 'win32'",
34
33
  "win11toast>=0.36; sys_platform == 'win32'",
35
34
  ]
36
35
 
37
36
  [project.optional-dependencies]
38
- x11 = ["pynput>=1.7.6"]
39
- mac = ["pynput>=1.7.6"]
40
- win = ["pynput>=1.7.6", "pyperclip>=1.8.0", "win11toast>=0.36"]
37
+ win = ["pyperclip>=1.8.0", "win11toast>=0.36"]
41
38
  tray = ["pystray>=0.19", "Pillow>=10.0"]
42
39
  tts = ["piper-tts>=1.2.0"]
43
40
  tts-cloud = ["edge-tts>=6.1.0"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-voiceio
3
- Version: 0.3.9
3
+ Version: 0.3.11
4
4
  Summary: Speak → text, locally, instantly.
5
5
  Author: Hugo Montenegro
6
6
  License-Expression: MIT
@@ -26,16 +26,10 @@ Requires-Dist: numpy>=1.24.0
26
26
  Requires-Dist: onnxruntime>=1.16.0
27
27
  Requires-Dist: wordfreq>=3.0
28
28
  Requires-Dist: evdev>=1.6.0; sys_platform == "linux"
29
- Requires-Dist: pynput>=1.7.6; sys_platform == "win32"
30
- Requires-Dist: pynput>=1.7.6; sys_platform == "darwin"
29
+ Requires-Dist: pynput>=1.7.6
31
30
  Requires-Dist: pyperclip>=1.8.0; sys_platform == "win32"
32
31
  Requires-Dist: win11toast>=0.36; sys_platform == "win32"
33
- Provides-Extra: x11
34
- Requires-Dist: pynput>=1.7.6; extra == "x11"
35
- Provides-Extra: mac
36
- Requires-Dist: pynput>=1.7.6; extra == "mac"
37
32
  Provides-Extra: win
38
- Requires-Dist: pynput>=1.7.6; extra == "win"
39
33
  Requires-Dist: pyperclip>=1.8.0; extra == "win"
40
34
  Requires-Dist: win11toast>=0.36; extra == "win"
41
35
  Provides-Extra: tray
@@ -52,6 +46,15 @@ Dynamic: license-file
52
46
 
53
47
  # voiceio
54
48
 
49
+ ```
50
+ ██╗ ██╗ ██████╗ ██╗ ██████╗███████╗██╗ ██████╗
51
+ ██║ ██║██╔═══██╗██║██╔════╝██╔════╝██║██╔═══██╗
52
+ ██║ ██║██║ ██║██║██║ █████╗ ██║██║ ██║
53
+ ╚██╗ ██╔╝██║ ██║██║██║ ██╔══╝ ██║██║ ██║
54
+ ╚████╔╝ ╚██████╔╝██║╚██████╗███████╗██║╚██████╔╝
55
+ ╚═══╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═════╝
56
+ ```
57
+
55
58
  [![CI](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml/badge.svg)](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml)
56
59
  [![PyPI](https://img.shields.io/pypi/v/python-voiceio)](https://pypi.org/project/python-voiceio/)
57
60
  [![Python](https://img.shields.io/pypi/pyversions/python-voiceio)](https://pypi.org/project/python-voiceio/)
@@ -60,11 +63,13 @@ Dynamic: license-file
60
63
 
61
64
  Speak → text, locally, instantly.
62
65
 
66
+ https://github.com/user-attachments/assets/9cf5d1ac-b4bb-4cf8-b775-7a66dc16b376
67
+
63
68
  ## Quick start
64
69
 
65
70
  ```bash
66
71
  # 1. Install system dependencies (Ubuntu/Debian)
67
- sudo apt install pipx ibus gir1.2-ibus-1.0 python3-gi portaudio19-dev
72
+ sudo apt install pipx ibus gir1.2-ibus-1.0 python3-gi python3-dev portaudio19-dev
68
73
 
69
74
  # 2. Install voiceio
70
75
  pipx install python-voiceio
@@ -79,7 +84,7 @@ That's it. Press **Ctrl+Alt+V** (or your chosen hotkey) to start dictating.
79
84
  <summary><strong>Fedora</strong></summary>
80
85
 
81
86
  ```bash
82
- sudo dnf install pipx ibus python3-gobject portaudio-devel
87
+ sudo dnf install pipx ibus python3-gobject python3-devel portaudio-devel
83
88
  pipx install python-voiceio
84
89
  voiceio setup
85
90
  ```
@@ -90,6 +95,7 @@ voiceio setup
90
95
 
91
96
  ```bash
92
97
  sudo pacman -S python-pipx ibus python-gobject portaudio
98
+ # Note: Arch includes Python headers by default with the python package
93
99
  pipx install python-voiceio
94
100
  voiceio setup
95
101
  ```
@@ -3,15 +3,12 @@ sounddevice>=0.4.6
3
3
  numpy>=1.24.0
4
4
  onnxruntime>=1.16.0
5
5
  wordfreq>=3.0
6
-
7
- [:sys_platform == "darwin"]
8
6
  pynput>=1.7.6
9
7
 
10
8
  [:sys_platform == "linux"]
11
9
  evdev>=1.6.0
12
10
 
13
11
  [:sys_platform == "win32"]
14
- pynput>=1.7.6
15
12
  pyperclip>=1.8.0
16
13
  win11toast>=0.36
17
14
 
@@ -19,9 +16,6 @@ win11toast>=0.36
19
16
  pytest>=7.0
20
17
  pytest-mock
21
18
 
22
- [mac]
23
- pynput>=1.7.6
24
-
25
19
  [tray]
26
20
  pystray>=0.19
27
21
  Pillow>=10.0
@@ -33,9 +27,5 @@ piper-tts>=1.2.0
33
27
  edge-tts>=6.1.0
34
28
 
35
29
  [win]
36
- pynput>=1.7.6
37
30
  pyperclip>=1.8.0
38
31
  win11toast>=0.36
39
-
40
- [x11]
41
- pynput>=1.7.6
@@ -59,30 +59,36 @@ class TestFormatting:
59
59
 
60
60
  class TestUndo:
61
61
  def test_scratch_that(self):
62
- cp = CommandProcessor()
62
+ cp = CommandProcessor(editing=True)
63
63
  result = cp.process("hello world scratch that")
64
64
  assert result == "hello world"
65
65
  assert cp.undo_requested is True
66
66
 
67
67
  def test_undo_that(self):
68
- cp = CommandProcessor()
68
+ cp = CommandProcessor(editing=True)
69
69
  result = cp.process("hello world undo that")
70
70
  assert result == "hello world"
71
71
  assert cp.undo_requested is True
72
72
 
73
73
  def test_scratch_that_only(self):
74
- cp = CommandProcessor()
74
+ cp = CommandProcessor(editing=True)
75
75
  result = cp.process("scratch that")
76
76
  assert result == ""
77
77
  assert cp.undo_requested is True
78
78
 
79
79
  def test_undo_resets_between_calls(self):
80
- cp = CommandProcessor()
80
+ cp = CommandProcessor(editing=True)
81
81
  cp.process("scratch that")
82
82
  assert cp.undo_requested is True
83
83
  cp.process("hello world")
84
84
  assert cp.undo_requested is False
85
85
 
86
+ def test_editing_off_ignores_scratch(self):
87
+ cp = CommandProcessor(editing=False)
88
+ result = cp.process("hello world scratch that")
89
+ assert result == "hello world scratch that"
90
+ assert cp.undo_requested is False
91
+
86
92
 
87
93
  class TestEdgeCases:
88
94
  def test_no_commands(self):
@@ -127,27 +133,27 @@ class TestEdgeCases:
127
133
 
128
134
  class TestCorrectThat:
129
135
  def test_correct_that_sets_flag(self):
130
- cp = CommandProcessor()
136
+ cp = CommandProcessor(editing=True)
131
137
  result = cp.process("hello world correct that")
132
138
  assert cp.flag_requested is True
133
139
  assert cp.flagged_word == "world"
134
140
  assert result == "hello"
135
141
 
136
142
  def test_correct_that_no_preceding_word(self):
137
- cp = CommandProcessor()
143
+ cp = CommandProcessor(editing=True)
138
144
  result = cp.process("correct that")
139
145
  assert cp.flag_requested is True
140
146
  assert cp.flagged_word == ""
141
147
  assert result == ""
142
148
 
143
149
  def test_correct_that_returns_text_before(self):
144
- cp = CommandProcessor()
150
+ cp = CommandProcessor(editing=True)
145
151
  result = cp.process("one two three correct that")
146
152
  assert result == "one two"
147
153
  assert cp.flagged_word == "three"
148
154
 
149
155
  def test_flag_resets_between_calls(self):
150
- cp = CommandProcessor()
156
+ cp = CommandProcessor(editing=True)
151
157
  cp.process("hello correct that")
152
158
  assert cp.flag_requested is True
153
159
  cp.process("hello world")
@@ -155,11 +161,17 @@ class TestCorrectThat:
155
161
  assert cp.flagged_word == ""
156
162
 
157
163
  def test_undo_not_set_on_flag(self):
158
- cp = CommandProcessor()
164
+ cp = CommandProcessor(editing=True)
159
165
  cp.process("hello correct that")
160
166
  assert cp.flag_requested is True
161
167
  assert cp.undo_requested is False
162
168
 
169
+ def test_editing_off_ignores_correct(self):
170
+ cp = CommandProcessor(editing=False)
171
+ result = cp.process("hello world correct that")
172
+ assert result == "hello world correct that"
173
+ assert cp.flag_requested is False
174
+
163
175
 
164
176
  class TestSpacing:
165
177
  def test_no_double_spaces(self):
@@ -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.11"
@@ -126,6 +126,24 @@ class VoiceIO:
126
126
  self._socket: SocketHotkey | None = None
127
127
  if self._hotkey.name != "socket":
128
128
  self._socket = SocketHotkey()
129
+ elif self.platform.desktop not in ("GNOME", "KDE"):
130
+ # Socket backend has no physical hotkey listener — user must bind
131
+ # `voiceio toggle` to a key in their WM config.
132
+ log.warning(
133
+ "Hotkey backend is 'socket' on desktop '%s'. "
134
+ "Physical hotkey capture is not available. "
135
+ "Bind 'voiceio toggle' to a key in your window manager config "
136
+ "(e.g. i3: bindsym Ctrl+Alt+v exec voiceio toggle). "
137
+ "Or: sudo usermod -aG input $USER && log out/in to enable evdev.",
138
+ self.platform.desktop,
139
+ )
140
+ print(
141
+ f"\n NOTE: No physical hotkey listener available on '{self.platform.desktop}'.\n"
142
+ f" Bind 'voiceio toggle' to a key in your WM config, e.g.:\n"
143
+ f" i3/sway: bindsym Ctrl+Alt+v exec voiceio toggle\n"
144
+ f" hyprland: bind = CTRL ALT, V, exec, voiceio toggle\n"
145
+ f" Or fix evdev: sudo usermod -aG input $USER (then log out/in)\n",
146
+ )
129
147
 
130
148
  print(f"Loading model '{cfg.model.name}'...", end="", flush=True)
131
149
  t0 = time.monotonic()
@@ -146,7 +164,7 @@ class VoiceIO:
146
164
  if vocab:
147
165
  self.transcriber.set_initial_prompt(vocab)
148
166
 
149
- self._command_processor = CommandProcessor(enabled=cfg.commands.enabled)
167
+ self._command_processor = CommandProcessor(enabled=cfg.commands.enabled, editing=cfg.commands.editing)
150
168
  self._cleanup = cfg.output.punctuation_cleanup
151
169
  self._number_conversion = cfg.output.number_conversion
152
170
  self._streaming = cfg.output.streaming
@@ -648,6 +666,26 @@ class VoiceIO:
648
666
  self._set_gnome_input_source_index(0)
649
667
  log.info("VoiceIO IBus engine ready (dormant until recording)")
650
668
 
669
+ def _ping_ibus_engine(self) -> bool:
670
+ """Check if the IBus engine's socket listener is alive.
671
+
672
+ Returns True if the engine responds to a ping within 1 second.
673
+ A False result means the engine process is alive but its socket
674
+ listener / GLib loop is dead (zombie engine).
675
+ """
676
+ import socket as _socket
677
+ from voiceio.ibus import SOCKET_PATH
678
+ try:
679
+ sock = _socket.socket(_socket.AF_UNIX, _socket.SOCK_DGRAM)
680
+ sock.settimeout(1.0)
681
+ sock.bind("")
682
+ sock.sendto(b"ping", str(SOCKET_PATH))
683
+ data, _ = sock.recvfrom(64)
684
+ sock.close()
685
+ return data == b"pong"
686
+ except (OSError, _socket.timeout):
687
+ return False
688
+
651
689
  def _reactivate_ibus_if_stale(self) -> None:
652
690
  """Re-activate IBus engine if it lost registration (e.g. after hibernate).
653
691
 
@@ -933,7 +971,7 @@ class VoiceIO:
933
971
  self.platform = _redetect_platform()
934
972
  self._try_upgrade_typer(reason="probe-failed")
935
973
 
936
- # Check IBus engine (restart if died, re-activate if stale after resume)
974
+ # Check IBus engine (restart if died, zombie, or stale after resume)
937
975
  if self._typer.name == "ibus" and self._engine_proc is not None:
938
976
  if self._engine_proc.poll() is not None:
939
977
  log.warning("IBus engine process died (rc=%d), restarting",
@@ -945,9 +983,27 @@ class VoiceIO:
945
983
  except Exception:
946
984
  log.exception("IBus engine recovery failed")
947
985
  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()
986
+ # Engine process alive check if its socket listener is
987
+ # actually responding. A zombie engine (process alive but
988
+ # GLib loop stuck / socket thread dead) silently drops all
989
+ # preedit and commit messages, causing the "transcription
990
+ # works but no text appears" symptom.
991
+ if not self._ping_ibus_engine():
992
+ log.warning("IBus engine not responding to ping, restarting")
993
+ self._engine_proc.kill()
994
+ try:
995
+ self._engine_proc.wait(timeout=3)
996
+ except subprocess.TimeoutExpired:
997
+ pass
998
+ self._engine_proc = None
999
+ try:
1000
+ self._ensure_ibus_engine()
1001
+ log.info("IBus engine recovered (was zombie)")
1002
+ except Exception:
1003
+ log.exception("IBus engine recovery failed")
1004
+ else:
1005
+ # Engine alive and responding — check IBus registration
1006
+ self._reactivate_ibus_if_stale()
951
1007
 
952
1008
  # ── Main loop ───────────────────────────────────────────────────────
953
1009
 
@@ -83,9 +83,12 @@ def _normalize_spacing(text: str) -> str:
83
83
  class CommandProcessor:
84
84
  """Detects and replaces voice commands in transcribed text."""
85
85
 
86
- def __init__(self, enabled: bool = True):
86
+ def __init__(self, enabled: bool = True, editing: bool = False):
87
87
  self._enabled = enabled
88
- self._commands = DEFAULT_COMMANDS
88
+ self._commands = {
89
+ k: v for k, v in DEFAULT_COMMANDS.items()
90
+ if editing or v not in (_UNDO_SENTINEL, _FLAG_SENTINEL)
91
+ }
89
92
  self._max_words = max(len(k) for k in self._commands) if self._commands else 1
90
93
  self.undo_requested = False
91
94
  self.flag_requested = False
@@ -85,6 +85,7 @@ class DaemonConfig:
85
85
  @dataclass
86
86
  class CommandsConfig:
87
87
  enabled: bool = True
88
+ editing: bool = False
88
89
 
89
90
 
90
91
  @dataclass
@@ -234,6 +234,10 @@ def open_in_terminal(cmd: list[str]) -> bool:
234
234
  _TERMINALS = [
235
235
  (["gnome-terminal", "--"], "gnome-terminal"),
236
236
  (["konsole", "-e"], "konsole"),
237
+ (["alacritty", "-e"], "alacritty"),
238
+ (["kitty", "--"], "kitty"),
239
+ (["foot", "--"], "foot"),
240
+ (["wezterm", "start", "--"], "wezterm"),
237
241
  (["xfce4-terminal", "-e"], "xfce4-terminal"),
238
242
  (["xterm", "-e"], "xterm"),
239
243
  ]
@@ -80,7 +80,7 @@ class ClipboardTyper:
80
80
  return ProbeResult(
81
81
  ok=False,
82
82
  reason="No clipboard tool found",
83
- fix_hint="Install xclip (X11), wl-copy (Wayland), or pbcopy (macOS).",
83
+ fix_hint="Install xclip + xdotool (X11), wl-copy + ydotool/wtype (Wayland), or pbcopy (macOS).",
84
84
  )
85
85
  if sys.platform == "win32":
86
86
  try:
@@ -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).
@@ -945,10 +945,11 @@ def run_wizard() -> None:
945
945
  "will use GPU" if checks["cuda"] else "will use CPU (still fast)",
946
946
  optional=True)
947
947
 
948
- if checks["display"] == "wayland":
948
+ if checks["is_linux"]:
949
949
  _print_check("Input group (evdev)", checks["input_group"],
950
- "" if checks["input_group"] else "optional: sudo usermod -aG input $USER",
951
- optional=True)
950
+ "evdev hotkeys available" if checks["input_group"]
951
+ else "sudo usermod -aG input $USER (then log out/in)",
952
+ optional=checks["input_group"])
952
953
 
953
954
  # Tray icon
954
955
  from voiceio.tray import probe_availability
@@ -1067,13 +1068,12 @@ def run_wizard() -> None:
1067
1068
  else:
1068
1069
  print(f" {YELLOW}⚠{RESET} {DIM}Could not install IBus component, will use fallback{RESET}")
1069
1070
 
1070
- if checks["display"] == "wayland":
1071
- if checks["input_group"]:
1072
- backend = "evdev"
1073
- else:
1074
- backend = "socket"
1071
+ if checks["input_group"]:
1072
+ backend = "evdev"
1073
+ elif checks["pynput"] and checks["display"] != "wayland":
1074
+ backend = "auto" # pynput works on X11
1075
1075
  else:
1076
- backend = "auto"
1076
+ backend = "socket"
1077
1077
 
1078
1078
  # ── Defaults for optional settings ──────────────────────────────────
1079
1079
  sound_enabled = True
@@ -1286,9 +1286,12 @@ def run_wizard() -> None:
1286
1286
  print(f" {YELLOW}⚠{RESET} Auto-setup failed. Add manually in Settings → Keyboard → Shortcuts:")
1287
1287
  print(" Command: voiceio-toggle")
1288
1288
  elif backend == "socket":
1289
- print(f"\n {YELLOW}ℹ{RESET} Add a keyboard shortcut manually in your DE settings:")
1290
- print(f" Shortcut: {BOLD}{hotkey}{RESET}")
1291
- print(f" Command: {BOLD}voiceio-toggle{RESET}")
1289
+ print(f"\n {YELLOW}ℹ{RESET} Add a keyboard shortcut in your window manager config:")
1290
+ print(f" {DIM}i3/sway:{RESET} bindsym Ctrl+Alt+v exec voiceio toggle")
1291
+ print(f" {DIM}hyprland:{RESET} bind = CTRL ALT, V, exec, voiceio toggle")
1292
+ print(f" {DIM}other:{RESET} bind {BOLD}{hotkey}{RESET} → {BOLD}voiceio toggle{RESET}")
1293
+ print(f"\n {DIM}Or enable evdev (no WM config needed):{RESET}")
1294
+ print(f" sudo usermod -aG input $USER {DIM}(then log out and back in){RESET}")
1292
1295
 
1293
1296
  # Autostart
1294
1297
  from voiceio.service import install_service
@@ -1 +0,0 @@
1
- __version__ = "0.3.9"
File without changes