From fa17b88f74b9a809774e68a407bb8574903819cb Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Sat, 21 Mar 2026 12:15:10 -0700 Subject: [PATCH] chore: bump minimum Python from 3.8 to 3.9 (#336) * chore: bump minimum Python from 3.8 to 3.9 and adopt PEP 585 builtins - Update requires-python, classifiers, mypy target, and black target in pyproject.toml - Replace Python 3.8 with 3.9 in CI matrix and nightly workflow - Replace typing.List/Dict/Tuple/Set with builtin list/dict/tuple/set (PEP 585) - Update all 3.8 version references in README, docs, website, and install.sh - Fix test_wayland_no_tools to mock IBus availability - Remove List from common_types re-exports and update test * style: reformat test files for Black 26.x and pin version in CI Black 26.x introduced parenthesized context manager formatting. Reformat 7 test files and pin Black>=26,<27 in CI to prevent future drift from unpinned version upgrades. --- .github/workflows/nightly.yml | 2 +- .github/workflows/unified-pipeline.yml | 4 +- README.md | 4 +- docs/DISTRO_COMPATIBILITY.md | 2 +- docs/INSTALL.md | 2 +- install.sh | 4 +- pyproject.toml | 7 +- src/vocalinux/common_types.py | 2 +- .../speech_recognition/command_processor.py | 3 +- .../speech_recognition/recognition_manager.py | 22 ++-- src/vocalinux/ui/config_manager.py | 12 +- .../ui/keyboard_backends/evdev_backend.py | 14 +-- src/vocalinux/ui/logging_manager.py | 8 +- src/vocalinux/utils/whispercpp_model_info.py | 10 +- tests/test_audio_feedback.py | 97 +++++++++------ tests/test_autostart_manager.py | 17 ++- tests/test_common_types_imports.py | 2 - tests/test_ibus_engine_core.py | 26 ++-- tests/test_keyboard_shortcuts.py | 14 ++- tests/test_text_injector_ext.py | 7 +- tests/test_tray_indicator_ext.py | 117 ++++++++---------- tests/test_whisper_support.py | 54 ++++---- tests/test_whispercpp_model_info_ext.py | 42 +++---- web/src/app/faq/page.tsx | 2 +- web/src/app/install/[distro]/page.tsx | 12 +- web/src/app/page.tsx | 4 +- web/src/app/troubleshooting/page.tsx | 2 +- 27 files changed, 265 insertions(+), 227 deletions(-) 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 @@ [![Vocalinux CI](https://github.com/jatinkrmalik/vocalinux/workflows/Vocalinux%20CI/badge.svg)](https://github.com/jatinkrmalik/vocalinux/actions) [![Platform: Linux](https://img.shields.io/badge/platform-Linux-lightgrey)](https://github.com/jatinkrmalik/vocalinux) -[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) +[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/) [![Made with GTK](https://img.shields.io/badge/Made%20with-GTK-green)](https://www.gtk.org/) [![codecov](https://codecov.io/gh/jatinkrmalik/vocalinux/branch/main/graph/badge.svg)](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", ], },