Fixed keyboard handling regression.
This commit is contained in:
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2026.05.30"
|
version = "2026.05.31"
|
||||||
code_name = "testing"
|
code_name = "testing"
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ class driver(inputDriver):
|
|||||||
self.fenrir_keys = set()
|
self.fenrir_keys = set()
|
||||||
self.failed_grabs = 0
|
self.failed_grabs = 0
|
||||||
self.modifier_state = 0
|
self.modifier_state = 0
|
||||||
|
self.modifier_interrupt_state = 0
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
@@ -194,6 +195,7 @@ class driver(inputDriver):
|
|||||||
)
|
)
|
||||||
self.num_lock_mask = self.find_num_lock_mask()
|
self.num_lock_mask = self.find_num_lock_mask()
|
||||||
self.refresh_modifier_state()
|
self.refresh_modifier_state()
|
||||||
|
self.modifier_interrupt_state = self.modifier_state
|
||||||
self.refresh_interesting_keys()
|
self.refresh_interesting_keys()
|
||||||
self.refresh_grabs(force=True)
|
self.refresh_grabs(force=True)
|
||||||
self.env["runtime"]["ProcessManager"].add_custom_event_thread(
|
self.env["runtime"]["ProcessManager"].add_custom_event_thread(
|
||||||
@@ -274,6 +276,7 @@ class driver(inputDriver):
|
|||||||
while active.value:
|
while active.value:
|
||||||
try:
|
try:
|
||||||
self.refresh_grabs()
|
self.refresh_grabs()
|
||||||
|
self.poll_modifier_interrupt_keys()
|
||||||
if not self.display.pending_events():
|
if not self.display.pending_events():
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
continue
|
continue
|
||||||
@@ -371,6 +374,56 @@ class driver(inputDriver):
|
|||||||
"event_x_time": getattr(event, "time", X.CurrentTime),
|
"event_x_time": getattr(event, "time", X.CurrentTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def poll_modifier_interrupt_keys(self):
|
||||||
|
if not self.active or not self.should_poll_modifier_interrupt_keys():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
pointer = self.root.query_pointer()
|
||||||
|
current_state = getattr(pointer, "mask", 0)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
previous_state = self.modifier_interrupt_state
|
||||||
|
self.modifier_interrupt_state = current_state
|
||||||
|
self.modifier_state = current_state
|
||||||
|
for key_name, modifier_mask in self.interrupt_modifier_masks():
|
||||||
|
if current_state & modifier_mask and not previous_state & modifier_mask:
|
||||||
|
self.interrupt_output_on_modifier_key(key_name)
|
||||||
|
|
||||||
|
def should_poll_modifier_interrupt_keys(self):
|
||||||
|
try:
|
||||||
|
settings_manager = self.env["runtime"]["SettingsManager"]
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
if not settings_manager.get_setting_as_bool(
|
||||||
|
"keyboard", "interrupt_on_key_press"
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
return (
|
||||||
|
settings_manager.get_setting(
|
||||||
|
"keyboard", "interrupt_on_key_press_filter"
|
||||||
|
).strip()
|
||||||
|
== ""
|
||||||
|
)
|
||||||
|
|
||||||
|
def interrupt_modifier_masks(self):
|
||||||
|
return [
|
||||||
|
("KEY_CTRL", X.ControlMask),
|
||||||
|
("KEY_SHIFT", X.ShiftMask),
|
||||||
|
("KEY_ALT", X.Mod1Mask),
|
||||||
|
]
|
||||||
|
|
||||||
|
def interrupt_output_on_modifier_key(self, key_name):
|
||||||
|
try:
|
||||||
|
self.env["runtime"]["OutputManager"].interrupt_output_async()
|
||||||
|
except Exception as e:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"x11Driver modifier interrupt failed for "
|
||||||
|
+ key_name
|
||||||
|
+ ": "
|
||||||
|
+ str(e),
|
||||||
|
debug.DebugLevel.ERROR,
|
||||||
|
)
|
||||||
|
|
||||||
def refresh_modifier_state(self):
|
def refresh_modifier_state(self):
|
||||||
try:
|
try:
|
||||||
pointer = self.root.query_pointer()
|
pointer = self.root.query_pointer()
|
||||||
|
|||||||
@@ -217,6 +217,47 @@ def test_x11_build_passive_grabs_for_fenrir_keys_and_shortcuts():
|
|||||||
assert ("KEY_BACKSPACE", X.Mod4Mask, True) in grabs
|
assert ("KEY_BACKSPACE", X.Mod4Mask, True) in grabs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"modifier_mask",
|
||||||
|
[X.ControlMask, X.ShiftMask, X.Mod1Mask],
|
||||||
|
)
|
||||||
|
def test_x11_poll_modifier_interrupt_keys_interrupts_without_input_events(
|
||||||
|
modifier_mask,
|
||||||
|
):
|
||||||
|
x11 = X11Driver()
|
||||||
|
x11.active = True
|
||||||
|
x11.modifier_interrupt_state = 0
|
||||||
|
x11.modifier_state = 0
|
||||||
|
x11.root = Mock()
|
||||||
|
x11.root.query_pointer.return_value = Mock(mask=modifier_mask)
|
||||||
|
settings_manager = Mock()
|
||||||
|
settings_manager.get_setting_as_bool.return_value = True
|
||||||
|
settings_manager.get_setting.return_value = ""
|
||||||
|
output_manager = Mock()
|
||||||
|
x11.env = {
|
||||||
|
"input": {"event_buffer": []},
|
||||||
|
"runtime": {
|
||||||
|
"SettingsManager": settings_manager,
|
||||||
|
"OutputManager": output_manager,
|
||||||
|
"DebugManager": Mock(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
x11.poll_modifier_interrupt_keys()
|
||||||
|
|
||||||
|
output_manager.interrupt_output_async.assert_called_once()
|
||||||
|
assert x11.env["input"]["event_buffer"] == []
|
||||||
|
|
||||||
|
output_manager.interrupt_output_async.reset_mock()
|
||||||
|
x11.root.query_pointer.return_value = Mock(mask=0)
|
||||||
|
|
||||||
|
x11.poll_modifier_interrupt_keys()
|
||||||
|
|
||||||
|
output_manager.interrupt_output_async.assert_not_called()
|
||||||
|
assert x11.env["input"]["event_buffer"] == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
def test_x11_optional_modifier_masks_can_exclude_numlock():
|
def test_x11_optional_modifier_masks_can_exclude_numlock():
|
||||||
x11 = X11Driver()
|
x11 = X11Driver()
|
||||||
|
|||||||
Reference in New Issue
Block a user