Install Wnck session support helpers
This commit is contained in:
@@ -108,6 +108,7 @@ cthulhu_python_sources = files([
|
||||
'translation_manager.py',
|
||||
'tutorialgenerator.py',
|
||||
'typing_echo_presenter.py',
|
||||
'wnck_support.py',
|
||||
'where_am_i_presenter.py',
|
||||
])
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ from gi.repository import Atspi
|
||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||
from cthulhu import debug
|
||||
from cthulhu import settings_manager
|
||||
from cthulhu.wnck_support import load_wnck
|
||||
|
||||
# Note: Removed complex beep system - simple announcements work perfectly!
|
||||
|
||||
@@ -68,17 +69,18 @@ try:
|
||||
except ImportError:
|
||||
WEBCOLORS_AVAILABLE = False
|
||||
|
||||
# GTK/GDK/Wnck
|
||||
# GTK/GDK
|
||||
try:
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("Gdk", "3.0")
|
||||
gi.require_version("Wnck", "3.0")
|
||||
from gi.repository import Gtk, Gdk, Wnck
|
||||
from gi.repository import Gtk, Gdk
|
||||
GTK_AVAILABLE = True
|
||||
except ImportError:
|
||||
except Exception:
|
||||
GTK_AVAILABLE = False
|
||||
|
||||
Wnck = load_wnck()
|
||||
WNCK_AVAILABLE = Wnck is not None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class OCRDesktop(Plugin):
|
||||
@@ -151,7 +153,7 @@ class OCRDesktop(Plugin):
|
||||
if not PYTESSERACT_AVAILABLE:
|
||||
missing_deps.append("python-pytesseract")
|
||||
if not GTK_AVAILABLE:
|
||||
missing_deps.append("GTK3/GDK/Wnck")
|
||||
missing_deps.append("GTK3/GDK")
|
||||
|
||||
if missing_deps:
|
||||
debug.printMessage(debug.LEVEL_INFO,
|
||||
@@ -360,6 +362,10 @@ class OCRDesktop(Plugin):
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for screenshots", True)
|
||||
return False
|
||||
|
||||
if not WNCK_AVAILABLE:
|
||||
debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Wnck not available for active window screenshots", True)
|
||||
return False
|
||||
|
||||
try:
|
||||
time.sleep(0.3) # Brief delay
|
||||
gdkCurrDesktop = Gdk.get_default_root_window()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/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.
|
||||
|
||||
"""Helpers for loading Wnck only when the session can support it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import os
|
||||
from types import ModuleType
|
||||
|
||||
|
||||
def get_session_type() -> str:
|
||||
sessionType = (os.environ.get("XDG_SESSION_TYPE") or "").strip().lower()
|
||||
if sessionType:
|
||||
return sessionType
|
||||
if os.environ.get("WAYLAND_DISPLAY"):
|
||||
return "wayland"
|
||||
if os.environ.get("DISPLAY"):
|
||||
return "x11"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def can_use_wnck(session_type: str | None = None) -> bool:
|
||||
return (session_type or get_session_type()).strip().lower() == "x11"
|
||||
|
||||
|
||||
def load_wnck(session_type: str | None = None) -> ModuleType | None:
|
||||
if not can_use_wnck(session_type):
|
||||
return None
|
||||
|
||||
gi = importlib.import_module("gi")
|
||||
gi.require_version("Wnck", "3.0")
|
||||
return importlib.import_module("gi.repository.Wnck")
|
||||
@@ -0,0 +1,66 @@
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Gdk", "3.0")
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
|
||||
from cthulhu import wnck_support
|
||||
from cthulhu.plugins.OCR import plugin as ocr_plugin
|
||||
|
||||
|
||||
class WnckSupportTests(unittest.TestCase):
|
||||
def test_load_wnck_skips_import_when_session_is_wayland(self):
|
||||
with mock.patch.object(
|
||||
wnck_support.importlib,
|
||||
"import_module",
|
||||
side_effect=AssertionError("import_module should not be called"),
|
||||
):
|
||||
self.assertIsNone(wnck_support.load_wnck(session_type="wayland"))
|
||||
|
||||
def test_load_wnck_imports_wnck_when_session_is_x11(self):
|
||||
fakeGi = mock.Mock()
|
||||
fakeWnck = object()
|
||||
|
||||
def importModule(name):
|
||||
if name == "gi":
|
||||
return fakeGi
|
||||
if name == "gi.repository.Wnck":
|
||||
return fakeWnck
|
||||
raise AssertionError(f"Unexpected import: {name}")
|
||||
|
||||
with mock.patch.object(wnck_support.importlib, "import_module", side_effect=importModule):
|
||||
self.assertIs(wnck_support.load_wnck(session_type="x11"), fakeWnck)
|
||||
|
||||
fakeGi.require_version.assert_called_once_with("Wnck", "3.0")
|
||||
|
||||
|
||||
class OCRWnckHandlingTests(unittest.TestCase):
|
||||
def test_check_dependencies_does_not_fail_when_wnck_is_unavailable(self):
|
||||
testPlugin = ocr_plugin.OCRDesktop.__new__(ocr_plugin.OCRDesktop)
|
||||
|
||||
with (
|
||||
mock.patch.object(ocr_plugin, "PIL_AVAILABLE", True),
|
||||
mock.patch.object(ocr_plugin, "PYTESSERACT_AVAILABLE", True),
|
||||
mock.patch.object(ocr_plugin, "GTK_AVAILABLE", True),
|
||||
mock.patch.object(ocr_plugin, "WNCK_AVAILABLE", False),
|
||||
):
|
||||
self.assertTrue(testPlugin._checkDependencies())
|
||||
|
||||
def test_screen_shot_window_returns_false_when_wnck_is_unavailable(self):
|
||||
testPlugin = ocr_plugin.OCRDesktop.__new__(ocr_plugin.OCRDesktop)
|
||||
|
||||
with (
|
||||
mock.patch.object(ocr_plugin, "GTK_AVAILABLE", True),
|
||||
mock.patch.object(ocr_plugin, "WNCK_AVAILABLE", False),
|
||||
):
|
||||
self.assertFalse(testPlugin._screenShotWindow())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user