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.
- {python_voiceio-0.3.9/python_voiceio.egg-info → python_voiceio-0.3.11}/PKG-INFO +16 -10
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/README.md +14 -2
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/pyproject.toml +3 -6
- {python_voiceio-0.3.9 → python_voiceio-0.3.11/python_voiceio.egg-info}/PKG-INFO +16 -10
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/requires.txt +0 -10
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_commands.py +21 -9
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_ibus_typer.py +2 -1
- python_voiceio-0.3.11/voiceio/__init__.py +1 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/app.py +61 -5
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/commands.py +5 -2
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/config.py +1 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/platform.py +4 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/clipboard.py +1 -1
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/ibus.py +24 -7
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/wizard.py +15 -12
- python_voiceio-0.3.9/voiceio/__init__.py +0 -1
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/LICENSE +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/SOURCES.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/setup.cfg +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_app_wiring.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_autocorrect.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_backend_probes.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_config.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_corrections.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_fallback.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_health.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_hints.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_history.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_llm.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_llm_api.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_numbers.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_platform.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_postprocess.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_prebuffer.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_prompt.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_robustness.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_streaming.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_transcriber.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_tts.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_vad.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_vocabulary.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/tests/test_wordfreq.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/__main__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/autocorrect.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/backends.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/cli.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/corrections.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/demo.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/feedback.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/health.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hints.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/history.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/llm.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/llm_api.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/models/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/numbers.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/pidlock.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/postprocess.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/prompt.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/recorder.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/service.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/streaming.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/transcriber.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/base.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/chain.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/tts/player.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/base.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/chain.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/vad.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/vocabulary.py +0 -0
- {python_voiceio-0.3.9 → python_voiceio-0.3.11}/voiceio/wordfreq.py +0 -0
- {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.
|
|
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
|
|
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
|
[](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml)
|
|
56
59
|
[](https://pypi.org/project/python-voiceio/)
|
|
57
60
|
[](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
|
[](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml)
|
|
4
13
|
[](https://pypi.org/project/python-voiceio/)
|
|
5
14
|
[](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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
[](https://github.com/Hugo0/voiceio/actions/workflows/ci.yml)
|
|
56
59
|
[](https://pypi.org/project/python-voiceio/)
|
|
57
60
|
[](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,
|
|
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
|
|
949
|
-
#
|
|
950
|
-
|
|
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 =
|
|
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
|
|
@@ -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.
|
|
378
|
+
sock.settimeout(0.5)
|
|
379
379
|
sock.bind("") # autobind for receiving reply
|
|
380
380
|
sock.sendto(b"focus?", str(SOCKET_PATH))
|
|
381
381
|
data, _ = sock.recvfrom(64)
|
|
382
382
|
sock.close()
|
|
383
|
-
|
|
383
|
+
focused = data == b"focused"
|
|
384
|
+
log.debug("IBus engine focus: %s", data.decode(errors="replace"))
|
|
385
|
+
return focused
|
|
384
386
|
except (OSError, socket.timeout):
|
|
385
|
-
|
|
387
|
+
# Engine not responding — assume NOT focused so we fall back
|
|
388
|
+
# to clipboard paste. Worst case: a redundant Ctrl+V that
|
|
389
|
+
# pastes over IBus-committed text. Better than silently losing
|
|
390
|
+
# the text entirely.
|
|
391
|
+
log.warning("IBus engine not responding to focus query, assuming unfocused")
|
|
392
|
+
return False
|
|
386
393
|
|
|
387
394
|
@staticmethod
|
|
388
395
|
def _ydotoold_running() -> bool:
|
|
@@ -401,19 +408,29 @@ class IBusTyper:
|
|
|
401
408
|
try:
|
|
402
409
|
if self._ydotool and self._ydotoold_running():
|
|
403
410
|
# ydotool key scancodes: 29=LCtrl, 47=V
|
|
404
|
-
subprocess.run(
|
|
411
|
+
result = subprocess.run(
|
|
405
412
|
[self._ydotool, "key", "29:1", "47:1", "47:0", "29:0"],
|
|
406
413
|
capture_output=True, timeout=3,
|
|
407
414
|
)
|
|
415
|
+
if result.returncode != 0:
|
|
416
|
+
log.warning("ydotool paste failed (rc=%d): %s",
|
|
417
|
+
result.returncode, result.stderr.decode(errors="replace").strip())
|
|
418
|
+
else:
|
|
419
|
+
log.debug("Pasted via ydotool")
|
|
408
420
|
elif self._wtype:
|
|
409
|
-
subprocess.run(
|
|
421
|
+
result = subprocess.run(
|
|
410
422
|
[self._wtype, "-M", "ctrl", "-k", "v", "-m", "ctrl"],
|
|
411
423
|
capture_output=True, timeout=3,
|
|
412
424
|
)
|
|
425
|
+
if result.returncode != 0:
|
|
426
|
+
log.warning("wtype paste failed (rc=%d): %s",
|
|
427
|
+
result.returncode, result.stderr.decode(errors="replace").strip())
|
|
428
|
+
else:
|
|
429
|
+
log.debug("Pasted via wtype")
|
|
413
430
|
else:
|
|
414
|
-
log.
|
|
431
|
+
log.warning("No paste tool available (ydotoold not running, wtype not found)")
|
|
415
432
|
except (subprocess.TimeoutExpired, OSError) as e:
|
|
416
|
-
log.
|
|
433
|
+
log.warning("Paste simulation failed: %s", e)
|
|
417
434
|
|
|
418
435
|
def _copy_to_clipboard(self, text: str) -> None:
|
|
419
436
|
"""Copy text to clipboard as backup for non-IBus apps (terminals).
|
|
@@ -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["
|
|
948
|
+
if checks["is_linux"]:
|
|
949
949
|
_print_check("Input group (evdev)", checks["input_group"],
|
|
950
|
-
"" if checks["input_group"]
|
|
951
|
-
|
|
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["
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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 = "
|
|
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
|
|
1290
|
-
print(f"
|
|
1291
|
-
print(f"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|