diff --git a/keymapper/dev/keycode_mapper.py b/keymapper/dev/keycode_mapper.py index 088ae857..dae73fbe 100644 --- a/keymapper/dev/keycode_mapper.py +++ b/keymapper/dev/keycode_mapper.py @@ -30,6 +30,10 @@ from keymapper.logger import logger from keymapper.dev.ev_abs_mapper import JOYSTICK +# maps mouse buttons to running macros +active_macros = {} + + def should_map_event_as_btn(ev_type, code): """Does this event describe a button. @@ -52,13 +56,13 @@ def should_map_event_as_btn(ev_type, code): return False -def handle_keycode(_code_to_code, macros, event, uinput): +def handle_keycode(code_to_code, macros, event, uinput): """Write the mapped keycode or forward unmapped ones. Parameters ---------- - _code_to_code : dict - mapping of linux-keycode to linux-keycode. + code_to_code : dict + mapping of linux-keycode to linux-keycode macros : dict mapping of linux-keycode to _Macro objects """ @@ -84,8 +88,8 @@ def handle_keycode(_code_to_code, macros, event, uinput): asyncio.ensure_future(macro.run()) return - if input_keycode in _code_to_code: - target_keycode = _code_to_code[input_keycode] + if input_keycode in code_to_code: + target_keycode = code_to_code[input_keycode] target_type = evdev.events.EV_KEY logger.spam( 'got code:%s value:%s event:%s, maps to EV_KEY:%s', diff --git a/keymapper/dev/macros.py b/keymapper/dev/macros.py index ebc5d430..89e32fba 100644 --- a/keymapper/dev/macros.py +++ b/keymapper/dev/macros.py @@ -57,32 +57,36 @@ def is_this_a_macro(output): class _Macro: - """Supports chaining and preparing actions.""" - def __init__(self, depth, code): + """Supports chaining and preparing actions. + + Calling functions on _Macro does not inject anything yet, it means that + once .run is used it will be executed along with all other queued tasks. + """ + def __init__(self, code): """Create a macro instance that can be populated with tasks. Parameters ---------- - depth : int - 0 for the outermost parent macro, 1 or greater for child macros, - like the second argument of repeat. code : string The original parsed code, for logging purposes. """ self.tasks = [] self.handler = lambda *args: logger.error('No handler set') - self.depth = depth self.code = code + self.running = False + + # supposed to be True between key event values 1 (down) and 0 (up) + self.holding = False # all required capabilities, without those of child macros self.capabilities = set() + self.child_macros = [] def get_capabilities(self): """Resolve all capabilities of the macro and those of its children.""" capabilities = self.capabilities.copy() - for task_type, task in self.tasks: - if task_type == CHILD_MACRO: - capabilities.update(task.get_capabilities()) + for macro in self.child_macros: + capabilities.update(macro.get_capabilities()) return capabilities def set_handler(self, handler): @@ -102,7 +106,13 @@ class _Macro: async def run(self): """Run the macro.""" + self.running = True + for task_type, task in self.tasks: + if not self.running: + logger.debug('Macro execution stopped') + break + if task_type == CHILD_MACRO: task = task.run @@ -110,6 +120,28 @@ class _Macro: if asyncio.iscoroutine(coroutine): await coroutine + def stop(self): + """Stop the macro.""" + # TODO test + self.running = False + + async def hold(self, macro): + """Loops the execution until key release.""" + if not isinstance(macro, _Macro): + raise ValueError( + 'Expected the param for hold to be ' + f'a macro, but got "{macro}"' + ) + + # TODO test + def task(): + while self.holding and self.running: + await self.run() + + self.tasks.append((REPEAT, task)) + + return self + def modify(self, modifier, macro): """Do stuff while a modifier is activated. @@ -288,7 +320,7 @@ def _parse_recurse(macro, macro_instance=None, depth=0): assert isinstance(depth, int) if macro_instance is None: - macro_instance = _Macro(depth, macro) + macro_instance = _Macro(macro) else: assert isinstance(macro_instance, _Macro) @@ -305,7 +337,8 @@ def _parse_recurse(macro, macro_instance=None, depth=0): 'm': (macro_instance.modify, 2), 'r': (macro_instance.repeat, 2), 'k': (macro_instance.keycode, 1), - 'w': (macro_instance.wait, 1) + 'w': (macro_instance.wait, 1), + 'h': (macro_instance.hold, 1) } if functions.get(call) is None: