diff --git a/distro-packages/Arch-Linux/PKGBUILD b/distro-packages/Arch-Linux/PKGBUILD index 15abe3b..92b5441 100644 --- a/distro-packages/Arch-Linux/PKGBUILD +++ b/distro-packages/Arch-Linux/PKGBUILD @@ -14,7 +14,7 @@ depends=( python-gobject python-cairo gtk3 - python-pywayland + # Audio and speech speech-dispatcher gstreamer diff --git a/meson.build b/meson.build index 0cce3df..d37e763 100644 --- a/meson.build +++ b/meson.build @@ -62,7 +62,6 @@ optional_modules = { 'speechd': 'speech output', 'dasbus': 'D-Bus remote controller', 'psutil': 'system information commands', - 'pywayland': 'Wayland shared workspace backend', 'gi.repository.Wnck': 'mouse review', 'pdf2image': 'PDF processing for OCR', 'scipy': 'Scientific computing for OCR analysis', diff --git a/pyproject.toml b/pyproject.toml index 7643916..3f914f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,14 +20,15 @@ dependencies = [ "louis; extra == 'braille'" ] -[project.optional-dependencies] -wayland = [ - "pywayland" -] - [project.scripts] cthulhu = "cthulhu.cthulhu:main" +[tool.ruff.lint.per-file-ignores] +"src/cthulhu.py" = ["E402"] +"src/cthulhu/*.py" = ["E402"] +"src/cthulhu/**/*.py" = ["E402"] +"tests/*.py" = ["E402"] + [tool.hatch.version] path = "src/cthulhu/__init__.py" diff --git a/src/cthulhu.py b/src/cthulhu.py index aecf2d9..c65dbdc 100644 --- a/src/cthulhu.py +++ b/src/cthulhu.py @@ -88,7 +88,6 @@ setup_paths() from cthulhu import debug from cthulhu import messages -from cthulhu import settings from cthulhu.ax_object import AXObject from cthulhu.ax_utilities import AXUtilities from cthulhu.cthulhu_platform import version, revision diff --git a/src/cthulhu/bookmarks.py b/src/cthulhu/bookmarks.py index 6d474f6..3428bbf 100644 --- a/src/cthulhu/bookmarks.py +++ b/src/cthulhu/bookmarks.py @@ -35,7 +35,6 @@ from . import cmdnames from . import keybindings from . import input_event from . import messages -from . import settings_manager from .ax_object import AXObject _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager diff --git a/src/cthulhu/braille.py b/src/cthulhu/braille.py index 9445144..da4ad5b 100644 --- a/src/cthulhu/braille.py +++ b/src/cthulhu/braille.py @@ -50,10 +50,8 @@ from gi.repository import GLib from . import brltablenames from . import cmdnames from . import debug -from . import logger from . import cthulhu_state from . import settings -from . import settings_manager from .ax_event_synthesizer import AXEventSynthesizer from .ax_object import AXObject diff --git a/src/cthulhu/braille_generator.py b/src/cthulhu/braille_generator.py index 555501e..19a77ab 100644 --- a/src/cthulhu/braille_generator.py +++ b/src/cthulhu/braille_generator.py @@ -46,7 +46,6 @@ from . import object_properties from . import role_keys from . import cthulhu_state from . import settings -from . import settings_manager from .ax_object import AXObject from .ax_text import AXText from .ax_utilities import AXUtilities diff --git a/src/cthulhu/chat.py b/src/cthulhu/chat.py index cc587f8..11321c1 100644 --- a/src/cthulhu/chat.py +++ b/src/cthulhu/chat.py @@ -40,7 +40,6 @@ from . import keybindings from . import messages from . import cthulhu_state from . import settings -from . import settings_manager from .ax_object import AXObject from .ax_utilities import AXUtilities diff --git a/src/cthulhu/compositor_state_adapter.py b/src/cthulhu/compositor_state_adapter.py index a00081c..bce069f 100644 --- a/src/cthulhu/compositor_state_adapter.py +++ b/src/cthulhu/compositor_state_adapter.py @@ -29,10 +29,27 @@ from .compositor_state_types import ( RESUME_ATSPI_CHURN, WORKSPACE_STATE_CHANGED, ) -from .compositor_state_wayland import NullWorkspaceBackend, WaylandSharedProtocolsBackend from .wnck_support import get_session_type +class NullWorkspaceBackend: + """No-op backend used when no real workspace backend is available.""" + + name = "null" + + def __init__(self) -> None: + self._emitSignal = None + + def is_available(self, session_type: str | None = None) -> bool: + return True + + def activate(self, emit_signal: Any = None) -> None: + self._emitSignal = emit_signal + + def deactivate(self, emit_signal: Any = None) -> None: + self._emitSignal = None + + class CompositorStateAdapter: """Normalizes compositor state and desktop-focus context changes.""" @@ -42,7 +59,6 @@ class CompositorStateAdapter: ) -> None: if workspace_backends is None: workspace_backends = [ - WaylandSharedProtocolsBackend(), NullWorkspaceBackend(), ] self._workspaceBackends = list(workspace_backends) diff --git a/src/cthulhu/compositor_state_wayland.py b/src/cthulhu/compositor_state_wayland.py deleted file mode 100644 index ba6f22a..0000000 --- a/src/cthulhu/compositor_state_wayland.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2026 Stormux -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. - -"""Wayland workspace backends for normalized compositor state tracking.""" - -from __future__ import annotations - -import os -from collections.abc import Mapping, Sequence -from typing import Any - -from gi.repository import GLib - -from . import debug -from .compositor_state_types import ( - DESKTOP_TRANSITION_FINISHED, - DESKTOP_TRANSITION_STARTED, - WORKSPACE_STATE_CHANGED, -) -from .wayland_protocols import ext_workspace_v1 -from .wnck_support import get_session_type - - -class NullWorkspaceBackend: - """No-op backend used when no real workspace backend is available.""" - - name = "null" - - def __init__(self) -> None: - self._emitSignal = None - - def is_available(self, session_type: str | None = None) -> bool: - return True - - def activate(self, emit_signal: Any = None) -> None: - self._emitSignal = emit_signal - - def deactivate(self, emit_signal: Any = None) -> None: - self._emitSignal = None - - -class WaylandSharedProtocolsBackend: - """Runtime-optional backend for the ext-workspace shared Wayland protocol.""" - - name = "wayland-shared-protocols" - - def __init__( - self, - *, - environment: Mapping[str, str] | None = None, - protocols: Any = None, - ) -> None: - self._environment = environment if environment is not None else os.environ - self._protocols = protocols or ext_workspace_v1 - self._emitSignal = None - self._display = None - self._registry = None - self._workspaceManager = None - self._workspaceStates: dict[Any, dict[str, Any]] = {} - self._batchDirty = False - self._transitionPending = False - self._dispatchSourceId = 0 - - def is_available(self, session_type: str | None = None) -> bool: - effectiveSessionType = (session_type or get_session_type()).strip().lower() - return ( - effectiveSessionType == "wayland" - and bool((self._environment.get("WAYLAND_DISPLAY") or "").strip()) - and bool(self._protocols.has_runtime_support()) - ) - - def activate(self, emit_signal: Any = None) -> None: - self.deactivate() - self._emitSignal = emit_signal - - if emit_signal is None or not self.is_available(): - return - - displayClass = self._protocols.get_display_class() - if displayClass is None: - return - - try: - self._display = displayClass() - connect = getattr(self._display, "connect", None) - if callable(connect): - connect() - get_registry = getattr(self._display, "get_registry", None) - if not callable(get_registry): - raise RuntimeError("Wayland display missing registry access") - self._registry = get_registry() - self._bind_listener(self._registry, "global", self._handle_registry_global) - self._bind_listener(self._registry, "global_remove", self._handle_registry_global_remove) - self._roundtrip() - self._install_dispatch_watch() - except Exception as error: - msg = f"COMPOSITOR STATE: Wayland backend activation failed: {error}" - debug.printMessage(debug.LEVEL_WARNING, msg, True) - self.deactivate() - - def deactivate(self, emit_signal: Any = None) -> None: - self._workspaceStates = {} - self._batchDirty = False - self._transitionPending = False - self._remove_dispatch_watch() - self._safe_close_proxy(self._workspaceManager) - self._safe_close_proxy(self._registry) - self._workspaceManager = None - self._registry = None - - if self._display is not None: - disconnect = getattr(self._display, "disconnect", None) - if callable(disconnect): - try: - disconnect() - except Exception: - pass - self._display = None - self._emitSignal = None - - def _bind_listener(self, target: Any, event_name: str, callback: Any) -> bool: - dispatcher = getattr(target, "dispatcher", None) - if dispatcher is not None: - try: - dispatcher[event_name] = callback - return True - except Exception: - pass - - add_listener = getattr(target, "add_listener", None) - if callable(add_listener): - try: - add_listener(**{event_name: callback}) - return True - except TypeError: - try: - add_listener(event_name, callback) - return True - except TypeError: - pass - - attributeName = f"on_{event_name}" - if hasattr(target, attributeName): - setattr(target, attributeName, callback) - return True - - return False - - def _roundtrip(self) -> None: - if self._display is None: - return - - for methodName in ("roundtrip", "dispatch", "dispatch_pending"): - method = getattr(self._display, methodName, None) - if callable(method): - method() - break - - def _install_dispatch_watch(self) -> None: - if self._display is None or self._dispatchSourceId: - return - - getFd = getattr(self._display, "get_fd", None) - if not callable(getFd): - return - - try: - displayFd = int(getFd()) - except Exception: - return - - if displayFd < 0: - return - - self._dispatchSourceId = GLib.io_add_watch( - displayFd, - GLib.PRIORITY_DEFAULT, - GLib.IO_IN | GLib.IO_ERR | GLib.IO_HUP, - self._dispatch_display_events, - ) - - def _remove_dispatch_watch(self) -> None: - if not self._dispatchSourceId: - return - - try: - GLib.source_remove(self._dispatchSourceId) - except Exception: - pass - - self._dispatchSourceId = 0 - - def _dispatch_display_events(self, _fd: int, condition: Any) -> bool: - if condition & (GLib.IO_ERR | GLib.IO_HUP): - self.deactivate() - return False - - dispatch = getattr(self._display, "dispatch", None) - if not callable(dispatch): - self.deactivate() - return False - - try: - while True: - dispatched = dispatch(block=False) - if not dispatched: - break - except TypeError: - try: - dispatch() - except Exception: - self.deactivate() - return False - except Exception: - self.deactivate() - return False - - return True - - def _safe_close_proxy(self, proxy: Any) -> None: - if proxy is None: - return - - for methodName in ("destroy", "release"): - method = getattr(proxy, methodName, None) - if callable(method): - try: - method() - except Exception: - pass - return - - def _handle_registry_global(self, *args: Any) -> None: - if self._workspaceManager is not None: - return - - globalName, interfaceName, interfaceVersion = self._parse_registry_global_args(args) - if interfaceName != self._protocols.INTERFACE_NAME: - return - - registry = self._registry - if registry is None: - return - - bindVersion = min(interfaceVersion, self._protocols.INTERFACE_VERSION) - try: - self._workspaceManager = self._protocols.bind_workspace_manager( - registry, - globalName, - bindVersion, - ) - except Exception: - self._workspaceManager = None - - if self._workspaceManager is None: - return - - self._bind_listener(self._workspaceManager, "workspace", self._handle_workspace_manager_workspace) - self._bind_listener(self._workspaceManager, "done", self._handle_workspace_manager_done) - self._bind_listener(self._workspaceManager, "finished", self._handle_workspace_manager_finished) - - def _handle_registry_global_remove(self, *args: Any) -> None: - return - - def _handle_workspace_manager_workspace(self, *args: Any) -> None: - workspaceHandle = args[-1] if args else None - if workspaceHandle is None or workspaceHandle in self._workspaceStates: - return - - self._workspaceStates[workspaceHandle] = {"active": False} - self._bind_listener(workspaceHandle, "id", self._handle_workspace_id) - self._bind_listener(workspaceHandle, "state", self._handle_workspace_state) - self._bind_listener(workspaceHandle, "removed", self._handle_workspace_removed) - - def _handle_workspace_id(self, workspaceHandle: Any, workspaceId: str, *_args: Any) -> None: - workspaceState = self._workspaceStates.setdefault(workspaceHandle, {"active": False}) - workspaceState["workspace_id"] = (workspaceId or "").strip() - self._batchDirty = True - - def _handle_workspace_state(self, workspaceHandle: Any, stateValue: Any, value: Any = None, *_args: Any) -> None: - workspaceState = self._workspaceStates.setdefault(workspaceHandle, {"active": False}) - workspaceState["active"] = self._workspace_is_active(stateValue, value) - self._batchDirty = True - self._emit_transition_started("workspace-state-update") - - def _handle_workspace_removed(self, workspaceHandle: Any, *_args: Any) -> None: - self._workspaceStates.pop(workspaceHandle, None) - self._batchDirty = True - self._emit_transition_started("workspace-removed") - - def _handle_workspace_manager_done(self, *_args: Any) -> None: - if not self._batchDirty: - return - - activeWorkspaceIds = self._active_workspace_ids() - self._emit_signal(WORKSPACE_STATE_CHANGED, activeWorkspaceIds, "workspace-batch-done") - self._emit_signal(DESKTOP_TRANSITION_FINISHED, activeWorkspaceIds, "workspace-batch-done") - self._batchDirty = False - self._transitionPending = False - - def _handle_workspace_manager_finished(self, *_args: Any) -> None: - self._workspaceManager = None - - def _workspace_is_active(self, stateValue: Any, value: Any = None) -> bool: - if isinstance(stateValue, str): - return stateValue.strip().lower() == "active" and bool(value) - - if isinstance(stateValue, Mapping): - return bool(stateValue.get("active")) - - if isinstance(stateValue, Sequence) and not isinstance(stateValue, (str, bytes, bytearray)): - return self._protocols.ACTIVE_STATE_VALUE in stateValue - - return bool(stateValue) - - def _active_workspace_ids(self) -> set[str]: - workspaceIds = set() - for workspaceHandle, workspaceState in self._workspaceStates.items(): - if not workspaceState.get("active"): - continue - workspaceId = (workspaceState.get("workspace_id") or "").strip() - if not workspaceId: - workspaceId = self._workspace_id(workspaceHandle) - workspaceIds.add(workspaceId) - return workspaceIds - - def _workspace_id(self, workspaceHandle: Any) -> str: - return f"workspace:{id(workspaceHandle)}" - - def _emit_transition_started(self, reason: str) -> None: - if self._transitionPending: - return - self._transitionPending = True - self._emit_signal(DESKTOP_TRANSITION_STARTED, self._active_workspace_ids(), reason) - - def _emit_signal(self, signalType: str, workspaceIds: set[str], reason: str) -> None: - if callable(self._emitSignal): - self._emitSignal(signalType, workspaceIds, reason) - return - - def _parse_registry_global_args(self, args: Sequence[Any]) -> tuple[int, str, int]: - if len(args) >= 4: - _, globalName, interfaceName, interfaceVersion = args[-4:] - elif len(args) >= 3: - globalName, interfaceName, interfaceVersion = args[-3:] - else: - return (-1, "", 0) - - try: - parsedName = int(globalName) - except (TypeError, ValueError): - parsedName = -1 - - try: - parsedVersion = int(interfaceVersion) - except (TypeError, ValueError): - parsedVersion = self._protocols.INTERFACE_VERSION - - return parsedName, str(interfaceName or ""), parsedVersion diff --git a/src/cthulhu/cthulhu.py b/src/cthulhu/cthulhu.py index 10a811c..7765742 100644 --- a/src/cthulhu/cthulhu.py +++ b/src/cthulhu/cthulhu.py @@ -36,26 +36,20 @@ __copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \ __license__ = "LGPL" import faulthandler -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional from . import dbus_service if TYPE_CHECKING: from types import FrameType from gi.repository.Gio import Settings as GSettings - from gi.repository import Gtk from .settings_manager import SettingsManager from .script_manager import ScriptManager from .plugin_system_manager import PluginSystemManager - from .input_event import InputEvent - from .input_event_manager import InputEventManager from .event_manager import EventManager from .signal_manager import SignalManager from .dynamic_api_manager import DynamicApiManager - from .speech import Speech - from .braille import Braille - from .script import Script class APIHelper: """Helper class for plugin API interactions, including keybindings.""" diff --git a/src/cthulhu/date_and_time_presenter.py b/src/cthulhu/date_and_time_presenter.py index 8371f1d..1866ceb 100644 --- a/src/cthulhu/date_and_time_presenter.py +++ b/src/cthulhu/date_and_time_presenter.py @@ -33,13 +33,12 @@ __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \ __license__ = "LGPL" import time -from typing import Optional, Dict, Callable, Any +from typing import Optional, Dict, Any from . import cthulhu # Need access to cthulhuApp from . import cmdnames from . import input_event from . import keybindings -from . import settings_manager _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager diff --git a/src/cthulhu/dynamic_api_manager.py b/src/cthulhu/dynamic_api_manager.py index e9a7bb3..232eea2 100644 --- a/src/cthulhu/dynamic_api_manager.py +++ b/src/cthulhu/dynamic_api_manager.py @@ -23,8 +23,6 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -import gi -from gi.repository import GObject from cthulhu import resource_manager diff --git a/src/cthulhu/generator.py b/src/cthulhu/generator.py index c9fd97c..321876d 100644 --- a/src/cthulhu/generator.py +++ b/src/cthulhu/generator.py @@ -51,7 +51,6 @@ from . import messages from . import object_properties from . import role_keys from . import settings -from . import settings_manager from .ax_object import AXObject from .ax_text import AXText from .ax_utilities import AXUtilities diff --git a/src/cthulhu/gsettings_registry.py b/src/cthulhu/gsettings_registry.py index 8ac9b95..d65efe6 100644 --- a/src/cthulhu/gsettings_registry.py +++ b/src/cthulhu/gsettings_registry.py @@ -11,7 +11,6 @@ from __future__ import annotations -import os from collections.abc import Callable from dataclasses import dataclass from pathlib import Path diff --git a/src/cthulhu/keybindings.py b/src/cthulhu/keybindings.py index 6f27d4d..2bb1740 100644 --- a/src/cthulhu/keybindings.py +++ b/src/cthulhu/keybindings.py @@ -45,12 +45,11 @@ from typing import TYPE_CHECKING from . import debug from . import settings -from . import cthulhu_state from .cthulhu_i18n import _ if TYPE_CHECKING: - from .input_event import KeyboardEvent, InputEventHandler + pass _keysymsCache = {} _keycodeCache = {} diff --git a/src/cthulhu/learn_mode_presenter.py b/src/cthulhu/learn_mode_presenter.py index d7ad2d9..38abec9 100644 --- a/src/cthulhu/learn_mode_presenter.py +++ b/src/cthulhu/learn_mode_presenter.py @@ -53,7 +53,6 @@ from . import keybindings from . import messages from . import cthulhu_state from . import settings -from . import settings_manager from .ax_object import AXObject diff --git a/src/cthulhu/liveregions.py b/src/cthulhu/liveregions.py index 5f9e773..de846db 100644 --- a/src/cthulhu/liveregions.py +++ b/src/cthulhu/liveregions.py @@ -25,7 +25,6 @@ import gi gi.require_version("Atspi", "2.0") -from gi.repository import Atspi import bisect import copy @@ -40,7 +39,6 @@ from . import keybindings from . import messages from . import input_event from . import cthulhu_state -from . import settings_manager from .ax_collection import AXCollection from .ax_object import AXObject from .ax_text import AXText diff --git a/src/cthulhu/meson.build b/src/cthulhu/meson.build index aa5cb60..fda8071 100644 --- a/src/cthulhu/meson.build +++ b/src/cthulhu/meson.build @@ -50,7 +50,6 @@ cthulhu_python_sources = files([ 'command_manager.py', 'compositor_state_adapter.py', 'compositor_state_types.py', - 'compositor_state_wayland.py', 'common_keyboardmap.py', 'cthulhuVersion.py', 'cthulhu_modifier_manager.py', @@ -219,5 +218,4 @@ install_data( # Subdirectories subdir('backends') subdir('scripts') -subdir('wayland_protocols') subdir('plugins') diff --git a/src/cthulhu/mouse_review.py b/src/cthulhu/mouse_review.py index 98a515c..cb963ef 100644 --- a/src/cthulhu/mouse_review.py +++ b/src/cthulhu/mouse_review.py @@ -54,8 +54,6 @@ from . import input_event_manager from . import messages from . import cthulhu from . import cthulhu_state -from . import script_manager -from . import settings_manager from .ax_component import AXComponent from .ax_object import AXObject from .ax_text import AXText diff --git a/src/cthulhu/plugin.py b/src/cthulhu/plugin.py index a9e0eca..9583971 100644 --- a/src/cthulhu/plugin.py +++ b/src/cthulhu/plugin.py @@ -8,7 +8,6 @@ """Base class for Cthulhu plugins using pluggy.""" -import os import logging import pluggy diff --git a/src/cthulhu/plugins/AIAssistant/__init__.py b/src/cthulhu/plugins/AIAssistant/__init__.py index 34733fd..1d2a62f 100644 --- a/src/cthulhu/plugins/AIAssistant/__init__.py +++ b/src/cthulhu/plugins/AIAssistant/__init__.py @@ -19,4 +19,4 @@ """AI Assistant plugin package.""" -from .plugin import AIAssistant \ No newline at end of file +from .plugin import AIAssistant as AIAssistant diff --git a/src/cthulhu/plugins/AIAssistant/ai_providers.py b/src/cthulhu/plugins/AIAssistant/ai_providers.py index a668466..275d2c2 100644 --- a/src/cthulhu/plugins/AIAssistant/ai_providers.py +++ b/src/cthulhu/plugins/AIAssistant/ai_providers.py @@ -238,8 +238,6 @@ class ClaudeCodeProvider(AIProvider): def _call_claude_code(self, prompt, image_path=None): """Call Claude Code CLI with the prompt and optional image.""" import subprocess - import tempfile - import os try: # Build the command diff --git a/src/cthulhu/plugins/AIAssistant/plugin.py b/src/cthulhu/plugins/AIAssistant/plugin.py index 6a64018..8f4cee6 100644 --- a/src/cthulhu/plugins/AIAssistant/plugin.py +++ b/src/cthulhu/plugins/AIAssistant/plugin.py @@ -11,16 +11,14 @@ import logging import os -import json import base64 -from io import BytesIO import gi gi.require_version('Gdk', '3.0') gi.require_version('GdkPixbuf', '2.0') gi.require_version('Atspi', '2.0') gi.require_version('Gtk', '3.0') -from gi.repository import Gdk, GdkPixbuf, Atspi, Gtk +from gi.repository import Gdk, Atspi, Gtk from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu import settings @@ -29,7 +27,6 @@ from cthulhu import cthulhu_state from cthulhu import ax_object from cthulhu.ax_text import AXText from cthulhu.ax_value import AXValue -from cthulhu import ax_utilities from cthulhu.ax_utilities_state import AXUtilitiesState from cthulhu.plugins.AIAssistant.ai_providers import create_provider diff --git a/src/cthulhu/plugins/ByeCthulhu/plugin.py b/src/cthulhu/plugins/ByeCthulhu/plugin.py index 95cb249..474386e 100644 --- a/src/cthulhu/plugins/ByeCthulhu/plugin.py +++ b/src/cthulhu/plugins/ByeCthulhu/plugin.py @@ -10,7 +10,6 @@ """Bye Cthulhu plugin for Cthulhu.""" import logging -import time from cthulhu.plugin import Plugin, cthulhu_hookimpl logger = logging.getLogger(__name__) diff --git a/src/cthulhu/plugins/HelloCthulhu/plugin.py b/src/cthulhu/plugins/HelloCthulhu/plugin.py index a790757..5907919 100644 --- a/src/cthulhu/plugins/HelloCthulhu/plugin.py +++ b/src/cthulhu/plugins/HelloCthulhu/plugin.py @@ -21,7 +21,6 @@ """Hello Cthulhu plugin for Cthulhu.""" import logging -import weakref from cthulhu.plugin import Plugin, cthulhu_hookimpl logger = logging.getLogger(__name__) diff --git a/src/cthulhu/plugins/OCR/__init__.py b/src/cthulhu/plugins/OCR/__init__.py index 55b30a0..3e7891b 100644 --- a/src/cthulhu/plugins/OCR/__init__.py +++ b/src/cthulhu/plugins/OCR/__init__.py @@ -20,4 +20,4 @@ """OCRDesktop plugin package.""" -from .plugin import OCRDesktop \ No newline at end of file +from .plugin import OCRDesktop as OCRDesktop diff --git a/src/cthulhu/plugins/OCR/plugin.py b/src/cthulhu/plugins/OCR/plugin.py index 16b3b07..a5cd84b 100644 --- a/src/cthulhu/plugins/OCR/plugin.py +++ b/src/cthulhu/plugins/OCR/plugin.py @@ -10,15 +10,11 @@ """OCRDesktop plugin for Cthulhu screen reader.""" +import importlib.util import logging -import os -import sys import locale import time import re -import tempfile -import threading -from mimetypes import MimeTypes import gi gi.require_version('Atspi', '2.0') @@ -33,6 +29,11 @@ from cthulhu.wnck_support import load_wnck # Note: Removed complex beep system - simple announcements work perfectly! + +def _has_module(module_name: str) -> bool: + return importlib.util.find_spec(module_name) is not None + + # PIL try: from PIL import Image @@ -42,34 +43,16 @@ except ImportError: PIL_AVAILABLE = False # pytesseract -try: - import pytesseract - from pytesseract import Output - PYTESSERACT_AVAILABLE = True -except ImportError: - PYTESSERACT_AVAILABLE = False +PYTESSERACT_AVAILABLE = _has_module("pytesseract") # pdf2image -try: - from pdf2image import convert_from_path - PDF2IMAGE_AVAILABLE = True -except ImportError: - PDF2IMAGE_AVAILABLE = False +PDF2IMAGE_AVAILABLE = _has_module("pdf2image") # scipy -try: - from scipy.spatial import KDTree - SCIPY_AVAILABLE = True -except ImportError: - SCIPY_AVAILABLE = False +SCIPY_AVAILABLE = _has_module("scipy") # webcolors -try: - from webcolors import CSS3_HEX_TO_NAMES - from webcolors import hex_to_rgb - WEBCOLORS_AVAILABLE = True -except ImportError: - WEBCOLORS_AVAILABLE = False +WEBCOLORS_AVAILABLE = _has_module("webcolors") # GTK/GDK try: @@ -664,6 +647,7 @@ class OCRDesktop(Plugin): modifiedImg = self._transformImg(img) try: # Extract coordinate data using image_to_data + import pytesseract from pytesseract import Output OCRWords = pytesseract.image_to_data(modifiedImg, output_type=Output.DICT, lang=self._languageCode, config='--psm 4') diff --git a/src/cthulhu/plugins/PluginManager/plugin.py b/src/cthulhu/plugins/PluginManager/plugin.py index a45c58a..e0e206e 100644 --- a/src/cthulhu/plugins/PluginManager/plugin.py +++ b/src/cthulhu/plugins/PluginManager/plugin.py @@ -12,7 +12,6 @@ import logging import os import configparser -from pathlib import Path import gi gi.require_version('Gtk', '3.0') diff --git a/src/cthulhu/plugins/SSIPProxy/plugin.py b/src/cthulhu/plugins/SSIPProxy/plugin.py index ea79179..de4134a 100644 --- a/src/cthulhu/plugins/SSIPProxy/plugin.py +++ b/src/cthulhu/plugins/SSIPProxy/plugin.py @@ -28,7 +28,6 @@ to have their speech output go through Cthulhu for both speech and braille suppo import logging import select import socket -import threading from threading import Thread, Lock from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu import debug diff --git a/src/cthulhu/plugins/SimplePluginSystem/plugin.py b/src/cthulhu/plugins/SimplePluginSystem/plugin.py index cc7d5a5..677bb12 100644 --- a/src/cthulhu/plugins/SimplePluginSystem/plugin.py +++ b/src/cthulhu/plugins/SimplePluginSystem/plugin.py @@ -22,12 +22,9 @@ import glob import os -import importlib.util import random import string -import _thread import logging -from subprocess import Popen, PIPE import gettext from cthulhu.plugin import Plugin, cthulhu_hookimpl diff --git a/src/cthulhu/plugins/SpeechHistory/__init__.py b/src/cthulhu/plugins/SpeechHistory/__init__.py index 08c77bb..7a739e2 100644 --- a/src/cthulhu/plugins/SpeechHistory/__init__.py +++ b/src/cthulhu/plugins/SpeechHistory/__init__.py @@ -1,2 +1 @@ -from .plugin import SpeechHistory - +from .plugin import SpeechHistory as SpeechHistory diff --git a/src/cthulhu/plugins/self_voice/plugin.py b/src/cthulhu/plugins/self_voice/plugin.py index e2893ff..2549e32 100644 --- a/src/cthulhu/plugins/self_voice/plugin.py +++ b/src/cthulhu/plugins/self_voice/plugin.py @@ -24,7 +24,6 @@ import os import socket import select import logging -import threading from threading import Thread, Lock from cthulhu.plugin import Plugin, cthulhu_hookimpl diff --git a/src/cthulhu/script_utilities.py b/src/cthulhu/script_utilities.py index c15e698..18af84a 100644 --- a/src/cthulhu/script_utilities.py +++ b/src/cthulhu/script_utilities.py @@ -43,7 +43,7 @@ import math import re import time from difflib import SequenceMatcher -from typing import Any, Callable, Generator, Optional, TYPE_CHECKING +from typing import Any, Callable, Optional, TYPE_CHECKING gi.require_version("Atspi", "2.0") from gi.repository import Atspi @@ -63,7 +63,6 @@ from . import cthulhu_state from . import object_properties from . import pronunciation_dict from . import settings -from . import settings_manager from . import text_attribute_names from .ax_document_selection import AXDocumentSelection from .ax_object import AXObject diff --git a/src/cthulhu/scripts/apps/Banshee/__init__.py b/src/cthulhu/scripts/apps/Banshee/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/Banshee/__init__.py +++ b/src/cthulhu/scripts/apps/Banshee/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/Eclipse/__init__.py b/src/cthulhu/scripts/apps/Eclipse/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/Eclipse/__init__.py +++ b/src/cthulhu/scripts/apps/Eclipse/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/Mumble/__init__.py b/src/cthulhu/scripts/apps/Mumble/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/Mumble/__init__.py +++ b/src/cthulhu/scripts/apps/Mumble/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/gajim/__init__.py b/src/cthulhu/scripts/apps/gajim/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/gajim/__init__.py +++ b/src/cthulhu/scripts/apps/gajim/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/gnome-shell/__init__.py b/src/cthulhu/scripts/apps/gnome-shell/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/gnome-shell/__init__.py +++ b/src/cthulhu/scripts/apps/gnome-shell/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/notify-osd/script.py b/src/cthulhu/scripts/apps/notify-osd/script.py index 6c48018..d69063c 100644 --- a/src/cthulhu/scripts/apps/notify-osd/script.py +++ b/src/cthulhu/scripts/apps/notify-osd/script.py @@ -34,7 +34,6 @@ __license__ = "LGPL" import cthulhu.messages as messages import cthulhu.scripts.default as default import cthulhu.settings as settings -import cthulhu.settings_manager as settings_manager from cthulhu.ax_object import AXObject from cthulhu.ax_value import AXValue diff --git a/src/cthulhu/scripts/apps/pidgin/__init__.py b/src/cthulhu/scripts/apps/pidgin/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/pidgin/__init__.py +++ b/src/cthulhu/scripts/apps/pidgin/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/smuxi-frontend-gnome/__init__.py b/src/cthulhu/scripts/apps/smuxi-frontend-gnome/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/smuxi-frontend-gnome/__init__.py +++ b/src/cthulhu/scripts/apps/smuxi-frontend-gnome/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/soffice/__init__.py b/src/cthulhu/scripts/apps/soffice/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/apps/soffice/__init__.py +++ b/src/cthulhu/scripts/apps/soffice/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/apps/soffice/braille_generator.py b/src/cthulhu/scripts/apps/soffice/braille_generator.py index c205d2b..03e368d 100644 --- a/src/cthulhu/scripts/apps/soffice/braille_generator.py +++ b/src/cthulhu/scripts/apps/soffice/braille_generator.py @@ -38,7 +38,6 @@ from gi.repository import Atspi import cthulhu.braille as braille import cthulhu.braille_generator as braille_generator import cthulhu.object_properties as object_properties -import cthulhu.settings_manager as settings_manager from cthulhu.ax_object import AXObject from cthulhu.ax_utilities import AXUtilities diff --git a/src/cthulhu/scripts/apps/soffice/speech_generator.py b/src/cthulhu/scripts/apps/soffice/speech_generator.py index e94f2ba..c1c2eeb 100644 --- a/src/cthulhu/scripts/apps/soffice/speech_generator.py +++ b/src/cthulhu/scripts/apps/soffice/speech_generator.py @@ -40,7 +40,6 @@ import cthulhu.role_keys as role_keys import cthulhu.settings_manager as settings_manager import cthulhu.speech_generator as speech_generator from cthulhu.ax_object import AXObject -from cthulhu.ax_text import AXText from cthulhu.ax_utilities import AXUtilities _settingsManager = settings_manager.getManager() diff --git a/src/cthulhu/scripts/apps/steamwebhelper/__init__.py b/src/cthulhu/scripts/apps/steamwebhelper/__init__.py index 585c964..6605491 100644 --- a/src/cthulhu/scripts/apps/steamwebhelper/__init__.py +++ b/src/cthulhu/scripts/apps/steamwebhelper/__init__.py @@ -1 +1 @@ -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/terminal/__init__.py b/src/cthulhu/scripts/terminal/__init__.py index 57ee8c8..9d4980f 100644 --- a/src/cthulhu/scripts/terminal/__init__.py +++ b/src/cthulhu/scripts/terminal/__init__.py @@ -23,8 +23,7 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .braille_generator import BrailleGenerator -from .script import Script -from .speech_generator import SpeechGenerator -from .script_utilities import Utilities - +from .braille_generator import BrailleGenerator as BrailleGenerator +from .script import Script as Script +from .speech_generator import SpeechGenerator as SpeechGenerator +from .script_utilities import Utilities as Utilities diff --git a/src/cthulhu/scripts/terminal/script_utilities.py b/src/cthulhu/scripts/terminal/script_utilities.py index b61d9f7..73a7dbc 100644 --- a/src/cthulhu/scripts/terminal/script_utilities.py +++ b/src/cthulhu/scripts/terminal/script_utilities.py @@ -31,7 +31,6 @@ __license__ = "LGPL" import gi gi.require_version("Atspi", "2.0") -from gi.repository import Atspi import re @@ -40,7 +39,6 @@ from cthulhu import cthulhu from cthulhu import keybindings from cthulhu import cthulhu_state from cthulhu import script_utilities -from cthulhu import settings_manager from cthulhu.ax_object import AXObject from cthulhu.ax_text import AXText from cthulhu.ax_utilities import AXUtilities diff --git a/src/cthulhu/scripts/toolkits/GAIL/__init__.py b/src/cthulhu/scripts/toolkits/GAIL/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/toolkits/GAIL/__init__.py +++ b/src/cthulhu/scripts/toolkits/GAIL/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/toolkits/Gecko/__init__.py b/src/cthulhu/scripts/toolkits/Gecko/__init__.py index fec8f65..5d54926 100644 --- a/src/cthulhu/scripts/toolkits/Gecko/__init__.py +++ b/src/cthulhu/scripts/toolkits/Gecko/__init__.py @@ -23,5 +23,5 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script -from .script_utilities import Utilities +from .script import Script as Script +from .script_utilities import Utilities as Utilities diff --git a/src/cthulhu/scripts/toolkits/J2SE-access-bridge/__init__.py b/src/cthulhu/scripts/toolkits/J2SE-access-bridge/__init__.py index 26d05ee..f8688a7 100644 --- a/src/cthulhu/scripts/toolkits/J2SE-access-bridge/__init__.py +++ b/src/cthulhu/scripts/toolkits/J2SE-access-bridge/__init__.py @@ -23,5 +23,5 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script -from .speech_generator import SpeechGenerator +from .script import Script as Script +from .speech_generator import SpeechGenerator as SpeechGenerator diff --git a/src/cthulhu/scripts/toolkits/Qt/__init__.py b/src/cthulhu/scripts/toolkits/Qt/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/toolkits/Qt/__init__.py +++ b/src/cthulhu/scripts/toolkits/Qt/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/toolkits/WebKitGtk/__init__.py b/src/cthulhu/scripts/toolkits/WebKitGtk/__init__.py index 0ecdbf7..d04a32e 100644 --- a/src/cthulhu/scripts/toolkits/WebKitGtk/__init__.py +++ b/src/cthulhu/scripts/toolkits/WebKitGtk/__init__.py @@ -23,7 +23,7 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script -from .speech_generator import SpeechGenerator -from .braille_generator import BrailleGenerator -from .script_utilities import Utilities +from .script import Script as Script +from .speech_generator import SpeechGenerator as SpeechGenerator +from .braille_generator import BrailleGenerator as BrailleGenerator +from .script_utilities import Utilities as Utilities diff --git a/src/cthulhu/scripts/toolkits/clutter/__init__.py b/src/cthulhu/scripts/toolkits/clutter/__init__.py index b006f6c..739bed2 100644 --- a/src/cthulhu/scripts/toolkits/clutter/__init__.py +++ b/src/cthulhu/scripts/toolkits/clutter/__init__.py @@ -23,4 +23,4 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script +from .script import Script as Script diff --git a/src/cthulhu/scripts/toolkits/gtk/__init__.py b/src/cthulhu/scripts/toolkits/gtk/__init__.py index fec8f65..5d54926 100644 --- a/src/cthulhu/scripts/toolkits/gtk/__init__.py +++ b/src/cthulhu/scripts/toolkits/gtk/__init__.py @@ -23,5 +23,5 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -from .script import Script -from .script_utilities import Utilities +from .script import Script as Script +from .script_utilities import Utilities as Utilities diff --git a/src/cthulhu/scripts/web/script_utilities.py b/src/cthulhu/scripts/web/script_utilities.py index 749ae68..e35b770 100644 --- a/src/cthulhu/scripts/web/script_utilities.py +++ b/src/cthulhu/scripts/web/script_utilities.py @@ -46,8 +46,6 @@ from cthulhu import messages from cthulhu import cthulhu from cthulhu import cthulhu_state from cthulhu import script_utilities -from cthulhu import script_manager -from cthulhu import settings_manager from cthulhu.ax_collection import AXCollection from cthulhu.ax_component import AXComponent from cthulhu.ax_document import AXDocument diff --git a/src/cthulhu/scripts/web/sound_generator.py b/src/cthulhu/scripts/web/sound_generator.py index ed2213e..751a00a 100644 --- a/src/cthulhu/scripts/web/sound_generator.py +++ b/src/cthulhu/scripts/web/sound_generator.py @@ -36,7 +36,6 @@ gi.require_version("Atspi", "2.0") from gi.repository import Atspi from cthulhu import cthulhu -from cthulhu import settings_manager from cthulhu import sound_generator _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager diff --git a/src/cthulhu/scripts/web/speech_generator.py b/src/cthulhu/scripts/web/speech_generator.py index c715cf2..84886ad 100644 --- a/src/cthulhu/scripts/web/speech_generator.py +++ b/src/cthulhu/scripts/web/speech_generator.py @@ -44,8 +44,6 @@ from cthulhu import object_properties from cthulhu import role_keys from cthulhu import cthulhu_state from cthulhu import settings -from cthulhu import settings_manager -from cthulhu import sound_theme_manager from cthulhu import speech_generator from cthulhu.ax_object import AXObject from cthulhu.ax_text import AXText diff --git a/src/cthulhu/signal_manager.py b/src/cthulhu/signal_manager.py index 762e3a6..09c94ed 100644 --- a/src/cthulhu/signal_manager.py +++ b/src/cthulhu/signal_manager.py @@ -23,7 +23,6 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -import gi from gi.repository import GObject from typing import Optional, Any, Callable, Tuple diff --git a/src/cthulhu/sound_generator.py b/src/cthulhu/sound_generator.py index a81ab46..d9af956 100644 --- a/src/cthulhu/sound_generator.py +++ b/src/cthulhu/sound_generator.py @@ -39,7 +39,6 @@ import os from . import generator from . import role_keys -from . import settings_manager from .ax_object import AXObject from .ax_utilities import AXUtilities from .sound import Icon, Tone diff --git a/src/cthulhu/speech.py b/src/cthulhu/speech.py index 93c16d2..1b183c4 100644 --- a/src/cthulhu/speech.py +++ b/src/cthulhu/speech.py @@ -36,10 +36,9 @@ __license__ = "LGPL" import importlib import time -from typing import TYPE_CHECKING, Optional, List, Dict, Any, Union, Callable +from typing import TYPE_CHECKING, Optional, List, Any, Union, Callable from . import debug -from . import logger from . import settings from . import speech_generator from . import sound diff --git a/src/cthulhu/speech_and_verbosity_manager.py b/src/cthulhu/speech_and_verbosity_manager.py index 5ff8a6d..cbd1363 100644 --- a/src/cthulhu/speech_and_verbosity_manager.py +++ b/src/cthulhu/speech_and_verbosity_manager.py @@ -40,10 +40,8 @@ from . import input_event from . import keybindings from . import messages from . import cthulhu_state -from . import script_manager from . import speechserver from . import settings -from . import settings_manager from . import speech # Removed global _settings_manager diff --git a/src/cthulhu/speech_generator.py b/src/cthulhu/speech_generator.py index 76fe3c2..b063247 100644 --- a/src/cthulhu/speech_generator.py +++ b/src/cthulhu/speech_generator.py @@ -52,7 +52,6 @@ from . import messages from . import object_properties from . import role_keys from . import settings -from . import settings_manager from . import sound_theme_manager from . import speech from . import text_attribute_names diff --git a/src/cthulhu/speechdispatcherfactory.py b/src/cthulhu/speechdispatcherfactory.py index 6802cf1..60f3efd 100644 --- a/src/cthulhu/speechdispatcherfactory.py +++ b/src/cthulhu/speechdispatcherfactory.py @@ -45,7 +45,6 @@ from . import speechserver from . import settings from . import cthulhu_state from . import punctuation_settings -from . import settings_manager from .acss import ACSS _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager diff --git a/src/cthulhu/spellcheck.py b/src/cthulhu/spellcheck.py index 4e91219..25d482d 100644 --- a/src/cthulhu/spellcheck.py +++ b/src/cthulhu/spellcheck.py @@ -33,7 +33,6 @@ __license__ = "LGPL" import gi gi.require_version("Atspi", "2.0") -from gi.repository import Atspi import re @@ -42,7 +41,6 @@ from cthulhu import guilabels from cthulhu import messages from cthulhu import object_properties from cthulhu import cthulhu_state -from cthulhu import settings_manager from cthulhu.ax_object import AXObject from cthulhu.ax_text import AXText from cthulhu.ax_utilities import AXUtilities diff --git a/src/cthulhu/structural_navigation.py b/src/cthulhu/structural_navigation.py index 9764d4c..321c9a7 100644 --- a/src/cthulhu/structural_navigation.py +++ b/src/cthulhu/structural_navigation.py @@ -38,7 +38,6 @@ from gi.repository import Atspi from . import cmdnames from . import cthulhu -from . import dbus_service from . import debug from . import guilabels from . import input_event @@ -48,7 +47,6 @@ from . import object_properties from . import cthulhu_gui_navlist from . import cthulhu_state from . import settings -from . import settings_manager from .ax_collection import AXCollection from .ax_event_synthesizer import AXEventSynthesizer from .ax_object import AXObject diff --git a/src/cthulhu/translation_context.py b/src/cthulhu/translation_context.py index 6e6fe9c..74fa473 100644 --- a/src/cthulhu/translation_context.py +++ b/src/cthulhu/translation_context.py @@ -23,8 +23,7 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -import gi, os, locale, gettext -from gi.repository import GObject +import locale, gettext import gettext from cthulhu import cthulhu_i18n diff --git a/src/cthulhu/translation_manager.py b/src/cthulhu/translation_manager.py index b5394ec..dc869b4 100644 --- a/src/cthulhu/translation_manager.py +++ b/src/cthulhu/translation_manager.py @@ -23,11 +23,8 @@ # Forked from Orca screen reader. # Cthulhu project: https://git.stormux.org/storm/cthulhu -import gi, os, locale, gettext -from gi.repository import GObject -import gettext +import os -from cthulhu import cthulhu_i18n from cthulhu import translation_context class TranslationManager(): diff --git a/src/cthulhu/wayland_protocols/__init__.py b/src/cthulhu/wayland_protocols/__init__.py deleted file mode 100644 index 5083127..0000000 --- a/src/cthulhu/wayland_protocols/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2026 Stormux -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. - -"""Runtime-optional Wayland protocol compatibility wrappers.""" - -from . import ext_workspace_v1 - -__all__ = ["ext_workspace_v1"] diff --git a/src/cthulhu/wayland_protocols/ext_workspace_v1.py b/src/cthulhu/wayland_protocols/ext_workspace_v1.py deleted file mode 100644 index c3d2a46..0000000 --- a/src/cthulhu/wayland_protocols/ext_workspace_v1.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2026 Stormux -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. - -"""Local ext-workspace protocol wrapper built on top of base pywayland.""" - -from __future__ import annotations - -import importlib -from types import ModuleType -from typing import Any - -INTERFACE_NAME = "ext_workspace_manager_v1" -INTERFACE_VERSION = 1 -ACTIVE_STATE_VALUE = 1 -WORKSPACE_HANDLE_INTERFACE_NAME = "ext_workspace_handle_v1" - - -def _load_module(module_names: list[str]) -> ModuleType | None: - for moduleName in module_names: - try: - return importlib.import_module(moduleName) - except ImportError: - continue - return None - - -def _first_attribute(module: ModuleType | None, attribute_names: list[str]) -> Any: - if module is None: - return None - - for attributeName in attribute_names: - if hasattr(module, attributeName): - return getattr(module, attributeName) - return None - - -_pywaylandClientModule = _load_module( - [ - "pywayland.client", - "pywayland.client.display", - ] -) -Display = _first_attribute(_pywaylandClientModule, ["Display"]) - - -class _LocalProtocolProxy: - """Minimal local proxy descriptor with a dispatcher-compatible shape.""" - - interface_name = "" - version = 1 - event_names: tuple[str, ...] = () - - def __init__(self, *args: Any, **kwargs: Any) -> None: - try: - super().__init__(*args, **kwargs) - except Exception: - pass - self.dispatcher = getattr(self, "dispatcher", {}) or {} - - -class ExtWorkspaceManagerV1(_LocalProtocolProxy): - interface_name = INTERFACE_NAME - version = INTERFACE_VERSION - event_names = ("workspace", "done", "finished") - - -class ExtWorkspaceHandleV1(_LocalProtocolProxy): - interface_name = WORKSPACE_HANDLE_INTERFACE_NAME - version = INTERFACE_VERSION - event_names = ("name", "coordinates", "state", "removed") - - -def get_display_class() -> Any: - return Display - - -def has_runtime_support() -> bool: - return Display is not None - - -def bind_workspace_manager(registry: Any, global_name: int, version: int) -> Any: - bind = getattr(registry, "bind", None) - if not callable(bind): - return None - - for bindArgs in ( - (global_name, ExtWorkspaceManagerV1, version), - (global_name, version, ExtWorkspaceManagerV1), - (global_name, INTERFACE_NAME, version), - (global_name, version, INTERFACE_NAME), - ): - try: - proxy = bind(*bindArgs) - return _ensure_dispatcher(proxy) - except TypeError: - continue - - return None - - -def _ensure_dispatcher(proxy: Any) -> Any: - if proxy is None: - return None - - if getattr(proxy, "dispatcher", None) is None: - try: - proxy.dispatcher = {} - except Exception: - pass - return proxy - - -__all__ = [ - "ACTIVE_STATE_VALUE", - "Display", - "ExtWorkspaceHandleV1", - "ExtWorkspaceManagerV1", - "INTERFACE_NAME", - "INTERFACE_VERSION", - "WORKSPACE_HANDLE_INTERFACE_NAME", - "bind_workspace_manager", - "get_display_class", - "has_runtime_support", -] diff --git a/src/cthulhu/wayland_protocols/meson.build b/src/cthulhu/wayland_protocols/meson.build deleted file mode 100644 index 71f53e6..0000000 --- a/src/cthulhu/wayland_protocols/meson.build +++ /dev/null @@ -1,9 +0,0 @@ -wayland_protocol_python_sources = files([ - '__init__.py', - 'ext_workspace_v1.py', -]) - -python3.install_sources( - wayland_protocol_python_sources, - subdir: 'cthulhu/wayland_protocols' -) diff --git a/tests/test_compositor_state_adapter_regressions.py b/tests/test_compositor_state_adapter_regressions.py index e6f4b99..383cd6d 100644 --- a/tests/test_compositor_state_adapter_regressions.py +++ b/tests/test_compositor_state_adapter_regressions.py @@ -1,6 +1,4 @@ -import importlib import sys -import types import unittest from pathlib import Path from unittest import mock @@ -10,7 +8,6 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from cthulhu import cthulhu_state from cthulhu import compositor_state_adapter from cthulhu import compositor_state_types -from cthulhu import compositor_state_wayland class FakeWorkspaceBackend: @@ -52,7 +49,7 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase): self.assertTrue(callable(selectedBackend.activate_calls[0])) self.assertEqual(adapter.get_snapshot().backend_name, "selected") - def test_default_workspace_backends_include_wayland_then_null_backend(self) -> None: + def test_default_workspace_backends_include_null_backend(self) -> None: adapter = compositor_state_adapter.CompositorStateAdapter() backend_types = [type(backend) for backend in adapter._workspaceBackends] @@ -60,117 +57,15 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase): self.assertEqual( backend_types, [ - compositor_state_wayland.WaylandSharedProtocolsBackend, - compositor_state_wayland.NullWorkspaceBackend, + compositor_state_adapter.NullWorkspaceBackend, ], ) - def test_wayland_backend_is_unavailable_without_wayland_session(self) -> None: - backend = compositor_state_wayland.WaylandSharedProtocolsBackend() - - with mock.patch.dict(compositor_state_wayland.os.environ, {}, clear=True): - self.assertFalse(backend.is_available("x11")) - self.assertFalse(backend.is_available("wayland")) - - def test_wayland_backend_installs_dispatch_watch_and_processes_runtime_events(self) -> None: - fakeDisplay = mock.Mock() - fakeRegistry = mock.Mock() - fakeDisplay.connect = mock.Mock() - fakeDisplay.get_registry = mock.Mock(return_value=fakeRegistry) - fakeDisplay.get_fd = mock.Mock(return_value=17) - fakeDisplay.roundtrip = mock.Mock() - fakeDisplay.dispatch = mock.Mock(side_effect=[1, 0]) - fakeProtocols = mock.Mock() - fakeProtocols.INTERFACE_NAME = "ext_workspace_manager_v1" - fakeProtocols.INTERFACE_VERSION = 1 - fakeProtocols.ACTIVE_STATE_VALUE = 1 - fakeProtocols.has_runtime_support.return_value = True - fakeProtocols.get_display_class.return_value = mock.Mock(return_value=fakeDisplay) - emitSignal = mock.Mock() - backend = compositor_state_wayland.WaylandSharedProtocolsBackend( - environment={"WAYLAND_DISPLAY": "wayland-0"}, - protocols=fakeProtocols, - ) - - with ( - mock.patch.object(compositor_state_wayland, "get_session_type", return_value="wayland"), - mock.patch.object(compositor_state_wayland.GLib, "io_add_watch", return_value=41) as ioAddWatch, - mock.patch.object(compositor_state_wayland.GLib, "source_remove") as sourceRemove, - ): - backend.activate(emitSignal) - - ioAddWatch.assert_called_once() - watchCallback = ioAddWatch.call_args.args[3] - self.assertTrue(watchCallback(17, compositor_state_wayland.GLib.IO_IN)) - self.assertEqual(fakeDisplay.dispatch.call_count, 2) - - backend.deactivate() - - sourceRemove.assert_called_once_with(41) - - def test_wayland_backend_logs_activation_failure_reason(self) -> None: - fakeDisplay = mock.Mock() - fakeDisplay.connect.side_effect = ValueError("Unable to connect to display") - fakeProtocols = mock.Mock() - fakeProtocols.has_runtime_support.return_value = True - fakeProtocols.get_display_class.return_value = mock.Mock(return_value=fakeDisplay) - backend = compositor_state_wayland.WaylandSharedProtocolsBackend( - environment={"WAYLAND_DISPLAY": "wayland-0"}, - protocols=fakeProtocols, - ) - - with ( - mock.patch.object(compositor_state_wayland, "get_session_type", return_value="wayland"), - mock.patch.object(compositor_state_wayland.debug, "printMessage") as printMessage, - ): - backend.activate(mock.Mock()) - - printMessage.assert_any_call( - compositor_state_wayland.debug.LEVEL_WARNING, - mock.ANY, - True, - ) - self.assertIn("Unable to connect to display", printMessage.call_args_list[-1].args[1]) - self.assertIsNone(backend._display) - - def test_local_ext_workspace_wrapper_supports_base_pywayland_without_distro_protocol_module(self) -> None: - from cthulhu.wayland_protocols import ext_workspace_v1 - - fakeClientModule = types.ModuleType("pywayland.client") - - class FakeDisplay: - pass - - fakeClientModule.Display = FakeDisplay - - def fake_import(moduleName: str, package=None): - if moduleName in ("pywayland.client", "pywayland.client.display"): - return fakeClientModule - raise ImportError(moduleName) - - try: - with mock.patch("importlib.import_module", side_effect=fake_import): - importlib.reload(ext_workspace_v1) - self.assertTrue(ext_workspace_v1.has_runtime_support()) - self.assertIs(ext_workspace_v1.get_display_class(), FakeDisplay) - self.assertIsNotNone(ext_workspace_v1.ExtWorkspaceManagerV1) - self.assertIsNotNone(ext_workspace_v1.ExtWorkspaceHandleV1) - self.assertEqual( - ext_workspace_v1.ExtWorkspaceManagerV1.interface_name, - "ext_workspace_manager_v1", - ) - self.assertEqual( - ext_workspace_v1.ExtWorkspaceHandleV1.interface_name, - "ext_workspace_handle_v1", - ) - finally: - importlib.reload(ext_workspace_v1) - - def test_wayland_backend_is_selected_when_available(self) -> None: + def test_injected_workspace_backend_is_selected_when_available(self) -> None: adapter = compositor_state_adapter.CompositorStateAdapter() selected_backend = mock.Mock(name="selected-backend") fallback_backend = mock.Mock(name="fallback-backend") - selected_backend.name = "wayland-shared-protocols" + selected_backend.name = "external-workspace-signals" selected_backend.is_available.return_value = True fallback_backend.name = "null" fallback_backend.is_available.return_value = True @@ -181,7 +76,7 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase): selected_backend.activate.assert_called_once() fallback_backend.activate.assert_not_called() self.assertTrue(callable(selected_backend.activate.call_args.args[0])) - self.assertEqual(adapter.get_snapshot().backend_name, "wayland-shared-protocols") + self.assertEqual(adapter.get_snapshot().backend_name, "external-workspace-signals") def test_activate_is_idempotent_and_deactivates_previous_backend(self) -> None: backend = FakeWorkspaceBackend(True, "selected") @@ -245,24 +140,31 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase): self.assertEqual(snapshot.active_window_token, "4242:Terminal") self.assertEqual(cthulhu_state.compositorSnapshot.active_window_token, "4242:Terminal") - def test_workspace_backend_normalizes_initial_done_and_handoff(self) -> None: + def test_external_workspace_signal_normalizes_transition_and_handoff(self) -> None: adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[]) events = [] adapter.add_listener(events.append) - workspace = compositor_state_wayland.WaylandSharedProtocolsBackend() - first_workspace = mock.Mock() - second_workspace = mock.Mock() - first_workspace_token = f"workspace:{id(first_workspace)}" adapter._snapshot = compositor_state_types.DesktopContextSnapshot( session_type="wayland", - backend_name="wayland-shared-protocols", + backend_name="external-workspace-signals", ) - with mock.patch.object(workspace, "is_available", return_value=False): - workspace.activate(adapter._handle_workspace_signal) - workspace._handle_workspace_state(first_workspace, "active", True) - workspace._handle_workspace_manager_done() + adapter._handle_workspace_signal( + compositor_state_types.DESKTOP_TRANSITION_STARTED, + ["workspace-1"], + "transition-started", + ) + adapter._handle_workspace_signal( + compositor_state_types.WORKSPACE_STATE_CHANGED, + ["workspace-1"], + "workspace-changed", + ) + adapter._handle_workspace_signal( + compositor_state_types.DESKTOP_TRANSITION_FINISHED, + ["workspace-1"], + "transition-finished", + ) self.assertEqual( [event.type for event in events], @@ -275,18 +177,29 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase): compositor_state_types.FLUSH_STALE_ATSPI_EVENTS, ], ) - self.assertEqual(events[0].snapshot.active_workspace_ids, frozenset({first_workspace_token})) + self.assertEqual(events[0].snapshot.active_workspace_ids, frozenset({"workspace-1"})) self.assertTrue(events[0].snapshot.workspace_transition_pending) - self.assertEqual(events[2].snapshot.active_workspace_ids, frozenset({first_workspace_token})) + self.assertEqual(events[2].snapshot.active_workspace_ids, frozenset({"workspace-1"})) self.assertFalse(adapter.get_snapshot().workspace_transition_pending) - self.assertEqual(adapter.get_snapshot().active_workspace_ids, frozenset({first_workspace_token})) - self.assertEqual(cthulhu_state.compositorSnapshot.active_workspace_ids, frozenset({first_workspace_token})) + self.assertEqual(adapter.get_snapshot().active_workspace_ids, frozenset({"workspace-1"})) + self.assertEqual(cthulhu_state.compositorSnapshot.active_workspace_ids, frozenset({"workspace-1"})) events.clear() - workspace._handle_workspace_id(second_workspace, "ws-2") - workspace._handle_workspace_state(first_workspace, "active", False) - workspace._handle_workspace_state(second_workspace, "active", True) - workspace._handle_workspace_manager_done() + adapter._handle_workspace_signal( + compositor_state_types.DESKTOP_TRANSITION_STARTED, + [], + "transition-started", + ) + adapter._handle_workspace_signal( + compositor_state_types.WORKSPACE_STATE_CHANGED, + ["ws-2"], + "workspace-changed", + ) + adapter._handle_workspace_signal( + compositor_state_types.DESKTOP_TRANSITION_FINISHED, + ["ws-2"], + "transition-finished", + ) event_types = [event.type for event in events] diff --git a/tests/test_sound_preferences_regressions.py b/tests/test_sound_preferences_regressions.py index 531a1c3..bc78f4a 100644 --- a/tests/test_sound_preferences_regressions.py +++ b/tests/test_sound_preferences_regressions.py @@ -7,7 +7,6 @@ from unittest import mock sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) -from cthulhu import cthulhu from cthulhu import settings from cthulhu import cthulhu_gui_prefs from cthulhu import settings_manager