diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 5e0f51b..4bd77c4 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -175,7 +175,7 @@ jobs:
| **Version** | ${{ steps.version.outputs.version }} |
| **Base Version** | ${{ steps.version.outputs.base_version }} |
| **Build Date** | ${{ steps.version.outputs.date }} |
- | **Python** | 3.8+ |
+ | **Python** | 3.9+ |
| **OS** | Linux (X11 & Wayland) |
---
diff --git a/.github/workflows/unified-pipeline.yml b/.github/workflows/unified-pipeline.yml
index dc380c6..93e29cb 100644
--- a/.github/workflows/unified-pipeline.yml
+++ b/.github/workflows/unified-pipeline.yml
@@ -79,7 +79,7 @@ jobs:
- name: Install linting tools
run: |
python -m pip install --upgrade pip
- pip install flake8 black isort
+ pip install flake8 "black>=26,<27" isort
- name: Lint with flake8 (critical errors)
run: |
@@ -102,7 +102,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ['3.8', '3.10', '3.11', '3.13']
+ python-version: ['3.9', '3.10', '3.11', '3.13']
steps:
- uses: actions/checkout@v4
diff --git a/README.md b/README.md
index a4bd642..9e898c8 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
[](https://github.com/jatinkrmalik/vocalinux/actions)
[](https://github.com/jatinkrmalik/vocalinux)
-[](https://www.python.org/downloads/)
+[](https://www.python.org/downloads/)
[](https://www.gtk.org/)
[](https://codecov.io/gh/jatinkrmalik/vocalinux)
@@ -204,7 +204,7 @@ Or launch it from your application menu!
## 📋 Requirements
- **OS**: Linux (tested on Ubuntu 22.04+, Debian 11+, Fedora 39+, Arch Linux, openSUSE Tumbleweed)
-- **Python**: 3.8 or newer
+- **Python**: 3.9 or newer
- **Display**: X11 or Wayland
- **Hardware**: Microphone for voice input
diff --git a/docs/DISTRO_COMPATIBILITY.md b/docs/DISTRO_COMPATIBILITY.md
index 1da5247..17b9076 100644
--- a/docs/DISTRO_COMPATIBILITY.md
+++ b/docs/DISTRO_COMPATIBILITY.md
@@ -112,7 +112,7 @@ sudo zypper install -y python3-gobject python3-gobject-cairo gtk3 \
### Gentoo
```bash
sudo emerge dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libappindicator:3 \
- media-libs/portaudio dev-lang/python:3.8
+ media-libs/portaudio dev-lang/python:3.9
```
**Note:** Gentoo compiles packages from source, which will take longer.
diff --git a/docs/INSTALL.md b/docs/INSTALL.md
index a3a59ee..9009144 100644
--- a/docs/INSTALL.md
+++ b/docs/INSTALL.md
@@ -31,7 +31,7 @@ git clone https://github.com/jatinkrmalik/vocalinux.git && cd vocalinux && ./ins
| Requirement | Details |
|-------------|---------|
| **Operating System** | Ubuntu 22.04+ (recommended), Debian 11+, Fedora 38+, Arch Linux |
-| **Python** | 3.8 or newer |
+| **Python** | 3.9 or newer |
| **Display Server** | X11 or Wayland |
| **Hardware** | Microphone for speech input |
| **Disk Space** | ~200MB (including whisper.cpp model) |
diff --git a/install.sh b/install.sh
index fb802e1..27ca0dd 100755
--- a/install.sh
+++ b/install.sh
@@ -1152,7 +1152,7 @@ install_system_dependencies() {
local PACMAN_PACKAGES="python-pip python-gobject gtk3 libappindicator-gtk3 ibus gobject-introspection python-cairo portaudio python-virtualenv pkg-config wget curl unzip base-devel vulkan-tools vulkan-headers glslang xclip wl-clipboard"
local ZYPPER_PACKAGES="python3-pip python3-gobject python3-gobject-cairo gtk3 libappindicator-gtk3 ibus-devel gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv pkg-config wget curl unzip vulkan-tools vulkan-devel glslang xclip wl-clipboard"
# Gentoo uses Portage and different package naming convention
- local EMERGE_PACKAGES="dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libayatana-appindicator media-libs/portaudio dev-lang/python:3.8 pkgconf dev-util/glslang x11-misc/xclip gui-apps/wl-clipboard"
+ local EMERGE_PACKAGES="dev-python/pygobject:3 x11-libs/gtk+:3 dev-libs/libayatana-appindicator media-libs/portaudio dev-lang/python:3.9 pkgconf dev-util/glslang x11-misc/xclip gui-apps/wl-clipboard"
# Alpine Linux uses apk and has musl libc
local APK_PACKAGES="py3-gobject3 py3-pip gtk+3.0 py3-cairo portaudio-dev py3-virtualenv pkgconf wget curl unzip glslang vulkan-tools xclip wl-clipboard"
# Void Linux uses xbps
@@ -1721,7 +1721,7 @@ mkdir -p "$ICON_DIR"
# Check Python version
check_python_version() {
- local MIN_VERSION="3.8"
+ local MIN_VERSION="3.9"
local PYTHON_CMD="python3"
# Check if python3 command exists
diff --git a/pyproject.toml b/pyproject.toml
index 53b2915..59b3ab7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,7 @@ authors = [
maintainers = [
{name = "Jatin K Malik", email = "jatinkrmalik@gmail.com"}
]
-requires-python = ">=3.8"
+requires-python = ">=3.9"
keywords = [
"voice", "dictation", "speech", "recognition",
"linux", "accessibility", "voice-typing", "speech-to-text"
@@ -26,7 +26,6 @@ classifiers = [
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
@@ -99,7 +98,7 @@ version = {attr = "vocalinux.version.__version__"}
[tool.black]
line-length = 100
-target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313']
+target-version = ['py39', 'py310', 'py311', 'py312', 'py313']
include = '\.pyi?$'
exclude = '''
/(
@@ -157,7 +156,7 @@ exclude_lines = [
show_missing = true
[tool.mypy]
-python_version = "3.8"
+python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true
diff --git a/src/vocalinux/common_types.py b/src/vocalinux/common_types.py
index f9bf502..5f04471 100644
--- a/src/vocalinux/common_types.py
+++ b/src/vocalinux/common_types.py
@@ -4,7 +4,7 @@ This module provides type definitions to avoid circular imports.
"""
from enum import Enum, auto
-from typing import Callable, List, Optional, Protocol # noqa: F401
+from typing import Callable, Optional, Protocol # noqa: F401
class RecognitionState(Enum):
diff --git a/src/vocalinux/speech_recognition/command_processor.py b/src/vocalinux/speech_recognition/command_processor.py
index 1eefc94..0217fb5 100644
--- a/src/vocalinux/speech_recognition/command_processor.py
+++ b/src/vocalinux/speech_recognition/command_processor.py
@@ -7,7 +7,6 @@ This module processes text commands from speech recognition, such as
import logging
import re
-from typing import List, Tuple
logger = logging.getLogger(__name__)
@@ -99,7 +98,7 @@ class CommandProcessor:
)
self.format_cmd_regex = re.compile(format_cmd_pattern, re.IGNORECASE)
- def process_text(self, text: str) -> Tuple[str, List[str]]:
+ def process_text(self, text: str) -> tuple[str, list[str]]:
"""
Process text commands in the recognized text.
diff --git a/src/vocalinux/speech_recognition/recognition_manager.py b/src/vocalinux/speech_recognition/recognition_manager.py
index d6e3153..4721499 100644
--- a/src/vocalinux/speech_recognition/recognition_manager.py
+++ b/src/vocalinux/speech_recognition/recognition_manager.py
@@ -13,7 +13,7 @@ import queue
import sys
import threading
import time
-from typing import Callable, List, Optional
+from typing import Callable, Optional
from ..common_types import RecognitionState
from ..ui.audio_feedback import play_error_sound, play_start_sound, play_stop_sound
@@ -518,9 +518,9 @@ class SpeechRecognitionManager:
self._voice_commands_preference = kwargs.get("voice_commands_enabled")
self._voice_commands_enabled = self._resolve_voice_commands_enabled()
- self.text_callbacks: List[Callable[[str], None]] = []
- self.state_callbacks: List[Callable[[RecognitionState], None]] = []
- self.action_callbacks: List[Callable[[str], None]] = []
+ self.text_callbacks: list[Callable[[str], None]] = []
+ self.state_callbacks: list[Callable[[RecognitionState], None]] = []
+ self.action_callbacks: list[Callable[[str], None]] = []
# Download progress tracking
self._download_progress_callback: Optional[Callable[[float, float, str], None]] = None
@@ -537,7 +537,7 @@ class SpeechRecognitionManager:
# Audio diagnostics tracking
self._last_audio_level = 0.0
- self._audio_level_callbacks: List[Callable[[float], None]] = []
+ self._audio_level_callbacks: list[Callable[[float], None]] = []
# Recording control flags
self.should_record = False
@@ -699,7 +699,7 @@ class SpeechRecognitionManager:
self.state = RecognitionState.ERROR
raise
- def _transcribe_with_whisper(self, audio_buffer: List[bytes]) -> str:
+ def _transcribe_with_whisper(self, audio_buffer: list[bytes]) -> str:
"""
Transcribe audio buffer using Whisper.
@@ -911,7 +911,7 @@ class SpeechRecognitionManager:
self.state = RecognitionState.ERROR
raise
- def _transcribe_with_whispercpp(self, audio_buffer: List[bytes]) -> str:
+ def _transcribe_with_whispercpp(self, audio_buffer: list[bytes]) -> str:
"""
Transcribe audio buffer using whisper.cpp.
@@ -1397,11 +1397,11 @@ class SpeechRecognitionManager:
except ValueError:
logger.warning(f"Callback {callback} not found in text_callbacks.")
- def get_text_callbacks(self) -> List[Callable[[str], None]]:
+ def get_text_callbacks(self) -> list[Callable[[str], None]]:
"""Get a copy of the current text callbacks list."""
return list(self.text_callbacks)
- def set_text_callbacks(self, callbacks: List[Callable[[str], None]]):
+ def set_text_callbacks(self, callbacks: list[Callable[[str], None]]):
"""Set the text callbacks list (used for temporarily replacing callbacks)."""
self.text_callbacks = list(callbacks)
@@ -1829,7 +1829,7 @@ class SpeechRecognitionManager:
self._process_audio_buffer(audio_buffer)
- def _process_audio_buffer(self, audio_buffer: List[bytes]):
+ def _process_audio_buffer(self, audio_buffer: list[bytes]):
"""Process an immutable audio segment for transcription and commands."""
if not audio_buffer:
return
@@ -1959,7 +1959,7 @@ class SpeechRecognitionManager:
self._update_state(RecognitionState.LISTENING)
logger.info("DEBUG: _perform_recognition thread exiting")
- def _enqueue_audio_segment(self, audio_buffer: List[bytes]):
+ def _enqueue_audio_segment(self, audio_buffer: list[bytes]):
"""Queue an audio segment for asynchronous transcription."""
segment = audio_buffer.copy()
if not segment:
diff --git a/src/vocalinux/ui/config_manager.py b/src/vocalinux/ui/config_manager.py
index 1019b30..2900394 100644
--- a/src/vocalinux/ui/config_manager.py
+++ b/src/vocalinux/ui/config_manager.py
@@ -7,7 +7,7 @@ This module handles loading, saving, and accessing user preferences.
import json
import logging
import os
-from typing import Any, Dict
+from typing import Any
logger = logging.getLogger(__name__)
@@ -109,7 +109,7 @@ class ConfigManager:
except Exception as e:
logger.error(f"Failed to load config: {e}")
- def _check_needs_migration(self, user_config: Dict) -> bool:
+ def _check_needs_migration(self, user_config: dict) -> bool:
"""Check if the user config needs migration to add per-engine model sizes."""
sr_config = user_config.get("speech_recognition", {})
# Need migration if we have model_size but not the per-engine keys
@@ -117,7 +117,7 @@ class ConfigManager:
"vosk_model_size" not in sr_config or "whisper_model_size" not in sr_config
)
- def _migrate_config(self, user_config: Dict):
+ def _migrate_config(self, user_config: dict):
"""Migrate old config formats to the current format."""
sr_config = self.config.get("speech_recognition", {})
user_sr_config = user_config.get("speech_recognition", {})
@@ -210,7 +210,7 @@ class ConfigManager:
logger.error(f"Failed to set config value: {e}")
return False
- def get_settings(self) -> Dict[str, Any]:
+ def get_settings(self) -> dict[str, Any]:
"""Get the entire configuration dictionary."""
return self.config
@@ -267,7 +267,7 @@ class ConfigManager:
return enabled
- def update_speech_recognition_settings(self, settings: Dict[str, Any]):
+ def update_speech_recognition_settings(self, settings: dict[str, Any]):
"""Update multiple speech recognition settings at once."""
if "speech_recognition" not in self.config:
self.config["speech_recognition"] = {}
@@ -293,7 +293,7 @@ class ConfigManager:
self.config["sound_effects"] = {}
self.config["sound_effects"]["enabled"] = enabled
- def _update_dict_recursive(self, target: Dict, source: Dict):
+ def _update_dict_recursive(self, target: dict, source: dict):
"""
Update a dictionary recursively.
diff --git a/src/vocalinux/ui/keyboard_backends/evdev_backend.py b/src/vocalinux/ui/keyboard_backends/evdev_backend.py
index 7bdff5c..a96a89b 100644
--- a/src/vocalinux/ui/keyboard_backends/evdev_backend.py
+++ b/src/vocalinux/ui/keyboard_backends/evdev_backend.py
@@ -11,7 +11,7 @@ import os
import select
import threading
import time
-from typing import Dict, List, Optional, Set
+from typing import Optional
# Try to import evdev
try:
@@ -41,7 +41,7 @@ KEY_LEFTMETA = 125 # Super/Windows key
KEY_RIGHTMETA = 126
# Map modifier key names to evdev key codes
-MODIFIER_KEY_CODES: Dict[str, Set[int]] = {
+MODIFIER_KEY_CODES: dict[str, set[int]] = {
"ctrl": {KEY_LEFTCTRL, KEY_RIGHTCTRL},
"alt": {KEY_LEFTALT, KEY_RIGHTALT},
"shift": {KEY_LEFTSHIFT, KEY_RIGHTSHIFT},
@@ -55,7 +55,7 @@ MODIFIER_KEY_CODES: Dict[str, Set[int]] = {
}
-def find_keyboard_devices() -> List[str]:
+def find_keyboard_devices() -> list[str]:
"""
Find all keyboard input devices.
@@ -149,22 +149,22 @@ class EvdevKeyboardBackend(KeyboardBackend):
mode: The shortcut mode ("toggle" or "push_to_talk")
"""
super().__init__(shortcut, mode)
- self.devices: List[InputDevice] = []
- self.device_fds: List[int] = []
+ self.devices: list[InputDevice] = []
+ self.device_fds: list[int] = []
self.running = False
self.monitor_thread: Optional[threading.Thread] = None
self.last_trigger_time = 0
self.last_key_press_time = 0
self.double_tap_threshold = 0.3 # seconds
- self.key_pressed_devices: Set[int] = set()
+ self.key_pressed_devices: set[int] = set()
self._devices_lock = threading.Lock()
if not EVDEV_AVAILABLE:
logger.error("python-evdev not available")
- def _get_target_key_codes(self) -> Set[int]:
+ def _get_target_key_codes(self) -> set[int]:
"""Get the evdev key codes for the configured modifier."""
return MODIFIER_KEY_CODES.get(self._modifier_key, set())
diff --git a/src/vocalinux/ui/logging_manager.py b/src/vocalinux/ui/logging_manager.py
index a2c573f..c774428 100644
--- a/src/vocalinux/ui/logging_manager.py
+++ b/src/vocalinux/ui/logging_manager.py
@@ -9,7 +9,7 @@ import logging
import threading
from datetime import datetime
from pathlib import Path
-from typing import Callable, List, Optional
+from typing import Callable, Optional
logger = logging.getLogger(__name__)
@@ -58,8 +58,8 @@ class LoggingManager:
max_records: Maximum number of log records to keep in memory
"""
self.max_records = max_records
- self.log_records: List[LogRecord] = []
- self.log_callbacks: List[Callable[[LogRecord], None]] = []
+ self.log_records: list[LogRecord] = []
+ self.log_callbacks: list[Callable[[LogRecord], None]] = []
self.lock = threading.Lock()
# Create logs directory
@@ -130,7 +130,7 @@ class LoggingManager:
level_filter: Optional[str] = None,
module_filter: Optional[str] = None,
last_n: Optional[int] = None,
- ) -> List[LogRecord]:
+ ) -> list[LogRecord]:
"""
Get log records with optional filtering.
diff --git a/src/vocalinux/utils/whispercpp_model_info.py b/src/vocalinux/utils/whispercpp_model_info.py
index c823dc2..2138c08 100644
--- a/src/vocalinux/utils/whispercpp_model_info.py
+++ b/src/vocalinux/utils/whispercpp_model_info.py
@@ -8,7 +8,7 @@ for whisper.cpp, supporting Vulkan, CUDA, and CPU backends.
import logging
import os
import subprocess
-from typing import Dict, Optional, Tuple
+from typing import Optional
logger = logging.getLogger(__name__)
@@ -60,7 +60,7 @@ class ComputeBackend:
CPU = "cpu"
-def detect_vulkan_support() -> Tuple[bool, Optional[str]]:
+def detect_vulkan_support() -> tuple[bool, Optional[str]]:
"""
Detect if Vulkan is available and get device info.
@@ -91,7 +91,7 @@ def detect_vulkan_support() -> Tuple[bool, Optional[str]]:
return False, None
-def detect_cuda_support() -> Tuple[bool, Optional[str]]:
+def detect_cuda_support() -> tuple[bool, Optional[str]]:
"""
Detect if NVIDIA CUDA is available and get device info.
@@ -119,7 +119,7 @@ def detect_cuda_support() -> Tuple[bool, Optional[str]]:
return False, None
-def detect_compute_backend() -> Tuple[str, str]:
+def detect_compute_backend() -> tuple[str, str]:
"""
Detect the best available compute backend.
@@ -177,7 +177,7 @@ def detect_cpu_info() -> str:
return "CPU"
-def get_recommended_model() -> Tuple[str, str]:
+def get_recommended_model() -> tuple[str, str]:
"""
Get the recommended whisper.cpp model based on system configuration.
diff --git a/tests/test_audio_feedback.py b/tests/test_audio_feedback.py
index aac3564..3b1359a 100644
--- a/tests/test_audio_feedback.py
+++ b/tests/test_audio_feedback.py
@@ -175,8 +175,9 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback, "_get_audio_player", return_value=(None, [])
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(audio_feedback, "_get_audio_player", return_value=(None, [])),
):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -189,11 +190,15 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback,
- "_get_audio_player",
- return_value=("paplay", ["wav"]),
- ), patch.object(audio_feedback.subprocess, "Popen") as mock_popen:
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(
+ audio_feedback,
+ "_get_audio_player",
+ return_value=("paplay", ["wav"]),
+ ),
+ patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
+ ):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -209,11 +214,15 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback,
- "_get_audio_player",
- return_value=("aplay", ["wav"]),
- ), patch.object(audio_feedback.subprocess, "Popen") as mock_popen:
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(
+ audio_feedback,
+ "_get_audio_player",
+ return_value=("aplay", ["wav"]),
+ ),
+ patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
+ ):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -230,11 +239,15 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback,
- "_get_audio_player",
- return_value=("mplayer", ["wav"]),
- ), patch.object(audio_feedback.subprocess, "Popen") as mock_popen:
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(
+ audio_feedback,
+ "_get_audio_player",
+ return_value=("mplayer", ["wav"]),
+ ),
+ patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
+ ):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -251,9 +264,11 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback, "_get_audio_player", return_value=("play", ["wav"])
- ), patch.object(audio_feedback.subprocess, "Popen") as mock_popen:
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(audio_feedback, "_get_audio_player", return_value=("play", ["wav"])),
+ patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
+ ):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -270,14 +285,18 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback,
- "_get_audio_player",
- return_value=("paplay", ["wav"]),
- ), patch.object(
- audio_feedback.subprocess,
- "Popen",
- side_effect=Exception("Mock error"),
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(
+ audio_feedback,
+ "_get_audio_player",
+ return_value=("paplay", ["wav"]),
+ ),
+ patch.object(
+ audio_feedback.subprocess,
+ "Popen",
+ side_effect=Exception("Mock error"),
+ ),
):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -326,11 +345,15 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.os.path, "exists", return_value=True), patch.object(
- audio_feedback,
- "_get_audio_player",
- return_value=("ci_test_player", ["wav"]),
- ), patch.object(audio_feedback.subprocess, "Popen") as mock_popen:
+ with (
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.object(
+ audio_feedback,
+ "_get_audio_player",
+ return_value=("ci_test_player", ["wav"]),
+ ),
+ patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
+ ):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
@@ -346,9 +369,11 @@ class TestAudioFeedback(unittest.TestCase):
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
- with patch.object(audio_feedback.shutil, "which", return_value=None), patch.object(
- audio_feedback.os.path, "exists", return_value=True
- ), patch.dict("os.environ", {"GITHUB_ACTIONS": "true"}):
+ with (
+ patch.object(audio_feedback.shutil, "which", return_value=None),
+ patch.object(audio_feedback.os.path, "exists", return_value=True),
+ patch.dict("os.environ", {"GITHUB_ACTIONS": "true"}),
+ ):
# First verify _get_audio_player returns None
player, formats = audio_feedback._get_audio_player()
self.assertIsNone(player)
diff --git a/tests/test_autostart_manager.py b/tests/test_autostart_manager.py
index 2f89f30..f045b51 100644
--- a/tests/test_autostart_manager.py
+++ b/tests/test_autostart_manager.py
@@ -21,17 +21,22 @@ class TestAutostartManager(unittest.TestCase):
self.assertEqual(command, "/usr/bin/vocalinux --start-minimized")
def test_get_exec_command_falls_back_to_python_module(self):
- with patch("vocalinux.ui.autostart_manager.shutil.which", return_value=None), patch(
- "vocalinux.ui.autostart_manager.sys.executable", "/usr/bin/python3"
- ), patch("vocalinux.ui.autostart_manager.sys.frozen", new=False, create=True):
+ with (
+ patch("vocalinux.ui.autostart_manager.shutil.which", return_value=None),
+ patch("vocalinux.ui.autostart_manager.sys.executable", "/usr/bin/python3"),
+ patch("vocalinux.ui.autostart_manager.sys.frozen", new=False, create=True),
+ ):
command = autostart_manager.get_exec_command()
self.assertEqual(command, "/usr/bin/python3 -m vocalinux.main --start-minimized")
def test_enable_disable_autostart_creates_and_removes_entry(self):
with tempfile.TemporaryDirectory() as tmp_dir:
- with patch.dict("os.environ", {"XDG_CONFIG_HOME": tmp_dir}, clear=False), patch(
- "vocalinux.ui.autostart_manager.shutil.which",
- return_value="/usr/bin/vocalinux",
+ with (
+ patch.dict("os.environ", {"XDG_CONFIG_HOME": tmp_dir}, clear=False),
+ patch(
+ "vocalinux.ui.autostart_manager.shutil.which",
+ return_value="/usr/bin/vocalinux",
+ ),
):
enabled = autostart_manager.enable_autostart()
self.assertTrue(enabled)
diff --git a/tests/test_common_types_imports.py b/tests/test_common_types_imports.py
index 3781d2a..ba50a56 100644
--- a/tests/test_common_types_imports.py
+++ b/tests/test_common_types_imports.py
@@ -106,7 +106,6 @@ class TestImports:
"""Test that all types can be imported."""
from vocalinux.common_types import (
Callable,
- List,
Optional,
Protocol,
RecognitionState,
@@ -120,7 +119,6 @@ class TestImports:
SpeechRecognitionManagerProtocol,
TextInjectorProtocol,
Callable,
- List,
Optional,
Protocol,
]
diff --git a/tests/test_ibus_engine_core.py b/tests/test_ibus_engine_core.py
index af32d65..fb48106 100644
--- a/tests/test_ibus_engine_core.py
+++ b/tests/test_ibus_engine_core.py
@@ -803,11 +803,14 @@ class TestIBusEngineMainEntrypoint(unittest.TestCase):
"""Test --xml path prints engines XML and exits early."""
from vocalinux.text_injection import ibus_engine
- with patch.object(sys, "argv", ["ibus_engine.py", "--xml"]), patch.object(
- ibus_engine, "_get_engines_xml", return_value=""
- ) as mock_get_xml, patch("builtins.print") as mock_print, patch.object(
- ibus_engine.IBus, "init"
- ) as mock_init:
+ with (
+ patch.object(sys, "argv", ["ibus_engine.py", "--xml"]),
+ patch.object(
+ ibus_engine, "_get_engines_xml", return_value=""
+ ) as mock_get_xml,
+ patch("builtins.print") as mock_print,
+ patch.object(ibus_engine.IBus, "init") as mock_init,
+ ):
result = ibus_engine.main()
self.assertEqual(result, 0)
@@ -820,11 +823,14 @@ class TestIBusEngineMainEntrypoint(unittest.TestCase):
from vocalinux.text_injection import ibus_engine
mock_app = MagicMock()
- with patch.object(sys, "argv", ["ibus_engine.py", "--ibus"]), patch.object(
- ibus_engine, "IBUS_AVAILABLE", True
- ), patch.object(ibus_engine.IBus, "init") as mock_init, patch.object(
- ibus_engine, "VocalinuxEngineApplication", return_value=mock_app
- ) as mock_app_cls:
+ with (
+ patch.object(sys, "argv", ["ibus_engine.py", "--ibus"]),
+ patch.object(ibus_engine, "IBUS_AVAILABLE", True),
+ patch.object(ibus_engine.IBus, "init") as mock_init,
+ patch.object(
+ ibus_engine, "VocalinuxEngineApplication", return_value=mock_app
+ ) as mock_app_cls,
+ ):
result = ibus_engine.main()
self.assertEqual(result, 0)
diff --git a/tests/test_keyboard_shortcuts.py b/tests/test_keyboard_shortcuts.py
index ed644aa..44eab15 100644
--- a/tests/test_keyboard_shortcuts.py
+++ b/tests/test_keyboard_shortcuts.py
@@ -583,9 +583,10 @@ class TestBackendFactory(unittest.TestCase):
"""Test forcing pynput backend."""
from vocalinux.ui.keyboard_backends import create_backend
- with patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True), patch(
- "vocalinux.ui.keyboard_backends.PynputKeyboardBackend"
- ) as MockPynput:
+ with (
+ patch("vocalinux.ui.keyboard_backends.PYNPUT_AVAILABLE", True),
+ patch("vocalinux.ui.keyboard_backends.PynputKeyboardBackend") as MockPynput,
+ ):
mock_backend = MagicMock()
MockPynput.return_value = mock_backend
@@ -598,9 +599,10 @@ class TestBackendFactory(unittest.TestCase):
"""Test forcing evdev backend."""
from vocalinux.ui.keyboard_backends import create_backend
- with patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True), patch(
- "vocalinux.ui.keyboard_backends.EvdevKeyboardBackend"
- ) as MockEvdev:
+ with (
+ patch("vocalinux.ui.keyboard_backends.EVDEV_AVAILABLE", True),
+ patch("vocalinux.ui.keyboard_backends.EvdevKeyboardBackend") as MockEvdev,
+ ):
mock_backend = MagicMock()
MockEvdev.return_value = mock_backend
diff --git a/tests/test_text_injector_ext.py b/tests/test_text_injector_ext.py
index 82bd5b3..cf618ab 100644
--- a/tests/test_text_injector_ext.py
+++ b/tests/test_text_injector_ext.py
@@ -109,8 +109,11 @@ class TestCheckDependencies(unittest.TestCase):
obj = _make_injector(DesktopEnvironment.WAYLAND)
with patch("shutil.which", return_value=None):
- with self.assertRaises(RuntimeError):
- obj._check_dependencies()
+ with patch(
+ "vocalinux.text_injection.text_injector.is_ibus_available", return_value=False
+ ):
+ with self.assertRaises(RuntimeError):
+ obj._check_dependencies()
class TestInjectText(unittest.TestCase):
diff --git a/tests/test_tray_indicator_ext.py b/tests/test_tray_indicator_ext.py
index a30c96d..8f37c27 100644
--- a/tests/test_tray_indicator_ext.py
+++ b/tests/test_tray_indicator_ext.py
@@ -69,13 +69,12 @@ class TestTrayIndicatorInitialization(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
# Set up mocks
mock_speech_engine = MagicMock()
@@ -124,13 +123,12 @@ class TestTrayIndicatorInitialization(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -192,13 +190,12 @@ class TestTrayIndicatorMenuOperations(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -232,13 +229,12 @@ class TestTrayIndicatorMenuOperations(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -294,13 +290,12 @@ class TestTrayIndicatorStateHandling(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -334,13 +329,12 @@ class TestTrayIndicatorStateHandling(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -374,13 +368,12 @@ class TestTrayIndicatorStateHandling(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -436,13 +429,12 @@ class TestTrayIndicatorSignalHandling(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
@@ -480,13 +472,12 @@ class TestTrayIndicatorSignalHandling(unittest.TestCase):
from vocalinux.common_types import RecognitionState
from vocalinux.ui.tray_indicator import TrayIndicator
- with patch("vocalinux.ui.tray_indicator.logging"), patch(
- "vocalinux.ui.tray_indicator._resource_manager"
- ) as mock_resource_manager, patch(
- "vocalinux.ui.tray_indicator.ConfigManager"
- ) as mock_config_manager, patch(
- "vocalinux.ui.tray_indicator.KeyboardShortcutManager"
- ) as mock_keyboard_manager:
+ with (
+ patch("vocalinux.ui.tray_indicator.logging"),
+ patch("vocalinux.ui.tray_indicator._resource_manager") as mock_resource_manager,
+ patch("vocalinux.ui.tray_indicator.ConfigManager") as mock_config_manager,
+ patch("vocalinux.ui.tray_indicator.KeyboardShortcutManager") as mock_keyboard_manager,
+ ):
from vocalinux.ui.tray_indicator import TrayIndicator
diff --git a/tests/test_whisper_support.py b/tests/test_whisper_support.py
index aa0010d..fce3c5b 100644
--- a/tests/test_whisper_support.py
+++ b/tests/test_whisper_support.py
@@ -90,10 +90,14 @@ class TestWhisperSupport:
whisper_mock.load_model.return_value = model_mock
# Patch modules and mock the download method to avoid network/file operations
- with patch.dict(sys.modules, {"whisper": whisper_mock, "torch": torch_mock}), patch(
- "vocalinux.speech_recognition.recognition_manager."
- "SpeechRecognitionManager._download_whisper_model"
- ) as _mock_download, patch("os.path.exists", return_value=True):
+ with (
+ patch.dict(sys.modules, {"whisper": whisper_mock, "torch": torch_mock}),
+ patch(
+ "vocalinux.speech_recognition.recognition_manager."
+ "SpeechRecognitionManager._download_whisper_model"
+ ) as _mock_download,
+ patch("os.path.exists", return_value=True),
+ ):
from vocalinux.speech_recognition.recognition_manager import SpeechRecognitionManager
# Create manager with Whisper engine and defer_download=False to trigger model loading
@@ -132,23 +136,31 @@ class TestWhisperSupport:
):
from vocalinux.speech_recognition import recognition_manager as rm
- with patch("os.makedirs"), patch("os.path.exists", return_value=True), patch(
- "os.path.getsize", return_value=40 * 1024 * 1024
- ), patch("multiprocessing.cpu_count", return_value=4), patch(
- "vocalinux.utils.whispercpp_model_info.detect_compute_backend",
- return_value=("vulkan", "Intel GPU"),
- ), patch(
- "vocalinux.utils.whispercpp_model_info.get_backend_display_name",
- return_value="Vulkan",
- ), patch(
- "vocalinux.speech_recognition.recognition_manager.get_model_path",
- return_value="/tmp/mock-ggml-tiny.bin",
- ), patch(
- "vocalinux.speech_recognition.recognition_manager._show_notification"
- ) as notify_mock, patch.dict(
- os.environ,
- {"GGML_VULKAN": "1", "GGML_CUDA": "1"},
- clear=False,
+ with (
+ patch("os.makedirs"),
+ patch("os.path.exists", return_value=True),
+ patch("os.path.getsize", return_value=40 * 1024 * 1024),
+ patch("multiprocessing.cpu_count", return_value=4),
+ patch(
+ "vocalinux.utils.whispercpp_model_info.detect_compute_backend",
+ return_value=("vulkan", "Intel GPU"),
+ ),
+ patch(
+ "vocalinux.utils.whispercpp_model_info.get_backend_display_name",
+ return_value="Vulkan",
+ ),
+ patch(
+ "vocalinux.speech_recognition.recognition_manager.get_model_path",
+ return_value="/tmp/mock-ggml-tiny.bin",
+ ),
+ patch(
+ "vocalinux.speech_recognition.recognition_manager._show_notification"
+ ) as notify_mock,
+ patch.dict(
+ os.environ,
+ {"GGML_VULKAN": "1", "GGML_CUDA": "1"},
+ clear=False,
+ ),
):
manager = rm.SpeechRecognitionManager(
engine="whisper_cpp", model_size="tiny", defer_download=False
diff --git a/tests/test_whispercpp_model_info_ext.py b/tests/test_whispercpp_model_info_ext.py
index cc000c9..307e8e7 100644
--- a/tests/test_whispercpp_model_info_ext.py
+++ b/tests/test_whispercpp_model_info_ext.py
@@ -261,9 +261,10 @@ class TestDetectCPUInfo(unittest.TestCase):
def test_detect_cpu_info_fallback_to_nproc(self):
"""Test CPU detection falls back to nproc."""
- with patch("builtins.open", create=True, side_effect=Exception("No /proc/cpuinfo")), patch(
- "subprocess.run"
- ) as mock_run:
+ with (
+ patch("builtins.open", create=True, side_effect=Exception("No /proc/cpuinfo")),
+ patch("subprocess.run") as mock_run,
+ ):
mock_run.return_value = MagicMock(returncode=0, stdout="8\n")
@@ -275,8 +276,9 @@ class TestDetectCPUInfo(unittest.TestCase):
def test_detect_cpu_info_default_fallback(self):
"""Test CPU detection returns default when all methods fail."""
- with patch("builtins.open", create=True, side_effect=Exception()), patch(
- "subprocess.run", side_effect=Exception()
+ with (
+ patch("builtins.open", create=True, side_effect=Exception()),
+ patch("subprocess.run", side_effect=Exception()),
):
from vocalinux.utils.whispercpp_model_info import detect_cpu_info
@@ -299,11 +301,10 @@ class TestDetectComputeBackend(unittest.TestCase):
def test_detect_compute_backend_prefers_vulkan(self):
"""Test that detect_compute_backend prefers Vulkan."""
- with patch(
- "vocalinux.utils.whispercpp_model_info.detect_vulkan_support"
- ) as mock_vulkan, patch(
- "vocalinux.utils.whispercpp_model_info.detect_cuda_support"
- ) as mock_cuda:
+ with (
+ patch("vocalinux.utils.whispercpp_model_info.detect_vulkan_support") as mock_vulkan,
+ patch("vocalinux.utils.whispercpp_model_info.detect_cuda_support") as mock_cuda,
+ ):
mock_vulkan.return_value = (True, "Intel Arc GPU")
mock_cuda.return_value = (True, "NVIDIA RTX 3080 (10GB)")
@@ -319,11 +320,10 @@ class TestDetectComputeBackend(unittest.TestCase):
def test_detect_compute_backend_falls_back_to_cuda(self):
"""Test that detect_compute_backend falls back to CUDA."""
- with patch(
- "vocalinux.utils.whispercpp_model_info.detect_vulkan_support"
- ) as mock_vulkan, patch(
- "vocalinux.utils.whispercpp_model_info.detect_cuda_support"
- ) as mock_cuda:
+ with (
+ patch("vocalinux.utils.whispercpp_model_info.detect_vulkan_support") as mock_vulkan,
+ patch("vocalinux.utils.whispercpp_model_info.detect_cuda_support") as mock_cuda,
+ ):
mock_vulkan.return_value = (False, None)
mock_cuda.return_value = (True, "NVIDIA RTX 3080 (10GB)")
@@ -339,13 +339,11 @@ class TestDetectComputeBackend(unittest.TestCase):
def test_detect_compute_backend_falls_back_to_cpu(self):
"""Test that detect_compute_backend falls back to CPU."""
- with patch(
- "vocalinux.utils.whispercpp_model_info.detect_vulkan_support"
- ) as mock_vulkan, patch(
- "vocalinux.utils.whispercpp_model_info.detect_cuda_support"
- ) as mock_cuda, patch(
- "vocalinux.utils.whispercpp_model_info.detect_cpu_info"
- ) as mock_cpu:
+ with (
+ patch("vocalinux.utils.whispercpp_model_info.detect_vulkan_support") as mock_vulkan,
+ patch("vocalinux.utils.whispercpp_model_info.detect_cuda_support") as mock_cuda,
+ patch("vocalinux.utils.whispercpp_model_info.detect_cpu_info") as mock_cpu,
+ ):
mock_vulkan.return_value = (False, None)
mock_cuda.return_value = (False, None)
diff --git a/web/src/app/faq/page.tsx b/web/src/app/faq/page.tsx
index 2b1ef1a..4d32a8d 100644
--- a/web/src/app/faq/page.tsx
+++ b/web/src/app/faq/page.tsx
@@ -20,7 +20,7 @@ const faqCategories = [
},
{
q: "What are the system requirements?",
- a: "You need Python 3.8+, a working microphone, and about 500MB of disk space. For GPU acceleration with whisper.cpp, you need Vulkan support (AMD, Intel, or NVIDIA).",
+ a: "You need Python 3.9+, a working microphone, and about 500MB of disk space. For GPU acceleration with whisper.cpp, you need Vulkan support (AMD, Intel, or NVIDIA).",
},
],
},
diff --git a/web/src/app/install/[distro]/page.tsx b/web/src/app/install/[distro]/page.tsx
index 7a5f5cb..5600bac 100644
--- a/web/src/app/install/[distro]/page.tsx
+++ b/web/src/app/install/[distro]/page.tsx
@@ -37,8 +37,8 @@ const distroConfig: Record = {
intro:
"This Ubuntu guide is tuned for practical desktop use: terminals, browsers, IDEs, office suites, and chat apps. Vocalinux runs locally so your voice data never leaves your machine.",
prerequisites: [
- "Ubuntu 22.04+ (or Ubuntu-based distro)",
- "Python 3.8+",
+ "Ubuntu 22.04+ (or Ubuntu-based distro)",
+ "Python 3.9+",
"Working microphone",
"curl installed (sudo apt update && sudo apt install -y curl)",
],
@@ -68,8 +68,8 @@ const distroConfig: Record = {
intro:
"Fedora is an excellent match for Vocalinux thanks to current Python stacks and up-to-date system libraries. This guide focuses on reliable setup and minimal friction.",
prerequisites: [
- "Fedora Workstation 39+ (or Fedora-based distro)",
- "Python 3.8+",
+ "Fedora Workstation 39+ (or Fedora-based distro)",
+ "Python 3.9+",
"Working microphone",
"curl installed (sudo dnf install -y curl)",
],
@@ -99,8 +99,8 @@ const distroConfig: Record = {
intro:
"Arch users usually care about control and performance. This guide keeps setup lean while preserving compatibility across rolling-release desktop environments.",
prerequisites: [
- "Arch Linux / Manjaro / EndeavourOS",
- "Python 3.8+",
+ "Arch Linux / Manjaro / EndeavourOS",
+ "Python 3.9+",
"Working microphone",
"curl installed (sudo pacman -Sy --needed curl)",
],
diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx
index 16a0b98..66f6b21 100644
--- a/web/src/app/page.tsx
+++ b/web/src/app/page.tsx
@@ -858,7 +858,7 @@ export default function HomePage() {
• Ubuntu 22.04+ (or equivalent)
-
• Python 3.8 or newer
+
• Python 3.9 or newer
• 4GB RAM (8GB for large models)
• Microphone
• X11 or Wayland display server
@@ -1134,7 +1134,7 @@ export default function HomePage() {
question: "What are the system requirements?",
answer: (
<>
- Minimum: 4GB RAM, Python 3.8+, ~200MB disk space. 8GB+ RAM recommended for larger Whisper models. The default whisper.cpp tiny model (~39MB) works great on modest hardware. GPU acceleration is available via Vulkan for AMD, Intel, and NVIDIA GPUs.{" "}
+ Minimum: 4GB RAM, Python 3.9+, ~200MB disk space. 8GB+ RAM recommended for larger Whisper models. The default whisper.cpp tiny model (~39MB) works great on modest hardware. GPU acceleration is available via Vulkan for AMD, Intel, and NVIDIA GPUs.{" "}
View full installation requirements.
>
),
diff --git a/web/src/app/troubleshooting/page.tsx b/web/src/app/troubleshooting/page.tsx
index ec3b6c3..79b9906 100644
--- a/web/src/app/troubleshooting/page.tsx
+++ b/web/src/app/troubleshooting/page.tsx
@@ -11,7 +11,7 @@ const troubleshootingItems = [
solutions: [
"Ensure you ran the installer: curl -fsSL https://raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh | bash",
"Check if the virtual environment is activated: source ~/.local/share/vocalinux/venv/bin/activate",
- "Verify Python version (3.8+ required): python3 --version",
+ "Verify Python version (3.9+ required): python3 --version",
"Try reinstalling: ./uninstall.sh && ./install.sh",
],
},