Files
orca/native/computer-use-linux/runtime_keyboard_test.py
T
5fea2893f6 fix(computer-use): send correct named keys and hotkey letters on Linux (#22494)
* fix(computer-use): send correct named keys and hotkey letters on Linux

press_key passed the keysym for named keys (Return, Tab, arrows, Escape)
to AT-SPI as PRESSRELEASE, which expects a hardware keycode, so a
different key was pressed. Synthesize with SYM instead.

hotkey forwarded upper-case letters such as CmdOrCtrl+A to xdotool,
which reads them as shifted keysyms and sends Ctrl+Shift+A. Lower-case
single letters in key combinations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(computer-use): drop the unreachable lone-key hotkey case

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-26 00:13:41 -07:00

72 lines
2.4 KiB
Python

import importlib.util
import sys
import types
import unittest
from pathlib import Path
from unittest import mock
class FakeKeySynthType:
PRESSRELEASE = "pressrelease"
SYM = "sym"
STRING = "string"
KEYSYMS = {"Return": 0xFF0D, "Tab": 0xFF09, "Escape": 0xFF1B, "Left": 0xFF51, "Down": 0xFF54}
def load_runtime():
gi = types.ModuleType("gi")
repository = types.ModuleType("gi.repository")
gi.require_version = lambda *_: None
repository.Atspi = types.SimpleNamespace(KeySynthType=FakeKeySynthType, generate_keyboard_event=None)
repository.Gdk = types.SimpleNamespace(keyval_from_name=KEYSYMS.__getitem__)
repository.GdkPixbuf = types.SimpleNamespace()
gi.repository = repository
sys.modules["gi"] = gi
sys.modules["gi.repository"] = repository
path = Path(__file__).with_name("runtime.py")
spec = importlib.util.spec_from_file_location("orca_linux_runtime_keyboard_test", path)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
runtime = load_runtime()
class RuntimeKeyboardTest(unittest.TestCase):
def setUp(self):
self.events = []
patcher = mock.patch.object(
runtime.Atspi, "generate_keyboard_event", lambda *args: self.events.append(args)
)
patcher.start()
self.addCleanup(patcher.stop)
def test_named_keys_synthesize_the_keysym(self):
for raw, keysym in (("enter", 0xFF0D), ("Tab", 0xFF09), ("esc", 0xFF1B), ("left", 0xFF51), ("Down", 0xFF54)):
self.events.clear()
runtime.press_key(raw)
self.assertEqual(self.events, [(keysym, None, FakeKeySynthType.SYM)], raw)
def run_hotkey(self, raw):
with mock.patch.object(runtime.shutil, "which", return_value="/usr/bin/xdotool"), mock.patch.object(
runtime.subprocess, "run"
) as run:
runtime.hotkey(raw)
return run.call_args.args[0]
def test_hotkey_lowercases_letters_so_xdotool_does_not_add_shift(self):
self.assertEqual(self.run_hotkey("CmdOrCtrl+A"), ["/usr/bin/xdotool", "key", "ctrl+a"])
self.assertEqual(self.run_hotkey("ctrl+shift+T"), ["/usr/bin/xdotool", "key", "ctrl+shift+t"])
def test_hotkey_keeps_named_keys(self):
self.assertEqual(self.run_hotkey("alt+Tab"), ["/usr/bin/xdotool", "key", "alt+Tab"])
if __name__ == "__main__":
unittest.main()