diff --git a/src/vocalinux/main.py b/src/vocalinux/main.py index 0bb28cf..bac6be3 100644 --- a/src/vocalinux/main.py +++ b/src/vocalinux/main.py @@ -103,14 +103,9 @@ def check_dependencies(): "AppIndicator3/AyatanaAppIndicator3 - Required for system tray icon" ) - # pynput is used for keyboard detection but we check at module startup - # requests is used by various components - # These are intentional checks to provide user-friendly error messages - try: - import pynput # noqa: F401 - except ImportError: - missing_python_deps.append("pynput (install with: pip install pynput)") - + # Keyboard backends are optional and checked lazily by the shortcut manager. + # Importing pynput can fail on Wayland/X-less sessions even when installed. + # requests is used by various components and should remain a required check. try: import requests # noqa: F401 except ImportError: diff --git a/src/vocalinux/ui/keyboard_backends/__init__.py b/src/vocalinux/ui/keyboard_backends/__init__.py index 9b11f2e..5491955 100644 --- a/src/vocalinux/ui/keyboard_backends/__init__.py +++ b/src/vocalinux/ui/keyboard_backends/__init__.py @@ -26,17 +26,13 @@ logger = logging.getLogger(__name__) # Import available backends try: - from .evdev_backend import EvdevKeyboardBackend - - EVDEV_AVAILABLE = True + from .evdev_backend import EVDEV_AVAILABLE, EvdevKeyboardBackend except ImportError: EvdevKeyboardBackend = None # type: ignore EVDEV_AVAILABLE = False try: - from .pynput_backend import PynputKeyboardBackend - - PYNPUT_AVAILABLE = True + from .pynput_backend import PYNPUT_AVAILABLE, PynputKeyboardBackend except ImportError: PynputKeyboardBackend = None # type: ignore PYNPUT_AVAILABLE = False diff --git a/tests/test_keyboard_init_ext.py b/tests/test_keyboard_init_ext.py index 7dbf0d3..1d180e9 100644 --- a/tests/test_keyboard_init_ext.py +++ b/tests/test_keyboard_init_ext.py @@ -22,11 +22,20 @@ from vocalinux.ui.keyboard_backends import ( DesktopEnvironment, create_backend, ) +from vocalinux.ui.keyboard_backends.evdev_backend import EVDEV_AVAILABLE as EVDEV_BACKEND_AVAILABLE +from vocalinux.ui.keyboard_backends.pynput_backend import ( + PYNPUT_AVAILABLE as PYNPUT_BACKEND_AVAILABLE, +) class TestDesktopEnvironmentDetect: """Test DesktopEnvironment.detect() method.""" + def test_package_availability_flags_match_backend_modules(self): + """Test package-level availability flags mirror backend import checks.""" + assert EVDEV_AVAILABLE == EVDEV_BACKEND_AVAILABLE + assert PYNPUT_AVAILABLE == PYNPUT_BACKEND_AVAILABLE + def test_detect_xdg_session_type_wayland(self): """Test detection of Wayland via XDG_SESSION_TYPE.""" with patch.dict(os.environ, {"XDG_SESSION_TYPE": "wayland"}, clear=False): diff --git a/tests/test_main.py b/tests/test_main.py index 25e1017..2986ce8 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -447,6 +447,25 @@ class TestCheckDependencies(unittest.TestCase): result = check_dependencies() self.assertTrue(result) + def test_check_dependencies_does_not_require_pynput(self): + """Test startup is allowed when the optional pynput backend is unavailable.""" + mock_gi = MagicMock() + mock_gi.require_version = MagicMock() + mock_gtk = MagicMock() + mock_appindicator = MagicMock() + mock_requests = MagicMock() + + with patch.dict( + "sys.modules", + { + "gi": mock_gi, + "gi.repository": MagicMock(Gtk=mock_gtk, AppIndicator3=mock_appindicator), + "requests": mock_requests, + }, + ): + result = check_dependencies() + self.assertTrue(result) + def test_check_dependencies_missing_gtk(self): """Test when GTK is missing."""