Tests updated. Attempt to fix remaining problems with forward keypress and numlock reporting.

This commit is contained in:
Storm Dragon
2026-05-12 02:32:26 -04:00
parent 38ef1c2d72
commit 878eef5c5b
5 changed files with 171 additions and 10 deletions
@@ -28,15 +28,21 @@ class command:
# Only announce numlock changes if an actual numlock key was pressed # Only announce numlock changes if an actual numlock key was pressed
# AND the LED state actually changed (some numpads send spurious NUMLOCK events) # AND the LED state actually changed (some numpads send spurious NUMLOCK events)
current_input = self.env["input"]["curr_input"] current_input = self.env["input"]["curr_input"]
previous_input = self.env["input"]["prev_input"]
relevant_input = current_input or previous_input
# Check if this is a genuine numlock key press by verifying: # Check if this is a genuine numlock key press by verifying:
# 1. KEY_NUMLOCK is in the current input sequence # 1. KEY_NUMLOCK is in the current input sequence
# 2. The LED state has actually changed # 2. The LED state has actually changed
# 3. This isn't just a side effect from a KP_ key (which some buggy numpads do) # 3. This isn't just a side effect from a KP_ key (which some buggy numpads do)
is_genuine_numlock = ( is_genuine_numlock = (
current_input and relevant_input and
"KEY_NUMLOCK" in current_input and "KEY_NUMLOCK" in relevant_input and
not any(key.startswith("KEY_KP") for key in current_input if isinstance(key, str)) not any(
key.startswith("KEY_KP")
for key in relevant_input
if isinstance(key, str)
)
) )
if is_genuine_numlock: if is_genuine_numlock:
@@ -302,6 +302,35 @@ class driver(screenDriver):
return return
self.env["runtime"]["OutputManager"].interrupt_output() self.env["runtime"]["OutputManager"].interrupt_output()
def handle_stdin_input(self, msg_bytes, event_queue):
if self.synthesize_backspace_shortcut(msg_bytes, event_queue):
return
self.interrupt_output_on_stdin_input(msg_bytes)
self.inject_text_to_screen(msg_bytes)
def synthesize_backspace_shortcut(self, msg_bytes, event_queue):
if msg_bytes not in [b"\x7f", b"\x08"]:
return False
if "KEY_FENRIR" not in self.env["input"]["curr_input"]:
return False
event_time = time.time()
for event_state in [1, 0]:
event_queue.put(
{
"Type": FenrirEventType.keyboard_input,
"data": {
"event_name": "KEY_BACKSPACE",
"event_value": 0,
"event_sec": int(event_time),
"event_usec": int((event_time % 1) * 1000000),
"event_state": event_state,
"event_type": 0,
},
}
)
return True
def get_session_information(self): def get_session_information(self):
self.env["screen"]["autoIgnoreScreens"] = [] self.env["screen"]["autoIgnoreScreens"] = []
self.env["general"]["prev_user"] = getpass.getuser() self.env["general"]["prev_user"] = getpass.getuser()
@@ -434,8 +463,7 @@ class driver(screenDriver):
) )
break break
try: try:
self.interrupt_output_on_stdin_input(msg_bytes) self.handle_stdin_input(msg_bytes, event_queue)
self.inject_text_to_screen(msg_bytes)
except Exception as e: except Exception as e:
self.env["runtime"][ self.env["runtime"][
"DebugManager" "DebugManager"
+65
View File
@@ -0,0 +1,65 @@
import importlib.util
from pathlib import Path
from unittest.mock import Mock
import pytest
def load_numlock_command():
command_path = (
Path(__file__).parents[2]
/ "src"
/ "fenrirscreenreader"
/ "commands"
/ "onKeyInput"
/ "80500-numlock.py"
)
spec = importlib.util.spec_from_file_location(
"numlock_command", command_path
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.command
@pytest.mark.unit
def test_numlock_off_can_be_announced_from_release_event():
output_manager = Mock()
command = load_numlock_command()()
command.initialize(
{
"input": {
"old_num_lock": True,
"new_num_lock": False,
"curr_input": [],
"prev_input": ["KEY_NUMLOCK"],
},
"runtime": {"OutputManager": output_manager},
}
)
command.run()
output_manager.present_text.assert_called_once()
assert output_manager.present_text.call_args.args[0] == "Numlock off"
@pytest.mark.unit
def test_numlock_command_ignores_non_numlock_release():
output_manager = Mock()
command = load_numlock_command()()
command.initialize(
{
"input": {
"old_num_lock": True,
"new_num_lock": False,
"curr_input": [],
"prev_input": ["KEY_KP1"],
},
"runtime": {"OutputManager": output_manager},
}
)
command.run()
output_manager.present_text.assert_not_called()
+35
View File
@@ -1,6 +1,7 @@
import pytest import pytest
from unittest.mock import Mock from unittest.mock import Mock
from fenrirscreenreader.core.eventData import FenrirEventType
from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants
from fenrirscreenreader.screenDriver.ptyDriver import Terminal from fenrirscreenreader.screenDriver.ptyDriver import Terminal
from fenrirscreenreader.screenDriver.ptyDriver import driver as PtyDriver from fenrirscreenreader.screenDriver.ptyDriver import driver as PtyDriver
@@ -109,3 +110,37 @@ def test_pty_stdin_input_leaves_filtered_interrupts_to_key_events():
pty_driver.interrupt_output_on_stdin_input(b"a") pty_driver.interrupt_output_on_stdin_input(b"a")
output_manager.interrupt_output.assert_not_called() output_manager.interrupt_output.assert_not_called()
@pytest.mark.unit
def test_pty_backspace_with_fenrir_key_synthesizes_shortcut_events():
pty_driver = PtyDriver()
event_queue = Mock()
pty_driver.env = {
"input": {"curr_input": ["KEY_FENRIR"]},
}
handled = pty_driver.synthesize_backspace_shortcut(b"\x7f", event_queue)
assert handled is True
assert event_queue.put.call_count == 2
first_event = event_queue.put.call_args_list[0].args[0]
second_event = event_queue.put.call_args_list[1].args[0]
assert first_event["Type"] == FenrirEventType.keyboard_input
assert first_event["data"]["event_name"] == "KEY_BACKSPACE"
assert first_event["data"]["event_state"] == 1
assert second_event["data"]["event_state"] == 0
@pytest.mark.unit
def test_pty_plain_backspace_is_not_synthesized():
pty_driver = PtyDriver()
event_queue = Mock()
pty_driver.env = {
"input": {"curr_input": []},
}
handled = pty_driver.synthesize_backspace_shortcut(b"\x7f", event_queue)
assert handled is False
event_queue.put.assert_not_called()
+32 -5
View File
@@ -167,23 +167,50 @@ def test_x11_build_passive_grabs_for_fenrir_keys_and_shortcuts():
input_manager = Mock(convert_event_name=lambda key: key) input_manager = Mock(convert_event_name=lambda key: key)
x11.env = { x11.env = {
"input": { "input": {
"fenrir_key": ["KEY_KP0", "KEY_CAPSLOCK"], "fenrir_key": ["KEY_KP0", "KEY_META"],
"script_key": [], "script_key": [],
}, },
"rawBindings": { "rawBindings": {
"fenrir_combo": [1, ["KEY_FENRIR", "KEY_KP8"]], "fenrir_combo": [1, ["KEY_FENRIR", "KEY_KP8"]],
"bare_keypad": [1, ["KEY_KP5"]], "bare_keypad": [1, ["KEY_KP5"]],
"ctrl_keypad": [1, ["KEY_CTRL", "KEY_KP2"]], "ctrl_keypad": [1, ["KEY_CTRL", "KEY_KP2"]],
"meta_combo": [1, ["KEY_FENRIR", "KEY_BACKSPACE"]],
}, },
"runtime": {"InputManager": input_manager}, "runtime": {"InputManager": input_manager},
} }
grabs = x11.build_passive_grabs() grabs = x11.build_passive_grabs()
assert ("KEY_KP0", 0) in grabs assert ("KEY_KP0", 0, True) in grabs
assert ("KEY_CAPSLOCK", 0) in grabs assert ("KEY_META", 0, True) in grabs
assert ("KEY_KP5", 0) in grabs assert ("KEY_NUMLOCK", 0, True) in grabs
assert ("KEY_KP2", X.ControlMask) in grabs assert ("KEY_KP5", 0, False) in grabs
assert ("KEY_KP2", X.ControlMask, False) in grabs
assert ("KEY_BACKSPACE", X.Mod4Mask, True) in grabs
@pytest.mark.unit
def test_x11_optional_modifier_masks_can_exclude_numlock():
x11 = X11Driver()
x11.num_lock_mask = X.Mod2Mask
masks = x11.optional_modifier_masks(0, include_num_lock=False)
assert X.Mod2Mask not in masks
assert X.Mod2Mask | X.LockMask not in masks
@pytest.mark.unit
def test_x11_get_led_state_reads_lock_modifiers_from_pointer_mask():
x11 = X11Driver()
x11.num_lock_mask = X.Mod2Mask
pointer = Mock(mask=X.Mod2Mask | X.LockMask)
x11.root = Mock()
x11.root.query_pointer.return_value = pointer
assert x11.get_led_state(0) is True
assert x11.get_led_state(1) is True
assert x11.get_led_state(2) is False
@pytest.mark.unit @pytest.mark.unit