Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9dd2eb2bf | ||
|
|
d4bec38c18 | ||
|
|
e779ba20ff | ||
|
|
ae255f27cb |
@@ -4,7 +4,7 @@
|
||||
pkgbase=cthulhu-git
|
||||
pkgname=cthulhu-git
|
||||
_pkgname=cthulhu
|
||||
pkgver=2026.07.18.r465.g050ae25
|
||||
pkgver=2026.09.03.r475.gd4bec38
|
||||
pkgrel=1
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
@@ -68,7 +68,7 @@ optdepends=(
|
||||
)
|
||||
optdepends_x86_64=(
|
||||
# The helper is x86_64-only, but this package is architecture-independent.
|
||||
'cthulhu-wine-access-git: Wine, Proton, NVDA Controller, and Tolk integration'
|
||||
'cthulhu-wine-access: Wine, Proton, NVDA Controller, and Tolk integration'
|
||||
)
|
||||
makedepends=(
|
||||
git
|
||||
|
||||
@@ -3,21 +3,19 @@
|
||||
|
||||
pkgname=cthulhu-wine-access-git
|
||||
_pkgname=cthulhu
|
||||
pkgver=2026.07.18.r465.g050ae25
|
||||
pkgver=2026.09.03.r475.gd4bec38
|
||||
pkgrel=1
|
||||
pkgdesc="Optional Wine and Proton accessibility bridge for Cthulhu"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
arch=(x86_64)
|
||||
license=(LGPL-2.1-or-later)
|
||||
depends=(
|
||||
"cthulhu-git=${pkgver}"
|
||||
glib2
|
||||
wine
|
||||
)
|
||||
makedepends=(
|
||||
cmake
|
||||
git
|
||||
meson
|
||||
)
|
||||
provides=(cthulhu-wine-access)
|
||||
conflicts=(cthulhu-wine-access)
|
||||
@@ -40,9 +38,12 @@ pkgver() {
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "${_pkgname}"
|
||||
arch-meson _build -Dwine-access=enabled
|
||||
meson compile -C _build
|
||||
local sourceDir="${srcdir}/${_pkgname}"
|
||||
bash "${sourceDir}/scripts/build-wine-access.sh" \
|
||||
"${sourceDir}/wine-access" \
|
||||
"${sourceDir}/_build/wine-access-build" \
|
||||
"${sourceDir}/_build/cthulhu-wine-access.exe" \
|
||||
"${sourceDir}/_build/cthulhu-wine-access.exe.so"
|
||||
}
|
||||
|
||||
package() {
|
||||
|
||||
@@ -2,30 +2,31 @@
|
||||
# shellcheck shell=bash disable=SC2034,SC2154,SC2164
|
||||
|
||||
pkgname=cthulhu-wine-access
|
||||
pkgver=2026.08.06
|
||||
pkgver=2026.09.09
|
||||
pkgrel=1
|
||||
pkgdesc="Optional Wine and Proton accessibility bridge for Cthulhu"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
arch=(x86_64)
|
||||
license=(LGPL-2.1-or-later)
|
||||
depends=(
|
||||
"cthulhu=${pkgver}"
|
||||
glib2
|
||||
wine
|
||||
)
|
||||
makedepends=(
|
||||
cmake
|
||||
git
|
||||
meson
|
||||
)
|
||||
source=("git+https://git.stormux.org/storm/cthulhu.git#tag=${pkgver}")
|
||||
sha512sums=('SKIP')
|
||||
options=(!lto)
|
||||
|
||||
build() {
|
||||
cd cthulhu
|
||||
arch-meson _build -Dwine-access=enabled
|
||||
meson compile -C _build
|
||||
local sourceDir="${srcdir}/cthulhu"
|
||||
bash "${sourceDir}/scripts/build-wine-access.sh" \
|
||||
"${sourceDir}/wine-access" \
|
||||
"${sourceDir}/_build/wine-access-build" \
|
||||
"${sourceDir}/_build/cthulhu-wine-access.exe" \
|
||||
"${sourceDir}/_build/cthulhu-wine-access.exe.so"
|
||||
}
|
||||
|
||||
package() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
pkgbase=cthulhu
|
||||
pkgname=cthulhu
|
||||
pkgver=2026.08.06
|
||||
pkgver=2026.09.09
|
||||
pkgrel=1
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
cd "$(dirname "$0")" ; CWD=$(pwd)
|
||||
|
||||
PRGNAM=cthulhu
|
||||
VERSION=${VERSION:-2026.08.06}
|
||||
VERSION=${VERSION:-2026.09.09}
|
||||
BUILD=${BUILD:-1}
|
||||
TAG=storm
|
||||
PKGTYPE=txz
|
||||
|
||||
@@ -23,7 +23,6 @@ This package requires the following packages, all available from SlackBuilds.org
|
||||
- gtk3
|
||||
- liblouis
|
||||
- libwnck3
|
||||
- python3-atspi
|
||||
- python3-cairo
|
||||
- python3-dasbus
|
||||
- python3-gobject
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
PRGNAM="cthulhu"
|
||||
VERSION="2026.08.06"
|
||||
VERSION="2026.09.09"
|
||||
HOMEPAGE="https://git.stormux.org/storm/cthulhu"
|
||||
DOWNLOAD="https://git.stormux.org/storm/cthulhu.git"
|
||||
MD5SUM="SKIP"
|
||||
DOWNLOAD_x86_64=""
|
||||
MD5SUM_x86_64=""
|
||||
REQUIRES="at-spi2-core brltty gobject-introspection gstreamer gst-plugins-base gst-plugins-good gtk3 liblouis libwnck3 python3-atspi python3-cairo python3-dasbus python3-gobject python3-pluggy python3-pywayland python3-setproctitle python3-tomlkit speech-dispatcher"
|
||||
REQUIRES="at-spi2-core brltty gobject-introspection gstreamer gst-plugins-base gst-plugins-good gtk3 liblouis libwnck3 python3-cairo python3-dasbus python3-gobject python3-pluggy python3-pywayland python3-setproctitle python3-tomlkit speech-dispatcher"
|
||||
MAINTAINER="Storm Dragon"
|
||||
EMAIL="storm_dragon@stormux.org"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
cd "$(dirname "$0")" ; CWD=$(pwd)
|
||||
|
||||
PRGNAM=cthulhu
|
||||
VERSION=${VERSION:-2026.08.06}
|
||||
VERSION=${VERSION:-2026.09.09}
|
||||
BUILD=${BUILD:-1}
|
||||
TAG=storm
|
||||
PKGTYPE=txz
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
project('cthulhu',
|
||||
version: '2026.08.06',
|
||||
version: '2026.09.09',
|
||||
meson_version: '>= 1.0.0',
|
||||
)
|
||||
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
# Forked from Orca screen reader.
|
||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||
|
||||
version = "2026.08.06"
|
||||
codeName = "master"
|
||||
version = "2026.09.09"
|
||||
codeName = ""
|
||||
|
||||
@@ -46,7 +46,6 @@ import gi
|
||||
gi.require_version("Atspi", "2.0")
|
||||
from gi.repository import Atspi
|
||||
from gi.repository import GLib
|
||||
import pyatspi
|
||||
|
||||
from . import debug
|
||||
from . import focus_manager
|
||||
@@ -75,6 +74,14 @@ _X11_CONSUMABLE_MODIFIER_MASKS = (
|
||||
_X11_SHIFT_LOCK_MASK | _X11_NUM_LOCK_MASK,
|
||||
_X11_SHIFT_MASK | _X11_SHIFT_LOCK_MASK | _X11_NUM_LOCK_MASK,
|
||||
)
|
||||
_X11_CONSUMABLE_EVENT_TYPES = (
|
||||
(1 << int(Atspi.EventType.KEY_PRESSED_EVENT))
|
||||
| (1 << int(Atspi.EventType.KEY_RELEASED_EVENT))
|
||||
)
|
||||
_X11_CONSUMABLE_SYNC_TYPE = (
|
||||
Atspi.KeyListenerSyncType.SYNCHRONOUS
|
||||
| Atspi.KeyListenerSyncType.CANCONSUME
|
||||
)
|
||||
_X11_CONSUMABLE_KEYSYMS = (
|
||||
"a", "b", "c", "d", "e", "f", "g", "h", "i", "k", "l", "m",
|
||||
"o", "p", "q", "r", "s", "t", "u", "v", "x", "y",
|
||||
@@ -110,6 +117,8 @@ class InputEventManager:
|
||||
self._key_pressed_id: int = 0
|
||||
self._key_released_id: int = 0
|
||||
self._selective_legacy_listener_active: bool = False
|
||||
self._selective_legacy_listener: Optional[Atspi.DeviceListener] = None
|
||||
self._selective_legacy_registered_masks: List[int] = []
|
||||
self._selective_legacy_key_set: List[Atspi.KeyDefinition] = []
|
||||
self._selective_legacy_keycodes: set[int] = set()
|
||||
self._pointer_moved_id: int = 0
|
||||
@@ -179,15 +188,7 @@ class InputEventManager:
|
||||
}
|
||||
msg = "INPUT EVENT MANAGER: Adding selective consumable X11 key listener."
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
pyatspi.Registry.registerKeystrokeListener(
|
||||
self._process_selective_legacy_keyboard_event,
|
||||
key_set=self._selective_legacy_key_set,
|
||||
mask=list(_X11_CONSUMABLE_MODIFIER_MASKS),
|
||||
kind=(Atspi.EventType.KEY_PRESSED_EVENT, Atspi.EventType.KEY_RELEASED_EVENT),
|
||||
synchronous=True,
|
||||
preemptive=True,
|
||||
)
|
||||
self._selective_legacy_listener_active = True
|
||||
self._register_selective_legacy_key_listener()
|
||||
else:
|
||||
msg = "INPUT EVENT MANAGER: Using legacy AT-SPI key watcher."
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
@@ -201,16 +202,7 @@ class InputEventManager:
|
||||
|
||||
msg = "INPUT EVENT MANAGER: Stopping key watcher."
|
||||
debug.print_message(debug.LEVEL_INFO, msg, True)
|
||||
if self._selective_legacy_listener_active:
|
||||
pyatspi.Registry.deregisterKeystrokeListener(
|
||||
self._process_selective_legacy_keyboard_event,
|
||||
key_set=self._selective_legacy_key_set,
|
||||
mask=list(_X11_CONSUMABLE_MODIFIER_MASKS),
|
||||
kind=(Atspi.EventType.KEY_PRESSED_EVENT, Atspi.EventType.KEY_RELEASED_EVENT),
|
||||
)
|
||||
self._selective_legacy_listener_active = False
|
||||
self._selective_legacy_key_set = []
|
||||
self._selective_legacy_keycodes.clear()
|
||||
self._deregister_selective_legacy_key_listener()
|
||||
if self._device is not None:
|
||||
for handlerId in (self._key_pressed_id, self._key_released_id):
|
||||
if handlerId:
|
||||
@@ -1284,7 +1276,95 @@ class InputEventManager:
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
def _process_selective_legacy_keyboard_event(self, event: Atspi.DeviceEvent) -> bool:
|
||||
def _register_selective_legacy_key_listener(self) -> bool:
|
||||
"""Registers the consumable X11 listener directly through libatspi."""
|
||||
|
||||
self._selective_legacy_listener = Atspi.DeviceListener.new(
|
||||
self._process_selective_legacy_keyboard_event,
|
||||
None,
|
||||
)
|
||||
if self._selective_legacy_listener is None:
|
||||
msg = "INPUT EVENT MANAGER: Could not create selective consumable X11 listener."
|
||||
debug.print_message(debug.LEVEL_WARNING, msg, True)
|
||||
self._deregister_selective_legacy_key_listener()
|
||||
return False
|
||||
|
||||
for modifierMask in _X11_CONSUMABLE_MODIFIER_MASKS:
|
||||
try:
|
||||
registered = Atspi.register_keystroke_listener(
|
||||
self._selective_legacy_listener,
|
||||
self._selective_legacy_key_set,
|
||||
modifierMask,
|
||||
_X11_CONSUMABLE_EVENT_TYPES,
|
||||
_X11_CONSUMABLE_SYNC_TYPE,
|
||||
)
|
||||
except GLib.GError as error:
|
||||
msg = (
|
||||
"INPUT EVENT MANAGER: Could not register selective consumable "
|
||||
f"X11 listener for modifier mask {modifierMask}: {error}"
|
||||
)
|
||||
debug.print_message(debug.LEVEL_WARNING, msg, True)
|
||||
registered = False
|
||||
|
||||
if not registered:
|
||||
msg = (
|
||||
"INPUT EVENT MANAGER: Selective consumable X11 listener registration "
|
||||
f"failed for modifier mask {modifierMask}."
|
||||
)
|
||||
debug.print_message(debug.LEVEL_WARNING, msg, True)
|
||||
self._deregister_selective_legacy_key_listener()
|
||||
return False
|
||||
|
||||
self._selective_legacy_registered_masks.append(modifierMask)
|
||||
|
||||
self._selective_legacy_listener_active = True
|
||||
return True
|
||||
|
||||
def _deregister_selective_legacy_key_listener(self) -> None:
|
||||
"""Deregisters all masks associated with the consumable X11 listener."""
|
||||
|
||||
listener = self._selective_legacy_listener
|
||||
failedMasks = []
|
||||
if listener is not None:
|
||||
for modifierMask in self._selective_legacy_registered_masks:
|
||||
try:
|
||||
deregistered = Atspi.deregister_keystroke_listener(
|
||||
listener,
|
||||
self._selective_legacy_key_set,
|
||||
modifierMask,
|
||||
_X11_CONSUMABLE_EVENT_TYPES,
|
||||
)
|
||||
except GLib.GError as error:
|
||||
msg = (
|
||||
"INPUT EVENT MANAGER: Could not deregister selective consumable "
|
||||
f"X11 listener for modifier mask {modifierMask}: {error}"
|
||||
)
|
||||
debug.print_message(debug.LEVEL_WARNING, msg, True)
|
||||
deregistered = False
|
||||
|
||||
if not deregistered:
|
||||
failedMasks.append(modifierMask)
|
||||
|
||||
self._selective_legacy_registered_masks = failedMasks
|
||||
if failedMasks:
|
||||
self._selective_legacy_listener_active = True
|
||||
msg = (
|
||||
"INPUT EVENT MANAGER: Preserving selective consumable X11 listener "
|
||||
f"for {len(failedMasks)} failed deregistration(s)."
|
||||
)
|
||||
debug.print_message(debug.LEVEL_WARNING, msg, True)
|
||||
return
|
||||
|
||||
self._selective_legacy_listener_active = False
|
||||
self._selective_legacy_listener = None
|
||||
self._selective_legacy_key_set = []
|
||||
self._selective_legacy_keycodes.clear()
|
||||
|
||||
def _process_selective_legacy_keyboard_event(
|
||||
self,
|
||||
event: Atspi.DeviceEvent,
|
||||
_userData: object = None,
|
||||
) -> bool:
|
||||
"""Returns an X11 consume decision for unmodified browse-navigation keys."""
|
||||
|
||||
pressed = event.type == Atspi.EventType.KEY_PRESSED_EVENT
|
||||
|
||||
@@ -46,26 +46,21 @@ class ArchPkgbuildOptionalWineTests(unittest.TestCase):
|
||||
}
|
||||
self.assertNotIn("wine", dependencyLines)
|
||||
|
||||
def test_main_packages_advertise_matching_optional_helper(self) -> None:
|
||||
stableContents = self.pkgbuilds[0].read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"'cthulhu-wine-access: Wine, Proton, NVDA Controller, and Tolk integration'",
|
||||
self._array_contents(stableContents, "optdepends_x86_64"),
|
||||
)
|
||||
self.assertNotIn(
|
||||
"cthulhu-wine-access",
|
||||
self._array_contents(stableContents, "optdepends"),
|
||||
)
|
||||
|
||||
gitContents = self.pkgbuilds[1].read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"'cthulhu-wine-access-git: Wine, Proton, NVDA Controller, and Tolk integration'",
|
||||
self._array_contents(gitContents, "optdepends_x86_64"),
|
||||
)
|
||||
self.assertNotIn(
|
||||
"cthulhu-wine-access-git",
|
||||
self._array_contents(gitContents, "optdepends"),
|
||||
def test_main_packages_advertise_interchangeable_optional_helper(self) -> None:
|
||||
helperDescription = (
|
||||
"'cthulhu-wine-access: Wine, Proton, NVDA Controller, and Tolk integration'"
|
||||
)
|
||||
for pkgbuild in self.pkgbuilds:
|
||||
with self.subTest(pkgbuild=pkgbuild):
|
||||
contents = pkgbuild.read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
helperDescription,
|
||||
self._array_contents(contents, "optdepends_x86_64"),
|
||||
)
|
||||
self.assertNotIn(
|
||||
"cthulhu-wine-access",
|
||||
self._array_contents(contents, "optdepends"),
|
||||
)
|
||||
|
||||
def test_stable_packages_use_current_release_version(self) -> None:
|
||||
for pkgbuild in (self.pkgbuilds[0], self.helperPkgbuilds[0]):
|
||||
@@ -105,14 +100,31 @@ class ArchPkgbuildOptionalWineTests(unittest.TestCase):
|
||||
def test_stable_helper_has_release_contract(self) -> None:
|
||||
contents = self.helperPkgbuilds[0].read_text(encoding="utf-8")
|
||||
self.assertRegex(contents, r"(?m)^arch=\(x86_64\)$")
|
||||
self.assertRegex(contents, r'(?m)^ "cthulhu=\$\{pkgver\}"$')
|
||||
|
||||
def test_optional_helper_packages_own_the_wine_dependency(self) -> None:
|
||||
def test_optional_helpers_do_not_depend_on_main_package(self) -> None:
|
||||
for pkgbuild in self.helperPkgbuilds:
|
||||
with self.subTest(pkgbuild=pkgbuild):
|
||||
contents = pkgbuild.read_text(encoding="utf-8")
|
||||
self.assertIn("-Dwine-access=enabled", contents)
|
||||
dependencies = self._array_contents(contents, "depends")
|
||||
self.assertNotRegex(
|
||||
dependencies,
|
||||
r"(?m)^\s*['\"]?cthulhu(?:-git)?(?:[<>=].*)?['\"]?\s*$",
|
||||
)
|
||||
|
||||
def test_git_helper_provides_stable_helper_name(self) -> None:
|
||||
contents = self.helperPkgbuilds[1].read_text(encoding="utf-8")
|
||||
self.assertRegex(contents, r"(?m)^provides=\(cthulhu-wine-access\)$")
|
||||
self.assertRegex(contents, r"(?m)^conflicts=\(cthulhu-wine-access\)$")
|
||||
|
||||
def test_optional_helper_packages_build_only_the_helper(self) -> None:
|
||||
for pkgbuild in self.helperPkgbuilds:
|
||||
with self.subTest(pkgbuild=pkgbuild):
|
||||
contents = pkgbuild.read_text(encoding="utf-8")
|
||||
self.assertIn('/scripts/build-wine-access.sh"', contents)
|
||||
self.assertNotIn("arch-meson", contents)
|
||||
self.assertRegex(contents, r"(?m)^ wine$")
|
||||
makeDependencies = self._array_contents(contents, "makedepends")
|
||||
self.assertNotRegex(makeDependencies, r"(?m)^\s*meson\s*$")
|
||||
|
||||
def test_optional_helper_packages_disable_lto(self) -> None:
|
||||
for pkgbuild in self.helperPkgbuilds:
|
||||
|
||||
@@ -118,6 +118,9 @@ class InputEventManagerKeyWatcherTests(unittest.TestCase):
|
||||
device = FakeDevice()
|
||||
fakeAtspi = self._fake_atspi((2, 60, 5), device)
|
||||
fakeAtspi.EventType = types.SimpleNamespace(KEY_PRESSED_EVENT=0, KEY_RELEASED_EVENT=1)
|
||||
fakeAtspi.DeviceListener = types.SimpleNamespace(new=mock.Mock(return_value=object()))
|
||||
fakeAtspi.register_keystroke_listener = mock.Mock(return_value=True)
|
||||
fakeAtspi.deregister_keystroke_listener = mock.Mock(return_value=True)
|
||||
fakeKeySet = [types.SimpleNamespace(keycode=38), types.SimpleNamespace(keycode=56)]
|
||||
|
||||
with (
|
||||
@@ -128,8 +131,6 @@ class InputEventManagerKeyWatcherTests(unittest.TestCase):
|
||||
"_get_x11_consumable_key_set",
|
||||
return_value=fakeKeySet,
|
||||
),
|
||||
mock.patch.object(input_event_manager.pyatspi.Registry, "registerKeystrokeListener") as register,
|
||||
mock.patch.object(input_event_manager.pyatspi.Registry, "deregisterKeystrokeListener") as deregister,
|
||||
mock.patch.object(manager, "_initialize_xterm_handoff") as initializeXtermHandoff,
|
||||
):
|
||||
manager.start_key_watcher()
|
||||
@@ -148,19 +149,18 @@ class InputEventManagerKeyWatcherTests(unittest.TestCase):
|
||||
shiftLockMask | numLockMask,
|
||||
shiftMask | shiftLockMask | numLockMask,
|
||||
]
|
||||
register.assert_called_once_with(
|
||||
listener = fakeAtspi.DeviceListener.new.return_value
|
||||
fakeAtspi.DeviceListener.new.assert_called_once_with(
|
||||
manager._process_selective_legacy_keyboard_event,
|
||||
key_set=fakeKeySet,
|
||||
mask=expectedMasks,
|
||||
kind=(0, 1),
|
||||
synchronous=True,
|
||||
preemptive=True,
|
||||
None,
|
||||
)
|
||||
deregister.assert_called_once_with(
|
||||
manager._process_selective_legacy_keyboard_event,
|
||||
key_set=fakeKeySet,
|
||||
mask=expectedMasks,
|
||||
kind=(0, 1),
|
||||
self.assertEqual(
|
||||
fakeAtspi.register_keystroke_listener.call_args_list,
|
||||
[mock.call(listener, fakeKeySet, mask, 3, 3) for mask in expectedMasks],
|
||||
)
|
||||
self.assertEqual(
|
||||
fakeAtspi.deregister_keystroke_listener.call_args_list,
|
||||
[mock.call(listener, fakeKeySet, mask, 3) for mask in expectedMasks],
|
||||
)
|
||||
initializeXtermHandoff.assert_called_once_with()
|
||||
self.assertEqual(
|
||||
@@ -171,6 +171,83 @@ class InputEventManagerKeyWatcherTests(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_x11_consumable_listener_rolls_back_partial_registration(self) -> None:
|
||||
manager = input_event_manager.InputEventManager()
|
||||
device = FakeDevice()
|
||||
fakeAtspi = self._fake_atspi((2, 60, 5), device)
|
||||
fakeAtspi.EventType = types.SimpleNamespace(KEY_PRESSED_EVENT=0, KEY_RELEASED_EVENT=1)
|
||||
fakeAtspi.DeviceListener = types.SimpleNamespace(new=mock.Mock(return_value=object()))
|
||||
fakeAtspi.register_keystroke_listener = mock.Mock(
|
||||
side_effect=[True, True, False],
|
||||
)
|
||||
fakeAtspi.deregister_keystroke_listener = mock.Mock(return_value=True)
|
||||
fakeKeySet = [types.SimpleNamespace(keycode=56)]
|
||||
|
||||
with (
|
||||
mock.patch.object(input_event_manager, "Atspi", fakeAtspi),
|
||||
mock.patch.dict(input_event_manager.os.environ, {"XDG_SESSION_TYPE": "x11"}),
|
||||
mock.patch.object(
|
||||
input_event_manager,
|
||||
"_get_x11_consumable_key_set",
|
||||
return_value=fakeKeySet,
|
||||
),
|
||||
mock.patch.object(manager, "_initialize_xterm_handoff"),
|
||||
):
|
||||
manager.start_key_watcher()
|
||||
|
||||
expectedMasks = list(input_event_manager._X11_CONSUMABLE_MODIFIER_MASKS)
|
||||
listener = fakeAtspi.DeviceListener.new.return_value
|
||||
self.assertEqual(
|
||||
fakeAtspi.register_keystroke_listener.call_args_list,
|
||||
[
|
||||
mock.call(listener, fakeKeySet, expectedMasks[0], 3, 3),
|
||||
mock.call(listener, fakeKeySet, expectedMasks[1], 3, 3),
|
||||
mock.call(listener, fakeKeySet, expectedMasks[2], 3, 3),
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
fakeAtspi.deregister_keystroke_listener.call_args_list,
|
||||
[
|
||||
mock.call(listener, fakeKeySet, expectedMasks[0], 3),
|
||||
mock.call(listener, fakeKeySet, expectedMasks[1], 3),
|
||||
],
|
||||
)
|
||||
self.assertFalse(manager._selective_legacy_listener_active)
|
||||
self.assertEqual(manager._selective_legacy_key_set, [])
|
||||
self.assertEqual(manager._selective_legacy_keycodes, set())
|
||||
|
||||
def test_x11_consumable_listener_preserves_failed_deregistration_for_retry(self) -> None:
|
||||
manager = input_event_manager.InputEventManager()
|
||||
listener = object()
|
||||
keySet = [types.SimpleNamespace(keycode=56)]
|
||||
manager._selective_legacy_listener_active = True
|
||||
manager._selective_legacy_listener = listener
|
||||
manager._selective_legacy_registered_masks = [0]
|
||||
manager._selective_legacy_key_set = keySet
|
||||
manager._selective_legacy_keycodes = {56}
|
||||
|
||||
with mock.patch.object(
|
||||
input_event_manager.Atspi,
|
||||
"deregister_keystroke_listener",
|
||||
side_effect=[False, True],
|
||||
) as deregister:
|
||||
manager._deregister_selective_legacy_key_listener()
|
||||
|
||||
self.assertTrue(manager._selective_legacy_listener_active)
|
||||
self.assertIs(manager._selective_legacy_listener, listener)
|
||||
self.assertEqual(manager._selective_legacy_registered_masks, [0])
|
||||
self.assertEqual(manager._selective_legacy_key_set, keySet)
|
||||
self.assertEqual(manager._selective_legacy_keycodes, {56})
|
||||
|
||||
manager._deregister_selective_legacy_key_listener()
|
||||
|
||||
self.assertEqual(deregister.call_count, 2)
|
||||
self.assertFalse(manager._selective_legacy_listener_active)
|
||||
self.assertIsNone(manager._selective_legacy_listener)
|
||||
self.assertEqual(manager._selective_legacy_registered_masks, [])
|
||||
self.assertEqual(manager._selective_legacy_key_set, [])
|
||||
self.assertEqual(manager._selective_legacy_keycodes, set())
|
||||
|
||||
def test_x11_consumable_keycodes_include_structural_navigation_but_not_tab(self) -> None:
|
||||
keycodes = {
|
||||
keysym: index + 20
|
||||
|
||||
Reference in New Issue
Block a user