Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't suppress combinations that map to an analog axis #1054

Merged
merged 9 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion inputremapper/configs/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,12 @@ def get_output_type_code(self) -> Optional[Tuple[int, int]]:
otherwise looks the output_symbol up in the keyboard_layout
return None for unknown symbols and macros
"""
if self.output_code and self.output_type:
if self.output_code is not None and self.output_type is not None:
return self.output_type, self.output_code

if self.output_symbol and not Parser.is_this_a_macro(self.output_symbol):
return EV_KEY, keyboard_layout.get(self.output_symbol)

return None

def get_output_name_constant(self) -> str:
Expand Down
12 changes: 10 additions & 2 deletions inputremapper/injection/mapping_handlers/combination_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ def _handle_freshly_activated(
# Send key up events to the forwarded uinput if configured to do so.
self._forward_release()

logger.debug("Sending %s to sub-handler", self.mapping.input_combination)
logger.debug(
"Sending %s to sub-handler %s",
repr(event),
repr(self._sub_handler),
)
self._output_previously_active = bool(event.value)
sub_handler_result = self._sub_handler.notify(event, source, suppress)

Expand All @@ -176,7 +180,11 @@ def _handle_freshly_deactivated(
# In the case of output axis, this will enable us to activate multiple
# axis with the same button.

logger.debug("Sending %s to sub-handler", self.mapping.input_combination)
logger.debug(
"Sending %s to sub-handler %s",
repr(event),
repr(self._sub_handler),
)
self._output_previously_active = bool(event.value)
self._sub_handler.notify(event, source, suppress=False)

Expand Down
20 changes: 14 additions & 6 deletions inputremapper/injection/mapping_handlers/hierarchy_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,21 @@ def notify(
if event.input_match_hash != self._input_config.input_match_hash:
return False

success = False
handled = False
for handler in self.handlers:
if not success:
success = handler.notify(event, source)
else:
handler.notify(event, source, suppress=True)
return success
if handled:
# To allow an arbitrary number of output axes to be activated at the
# same time, we don't suppress them.
handler.notify(
event,
source,
suppress=not handler.mapping.input_combination.defines_analog_input,
)
continue

handled = handler.notify(event, source)

return handled

def reset(self) -> None:
for sub_handler in self.handlers:
Expand Down
2 changes: 1 addition & 1 deletion inputremapper/injection/mapping_handlers/mapping_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def _create_hierarchy_handlers(
)
sub_handlers: List[MappingHandler] = []
for combination in sorted_combinations:
sub_handlers.append(*handlers[combination])
sub_handlers.extend(handlers[combination])

sorted_handlers.add(
HierarchyHandler(
Expand Down
74 changes: 74 additions & 0 deletions tests/unit/test_event_pipeline/test_event_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,80 @@ async def test_key_axis_combination_to_disable(self):
],
)

async def test_multiple_axis_in_hierarchy_handler(self):
preset = Preset()

# Add two mappings that map EV_REL to EV_ABS. We want to test that they don't
# suppress each other when they are part of a hierarchy handler. So having at
# least two of them is important for this test.
cutoff = 2
for in_, out in [(REL_X, ABS_X), (REL_Y, ABS_Y)]:
input_combination = InputCombination(
[
InputConfig(type=EV_KEY, code=KEY_A),
InputConfig(type=EV_REL, code=in_),
]
)
mapping = Mapping(
input_combination=input_combination.to_config(),
target_uinput="gamepad",
output_type=EV_ABS,
output_code=out,
gain=0.5,
rel_to_abs_input_cutoff=cutoff,
release_timeout=0.1,
deadzone=0,
)
preset.add(mapping)

# Add a non-analog mapping, to make sure a HierarchyHandler exists
key_input = InputCombination(
(
InputConfig(type=EV_KEY, code=KEY_A),
InputConfig(type=EV_KEY, code=KEY_B),
)
)
key_mapping = Mapping(
input_combination=key_input.to_config(),
target_uinput="keyboard",
output_type=EV_KEY,
output_code=KEY_C,
)
preset.add(key_mapping)

event_reader = self.create_event_reader(preset, fixtures.gamepad)

# Trigger all of them at the same time
value = int(REL_XY_SCALING * cutoff)
await asyncio.sleep(0.1)
await self.send_events(
[
InputEvent(0, 0, EV_KEY, KEY_A, 1),
InputEvent(0, 0, EV_REL, REL_X, value),
InputEvent(0, 0, EV_REL, REL_Y, value),
InputEvent(0, 0, EV_KEY, KEY_B, 1),
InputEvent(0, 0, EV_KEY, KEY_B, 0),
],
event_reader,
)

await asyncio.sleep(0.4)

# We expect all of it to be present. No mapping was suppressed.
# So we can trigger combinations that inject keys while a joystick is being
# simulated in multiple directions.
self.assertEqual(
uinput_write_history,
[
InputEvent.from_tuple((EV_ABS, ABS_X, MAX_ABS / 2)),
InputEvent.from_tuple((EV_ABS, ABS_Y, MAX_ABS / 2)),
InputEvent.from_tuple((EV_KEY, KEY_C, 1)),
InputEvent.from_tuple((EV_KEY, KEY_C, 0)),
InputEvent.from_tuple((EV_ABS, ABS_X, 0)),
InputEvent.from_tuple((EV_ABS, ABS_Y, 0)),
],
)


@test_setup
class TestAbsToAbs(EventPipelineTestBase):
Expand Down