Fix X11 key injection modifier release handling
This commit is contained in:
parent
993f51712b
commit
98b13d1069
2 changed files with 54 additions and 5 deletions
|
|
@ -437,11 +437,17 @@ class X11Adapter:
|
||||||
if keycode == 0:
|
if keycode == 0:
|
||||||
continue
|
continue
|
||||||
keycodes.append(keycode)
|
keycodes.append(keycode)
|
||||||
for code in keycodes:
|
pressed: list[int] = []
|
||||||
xtest.fake_input(dpy, X.KeyPress, code)
|
try:
|
||||||
for code in reversed(keycodes):
|
for code in keycodes:
|
||||||
xtest.fake_input(dpy, X.KeyRelease, code)
|
xtest.fake_input(dpy, X.KeyPress, code)
|
||||||
dpy.flush()
|
pressed.append(code)
|
||||||
|
finally:
|
||||||
|
for code in reversed(pressed):
|
||||||
|
xtest.fake_input(dpy, X.KeyRelease, code)
|
||||||
|
# Ensure all synthetic key events are processed before the display
|
||||||
|
# may be closed by callers.
|
||||||
|
dpy.sync()
|
||||||
|
|
||||||
def _keysym_for_char(self, ch: str) -> tuple[int | None, bool]:
|
def _keysym_for_char(self, ch: str) -> tuple[int | None, bool]:
|
||||||
if ch.isupper():
|
if ch.isupper():
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,49 @@ class DesktopX11Tests(unittest.TestCase):
|
||||||
|
|
||||||
display_obj.close.assert_called_once_with()
|
display_obj.close.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch("desktop_x11.xtest.fake_input")
|
||||||
|
def test_send_combo_releases_keys_and_syncs(self, fake_input):
|
||||||
|
adapter = self._adapter()
|
||||||
|
dpy = MagicMock()
|
||||||
|
dpy.keysym_to_keycode.side_effect = [10, 20, 30]
|
||||||
|
|
||||||
|
adapter._send_combo(dpy, ["Control_L", "Shift_L", "v"])
|
||||||
|
|
||||||
|
expected_calls = [
|
||||||
|
((dpy, 2, 10),),
|
||||||
|
((dpy, 2, 20),),
|
||||||
|
((dpy, 2, 30),),
|
||||||
|
((dpy, 3, 30),),
|
||||||
|
((dpy, 3, 20),),
|
||||||
|
((dpy, 3, 10),),
|
||||||
|
]
|
||||||
|
self.assertEqual(fake_input.call_args_list, expected_calls)
|
||||||
|
dpy.sync.assert_called_once_with()
|
||||||
|
|
||||||
|
@patch("desktop_x11.xtest.fake_input")
|
||||||
|
def test_send_combo_releases_pressed_keys_when_press_fails(self, fake_input):
|
||||||
|
adapter = self._adapter()
|
||||||
|
dpy = MagicMock()
|
||||||
|
dpy.keysym_to_keycode.side_effect = [10, 20]
|
||||||
|
|
||||||
|
def _fake_input(_dpy, event_type, code):
|
||||||
|
if event_type == 2 and code == 20:
|
||||||
|
raise RuntimeError("press failed")
|
||||||
|
return None
|
||||||
|
|
||||||
|
fake_input.side_effect = _fake_input
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(RuntimeError, "press failed"):
|
||||||
|
adapter._send_combo(dpy, ["Control_L", "v"])
|
||||||
|
|
||||||
|
expected_calls = [
|
||||||
|
((dpy, 2, 10),),
|
||||||
|
((dpy, 2, 20),),
|
||||||
|
((dpy, 3, 10),),
|
||||||
|
]
|
||||||
|
self.assertEqual(fake_input.call_args_list, expected_calls)
|
||||||
|
dpy.sync.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue