Clean lint surface and drop PyWayland backend
Remove the direct PyWayland workspace backend and local protocol wrapper from the Orca 50 rebase branch. Keep the null/external workspace signal path, clear E402/F401 for this branch, and remove the Arch python-pywayland dependency.
This commit is contained in:
@@ -14,7 +14,7 @@ depends=(
|
|||||||
python-gobject
|
python-gobject
|
||||||
python-cairo
|
python-cairo
|
||||||
gtk3
|
gtk3
|
||||||
python-pywayland
|
|
||||||
# Audio and speech
|
# Audio and speech
|
||||||
speech-dispatcher
|
speech-dispatcher
|
||||||
gstreamer
|
gstreamer
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ optional_modules = {
|
|||||||
'speechd': 'speech output',
|
'speechd': 'speech output',
|
||||||
'dasbus': 'D-Bus remote controller',
|
'dasbus': 'D-Bus remote controller',
|
||||||
'psutil': 'system information commands',
|
'psutil': 'system information commands',
|
||||||
'pywayland': 'Wayland shared workspace backend',
|
|
||||||
'gi.repository.Wnck': 'mouse review',
|
'gi.repository.Wnck': 'mouse review',
|
||||||
'pdf2image': 'PDF processing for OCR',
|
'pdf2image': 'PDF processing for OCR',
|
||||||
'scipy': 'Scientific computing for OCR analysis',
|
'scipy': 'Scientific computing for OCR analysis',
|
||||||
|
|||||||
+6
-5
@@ -20,14 +20,15 @@ dependencies = [
|
|||||||
"louis; extra == 'braille'"
|
"louis; extra == 'braille'"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
|
||||||
wayland = [
|
|
||||||
"pywayland"
|
|
||||||
]
|
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
cthulhu = "cthulhu.cthulhu:main"
|
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]
|
[tool.hatch.version]
|
||||||
path = "src/cthulhu/__init__.py"
|
path = "src/cthulhu/__init__.py"
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ setup_paths()
|
|||||||
|
|
||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
from cthulhu import messages
|
from cthulhu import messages
|
||||||
from cthulhu import settings
|
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
from cthulhu.cthulhu_platform import version, revision
|
from cthulhu.cthulhu_platform import version, revision
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ from . import cmdnames
|
|||||||
from . import keybindings
|
from . import keybindings
|
||||||
from . import input_event
|
from . import input_event
|
||||||
from . import messages
|
from . import messages
|
||||||
from . import settings_manager
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
|
||||||
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
||||||
|
|||||||
@@ -50,10 +50,8 @@ from gi.repository import GLib
|
|||||||
from . import brltablenames
|
from . import brltablenames
|
||||||
from . import cmdnames
|
from . import cmdnames
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import logger
|
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
|
|
||||||
from .ax_event_synthesizer import AXEventSynthesizer
|
from .ax_event_synthesizer import AXEventSynthesizer
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ from . import object_properties
|
|||||||
from . import role_keys
|
from . import role_keys
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ from . import keybindings
|
|||||||
from . import messages
|
from . import messages
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,27 @@ from .compositor_state_types import (
|
|||||||
RESUME_ATSPI_CHURN,
|
RESUME_ATSPI_CHURN,
|
||||||
WORKSPACE_STATE_CHANGED,
|
WORKSPACE_STATE_CHANGED,
|
||||||
)
|
)
|
||||||
from .compositor_state_wayland import NullWorkspaceBackend, WaylandSharedProtocolsBackend
|
|
||||||
from .wnck_support import get_session_type
|
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:
|
class CompositorStateAdapter:
|
||||||
"""Normalizes compositor state and desktop-focus context changes."""
|
"""Normalizes compositor state and desktop-focus context changes."""
|
||||||
|
|
||||||
@@ -42,7 +59,6 @@ class CompositorStateAdapter:
|
|||||||
) -> None:
|
) -> None:
|
||||||
if workspace_backends is None:
|
if workspace_backends is None:
|
||||||
workspace_backends = [
|
workspace_backends = [
|
||||||
WaylandSharedProtocolsBackend(),
|
|
||||||
NullWorkspaceBackend(),
|
NullWorkspaceBackend(),
|
||||||
]
|
]
|
||||||
self._workspaceBackends = list(workspace_backends)
|
self._workspaceBackends = list(workspace_backends)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -36,26 +36,20 @@ __copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import faulthandler
|
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
|
from . import dbus_service
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
from gi.repository.Gio import Settings as GSettings
|
from gi.repository.Gio import Settings as GSettings
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
from .settings_manager import SettingsManager
|
from .settings_manager import SettingsManager
|
||||||
from .script_manager import ScriptManager
|
from .script_manager import ScriptManager
|
||||||
from .plugin_system_manager import PluginSystemManager
|
from .plugin_system_manager import PluginSystemManager
|
||||||
from .input_event import InputEvent
|
|
||||||
from .input_event_manager import InputEventManager
|
|
||||||
from .event_manager import EventManager
|
from .event_manager import EventManager
|
||||||
from .signal_manager import SignalManager
|
from .signal_manager import SignalManager
|
||||||
from .dynamic_api_manager import DynamicApiManager
|
from .dynamic_api_manager import DynamicApiManager
|
||||||
from .speech import Speech
|
|
||||||
from .braille import Braille
|
|
||||||
from .script import Script
|
|
||||||
|
|
||||||
class APIHelper:
|
class APIHelper:
|
||||||
"""Helper class for plugin API interactions, including keybindings."""
|
"""Helper class for plugin API interactions, including keybindings."""
|
||||||
|
|||||||
@@ -33,13 +33,12 @@ __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Dict, Callable, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
from . import cthulhu # Need access to cthulhuApp
|
from . import cthulhu # Need access to cthulhuApp
|
||||||
from . import cmdnames
|
from . import cmdnames
|
||||||
from . import input_event
|
from . import input_event
|
||||||
from . import keybindings
|
from . import keybindings
|
||||||
from . import settings_manager
|
|
||||||
|
|
||||||
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,6 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
import gi
|
|
||||||
from gi.repository import GObject
|
|
||||||
|
|
||||||
from cthulhu import resource_manager
|
from cthulhu import resource_manager
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ from . import messages
|
|||||||
from . import object_properties
|
from . import object_properties
|
||||||
from . import role_keys
|
from . import role_keys
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -45,12 +45,11 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import cthulhu_state
|
|
||||||
|
|
||||||
from .cthulhu_i18n import _
|
from .cthulhu_i18n import _
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .input_event import KeyboardEvent, InputEventHandler
|
pass
|
||||||
|
|
||||||
_keysymsCache = {}
|
_keysymsCache = {}
|
||||||
_keycodeCache = {}
|
_keycodeCache = {}
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ from . import keybindings
|
|||||||
from . import messages
|
from . import messages
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Atspi", "2.0")
|
gi.require_version("Atspi", "2.0")
|
||||||
from gi.repository import Atspi
|
|
||||||
|
|
||||||
import bisect
|
import bisect
|
||||||
import copy
|
import copy
|
||||||
@@ -40,7 +39,6 @@ from . import keybindings
|
|||||||
from . import messages
|
from . import messages
|
||||||
from . import input_event
|
from . import input_event
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import settings_manager
|
|
||||||
from .ax_collection import AXCollection
|
from .ax_collection import AXCollection
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ cthulhu_python_sources = files([
|
|||||||
'command_manager.py',
|
'command_manager.py',
|
||||||
'compositor_state_adapter.py',
|
'compositor_state_adapter.py',
|
||||||
'compositor_state_types.py',
|
'compositor_state_types.py',
|
||||||
'compositor_state_wayland.py',
|
|
||||||
'common_keyboardmap.py',
|
'common_keyboardmap.py',
|
||||||
'cthulhuVersion.py',
|
'cthulhuVersion.py',
|
||||||
'cthulhu_modifier_manager.py',
|
'cthulhu_modifier_manager.py',
|
||||||
@@ -219,5 +218,4 @@ install_data(
|
|||||||
# Subdirectories
|
# Subdirectories
|
||||||
subdir('backends')
|
subdir('backends')
|
||||||
subdir('scripts')
|
subdir('scripts')
|
||||||
subdir('wayland_protocols')
|
|
||||||
subdir('plugins')
|
subdir('plugins')
|
||||||
|
|||||||
@@ -54,8 +54,6 @@ from . import input_event_manager
|
|||||||
from . import messages
|
from . import messages
|
||||||
from . import cthulhu
|
from . import cthulhu
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import script_manager
|
|
||||||
from . import settings_manager
|
|
||||||
from .ax_component import AXComponent
|
from .ax_component import AXComponent
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
"""Base class for Cthulhu plugins using pluggy."""
|
"""Base class for Cthulhu plugins using pluggy."""
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
|
|
||||||
"""AI Assistant plugin package."""
|
"""AI Assistant plugin package."""
|
||||||
|
|
||||||
from .plugin import AIAssistant
|
from .plugin import AIAssistant as AIAssistant
|
||||||
|
|||||||
@@ -238,8 +238,6 @@ class ClaudeCodeProvider(AIProvider):
|
|||||||
def _call_claude_code(self, prompt, image_path=None):
|
def _call_claude_code(self, prompt, image_path=None):
|
||||||
"""Call Claude Code CLI with the prompt and optional image."""
|
"""Call Claude Code CLI with the prompt and optional image."""
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
import os
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Build the command
|
# Build the command
|
||||||
|
|||||||
@@ -11,16 +11,14 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import base64
|
import base64
|
||||||
from io import BytesIO
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gdk', '3.0')
|
gi.require_version('Gdk', '3.0')
|
||||||
gi.require_version('GdkPixbuf', '2.0')
|
gi.require_version('GdkPixbuf', '2.0')
|
||||||
gi.require_version('Atspi', '2.0')
|
gi.require_version('Atspi', '2.0')
|
||||||
gi.require_version('Gtk', '3.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.plugin import Plugin, cthulhu_hookimpl
|
||||||
from cthulhu import settings
|
from cthulhu import settings
|
||||||
@@ -29,7 +27,6 @@ from cthulhu import cthulhu_state
|
|||||||
from cthulhu import ax_object
|
from cthulhu import ax_object
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_value import AXValue
|
from cthulhu.ax_value import AXValue
|
||||||
from cthulhu import ax_utilities
|
|
||||||
from cthulhu.ax_utilities_state import AXUtilitiesState
|
from cthulhu.ax_utilities_state import AXUtilitiesState
|
||||||
from cthulhu.plugins.AIAssistant.ai_providers import create_provider
|
from cthulhu.plugins.AIAssistant.ai_providers import create_provider
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
"""Bye Cthulhu plugin for Cthulhu."""
|
"""Bye Cthulhu plugin for Cthulhu."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
"""Hello Cthulhu plugin for Cthulhu."""
|
"""Hello Cthulhu plugin for Cthulhu."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import weakref
|
|
||||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -20,4 +20,4 @@
|
|||||||
|
|
||||||
"""OCRDesktop plugin package."""
|
"""OCRDesktop plugin package."""
|
||||||
|
|
||||||
from .plugin import OCRDesktop
|
from .plugin import OCRDesktop as OCRDesktop
|
||||||
|
|||||||
@@ -10,15 +10,11 @@
|
|||||||
|
|
||||||
"""OCRDesktop plugin for Cthulhu screen reader."""
|
"""OCRDesktop plugin for Cthulhu screen reader."""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import locale
|
import locale
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
from mimetypes import MimeTypes
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Atspi', '2.0')
|
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!
|
# 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
|
# PIL
|
||||||
try:
|
try:
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -42,34 +43,16 @@ except ImportError:
|
|||||||
PIL_AVAILABLE = False
|
PIL_AVAILABLE = False
|
||||||
|
|
||||||
# pytesseract
|
# pytesseract
|
||||||
try:
|
PYTESSERACT_AVAILABLE = _has_module("pytesseract")
|
||||||
import pytesseract
|
|
||||||
from pytesseract import Output
|
|
||||||
PYTESSERACT_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
PYTESSERACT_AVAILABLE = False
|
|
||||||
|
|
||||||
# pdf2image
|
# pdf2image
|
||||||
try:
|
PDF2IMAGE_AVAILABLE = _has_module("pdf2image")
|
||||||
from pdf2image import convert_from_path
|
|
||||||
PDF2IMAGE_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
PDF2IMAGE_AVAILABLE = False
|
|
||||||
|
|
||||||
# scipy
|
# scipy
|
||||||
try:
|
SCIPY_AVAILABLE = _has_module("scipy")
|
||||||
from scipy.spatial import KDTree
|
|
||||||
SCIPY_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
SCIPY_AVAILABLE = False
|
|
||||||
|
|
||||||
# webcolors
|
# webcolors
|
||||||
try:
|
WEBCOLORS_AVAILABLE = _has_module("webcolors")
|
||||||
from webcolors import CSS3_HEX_TO_NAMES
|
|
||||||
from webcolors import hex_to_rgb
|
|
||||||
WEBCOLORS_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
WEBCOLORS_AVAILABLE = False
|
|
||||||
|
|
||||||
# GTK/GDK
|
# GTK/GDK
|
||||||
try:
|
try:
|
||||||
@@ -664,6 +647,7 @@ class OCRDesktop(Plugin):
|
|||||||
modifiedImg = self._transformImg(img)
|
modifiedImg = self._transformImg(img)
|
||||||
try:
|
try:
|
||||||
# Extract coordinate data using image_to_data
|
# Extract coordinate data using image_to_data
|
||||||
|
import pytesseract
|
||||||
from pytesseract import Output
|
from pytesseract import Output
|
||||||
OCRWords = pytesseract.image_to_data(modifiedImg, output_type=Output.DICT,
|
OCRWords = pytesseract.image_to_data(modifiedImg, output_type=Output.DICT,
|
||||||
lang=self._languageCode, config='--psm 4')
|
lang=self._languageCode, config='--psm 4')
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ to have their speech output go through Cthulhu for both speech and braille suppo
|
|||||||
import logging
|
import logging
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import threading
|
|
||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
|
|||||||
@@ -22,12 +22,9 @@
|
|||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import importlib.util
|
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import _thread
|
|
||||||
import logging
|
import logging
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
import gettext
|
import gettext
|
||||||
|
|
||||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
from .plugin import SpeechHistory
|
from .plugin import SpeechHistory as SpeechHistory
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import os
|
|||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
import logging
|
import logging
|
||||||
import threading
|
|
||||||
from threading import Thread, Lock
|
from threading import Thread, Lock
|
||||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ import math
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from difflib import SequenceMatcher
|
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")
|
gi.require_version("Atspi", "2.0")
|
||||||
from gi.repository import Atspi
|
from gi.repository import Atspi
|
||||||
@@ -63,7 +63,6 @@ from . import cthulhu_state
|
|||||||
from . import object_properties
|
from . import object_properties
|
||||||
from . import pronunciation_dict
|
from . import pronunciation_dict
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from . import text_attribute_names
|
from . import text_attribute_names
|
||||||
from .ax_document_selection import AXDocumentSelection
|
from .ax_document_selection import AXDocumentSelection
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ __license__ = "LGPL"
|
|||||||
import cthulhu.messages as messages
|
import cthulhu.messages as messages
|
||||||
import cthulhu.scripts.default as default
|
import cthulhu.scripts.default as default
|
||||||
import cthulhu.settings as settings
|
import cthulhu.settings as settings
|
||||||
import cthulhu.settings_manager as settings_manager
|
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_value import AXValue
|
from cthulhu.ax_value import AXValue
|
||||||
|
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ from gi.repository import Atspi
|
|||||||
import cthulhu.braille as braille
|
import cthulhu.braille as braille
|
||||||
import cthulhu.braille_generator as braille_generator
|
import cthulhu.braille_generator as braille_generator
|
||||||
import cthulhu.object_properties as object_properties
|
import cthulhu.object_properties as object_properties
|
||||||
import cthulhu.settings_manager as settings_manager
|
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import cthulhu.role_keys as role_keys
|
|||||||
import cthulhu.settings_manager as settings_manager
|
import cthulhu.settings_manager as settings_manager
|
||||||
import cthulhu.speech_generator as speech_generator
|
import cthulhu.speech_generator as speech_generator
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_text import AXText
|
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|
||||||
_settingsManager = settings_manager.getManager()
|
_settingsManager = settings_manager.getManager()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,8 +23,7 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .braille_generator import BrailleGenerator
|
from .braille_generator import BrailleGenerator as BrailleGenerator
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
from .speech_generator import SpeechGenerator
|
from .speech_generator import SpeechGenerator as SpeechGenerator
|
||||||
from .script_utilities import Utilities
|
from .script_utilities import Utilities as Utilities
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ __license__ = "LGPL"
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Atspi", "2.0")
|
gi.require_version("Atspi", "2.0")
|
||||||
from gi.repository import Atspi
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -40,7 +39,6 @@ from cthulhu import cthulhu
|
|||||||
from cthulhu import keybindings
|
from cthulhu import keybindings
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
from cthulhu import script_utilities
|
from cthulhu import script_utilities
|
||||||
from cthulhu import settings_manager
|
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,5 +23,5 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
from .script_utilities import Utilities
|
from .script_utilities import Utilities as Utilities
|
||||||
|
|||||||
@@ -23,5 +23,5 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
from .speech_generator import SpeechGenerator
|
from .speech_generator import SpeechGenerator as SpeechGenerator
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
from .speech_generator import SpeechGenerator
|
from .speech_generator import SpeechGenerator as SpeechGenerator
|
||||||
from .braille_generator import BrailleGenerator
|
from .braille_generator import BrailleGenerator as BrailleGenerator
|
||||||
from .script_utilities import Utilities
|
from .script_utilities import Utilities as Utilities
|
||||||
|
|||||||
@@ -23,4 +23,4 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
|
|||||||
@@ -23,5 +23,5 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
from .script import Script
|
from .script import Script as Script
|
||||||
from .script_utilities import Utilities
|
from .script_utilities import Utilities as Utilities
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ from cthulhu import messages
|
|||||||
from cthulhu import cthulhu
|
from cthulhu import cthulhu
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
from cthulhu import script_utilities
|
from cthulhu import script_utilities
|
||||||
from cthulhu import script_manager
|
|
||||||
from cthulhu import settings_manager
|
|
||||||
from cthulhu.ax_collection import AXCollection
|
from cthulhu.ax_collection import AXCollection
|
||||||
from cthulhu.ax_component import AXComponent
|
from cthulhu.ax_component import AXComponent
|
||||||
from cthulhu.ax_document import AXDocument
|
from cthulhu.ax_document import AXDocument
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ gi.require_version("Atspi", "2.0")
|
|||||||
from gi.repository import Atspi
|
from gi.repository import Atspi
|
||||||
|
|
||||||
from cthulhu import cthulhu
|
from cthulhu import cthulhu
|
||||||
from cthulhu import settings_manager
|
|
||||||
from cthulhu import sound_generator
|
from cthulhu import sound_generator
|
||||||
|
|
||||||
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ from cthulhu import object_properties
|
|||||||
from cthulhu import role_keys
|
from cthulhu import role_keys
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
from cthulhu import settings
|
from cthulhu import settings
|
||||||
from cthulhu import settings_manager
|
|
||||||
from cthulhu import sound_theme_manager
|
|
||||||
from cthulhu import speech_generator
|
from cthulhu import speech_generator
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
import gi
|
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
from typing import Optional, Any, Callable, Tuple
|
from typing import Optional, Any, Callable, Tuple
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import os
|
|||||||
|
|
||||||
from . import generator
|
from . import generator
|
||||||
from . import role_keys
|
from . import role_keys
|
||||||
from . import settings_manager
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
from .sound import Icon, Tone
|
from .sound import Icon, Tone
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ __license__ = "LGPL"
|
|||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import time
|
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 debug
|
||||||
from . import logger
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import speech_generator
|
from . import speech_generator
|
||||||
from . import sound
|
from . import sound
|
||||||
|
|||||||
@@ -40,10 +40,8 @@ from . import input_event
|
|||||||
from . import keybindings
|
from . import keybindings
|
||||||
from . import messages
|
from . import messages
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import script_manager
|
|
||||||
from . import speechserver
|
from . import speechserver
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from . import speech
|
from . import speech
|
||||||
|
|
||||||
# Removed global _settings_manager
|
# Removed global _settings_manager
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ from . import messages
|
|||||||
from . import object_properties
|
from . import object_properties
|
||||||
from . import role_keys
|
from . import role_keys
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from . import sound_theme_manager
|
from . import sound_theme_manager
|
||||||
from . import speech
|
from . import speech
|
||||||
from . import text_attribute_names
|
from . import text_attribute_names
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ from . import speechserver
|
|||||||
from . import settings
|
from . import settings
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import punctuation_settings
|
from . import punctuation_settings
|
||||||
from . import settings_manager
|
|
||||||
from .acss import ACSS
|
from .acss import ACSS
|
||||||
|
|
||||||
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ __license__ = "LGPL"
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Atspi", "2.0")
|
gi.require_version("Atspi", "2.0")
|
||||||
from gi.repository import Atspi
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -42,7 +41,6 @@ from cthulhu import guilabels
|
|||||||
from cthulhu import messages
|
from cthulhu import messages
|
||||||
from cthulhu import object_properties
|
from cthulhu import object_properties
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
from cthulhu import settings_manager
|
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ from gi.repository import Atspi
|
|||||||
|
|
||||||
from . import cmdnames
|
from . import cmdnames
|
||||||
from . import cthulhu
|
from . import cthulhu
|
||||||
from . import dbus_service
|
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import guilabels
|
from . import guilabels
|
||||||
from . import input_event
|
from . import input_event
|
||||||
@@ -48,7 +47,6 @@ from . import object_properties
|
|||||||
from . import cthulhu_gui_navlist
|
from . import cthulhu_gui_navlist
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
|
||||||
from .ax_collection import AXCollection
|
from .ax_collection import AXCollection
|
||||||
from .ax_event_synthesizer import AXEventSynthesizer
|
from .ax_event_synthesizer import AXEventSynthesizer
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
|||||||
@@ -23,8 +23,7 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
import gi, os, locale, gettext
|
import locale, gettext
|
||||||
from gi.repository import GObject
|
|
||||||
import gettext
|
import gettext
|
||||||
|
|
||||||
from cthulhu import cthulhu_i18n
|
from cthulhu import cthulhu_i18n
|
||||||
|
|||||||
@@ -23,11 +23,8 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
import gi, os, locale, gettext
|
import os
|
||||||
from gi.repository import GObject
|
|
||||||
import gettext
|
|
||||||
|
|
||||||
from cthulhu import cthulhu_i18n
|
|
||||||
from cthulhu import translation_context
|
from cthulhu import translation_context
|
||||||
|
|
||||||
class TranslationManager():
|
class TranslationManager():
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
@@ -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",
|
|
||||||
]
|
|
||||||
@@ -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'
|
|
||||||
)
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import importlib
|
|
||||||
import sys
|
import sys
|
||||||
import types
|
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import mock
|
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 cthulhu_state
|
||||||
from cthulhu import compositor_state_adapter
|
from cthulhu import compositor_state_adapter
|
||||||
from cthulhu import compositor_state_types
|
from cthulhu import compositor_state_types
|
||||||
from cthulhu import compositor_state_wayland
|
|
||||||
|
|
||||||
|
|
||||||
class FakeWorkspaceBackend:
|
class FakeWorkspaceBackend:
|
||||||
@@ -52,7 +49,7 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
self.assertTrue(callable(selectedBackend.activate_calls[0]))
|
self.assertTrue(callable(selectedBackend.activate_calls[0]))
|
||||||
self.assertEqual(adapter.get_snapshot().backend_name, "selected")
|
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()
|
adapter = compositor_state_adapter.CompositorStateAdapter()
|
||||||
|
|
||||||
backend_types = [type(backend) for backend in adapter._workspaceBackends]
|
backend_types = [type(backend) for backend in adapter._workspaceBackends]
|
||||||
@@ -60,117 +57,15 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
backend_types,
|
backend_types,
|
||||||
[
|
[
|
||||||
compositor_state_wayland.WaylandSharedProtocolsBackend,
|
compositor_state_adapter.NullWorkspaceBackend,
|
||||||
compositor_state_wayland.NullWorkspaceBackend,
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_wayland_backend_is_unavailable_without_wayland_session(self) -> None:
|
def test_injected_workspace_backend_is_selected_when_available(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:
|
|
||||||
adapter = compositor_state_adapter.CompositorStateAdapter()
|
adapter = compositor_state_adapter.CompositorStateAdapter()
|
||||||
selected_backend = mock.Mock(name="selected-backend")
|
selected_backend = mock.Mock(name="selected-backend")
|
||||||
fallback_backend = mock.Mock(name="fallback-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
|
selected_backend.is_available.return_value = True
|
||||||
fallback_backend.name = "null"
|
fallback_backend.name = "null"
|
||||||
fallback_backend.is_available.return_value = True
|
fallback_backend.is_available.return_value = True
|
||||||
@@ -181,7 +76,7 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
selected_backend.activate.assert_called_once()
|
selected_backend.activate.assert_called_once()
|
||||||
fallback_backend.activate.assert_not_called()
|
fallback_backend.activate.assert_not_called()
|
||||||
self.assertTrue(callable(selected_backend.activate.call_args.args[0]))
|
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:
|
def test_activate_is_idempotent_and_deactivates_previous_backend(self) -> None:
|
||||||
backend = FakeWorkspaceBackend(True, "selected")
|
backend = FakeWorkspaceBackend(True, "selected")
|
||||||
@@ -245,24 +140,31 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
self.assertEqual(snapshot.active_window_token, "4242:Terminal")
|
self.assertEqual(snapshot.active_window_token, "4242:Terminal")
|
||||||
self.assertEqual(cthulhu_state.compositorSnapshot.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=[])
|
adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[])
|
||||||
events = []
|
events = []
|
||||||
adapter.add_listener(events.append)
|
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(
|
adapter._snapshot = compositor_state_types.DesktopContextSnapshot(
|
||||||
session_type="wayland",
|
session_type="wayland",
|
||||||
backend_name="wayland-shared-protocols",
|
backend_name="external-workspace-signals",
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch.object(workspace, "is_available", return_value=False):
|
adapter._handle_workspace_signal(
|
||||||
workspace.activate(adapter._handle_workspace_signal)
|
compositor_state_types.DESKTOP_TRANSITION_STARTED,
|
||||||
workspace._handle_workspace_state(first_workspace, "active", True)
|
["workspace-1"],
|
||||||
workspace._handle_workspace_manager_done()
|
"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(
|
self.assertEqual(
|
||||||
[event.type for event in events],
|
[event.type for event in events],
|
||||||
@@ -275,18 +177,29 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
compositor_state_types.FLUSH_STALE_ATSPI_EVENTS,
|
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.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.assertFalse(adapter.get_snapshot().workspace_transition_pending)
|
||||||
self.assertEqual(adapter.get_snapshot().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({first_workspace_token}))
|
self.assertEqual(cthulhu_state.compositorSnapshot.active_workspace_ids, frozenset({"workspace-1"}))
|
||||||
|
|
||||||
events.clear()
|
events.clear()
|
||||||
workspace._handle_workspace_id(second_workspace, "ws-2")
|
adapter._handle_workspace_signal(
|
||||||
workspace._handle_workspace_state(first_workspace, "active", False)
|
compositor_state_types.DESKTOP_TRANSITION_STARTED,
|
||||||
workspace._handle_workspace_state(second_workspace, "active", True)
|
[],
|
||||||
workspace._handle_workspace_manager_done()
|
"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]
|
event_types = [event.type for event in events]
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from unittest import mock
|
|||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||||
|
|
||||||
from cthulhu import cthulhu
|
|
||||||
from cthulhu import settings
|
from cthulhu import settings
|
||||||
from cthulhu import cthulhu_gui_prefs
|
from cthulhu import cthulhu_gui_prefs
|
||||||
from cthulhu import settings_manager
|
from cthulhu import settings_manager
|
||||||
|
|||||||
Reference in New Issue
Block a user