Support modifier-first Fenrir shortcuts
This commit is contained in:
@@ -46,6 +46,7 @@ class FenrirManager:
|
|||||||
|
|
||||||
self.is_initialized = True
|
self.is_initialized = True
|
||||||
self.modifierInput = False
|
self.modifierInput = False
|
||||||
|
self.modifier_prefix_input = False
|
||||||
self.singleKeyCommand = False
|
self.singleKeyCommand = False
|
||||||
self.command = ""
|
self.command = ""
|
||||||
self.set_process_name()
|
self.set_process_name()
|
||||||
@@ -121,13 +122,16 @@ class FenrirManager:
|
|||||||
self.environment["runtime"][
|
self.environment["runtime"][
|
||||||
"InputManager"
|
"InputManager"
|
||||||
].clear_event_buffer()
|
].clear_event_buffer()
|
||||||
else:
|
# Hold conventional modifier presses long enough to determine
|
||||||
|
# whether a Fenrir or Script modifier follows them.
|
||||||
|
elif not self.modifier_prefix_input:
|
||||||
self.environment["runtime"][
|
self.environment["runtime"][
|
||||||
"InputManager"
|
"InputManager"
|
||||||
].write_event_buffer()
|
].write_event_buffer()
|
||||||
|
|
||||||
if self.environment["runtime"]["InputManager"].no_key_pressed():
|
if self.environment["runtime"]["InputManager"].no_key_pressed():
|
||||||
self.modifierInput = False
|
self.modifierInput = False
|
||||||
|
self.modifier_prefix_input = False
|
||||||
self.singleKeyCommand = False
|
self.singleKeyCommand = False
|
||||||
self.environment["runtime"]["InputManager"].write_event_buffer()
|
self.environment["runtime"]["InputManager"].write_event_buffer()
|
||||||
self.environment["runtime"]["InputManager"].handle_device_grab()
|
self.environment["runtime"]["InputManager"].handle_device_grab()
|
||||||
@@ -281,14 +285,26 @@ class FenrirManager:
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.environment["runtime"]["InputManager"].is_key_press():
|
input_manager = self.environment["runtime"]["InputManager"]
|
||||||
self.modifierInput = self.environment["runtime"][
|
if input_manager.is_key_press():
|
||||||
"InputManager"
|
self.modifierInput = input_manager.curr_input_has_command_modifier()
|
||||||
].curr_key_is_modifier()
|
self.modifier_prefix_input = (
|
||||||
|
not self.modifierInput
|
||||||
|
and input_manager.curr_input_is_modifier_prefix()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if not self.environment["runtime"][
|
if (
|
||||||
"InputManager"
|
not self.modifierInput
|
||||||
].no_key_pressed():
|
and input_manager.curr_input_has_command_modifier()
|
||||||
|
):
|
||||||
|
self.modifierInput = True
|
||||||
|
self.modifier_prefix_input = False
|
||||||
|
elif (
|
||||||
|
self.modifier_prefix_input
|
||||||
|
and not input_manager.curr_input_is_modifier_prefix()
|
||||||
|
):
|
||||||
|
self.modifier_prefix_input = False
|
||||||
|
if not input_manager.no_key_pressed():
|
||||||
if self.singleKeyCommand:
|
if self.singleKeyCommand:
|
||||||
self.singleKeyCommand = (
|
self.singleKeyCommand = (
|
||||||
len(self.environment["input"]["curr_input"]) == 1
|
len(self.environment["input"]["curr_input"]) == 1
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ currentdir = os.path.dirname(
|
|||||||
)
|
)
|
||||||
fenrir_path = os.path.dirname(currentdir)
|
fenrir_path = os.path.dirname(currentdir)
|
||||||
|
|
||||||
|
SHORTCUT_MODIFIER_KEYS = {
|
||||||
|
"KEY_ALT",
|
||||||
|
"KEY_CTRL",
|
||||||
|
"KEY_META",
|
||||||
|
"KEY_SHIFT",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class InputManager:
|
class InputManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -468,6 +475,16 @@ class InputManager:
|
|||||||
self.env["input"]["curr_input"][0] == "KEY_SCRIPT"
|
self.env["input"]["curr_input"][0] == "KEY_SCRIPT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def curr_input_has_command_modifier(self):
|
||||||
|
current_input = self.env["input"]["curr_input"]
|
||||||
|
return "KEY_FENRIR" in current_input or "KEY_SCRIPT" in current_input
|
||||||
|
|
||||||
|
def curr_input_is_modifier_prefix(self):
|
||||||
|
current_input = self.env["input"]["curr_input"]
|
||||||
|
return bool(current_input) and all(
|
||||||
|
key_name in SHORTCUT_MODIFIER_KEYS for key_name in current_input
|
||||||
|
)
|
||||||
|
|
||||||
def is_fenrir_key(self, event_name):
|
def is_fenrir_key(self, event_name):
|
||||||
return event_name in self.env["input"]["fenrir_key"]
|
return event_name in self.env["input"]["fenrir_key"]
|
||||||
|
|
||||||
|
|||||||
@@ -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.08.06"
|
version = "2026.08.12"
|
||||||
code_name = "testing"
|
code_name = "testing"
|
||||||
|
|||||||
@@ -148,6 +148,13 @@ class driver(inputDriver):
|
|||||||
|
|
||||||
modifier_key_names = set(modifier_masks.keys())
|
modifier_key_names = set(modifier_masks.keys())
|
||||||
|
|
||||||
|
canonical_modifier_masks = (
|
||||||
|
("KEY_SHIFT", X.ShiftMask if X else 1),
|
||||||
|
("KEY_CTRL", X.ControlMask if X else 4),
|
||||||
|
("KEY_ALT", X.Mod1Mask if X else 8),
|
||||||
|
("KEY_META", X.Mod4Mask if X else 64),
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
inputDriver.__init__(self)
|
inputDriver.__init__(self)
|
||||||
self.display = None
|
self.display = None
|
||||||
@@ -163,6 +170,8 @@ class driver(inputDriver):
|
|||||||
self.failed_grabs = 0
|
self.failed_grabs = 0
|
||||||
self.modifier_state = 0
|
self.modifier_state = 0
|
||||||
self.modifier_interrupt_state = 0
|
self.modifier_interrupt_state = 0
|
||||||
|
self.command_key_active = False
|
||||||
|
self.chord_modifiers = set()
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
@@ -316,6 +325,11 @@ class driver(inputDriver):
|
|||||||
if not self.should_emit_key(key_name):
|
if not self.should_emit_key(key_name):
|
||||||
return
|
return
|
||||||
self.update_modifier_state_from_event(input_event)
|
self.update_modifier_state_from_event(input_event)
|
||||||
|
is_command_key = self.is_command_key(key_name)
|
||||||
|
if is_command_key and input_event["event_state"] == 1:
|
||||||
|
self.start_command_chord(input_event, event_queue)
|
||||||
|
elif self.command_key_active and not is_command_key:
|
||||||
|
self.track_chord_modifier(input_event)
|
||||||
self.write_debug(
|
self.write_debug(
|
||||||
"x11Driver key event "
|
"x11Driver key event "
|
||||||
+ key_name
|
+ key_name
|
||||||
@@ -332,6 +346,73 @@ class driver(inputDriver):
|
|||||||
"data": input_event,
|
"data": input_event,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if is_command_key and input_event["event_state"] == 0:
|
||||||
|
self.finish_command_chord(input_event, event_queue)
|
||||||
|
|
||||||
|
def is_command_key(self, key_name):
|
||||||
|
if key_name in self.fenrir_keys or key_name in self.env["input"].get(
|
||||||
|
"script_key", []
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
converted_name = self.env["runtime"][
|
||||||
|
"InputManager"
|
||||||
|
].convert_event_name(key_name)
|
||||||
|
return converted_name in {"KEY_FENRIR", "KEY_SCRIPT"}
|
||||||
|
|
||||||
|
def start_command_chord(self, input_event, event_queue):
|
||||||
|
self.command_key_active = True
|
||||||
|
raw_state = input_event.get("event_raw_state", 0)
|
||||||
|
current_input = self.env["input"]["curr_input"]
|
||||||
|
for key_name, modifier_mask in self.canonical_modifier_masks:
|
||||||
|
if not raw_state & modifier_mask:
|
||||||
|
continue
|
||||||
|
self.chord_modifiers.add(key_name)
|
||||||
|
if key_name not in current_input:
|
||||||
|
self.queue_synthetic_modifier(
|
||||||
|
key_name, 1, input_event, event_queue
|
||||||
|
)
|
||||||
|
|
||||||
|
def track_chord_modifier(self, input_event):
|
||||||
|
key_name = self.canonical_modifier_name(input_event["event_name"])
|
||||||
|
if key_name is None:
|
||||||
|
return
|
||||||
|
if input_event["event_state"] == 1:
|
||||||
|
self.chord_modifiers.add(key_name)
|
||||||
|
elif input_event["event_state"] == 0:
|
||||||
|
self.chord_modifiers.discard(key_name)
|
||||||
|
|
||||||
|
def canonical_modifier_name(self, key_name):
|
||||||
|
modifier_mask = self.modifier_masks.get(key_name, 0)
|
||||||
|
for canonical_name, canonical_mask in self.canonical_modifier_masks:
|
||||||
|
if modifier_mask == canonical_mask:
|
||||||
|
return canonical_name
|
||||||
|
return None
|
||||||
|
|
||||||
|
def finish_command_chord(self, input_event, event_queue):
|
||||||
|
for key_name in sorted(self.chord_modifiers):
|
||||||
|
self.queue_synthetic_modifier(key_name, 0, input_event, event_queue)
|
||||||
|
self.chord_modifiers.clear()
|
||||||
|
self.command_key_active = False
|
||||||
|
|
||||||
|
def queue_synthetic_modifier(
|
||||||
|
self, key_name, event_state, source_event, event_queue
|
||||||
|
):
|
||||||
|
synthetic_event = {
|
||||||
|
"event_name": key_name,
|
||||||
|
"event_value": 0,
|
||||||
|
"event_sec": source_event["event_sec"],
|
||||||
|
"event_usec": source_event["event_usec"],
|
||||||
|
"event_state": event_state,
|
||||||
|
"event_type": 0,
|
||||||
|
"event_raw_state": source_event.get("event_raw_state", 0),
|
||||||
|
"event_x_time": source_event.get("event_x_time", X.CurrentTime),
|
||||||
|
}
|
||||||
|
event_queue.put(
|
||||||
|
{
|
||||||
|
"Type": FenrirEventType.keyboard_input,
|
||||||
|
"data": synthetic_event,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def event_is_in_target_tree(self, event):
|
def event_is_in_target_tree(self, event):
|
||||||
event_window = getattr(event, "event", None)
|
event_window = getattr(event, "event", None)
|
||||||
@@ -698,6 +779,8 @@ class driver(inputDriver):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def reset_input_state(self):
|
def reset_input_state(self):
|
||||||
|
self.command_key_active = False
|
||||||
|
self.chord_modifiers.clear()
|
||||||
try:
|
try:
|
||||||
self.env["runtime"]["InputManager"].reset_input_state()
|
self.env["runtime"]["InputManager"].reset_input_state()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.eventData import FenrirEventType
|
||||||
|
from fenrirscreenreader.core.fenrirManager import FenrirManager
|
||||||
|
from fenrirscreenreader.core.inputManager import InputManager
|
||||||
|
|
||||||
|
|
||||||
|
def create_handle_input_manager(no_key_pressed):
|
||||||
|
manager = FenrirManager.__new__(FenrirManager)
|
||||||
|
manager.modifierInput = False
|
||||||
|
manager.modifier_prefix_input = True
|
||||||
|
manager.singleKeyCommand = False
|
||||||
|
manager.command = ""
|
||||||
|
manager.detect_shortcut_command = Mock()
|
||||||
|
manager.update_key_forward = Mock()
|
||||||
|
|
||||||
|
input_manager = Mock(
|
||||||
|
convert_event_name=Mock(side_effect=lambda key_name: key_name),
|
||||||
|
no_key_pressed=Mock(return_value=no_key_pressed),
|
||||||
|
)
|
||||||
|
manager.environment = {
|
||||||
|
"input": {"key_forward": 0},
|
||||||
|
"runtime": {
|
||||||
|
"DebugManager": Mock(write_debug_out=Mock()),
|
||||||
|
"InputManager": input_manager,
|
||||||
|
"ScreenManager": Mock(is_ignored_screen=Mock(return_value=False)),
|
||||||
|
"HelpManager": Mock(is_tutorial_mode=Mock(return_value=False)),
|
||||||
|
"VmenuManager": Mock(get_active=Mock(return_value=False)),
|
||||||
|
"DiffReviewManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
"SpeechHistoryManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
"CommandManager": Mock(execute_default_trigger=Mock()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return manager, input_manager
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_input_manager_recognizes_conventional_modifier_prefix():
|
||||||
|
input_manager = InputManager()
|
||||||
|
input_manager.env = {"input": {"curr_input": ["KEY_CTRL", "KEY_SHIFT"]}}
|
||||||
|
|
||||||
|
assert input_manager.curr_input_is_modifier_prefix() is True
|
||||||
|
|
||||||
|
input_manager.env["input"]["curr_input"].append("KEY_S")
|
||||||
|
|
||||||
|
assert input_manager.curr_input_is_modifier_prefix() is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_first_conventional_modifier_starts_deferred_prefix():
|
||||||
|
manager = FenrirManager.__new__(FenrirManager)
|
||||||
|
manager.modifierInput = False
|
||||||
|
manager.modifier_prefix_input = False
|
||||||
|
manager.singleKeyCommand = False
|
||||||
|
manager.command = ""
|
||||||
|
input_manager = Mock(
|
||||||
|
is_key_press=Mock(return_value=True),
|
||||||
|
curr_input_has_command_modifier=Mock(return_value=False),
|
||||||
|
curr_input_is_modifier_prefix=Mock(return_value=True),
|
||||||
|
get_curr_shortcut=Mock(return_value=str([1, ["KEY_CTRL"]])),
|
||||||
|
get_command_for_shortcut=Mock(return_value=""),
|
||||||
|
)
|
||||||
|
manager.environment = {
|
||||||
|
"input": {
|
||||||
|
"key_forward": 0,
|
||||||
|
"prev_input": [],
|
||||||
|
"curr_input": ["KEY_CTRL"],
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"InputManager": input_manager,
|
||||||
|
"EventManager": Mock(put_to_event_queue=Mock()),
|
||||||
|
"VmenuManager": Mock(get_active=Mock(return_value=False)),
|
||||||
|
"DiffReviewManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
"SpeechHistoryManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.detect_shortcut_command()
|
||||||
|
|
||||||
|
assert manager.modifier_prefix_input is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_fenrir_shortcut_is_dispatched_when_ctrl_is_pressed_first():
|
||||||
|
manager = FenrirManager.__new__(FenrirManager)
|
||||||
|
manager.modifierInput = False
|
||||||
|
manager.modifier_prefix_input = True
|
||||||
|
manager.singleKeyCommand = False
|
||||||
|
manager.command = ""
|
||||||
|
|
||||||
|
event_manager = Mock(put_to_event_queue=Mock())
|
||||||
|
input_manager = Mock(
|
||||||
|
is_key_press=Mock(return_value=False),
|
||||||
|
no_key_pressed=Mock(return_value=False),
|
||||||
|
curr_input_has_command_modifier=Mock(return_value=True),
|
||||||
|
get_curr_shortcut=Mock(
|
||||||
|
side_effect=[
|
||||||
|
str([1, ["KEY_CTRL", "KEY_FENRIR"]]),
|
||||||
|
str([1, ["KEY_CTRL", "KEY_FENRIR", "KEY_S"]]),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
get_command_for_shortcut=Mock(side_effect=["", "SAVE_SETTINGS"]),
|
||||||
|
)
|
||||||
|
manager.environment = {
|
||||||
|
"input": {
|
||||||
|
"key_forward": 0,
|
||||||
|
"prev_input": ["KEY_CTRL"],
|
||||||
|
"curr_input": ["KEY_CTRL", "KEY_FENRIR"],
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"InputManager": input_manager,
|
||||||
|
"EventManager": event_manager,
|
||||||
|
"VmenuManager": Mock(get_active=Mock(return_value=False)),
|
||||||
|
"DiffReviewManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
"SpeechHistoryManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.detect_shortcut_command()
|
||||||
|
|
||||||
|
assert manager.modifierInput is True
|
||||||
|
assert manager.modifier_prefix_input is False
|
||||||
|
event_manager.put_to_event_queue.assert_not_called()
|
||||||
|
|
||||||
|
manager.environment["input"]["prev_input"] = [
|
||||||
|
"KEY_CTRL",
|
||||||
|
"KEY_FENRIR",
|
||||||
|
]
|
||||||
|
manager.environment["input"]["curr_input"] = [
|
||||||
|
"KEY_CTRL",
|
||||||
|
"KEY_FENRIR",
|
||||||
|
"KEY_S",
|
||||||
|
]
|
||||||
|
manager.detect_shortcut_command()
|
||||||
|
|
||||||
|
event_manager.put_to_event_queue.assert_called_once_with(
|
||||||
|
FenrirEventType.execute_command, "SAVE_SETTINGS"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_action_key_disarms_modifier_prefix():
|
||||||
|
manager = FenrirManager.__new__(FenrirManager)
|
||||||
|
manager.modifierInput = False
|
||||||
|
manager.modifier_prefix_input = True
|
||||||
|
manager.singleKeyCommand = False
|
||||||
|
manager.command = ""
|
||||||
|
input_manager = Mock(
|
||||||
|
is_key_press=Mock(return_value=False),
|
||||||
|
no_key_pressed=Mock(return_value=False),
|
||||||
|
curr_input_has_command_modifier=Mock(return_value=False),
|
||||||
|
curr_input_is_modifier_prefix=Mock(return_value=False),
|
||||||
|
get_curr_shortcut=Mock(return_value=str([1, ["KEY_C", "KEY_CTRL"]])),
|
||||||
|
get_command_for_shortcut=Mock(return_value=""),
|
||||||
|
)
|
||||||
|
manager.environment = {
|
||||||
|
"input": {
|
||||||
|
"key_forward": 0,
|
||||||
|
"prev_input": ["KEY_CTRL"],
|
||||||
|
"curr_input": ["KEY_C", "KEY_CTRL"],
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"InputManager": input_manager,
|
||||||
|
"EventManager": Mock(put_to_event_queue=Mock()),
|
||||||
|
"VmenuManager": Mock(get_active=Mock(return_value=False)),
|
||||||
|
"DiffReviewManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
"SpeechHistoryManager": Mock(is_active=Mock(return_value=False)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.detect_shortcut_command()
|
||||||
|
|
||||||
|
assert manager.modifier_prefix_input is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_modifier_prefix_is_not_forwarded_before_chord_is_known():
|
||||||
|
manager, input_manager = create_handle_input_manager(False)
|
||||||
|
|
||||||
|
manager.handle_input(
|
||||||
|
{"data": {"event_name": "KEY_CTRL", "event_state": 1}}
|
||||||
|
)
|
||||||
|
|
||||||
|
input_manager.write_event_buffer.assert_not_called()
|
||||||
|
input_manager.clear_event_buffer.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_modifier_prefix_is_forwarded_when_released_unused():
|
||||||
|
manager, input_manager = create_handle_input_manager(True)
|
||||||
|
|
||||||
|
manager.handle_input(
|
||||||
|
{"data": {"event_name": "KEY_CTRL", "event_state": 0}}
|
||||||
|
)
|
||||||
|
|
||||||
|
input_manager.write_event_buffer.assert_called_once_with()
|
||||||
|
assert manager.modifier_prefix_input is False
|
||||||
@@ -10,6 +10,7 @@ from fenrirscreenreader.core.fenrirManager import FenrirManager
|
|||||||
def test_speech_history_plain_key_modal_command_is_dispatched():
|
def test_speech_history_plain_key_modal_command_is_dispatched():
|
||||||
manager = FenrirManager.__new__(FenrirManager)
|
manager = FenrirManager.__new__(FenrirManager)
|
||||||
manager.modifierInput = False
|
manager.modifierInput = False
|
||||||
|
manager.modifier_prefix_input = False
|
||||||
manager.singleKeyCommand = False
|
manager.singleKeyCommand = False
|
||||||
manager.command = ""
|
manager.command = ""
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ def test_speech_history_plain_key_modal_command_is_dispatched():
|
|||||||
def test_vmenu_plain_key_modal_command_is_dispatched():
|
def test_vmenu_plain_key_modal_command_is_dispatched():
|
||||||
manager = FenrirManager.__new__(FenrirManager)
|
manager = FenrirManager.__new__(FenrirManager)
|
||||||
manager.modifierInput = False
|
manager.modifierInput = False
|
||||||
|
manager.modifier_prefix_input = False
|
||||||
manager.singleKeyCommand = False
|
manager.singleKeyCommand = False
|
||||||
manager.command = ""
|
manager.command = ""
|
||||||
|
|
||||||
|
|||||||
@@ -425,3 +425,63 @@ def test_x11_handle_key_event_keeps_event_buffer_for_input_manager():
|
|||||||
|
|
||||||
assert x11.env["input"]["event_buffer"][0]["event_name"] == "KEY_KP0"
|
assert x11.env["input"]["event_buffer"][0]["event_name"] == "KEY_KP0"
|
||||||
event_queue.put.assert_called_once()
|
event_queue.put.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_x11_fenrir_press_emits_modifiers_from_raw_state_first():
|
||||||
|
x11 = X11Driver()
|
||||||
|
x11.active = True
|
||||||
|
x11.fenrir_keys = {"KEY_KP0"}
|
||||||
|
x11.interesting_keys = {"KEY_KP0"}
|
||||||
|
x11.env = {
|
||||||
|
"input": {
|
||||||
|
"curr_input": [],
|
||||||
|
"event_buffer": [],
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"InputManager": Mock(convert_event_name=lambda key: key),
|
||||||
|
"DebugManager": Mock(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
event_queue = Mock()
|
||||||
|
event = Mock(type=X.KeyPress, detail=90, state=X.ControlMask)
|
||||||
|
x11.keycode_to_key_name = Mock(return_value="KEY_KP0")
|
||||||
|
|
||||||
|
x11.handle_x_event(event, event_queue)
|
||||||
|
|
||||||
|
modifier_event = event_queue.put.call_args_list[0].args[0]
|
||||||
|
fenrir_event = event_queue.put.call_args_list[1].args[0]
|
||||||
|
assert modifier_event["data"]["event_name"] == "KEY_CTRL"
|
||||||
|
assert modifier_event["data"]["event_state"] == 1
|
||||||
|
assert fenrir_event["data"]["event_name"] == "KEY_KP0"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_x11_fenrir_release_balances_synthesized_modifiers():
|
||||||
|
x11 = X11Driver()
|
||||||
|
x11.active = True
|
||||||
|
x11.fenrir_keys = {"KEY_KP0"}
|
||||||
|
x11.interesting_keys = {"KEY_KP0"}
|
||||||
|
x11.command_key_active = True
|
||||||
|
x11.chord_modifiers = {"KEY_CTRL"}
|
||||||
|
x11.env = {
|
||||||
|
"input": {
|
||||||
|
"curr_input": ["KEY_CTRL", "KEY_FENRIR"],
|
||||||
|
"event_buffer": [],
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"InputManager": Mock(convert_event_name=lambda key: key),
|
||||||
|
"DebugManager": Mock(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
event_queue = Mock()
|
||||||
|
event = Mock(type=X.KeyRelease, detail=90, state=X.ControlMask)
|
||||||
|
x11.keycode_to_key_name = Mock(return_value="KEY_KP0")
|
||||||
|
|
||||||
|
x11.handle_x_event(event, event_queue)
|
||||||
|
|
||||||
|
fenrir_event = event_queue.put.call_args_list[0].args[0]
|
||||||
|
modifier_event = event_queue.put.call_args_list[1].args[0]
|
||||||
|
assert fenrir_event["data"]["event_name"] == "KEY_KP0"
|
||||||
|
assert modifier_event["data"]["event_name"] == "KEY_CTRL"
|
||||||
|
assert modifier_event["data"]["event_state"] == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user