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
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
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:
# 1. KEY_NUMLOCK is in the current input sequence
# 2. The LED state has actually changed
# 3. This isn't just a side effect from a KP_ key (which some buggy numpads do)
is_genuine_numlock = (
current_input and
"KEY_NUMLOCK" in current_input and
not any(key.startswith("KEY_KP") for key in current_input if isinstance(key, str))
relevant_input and
"KEY_NUMLOCK" in relevant_input and
not any(
key.startswith("KEY_KP")
for key in relevant_input
if isinstance(key, str)
)
)
if is_genuine_numlock:
@@ -302,6 +302,35 @@ class driver(screenDriver):
return
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):
self.env["screen"]["autoIgnoreScreens"] = []
self.env["general"]["prev_user"] = getpass.getuser()
@@ -434,8 +463,7 @@ class driver(screenDriver):
)
break
try:
self.interrupt_output_on_stdin_input(msg_bytes)
self.inject_text_to_screen(msg_bytes)
self.handle_stdin_input(msg_bytes, event_queue)
except Exception as e:
self.env["runtime"][
"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
from unittest.mock import Mock
from fenrirscreenreader.core.eventData import FenrirEventType
from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants
from fenrirscreenreader.screenDriver.ptyDriver import Terminal
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")
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)
x11.env = {
"input": {
"fenrir_key": ["KEY_KP0", "KEY_CAPSLOCK"],
"fenrir_key": ["KEY_KP0", "KEY_META"],
"script_key": [],
},
"rawBindings": {
"fenrir_combo": [1, ["KEY_FENRIR", "KEY_KP8"]],
"bare_keypad": [1, ["KEY_KP5"]],
"ctrl_keypad": [1, ["KEY_CTRL", "KEY_KP2"]],
"meta_combo": [1, ["KEY_FENRIR", "KEY_BACKSPACE"]],
},
"runtime": {"InputManager": input_manager},
}
grabs = x11.build_passive_grabs()
assert ("KEY_KP0", 0) in grabs
assert ("KEY_CAPSLOCK", 0) in grabs
assert ("KEY_KP5", 0) in grabs
assert ("KEY_KP2", X.ControlMask) in grabs
assert ("KEY_KP0", 0, True) in grabs
assert ("KEY_META", 0, True) in grabs
assert ("KEY_NUMLOCK", 0, True) 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