From cb7cc67e7f5bdefb07f747a29eee3c6b2acb0a07 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Sat, 5 Dec 2020 11:58:29 +0100 Subject: [PATCH] repeating macros while holding keys --- README.md | 2 +- keymapper/dev/keycode_mapper.py | 16 +++++++++++- keymapper/dev/macros.py | 34 +++++++++++++++++--------- tests/test.py | 5 +++- tests/testcases/test_keycode_mapper.py | 1 + 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8e897bd1..ba7d33d9 100644 --- a/README.md +++ b/README.md @@ -117,11 +117,11 @@ cd key-mapper && sudo python3 setup.py install - [x] support timed macros, maybe using some sort of syntax - [x] add to the AUR, provide .deb file - [x] basic support for gamepads as keyboard and mouse combi +- [x] executing a macro forever while holding down the key - [ ] map D-Pad and Joystick directions as buttons, joystick purpose via config - [ ] automatically load presets when devices get plugged in after login - [ ] option to write hwdb configs for lower level mappings ([Remapping keys using hwdb files](https://www.reddit.com/r/linux_gaming/comments/k3h9qv/remapping_keys_using_hwdb_files/)) - [ ] mapping a combined button press to a key -- [ ] executing a macro while holding down the key ## Tests diff --git a/keymapper/dev/keycode_mapper.py b/keymapper/dev/keycode_mapper.py index dae73fbe..f5ee2307 100644 --- a/keymapper/dev/keycode_mapper.py +++ b/keymapper/dev/keycode_mapper.py @@ -65,20 +65,34 @@ def handle_keycode(code_to_code, macros, event, uinput): mapping of linux-keycode to linux-keycode macros : dict mapping of linux-keycode to _Macro objects + event : evdev.InputEvent """ if event.value == 2: - # button-hold event + # button-hold event. Linux seems to create them on its own, no need + # to inject them. return input_keycode = event.code input_type = event.type if input_keycode in macros: + if event.value == 0: + # key-release event. Tell the macro for that keycode + # that the key is released and let it decide what to with that + # information. + macro = active_macros.get(input_keycode) + # TODO test + if macro is not None: + macro.release_key() + if event.value != 1: # only key-down events trigger macros return macro = macros[input_keycode] + active_macros[input_keycode] = macro + # TODO test that holding is true + macro.holding = True logger.spam( 'got code:%s value:%s, maps to macro %s', input_keycode, diff --git a/keymapper/dev/macros.py b/keymapper/dev/macros.py index 89e32fba..7f46f56e 100644 --- a/keymapper/dev/macros.py +++ b/keymapper/dev/macros.py @@ -80,6 +80,8 @@ class _Macro: # all required capabilities, without those of child macros self.capabilities = set() + + # TODO test that child_macros is properly populated self.child_macros = [] def get_capabilities(self): @@ -100,9 +102,8 @@ class _Macro: macro will write to this function once executed with `.run()`. """ self.handler = handler - for task_type, task in self.tasks: - if task_type == CHILD_MACRO: - task.set_handler(handler) + for macro in self.child_macros: + macro.set_handler(handler) async def run(self): """Run the macro.""" @@ -113,9 +114,6 @@ class _Macro: logger.debug('Macro execution stopped') break - if task_type == CHILD_MACRO: - task = task.run - coroutine = task() if asyncio.iscoroutine(coroutine): await coroutine @@ -125,7 +123,13 @@ class _Macro: # TODO test self.running = False - async def hold(self, macro): + def release_key(self): + """Tell all child macros that the key was released.""" + self.holding = False + for macro in self.child_macros: + macro.release_key() + + def hold(self, macro): """Loops the execution until key release.""" if not isinstance(macro, _Macro): raise ValueError( @@ -134,12 +138,15 @@ class _Macro: ) # TODO test - def task(): + + async def task(): while self.holding and self.running: - await self.run() + await macro.run() self.tasks.append((REPEAT, task)) + self.child_macros.append(macro) + return self def modify(self, modifier, macro): @@ -164,9 +171,11 @@ class _Macro: self.capabilities.add(code) + self.child_macros.append(macro) + self.tasks.append((MODIFIER, lambda: self.handler(code, 1))) self.add_keycode_pause() - self.tasks.append((CHILD_MACRO, macro)) + self.tasks.append((CHILD_MACRO, macro.run)) self.add_keycode_pause() self.tasks.append((MODIFIER, lambda: self.handler(code, 0))) self.add_keycode_pause() @@ -195,7 +204,10 @@ class _Macro: ) for _ in range(repeats): - self.tasks.append((CHILD_MACRO, macro)) + self.tasks.append((CHILD_MACRO, macro.run)) + + self.child_macros.append(macro) + return self def add_keycode_pause(self): diff --git a/tests/test.py b/tests/test.py index 91302a17..0554009d 100644 --- a/tests/test.py +++ b/tests/test.py @@ -154,7 +154,10 @@ def push_event(device, event): class Event: - """Event to put into the injector for tests.""" + """Event to put into the injector for tests. + + fakes evdev.InputEvent + """ def __init__(self, type, code, value): """ Paramaters diff --git a/tests/testcases/test_keycode_mapper.py b/tests/testcases/test_keycode_mapper.py index 5769766d..1c994533 100644 --- a/tests/testcases/test_keycode_mapper.py +++ b/tests/testcases/test_keycode_mapper.py @@ -33,6 +33,7 @@ class TestKeycodeMapper(unittest.TestCase): self.assertFalse(should_map_event_as_btn(EV_ABS, ABS_X)) self.assertFalse(should_map_event_as_btn(EV_REL, REL_X)) + # TODO test for macro holding if __name__ == "__main__": unittest.main()