From 24571d9995ee0a0e640878c8bb1afa69c1cbfb8b Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Fri, 19 Mar 2021 23:03:03 +0100 Subject: [PATCH] #63 added e, mouse and wheel macros --- data/key-mapper.glade | 33 +++--- keymapper/injection/injector.py | 8 +- keymapper/injection/keycode_mapper.py | 4 +- keymapper/injection/macros.py | 133 ++++++++++++++++----- readme/development.md | 2 +- readme/usage.md | 17 ++- tests/testcases/test_keycode_mapper.py | 114 +++++++++--------- tests/testcases/test_macros.py | 155 ++++++++++++++++--------- 8 files changed, 303 insertions(+), 163 deletions(-) diff --git a/data/key-mapper.glade b/data/key-mapper.glade index aae6ae8e..cb038f9c 100644 --- a/data/key-mapper.glade +++ b/data/key-mapper.glade @@ -890,22 +890,29 @@ Don't hold down any keys while the injection starts. "disable" disables the key outside of combinations. Useful for turning a key into a modifier without any side effects. +Macro help: +- `r` repeats the execution of the second parameter +- `w` waits in milliseconds +- `k` writes a single keystroke +- `e` writes an event +- `m` holds a modifier while executing the second parameter +- `h` executes the parameter as long as the key is pressed down +- `.` executes two actions behind each other +- `mouse` and `wheel` take direction and speed as parameters + Macro examples: -- k(a) -- r(3, k(a).w(500)) -- h(k(a)).k(b) -- m(Control_L, k(a).k(x)) +- `k(1).k(2)` 1, 2 +- `r(3, k(a).w(500))` a, a, a with 500ms pause +- `m(Control_L, k(a).k(x))` CTRL + a, CTRL + x +- `k(1).h(k(2)).k(3)` writes 1 2 2 ... 2 2 3 while the key is pressed +- `e(EV_REL, REL_X, 10)` moves the mouse cursor 10px to the right +- `mouse(right, 4)` which keeps moving the mouse while pressed +- `wheel(down, 1)` keeps scrolling down while held -Help: -- r: repeats the execution of the second parameter -- w: waits in milliseconds -- k: writes a single keystroke -- m: holds a modifier while executing the second parameter -- h: executes the parameter as long as the key is pressed down +Combine keycodes with `+`, for example: `control_l + a`, to write combinations -Between calls to k, key down and key up events, macros -will sleep for 10ms by default. This can be configured in -~/.config/key-mapper/config +Between calls to k, key down and key up events, macros will sleep for 10ms by +default. This can be configured in ~/.config/key-mapper/config 5 5 Mapping diff --git a/keymapper/injection/injector.py b/keymapper/injection/injector.py index 2bd3d118..f421170a 100644 --- a/keymapper/injection/injector.py +++ b/keymapper/injection/injector.py @@ -251,7 +251,13 @@ class Injector(multiprocessing.Process): # and all keycodes that are injected by macros for macro in self.context.macros.values(): - capabilities[EV_KEY] += list(macro.get_capabilities()) + macro_capabilities = macro.get_capabilities() + for ev_type in macro_capabilities: + if len(macro_capabilities[ev_type]) == 0: + continue + if ev_type not in capabilities: + capabilities[ev_type] = [] + capabilities[ev_type] += list(macro_capabilities[ev_type]) if gamepad and self.context.joystick_as_mouse(): # REL_WHEEL was also required to recognize the gamepad diff --git a/keymapper/injection/keycode_mapper.py b/keymapper/injection/keycode_mapper.py index b0bae4db..8beffa7e 100644 --- a/keymapper/injection/keycode_mapper.py +++ b/keymapper/injection/keycode_mapper.py @@ -225,9 +225,9 @@ class KeycodeMapper: f'but got {key}' ) - def macro_write(self, code, value): + def macro_write(self, ev_type, code, value): """Handler for macros.""" - self.context.uinput.write(EV_KEY, code, value) + self.context.uinput.write(ev_type, code, value) self.context.uinput.syn() def write(self, key): diff --git a/keymapper/injection/macros.py b/keymapper/injection/macros.py index 28245c6f..27087b52 100644 --- a/keymapper/injection/macros.py +++ b/keymapper/injection/macros.py @@ -37,19 +37,15 @@ w(1000).m(Shift_L, r(2, k(a))).w(10).k(b): <1s> A A <10ms> b import asyncio import re +import copy + +from evdev.ecodes import ecodes, EV_KEY, EV_REL, REL_X, REL_Y, REL_WHEEL, \ + REL_HWHEEL from keymapper.logger import logger from keymapper.state import system_mapping -MODIFIER = 1 -CHILD_MACRO = 2 -SLEEP = 3 -REPEAT = 4 -KEYSTROKE = 5 -DEBUG = 6 - - def is_this_a_macro(output): """Figure out if this is a macro.""" if not isinstance(output, str): @@ -73,7 +69,7 @@ class _Macro: Parameters ---------- - code : string + code : string or None The original parsed code, for logging purposes. mapping : Mapping The preset object, needed for some config stuff @@ -88,7 +84,10 @@ class _Macro: self.running = False # all required capabilities, without those of child macros - self.capabilities = set() + self.capabilities = { + EV_KEY: set(), + EV_REL: set(), + } self.child_macros = [] @@ -98,9 +97,16 @@ class _Macro: def get_capabilities(self): """Resolve all capabilities of the macro and those of its children.""" - capabilities = self.capabilities.copy() + capabilities = copy.deepcopy(self.capabilities) + for macro in self.child_macros: - capabilities.update(macro.get_capabilities()) + macro_capabilities = macro.get_capabilities() + for ev_type in macro_capabilities: + if ev_type not in capabilities: + capabilities[ev_type] = set() + + capabilities[ev_type].update(macro_capabilities[ev_type]) + return capabilities async def run(self, handler): @@ -109,14 +115,17 @@ class _Macro: Parameters ---------- handler : function - Will receive int code and value for an EV_KEY event to write + Will receive int type, code and value for an event to write """ if self.running: logger.error('Tried to run already running macro "%s"', self.code) return self.running = True - for _, task in self.tasks: + for task in self.tasks: + # one could call tasks the compiled macros. it's lambda functions + # that receive the handler as an argument, so that they know + # where to send the event to. coroutine = task(handler) if asyncio.iscoroutine(coroutine): await coroutine @@ -161,7 +170,7 @@ class _Macro: # released logger.error('Failed h(): %s', error) - self.tasks.append((1234, task)) + self.tasks.append(task) else: if not isinstance(macro, _Macro): raise ValueError( @@ -175,7 +184,7 @@ class _Macro: # not-releasing any key await macro.run(handler) - self.tasks.append((REPEAT, task)) + self.tasks.append(task) self.child_macros.append(macro) return self @@ -200,15 +209,15 @@ class _Macro: if code is None: raise KeyError(f'Unknown modifier "{modifier}"') - self.capabilities.add(code) + self.capabilities[EV_KEY].add(code) self.child_macros.append(macro) - self.tasks.append((MODIFIER, lambda handler: handler(code, 1))) + self.tasks.append(lambda handler: handler(EV_KEY, code, 1)) self.add_keycode_pause() - self.tasks.append((CHILD_MACRO, macro.run)) + self.tasks.append(macro.run) self.add_keycode_pause() - self.tasks.append((MODIFIER, lambda handler: handler(code, 0))) + self.tasks.append(lambda handler: handler(EV_KEY, code, 0)) self.add_keycode_pause() return self @@ -235,7 +244,7 @@ class _Macro: ) from error for _ in range(repeats): - self.tasks.append((CHILD_MACRO, macro.run)) + self.tasks.append(macro.run) self.child_macros.append(macro) @@ -248,7 +257,7 @@ class _Macro: async def sleep(_): await asyncio.sleep(sleeptime) - self.tasks.append((SLEEP, sleep)) + self.tasks.append(sleep) def keycode(self, character): """Write the character.""" @@ -256,16 +265,77 @@ class _Macro: code = system_mapping.get(character) if code is None: - raise KeyError(f'aUnknown key "{character}"') + raise KeyError(f'Unknown key "{character}"') - self.capabilities.add(code) + if EV_KEY not in self.capabilities: + self.capabilities[EV_KEY] = set() + self.capabilities[EV_KEY].add(code) - self.tasks.append((KEYSTROKE, lambda handler: handler(code, 1))) + self.tasks.append(lambda handler: handler(EV_KEY, code, 1)) + self.add_keycode_pause() + self.tasks.append(lambda handler: handler(EV_KEY, code, 0)) self.add_keycode_pause() - self.tasks.append((KEYSTROKE, lambda handler: handler(code, 0))) + return self + + def event(self, ev_type, code, value): + """Write any event. + + Parameters + ---------- + ev_type: str or int + examples: 2, 'EV_KEY' + code : int or int + examples: 52, 'KEY_A' + value : int + """ + if isinstance(ev_type, str): + ev_type = ecodes[ev_type.upper()] + if isinstance(code, str): + code = ecodes[code.upper()] + + if ev_type not in self.capabilities: + self.capabilities[ev_type] = set() + + if ev_type == EV_REL: + # add all capabilities that are required for the display server + # to recognize the device as mouse + self.capabilities[EV_REL].add(REL_X) + self.capabilities[EV_REL].add(REL_Y) + self.capabilities[EV_REL].add(REL_WHEEL) + + self.capabilities[ev_type].add(code) + + self.tasks.append(lambda handler: handler(ev_type, code, value)) self.add_keycode_pause() + return self + def mouse(self, direction, speed): + """Shortcut for h(e(...)).""" + code, value = { + 'up': (REL_Y, -1), + 'down': (REL_Y, 1), + 'left': (REL_X, -1), + 'right': (REL_X, 1), + }[direction.lower()] + value *= speed + child_macro = _Macro(None, self.mapping) + child_macro.event(EV_REL, code, value) + self.hold(child_macro) + + def wheel(self, direction, speed): + """Shortcut for h(e(...)).""" + code, value = { + 'up': (REL_WHEEL, 1), + 'down': (REL_WHEEL, -1), + 'left': (REL_HWHEEL, 1), + 'right': (REL_HWHEEL, -1), + }[direction.lower()] + child_macro = _Macro(None, self.mapping) + child_macro.event(EV_REL, code, value) + child_macro.wait(100 / speed) + self.hold(child_macro) + def wait(self, sleeptime): """Wait time in milliseconds.""" try: @@ -281,7 +351,7 @@ class _Macro: async def sleep(_): await asyncio.sleep(sleeptime) - self.tasks.append((SLEEP, sleep)) + self.tasks.append(sleep) return self @@ -385,8 +455,11 @@ def _parse_recurse(macro, mapping, macro_instance=None, depth=0): 'm': (macro_instance.modify, 2, 2), 'r': (macro_instance.repeat, 2, 2), 'k': (macro_instance.keycode, 1, 1), + 'e': (macro_instance.event, 3, 3), 'w': (macro_instance.wait, 1, 1), - 'h': (macro_instance.hold, 0, 1) + 'h': (macro_instance.hold, 0, 1), + 'mouse': (macro_instance.mouse, 2, 2), + 'wheel': (macro_instance.wheel, 2, 2) } function = functions.get(call) @@ -396,7 +469,7 @@ def _parse_recurse(macro, mapping, macro_instance=None, depth=0): # get all the stuff inbetween position = _count_brackets(macro) - inner = macro[2:position - 1] + inner = macro[macro.index('(') + 1:position - 1] # split "3, k(a).w(10)" into parameters string_params = _extract_params(inner) @@ -510,5 +583,5 @@ def parse(macro, mapping, return_errors=False): macro_object = _parse_recurse(macro, mapping) return macro_object if not return_errors else None except Exception as error: - logger.error('Failed to parse macro "%s": %s', macro, error) + logger.error('Failed to parse macro "%s": %s', macro, error.__repr__()) return str(error) if return_errors else None diff --git a/readme/development.md b/readme/development.md index da42b869..89ec4495 100644 --- a/readme/development.md +++ b/readme/development.md @@ -35,7 +35,7 @@ key-mapper. - [x] inject in an additional device instead to avoid clashing capabilities - [ ] don't run any GTK code as root for wayland compatibility - [ ] injecting keys that aren't available in the systems keyboard layout -- [ ] ship with a list of all keys known to xkb and validate input in the gui +- [ ] add comprehensive tabbed help popup ## Tests diff --git a/readme/usage.md b/readme/usage.md index 22deb725..1484318f 100644 --- a/readme/usage.md +++ b/readme/usage.md @@ -73,19 +73,24 @@ names can be chained using ` + `. ## Macros It is possible to write timed macros into the center column: -- `k(1).k(2)` 1, 2 -- `r(3, k(a).w(500))` a, a, a with 500ms pause -- `m(Control_L, k(a).k(x))` CTRL + a, CTRL + x -- `k(1).h(k(2)).k(3)` writes 1 2 2 ... 2 2 3 while the key is pressed - -Documentation: - `r` repeats the execution of the second parameter - `w` waits in milliseconds - `k` writes a single keystroke +- `e` writes an event - `m` holds a modifier while executing the second parameter - `h` executes the parameter as long as the key is pressed down - `.` executes two actions behind each other +Examples: +- `k(1).k(2)` 1, 2 +- `r(3, k(a).w(500))` a, a, a with 500ms pause +- `m(Control_L, k(a).k(x))` CTRL + a, CTRL + x +- `k(1).h(k(2)).k(3)` writes 1 2 2 ... 2 2 3 while the key is pressed +- `e(EV_REL, REL_X, 10)` moves the mouse cursor 10px to the right +- `mouse(right, 4)` which keeps moving the mouse while pressed. + Made out of `h(e(...))` internally +- `wheel(down, 1)` keeps scrolling down while held + Syntax errors are shown in the UI on save. Each `k` function adds a short delay of 10ms between key-down, key-up and at the end. See [Configuration Files](#configuration-files) for more info. diff --git a/tests/testcases/test_keycode_mapper.py b/tests/testcases/test_keycode_mapper.py index 3945cae0..1f3774e8 100644 --- a/tests/testcases/test_keycode_mapper.py +++ b/tests/testcases/test_keycode_mapper.py @@ -547,10 +547,10 @@ class TestKeycodeMapper(unittest.TestCase): # 6 keycodes written, with down and up events self.assertEqual(len(history), 12) - self.assertIn((code_a, 1), history) - self.assertIn((code_a, 0), history) - self.assertIn((code_b, 1), history) - self.assertIn((code_b, 0), history) + self.assertIn((EV_KEY, code_a, 1), history) + self.assertIn((EV_KEY, code_a, 0), history) + self.assertIn((EV_KEY, code_b, 1), history) + self.assertIn((EV_KEY, code_b, 0), history) # releasing stuff self.assertIn((EV_KEY, 1), unreleased) @@ -611,14 +611,14 @@ class TestKeycodeMapper(unittest.TestCase): self.assertGreater(len(history), events * 0.9) self.assertLess(len(history), events * 1.1) - self.assertIn((code_a, 1), history) - self.assertIn((code_a, 0), history) - self.assertIn((code_b, 1), history) - self.assertIn((code_b, 0), history) - self.assertIn((code_c, 1), history) - self.assertIn((code_c, 0), history) - self.assertGreater(history.count((code_b, 1)), 1) - self.assertGreater(history.count((code_b, 0)), 1) + self.assertIn((EV_KEY, code_a, 1), history) + self.assertIn((EV_KEY, code_a, 0), history) + self.assertIn((EV_KEY, code_b, 1), history) + self.assertIn((EV_KEY, code_b, 0), history) + self.assertIn((EV_KEY, code_c, 1), history) + self.assertIn((EV_KEY, code_c, 0), history) + self.assertGreater(history.count((EV_KEY, code_b, 1)), 1) + self.assertGreater(history.count((EV_KEY, code_b, 0)), 1) # it's stopped and won't write stuff anymore count_before = len(history) @@ -667,8 +667,8 @@ class TestKeycodeMapper(unittest.TestCase): loop.run_until_complete(asyncio.sleep(0.1)) # starting code_c written - self.assertEqual(history.count((code_c, 1)), 1) - self.assertEqual(history.count((code_c, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 1) # spam garbage events for _ in range(5): @@ -684,8 +684,8 @@ class TestKeycodeMapper(unittest.TestCase): # there should only be one code_c in the events, because no key # up event was ever done so the hold just continued - self.assertEqual(history.count((code_c, 1)), 1) - self.assertEqual(history.count((code_c, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 1) # without an key up event on 2, it won't write code_d self.assertNotIn((code_d, 1), history) self.assertNotIn((code_d, 0), history) @@ -695,17 +695,17 @@ class TestKeycodeMapper(unittest.TestCase): loop.run_until_complete(asyncio.sleep(0.1)) # it stopped and didn't restart, so the count stays at 1 - self.assertEqual(history.count((code_c, 1)), 1) - self.assertEqual(history.count((code_c, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 1) # and the trailing d was written - self.assertEqual(history.count((code_d, 1)), 1) - self.assertEqual(history.count((code_d, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_d, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_d, 0)), 1) # it's stopped and won't write stuff anymore - count_before = history.count((code_a, 1)) + count_before = history.count((EV_KEY, code_a, 1)) self.assertGreater(count_before, 1) loop.run_until_complete(asyncio.sleep(0.1)) - count_after = history.count((code_a, 1)) + count_after = history.count((EV_KEY, code_a, 1)) self.assertEqual(count_before, count_after) """restart macro 2""" @@ -714,8 +714,8 @@ class TestKeycodeMapper(unittest.TestCase): keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1)) loop.run_until_complete(asyncio.sleep(0.1)) - self.assertEqual(history.count((code_c, 1)), 1) - self.assertEqual(history.count((code_c, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 1) # spam garbage events again, this time key-up events on all other # macros @@ -734,11 +734,11 @@ class TestKeycodeMapper(unittest.TestCase): keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 0)) loop.run_until_complete(asyncio.sleep(0.1)) # was started only once - self.assertEqual(history.count((code_c, 1)), 1) - self.assertEqual(history.count((code_c, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 1) # and the trailing d was also written only once - self.assertEqual(history.count((code_d, 1)), 1) - self.assertEqual(history.count((code_d, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_d, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_d, 0)), 1) # stop all macros keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0)) @@ -794,18 +794,18 @@ class TestKeycodeMapper(unittest.TestCase): loop.run_until_complete(asyncio.sleep(0.05)) # duplicate key down events don't do anything - self.assertEqual(history.count((code_a, 1)), 1) - self.assertEqual(history.count((code_a, 0)), 1) - self.assertEqual(history.count((code_c, 1)), 0) - self.assertEqual(history.count((code_c, 0)), 0) + self.assertEqual(history.count((EV_KEY, code_a, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_a, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 0) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 0) # stop keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0)) loop.run_until_complete(asyncio.sleep(0.1)) - self.assertEqual(history.count((code_a, 1)), 1) - self.assertEqual(history.count((code_a, 0)), 1) - self.assertEqual(history.count((code_c, 1)), 1) - self.assertEqual(history.count((code_c, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_a, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_a, 0)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 1)), 1) + self.assertEqual(history.count((EV_KEY, code_c, 0)), 1) self.assertFalse(active_macros[(EV_KEY, 1)].is_holding()) self.assertFalse(active_macros[(EV_KEY, 1)].running) @@ -925,22 +925,22 @@ class TestKeycodeMapper(unittest.TestCase): self.assertGreater(len(history), events * 0.9) self.assertLess(len(history), events * 1.1) - self.assertIn((code_a, 1), history) - self.assertIn((code_a, 0), history) - self.assertIn((code_b, 1), history) - self.assertIn((code_b, 0), history) - self.assertIn((code_c, 1), history) - self.assertIn((code_c, 0), history) - self.assertIn((code_1, 1), history) - self.assertIn((code_1, 0), history) - self.assertIn((code_2, 1), history) - self.assertIn((code_2, 0), history) - self.assertIn((code_3, 1), history) - self.assertIn((code_3, 0), history) - self.assertGreater(history.count((code_b, 1)), 1) - self.assertGreater(history.count((code_b, 0)), 1) - self.assertGreater(history.count((code_2, 1)), 1) - self.assertGreater(history.count((code_2, 0)), 1) + self.assertIn((EV_KEY, code_a, 1), history) + self.assertIn((EV_KEY, code_a, 0), history) + self.assertIn((EV_KEY, code_b, 1), history) + self.assertIn((EV_KEY, code_b, 0), history) + self.assertIn((EV_KEY, code_c, 1), history) + self.assertIn((EV_KEY, code_c, 0), history) + self.assertIn((EV_KEY, code_1, 1), history) + self.assertIn((EV_KEY, code_1, 0), history) + self.assertIn((EV_KEY, code_2, 1), history) + self.assertIn((EV_KEY, code_2, 0), history) + self.assertIn((EV_KEY, code_3, 1), history) + self.assertIn((EV_KEY, code_3, 0), history) + self.assertGreater(history.count((EV_KEY, code_b, 1)), 1) + self.assertGreater(history.count((EV_KEY, code_b, 0)), 1) + self.assertGreater(history.count((EV_KEY, code_2, 1)), 1) + self.assertGreater(history.count((EV_KEY, code_2, 0)), 1) # it's stopped and won't write stuff anymore count_before = len(history) @@ -994,10 +994,10 @@ class TestKeycodeMapper(unittest.TestCase): sleeptime = config.get('macros.keystroke_sleep_ms') / 1000 loop.run_until_complete(asyncio.sleep(1.1 * repeats * 2 * sleeptime)) - self.assertEqual(history.count((code_1, 1)), 10) - self.assertEqual(history.count((code_1, 0)), 10) - self.assertEqual(history.count((code_2, 1)), 10) - self.assertEqual(history.count((code_2, 0)), 10) + self.assertEqual(history.count((EV_KEY, code_1, 1)), 10) + self.assertEqual(history.count((EV_KEY, code_1, 0)), 10) + self.assertEqual(history.count((EV_KEY, code_2, 1)), 10) + self.assertEqual(history.count((EV_KEY, code_2, 0)), 10) self.assertEqual(len(history), repeats * 4) def test_filter_trigger_spam(self): @@ -1223,7 +1223,7 @@ class TestKeycodeMapper(unittest.TestCase): self.assertEqual(len(uinput_write_history), 0) self.assertGreater(len(macro_history), 1) self.assertIn(down_1[:2], unreleased) - self.assertIn((92, 1), macro_history) + self.assertIn((EV_KEY, 92, 1), macro_history) # combination triggered keycode_mapper.handle_keycode(new_event(*down_2)) diff --git a/tests/testcases/test_macros.py b/tests/testcases/test_macros.py index 0300ad17..f64b21c5 100644 --- a/tests/testcases/test_macros.py +++ b/tests/testcases/test_macros.py @@ -23,6 +23,8 @@ import time import unittest import asyncio +from evdev.ecodes import EV_REL, EV_KEY, REL_Y, REL_X, REL_WHEEL, REL_HWHEEL + from keymapper.injection.macros import parse, _Macro, _extract_params, \ is_this_a_macro, _parse_recurse, handle_plus_syntax from keymapper.config import config @@ -43,9 +45,10 @@ class TestMacros(unittest.TestCase): self.mapping.clear_config() quick_cleanup() - def handler(self, code, value): + def handler(self, ev_type, code, value): """Where macros should write codes to.""" - self.result.append((code, value)) + print(f'\033[90mmacro wrote{(ev_type, code, value)}\033[0m') + self.result.append((ev_type, code, value)) def test_is_this_a_macro(self): self.assertTrue(is_this_a_macro('k(1)')) @@ -79,7 +82,7 @@ class TestMacros(unittest.TestCase): def test_run_plus_syntax(self): macro = parse('a + b + c + d', self.mapping) - self.assertSetEqual(macro.get_capabilities(), { + self.assertSetEqual(macro.get_capabilities()[EV_KEY], { system_mapping.get('a'), system_mapping.get('b'), system_mapping.get('c'), @@ -92,19 +95,19 @@ class TestMacros(unittest.TestCase): self.assertTrue(macro.is_holding()) # starting from the left, presses each one down - self.assertEqual(self.result[0], (system_mapping.get('a'), 1)) - self.assertEqual(self.result[1], (system_mapping.get('b'), 1)) - self.assertEqual(self.result[2], (system_mapping.get('c'), 1)) - self.assertEqual(self.result[3], (system_mapping.get('d'), 1)) + self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('a'), 1)) + self.assertEqual(self.result[1], (EV_KEY, system_mapping.get('b'), 1)) + self.assertEqual(self.result[2], (EV_KEY, system_mapping.get('c'), 1)) + self.assertEqual(self.result[3], (EV_KEY, system_mapping.get('d'), 1)) # and then releases starting with the previously pressed key macro.release_key() self.loop.run_until_complete(asyncio.sleep(0.2)) self.assertFalse(macro.is_holding()) - self.assertEqual(self.result[4], (system_mapping.get('d'), 0)) - self.assertEqual(self.result[5], (system_mapping.get('c'), 0)) - self.assertEqual(self.result[6], (system_mapping.get('b'), 0)) - self.assertEqual(self.result[7], (system_mapping.get('a'), 0)) + self.assertEqual(self.result[4], (EV_KEY, system_mapping.get('d'), 0)) + self.assertEqual(self.result[5], (EV_KEY, system_mapping.get('c'), 0)) + self.assertEqual(self.result[6], (EV_KEY, system_mapping.get('b'), 0)) + self.assertEqual(self.result[7], (EV_KEY, system_mapping.get('a'), 0)) def test_extract_params(self): def expect(raw, expectation): @@ -142,15 +145,16 @@ class TestMacros(unittest.TestCase): def test_0(self): macro = parse('k(1)', self.mapping) one_code = system_mapping.get('1') - self.assertSetEqual(macro.get_capabilities(), {one_code}) + self.assertSetEqual(macro.get_capabilities()[EV_KEY], {one_code}) + self.assertSetEqual(macro.get_capabilities()[EV_REL], set()) self.loop.run_until_complete(macro.run(self.handler)) - self.assertListEqual(self.result, [(one_code, 1), (one_code, 0)]) + self.assertListEqual(self.result, [(EV_KEY, one_code, 1), (EV_KEY, one_code, 0)]) self.assertEqual(len(macro.child_macros), 0) def test_1(self): macro = parse('k(1).k(a).k(3)', self.mapping) - self.assertSetEqual(macro.get_capabilities(), { + self.assertSetEqual(macro.get_capabilities()[EV_KEY], { system_mapping.get('1'), system_mapping.get('a'), system_mapping.get('3') @@ -158,9 +162,9 @@ class TestMacros(unittest.TestCase): self.loop.run_until_complete(macro.run(self.handler)) self.assertListEqual(self.result, [ - (system_mapping.get('1'), 1), (system_mapping.get('1'), 0), - (system_mapping.get('a'), 1), (system_mapping.get('a'), 0), - (system_mapping.get('3'), 1), (system_mapping.get('3'), 0), + (EV_KEY, system_mapping.get('1'), 1), (EV_KEY, system_mapping.get('1'), 0), + (EV_KEY, system_mapping.get('a'), 1), (EV_KEY, system_mapping.get('a'), 0), + (EV_KEY, system_mapping.get('3'), 1), (EV_KEY, system_mapping.get('3'), 0), ]) self.assertEqual(len(macro.child_macros), 0) @@ -196,7 +200,7 @@ class TestMacros(unittest.TestCase): def test_hold(self): macro = parse('k(1).h(k(a)).k(3)', self.mapping) - self.assertSetEqual(macro.get_capabilities(), { + self.assertSetEqual(macro.get_capabilities()[EV_KEY], { system_mapping.get('1'), system_mapping.get('a'), system_mapping.get('3') @@ -212,17 +216,17 @@ class TestMacros(unittest.TestCase): self.loop.run_until_complete(asyncio.sleep(0.05)) self.assertFalse(macro.is_holding()) - self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) - self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) + self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1)) + self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0)) code_a = system_mapping.get('a') - self.assertGreater(self.result.count((code_a, 1)), 2) + self.assertGreater(self.result.count((EV_KEY, code_a, 1)), 2) self.assertEqual(len(macro.child_macros), 1) def test_dont_hold(self): macro = parse('k(1).h(k(a)).k(3)', self.mapping) - self.assertSetEqual(macro.get_capabilities(), { + self.assertSetEqual(macro.get_capabilities()[EV_KEY], { system_mapping.get('1'), system_mapping.get('a'), system_mapping.get('3') @@ -235,14 +239,14 @@ class TestMacros(unittest.TestCase): # and the child macro of hold is never called. self.assertEqual(len(self.result), 4) - self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) - self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) + self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1)) + self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0)) self.assertEqual(len(macro.child_macros), 1) def test_just_hold(self): macro = parse('k(1).h().k(3)', self.mapping) - self.assertSetEqual(macro.get_capabilities(), { + self.assertSetEqual(macro.get_capabilities()[EV_KEY], { system_mapping.get('1'), system_mapping.get('3') }) @@ -261,14 +265,14 @@ class TestMacros(unittest.TestCase): self.assertFalse(macro.is_holding()) self.assertEqual(len(self.result), 4) - self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) - self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) + self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1)) + self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0)) self.assertEqual(len(macro.child_macros), 0) def test_dont_just_hold(self): macro = parse('k(1).h().k(3)', self.mapping) - self.assertSetEqual(macro.get_capabilities(), { + self.assertSetEqual(macro.get_capabilities()[EV_KEY], { system_mapping.get('1'), system_mapping.get('3') }) @@ -280,8 +284,8 @@ class TestMacros(unittest.TestCase): # completely self.assertEqual(len(self.result), 4) - self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) - self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) + self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1)) + self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0)) self.assertEqual(len(macro.child_macros), 0) @@ -291,7 +295,7 @@ class TestMacros(unittest.TestCase): macro = parse(f'r({repeats}, k(k)).r(1, k(k))', self.mapping) k_code = system_mapping.get('k') - self.assertSetEqual(macro.get_capabilities(), {k_code}) + self.assertSetEqual(macro.get_capabilities()[EV_KEY], {k_code}) self.loop.run_until_complete(macro.run(self.handler)) keystroke_sleep = self.mapping.get('macros.keystroke_sleep_ms') @@ -301,7 +305,7 @@ class TestMacros(unittest.TestCase): self.assertListEqual( self.result, - [(k_code, 1), (k_code, 0)] * (repeats + 1) + [(EV_KEY, k_code, 1), (EV_KEY, k_code, 0)] * (repeats + 1) ) self.assertEqual(len(macro.child_macros), 2) @@ -311,7 +315,7 @@ class TestMacros(unittest.TestCase): start = time.time() macro = parse('r(3, k(m).w(100))', self.mapping) m_code = system_mapping.get('m') - self.assertSetEqual(macro.get_capabilities(), {m_code}) + self.assertSetEqual(macro.get_capabilities()[EV_KEY], {m_code}) self.loop.run_until_complete(macro.run(self.handler)) keystroke_time = 6 * self.mapping.get('macros.keystroke_sleep_ms') @@ -321,9 +325,9 @@ class TestMacros(unittest.TestCase): self.assertGreater(time.time() - start, total_time * 0.9) self.assertLess(time.time() - start, total_time * 1.1) self.assertListEqual(self.result, [ - (m_code, 1), (m_code, 0), - (m_code, 1), (m_code, 0), - (m_code, 1), (m_code, 0), + (EV_KEY, m_code, 1), (EV_KEY, m_code, 0), + (EV_KEY, m_code, 1), (EV_KEY, m_code, 0), + (EV_KEY, m_code, 1), (EV_KEY, m_code, 0), ]) self.assertEqual(len(macro.child_macros), 1) self.assertEqual(len(macro.child_macros[0].child_macros), 0) @@ -335,15 +339,15 @@ class TestMacros(unittest.TestCase): minus = system_mapping.get('minus') m = system_mapping.get('m') - self.assertSetEqual(macro.get_capabilities(), {r, minus, m}) + self.assertSetEqual(macro.get_capabilities()[EV_KEY], {r, minus, m}) self.loop.run_until_complete(macro.run(self.handler)) self.assertListEqual(self.result, [ - (r, 1), (r, 0), - (minus, 1), (minus, 0), - (r, 1), (r, 0), - (minus, 1), (minus, 0), - (m, 1), (m, 0), + (EV_KEY, r, 1), (EV_KEY, r, 0), + (EV_KEY, minus, 1), (EV_KEY, minus, 0), + (EV_KEY, r, 1), (EV_KEY, r, 0), + (EV_KEY, minus, 1), (EV_KEY, minus, 0), + (EV_KEY, m, 1), (EV_KEY, m, 0), ]) self.assertEqual(len(macro.child_macros), 1) self.assertEqual(len(macro.child_macros[0].child_macros), 0) @@ -359,7 +363,7 @@ class TestMacros(unittest.TestCase): left = system_mapping.get('bTn_lEfT') k = system_mapping.get('k') - self.assertSetEqual(macro.get_capabilities(), {w, left, k}) + self.assertSetEqual(macro.get_capabilities()[EV_KEY], {w, left, k}) self.loop.run_until_complete(macro.run(self.handler)) @@ -370,10 +374,10 @@ class TestMacros(unittest.TestCase): self.assertLess(time.time() - start, total_time * 1.1) self.assertGreater(time.time() - start, total_time * 0.9) - expected = [(w, 1)] - expected += [(left, 1), (left, 0)] * 2 - expected += [(w, 0)] - expected += [(k, 1), (k, 0)] + expected = [(EV_KEY, w, 1)] + expected += [(EV_KEY, left, 1), (EV_KEY, left, 0)] * 2 + expected += [(EV_KEY, w, 0)] + expected += [(EV_KEY, k, 1), (EV_KEY, k, 0)] expected *= 2 self.assertListEqual(self.result, expected) @@ -431,9 +435,9 @@ class TestMacros(unittest.TestCase): self.assertFalse(macro.is_holding()) expected = [ - (a, 1), (a, 0), - (b, 1), (b, 0), - (c, 1), (c, 0), + (EV_KEY, a, 1), (EV_KEY, a, 0), + (EV_KEY, b, 1), (EV_KEY, b, 0), + (EV_KEY, c, 1), (EV_KEY, c, 0), ] self.assertListEqual(self.result, expected) @@ -448,12 +452,57 @@ class TestMacros(unittest.TestCase): self.assertFalse(macro.is_holding()) expected = [ - (a, 1), (a, 0), - (b, 1), (b, 0), - (c, 1), (c, 0), + (EV_KEY, a, 1), (EV_KEY, a, 0), + (EV_KEY, b, 1), (EV_KEY, b, 0), + (EV_KEY, c, 1), (EV_KEY, c, 0), ] * 2 self.assertListEqual(self.result, expected) + def test_mouse(self): + macro_1 = parse('mouse(up, 4)', self.mapping) + macro_2 = parse('wheel(left, 3)', self.mapping) + macro_1.press_key() + macro_2.press_key() + asyncio.ensure_future(macro_1.run(self.handler)) + asyncio.ensure_future(macro_2.run(self.handler)) + self.loop.run_until_complete(asyncio.sleep(0.1)) + self.assertTrue(macro_1.is_holding()) + self.assertTrue(macro_2.is_holding()) + macro_1.release_key() + macro_2.release_key() + + self.assertIn((EV_REL, REL_Y, -4), self.result) + self.assertIn((EV_REL, REL_HWHEEL, 1), self.result) + + self.assertIn(REL_WHEEL, macro_1.get_capabilities()[EV_REL]) + self.assertIn(REL_Y, macro_1.get_capabilities()[EV_REL]) + self.assertIn(REL_X, macro_1.get_capabilities()[EV_REL]) + + self.assertIn(REL_WHEEL, macro_2.get_capabilities()[EV_REL]) + self.assertIn(REL_Y, macro_2.get_capabilities()[EV_REL]) + self.assertIn(REL_X, macro_2.get_capabilities()[EV_REL]) + + def test_event_1(self): + macro = parse('e(EV_KEY, KEY_A, 1)', self.mapping) + a_code = system_mapping.get('a') + self.assertSetEqual(macro.get_capabilities()[EV_KEY], {a_code}) + self.assertSetEqual(macro.get_capabilities()[EV_REL], set()) + + self.loop.run_until_complete(macro.run(self.handler)) + self.assertListEqual(self.result, [(EV_KEY, a_code, 1)]) + self.assertEqual(len(macro.child_macros), 0) + + def test_event_2(self): + macro = parse('e(5421, 324, 154)', self.mapping) + code = 324 + self.assertSetEqual(macro.get_capabilities()[5421], {324}) + self.assertSetEqual(macro.get_capabilities()[EV_REL], set()) + self.assertSetEqual(macro.get_capabilities()[EV_KEY], set()) + + self.loop.run_until_complete(macro.run(self.handler)) + self.assertListEqual(self.result, [(5421, code, 154)]) + self.assertEqual(len(macro.child_macros), 0) + if __name__ == '__main__': unittest.main()