#63 added e, mouse and wheel macros

xkb
sezanzeb 3 years ago committed by sezanzeb
parent edcd3c9e2e
commit fd759e02ad

@ -890,22 +890,29 @@ Don't hold down any keys while the injection starts.</property>
<property name="tooltip-text" translatable="yes">"disable" disables the key outside of combinations. <property name="tooltip-text" translatable="yes">"disable" disables the key outside of combinations.
Useful for turning a key into a modifier without any side effects. 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: Macro examples:
- k(a) - `k(1).k(2)` 1, 2
- r(3, k(a).w(500)) - `r(3, k(a).w(500))` a, a, a with 500ms pause
- h(k(a)).k(b) - `m(Control_L, k(a).k(x))` CTRL + a, CTRL + x
- m(Control_L, k(a).k(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: Combine keycodes with `+`, for example: `control_l + a`, to write combinations
- 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
Between calls to k, key down and key up events, macros Between calls to k, key down and key up events, macros will sleep for 10ms by
will sleep for 10ms by default. This can be configured in default. This can be configured in ~/.config/key-mapper/config</property>
~/.config/key-mapper/config</property>
<property name="margin-top">5</property> <property name="margin-top">5</property>
<property name="margin-bottom">5</property> <property name="margin-bottom">5</property>
<property name="label" translatable="yes">Mapping</property> <property name="label" translatable="yes">Mapping</property>

@ -251,7 +251,13 @@ class Injector(multiprocessing.Process):
# and all keycodes that are injected by macros # and all keycodes that are injected by macros
for macro in self.context.macros.values(): 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(): if gamepad and self.context.joystick_as_mouse():
# REL_WHEEL was also required to recognize the gamepad # REL_WHEEL was also required to recognize the gamepad

@ -225,9 +225,9 @@ class KeycodeMapper:
f'but got {key}' f'but got {key}'
) )
def macro_write(self, code, value): def macro_write(self, ev_type, code, value):
"""Handler for macros.""" """Handler for macros."""
self.context.uinput.write(EV_KEY, code, value) self.context.uinput.write(ev_type, code, value)
self.context.uinput.syn() self.context.uinput.syn()
def write(self, key): def write(self, key):

@ -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 asyncio
import re 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.logger import logger
from keymapper.state import system_mapping 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): def is_this_a_macro(output):
"""Figure out if this is a macro.""" """Figure out if this is a macro."""
if not isinstance(output, str): if not isinstance(output, str):
@ -73,7 +69,7 @@ class _Macro:
Parameters Parameters
---------- ----------
code : string code : string or None
The original parsed code, for logging purposes. The original parsed code, for logging purposes.
mapping : Mapping mapping : Mapping
The preset object, needed for some config stuff The preset object, needed for some config stuff
@ -88,7 +84,10 @@ class _Macro:
self.running = False self.running = False
# all required capabilities, without those of child macros # all required capabilities, without those of child macros
self.capabilities = set() self.capabilities = {
EV_KEY: set(),
EV_REL: set(),
}
self.child_macros = [] self.child_macros = []
@ -98,9 +97,16 @@ class _Macro:
def get_capabilities(self): def get_capabilities(self):
"""Resolve all capabilities of the macro and those of its children.""" """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: 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 return capabilities
async def run(self, handler): async def run(self, handler):
@ -109,14 +115,17 @@ class _Macro:
Parameters Parameters
---------- ----------
handler : function 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: if self.running:
logger.error('Tried to run already running macro "%s"', self.code) logger.error('Tried to run already running macro "%s"', self.code)
return return
self.running = True 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) coroutine = task(handler)
if asyncio.iscoroutine(coroutine): if asyncio.iscoroutine(coroutine):
await coroutine await coroutine
@ -161,7 +170,7 @@ class _Macro:
# released # released
logger.error('Failed h(): %s', error) logger.error('Failed h(): %s', error)
self.tasks.append((1234, task)) self.tasks.append(task)
else: else:
if not isinstance(macro, _Macro): if not isinstance(macro, _Macro):
raise ValueError( raise ValueError(
@ -175,7 +184,7 @@ class _Macro:
# not-releasing any key # not-releasing any key
await macro.run(handler) await macro.run(handler)
self.tasks.append((REPEAT, task)) self.tasks.append(task)
self.child_macros.append(macro) self.child_macros.append(macro)
return self return self
@ -200,15 +209,15 @@ class _Macro:
if code is None: if code is None:
raise KeyError(f'Unknown modifier "{modifier}"') raise KeyError(f'Unknown modifier "{modifier}"')
self.capabilities.add(code) self.capabilities[EV_KEY].add(code)
self.child_macros.append(macro) 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.add_keycode_pause()
self.tasks.append((CHILD_MACRO, macro.run)) self.tasks.append(macro.run)
self.add_keycode_pause() 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() self.add_keycode_pause()
return self return self
@ -235,7 +244,7 @@ class _Macro:
) from error ) from error
for _ in range(repeats): for _ in range(repeats):
self.tasks.append((CHILD_MACRO, macro.run)) self.tasks.append(macro.run)
self.child_macros.append(macro) self.child_macros.append(macro)
@ -248,7 +257,7 @@ class _Macro:
async def sleep(_): async def sleep(_):
await asyncio.sleep(sleeptime) await asyncio.sleep(sleeptime)
self.tasks.append((SLEEP, sleep)) self.tasks.append(sleep)
def keycode(self, character): def keycode(self, character):
"""Write the character.""" """Write the character."""
@ -256,16 +265,77 @@ class _Macro:
code = system_mapping.get(character) code = system_mapping.get(character)
if code is None: 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.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() self.add_keycode_pause()
return self 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): def wait(self, sleeptime):
"""Wait time in milliseconds.""" """Wait time in milliseconds."""
try: try:
@ -281,7 +351,7 @@ class _Macro:
async def sleep(_): async def sleep(_):
await asyncio.sleep(sleeptime) await asyncio.sleep(sleeptime)
self.tasks.append((SLEEP, sleep)) self.tasks.append(sleep)
return self return self
@ -385,8 +455,11 @@ def _parse_recurse(macro, mapping, macro_instance=None, depth=0):
'm': (macro_instance.modify, 2, 2), 'm': (macro_instance.modify, 2, 2),
'r': (macro_instance.repeat, 2, 2), 'r': (macro_instance.repeat, 2, 2),
'k': (macro_instance.keycode, 1, 1), 'k': (macro_instance.keycode, 1, 1),
'e': (macro_instance.event, 3, 3),
'w': (macro_instance.wait, 1, 1), '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) function = functions.get(call)
@ -396,7 +469,7 @@ def _parse_recurse(macro, mapping, macro_instance=None, depth=0):
# get all the stuff inbetween # get all the stuff inbetween
position = _count_brackets(macro) position = _count_brackets(macro)
inner = macro[2:position - 1] inner = macro[macro.index('(') + 1:position - 1]
# split "3, k(a).w(10)" into parameters # split "3, k(a).w(10)" into parameters
string_params = _extract_params(inner) string_params = _extract_params(inner)
@ -510,5 +583,5 @@ def parse(macro, mapping, return_errors=False):
macro_object = _parse_recurse(macro, mapping) macro_object = _parse_recurse(macro, mapping)
return macro_object if not return_errors else None return macro_object if not return_errors else None
except Exception as error: 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 return str(error) if return_errors else None

@ -35,7 +35,7 @@ key-mapper.
- [x] inject in an additional device instead to avoid clashing capabilities - [x] inject in an additional device instead to avoid clashing capabilities
- [ ] don't run any GTK code as root for wayland compatibility - [ ] don't run any GTK code as root for wayland compatibility
- [ ] injecting keys that aren't available in the systems keyboard layout - [ ] 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 ## Tests

@ -73,19 +73,24 @@ names can be chained using ` + `.
## Macros ## Macros
It is possible to write timed macros into the center column: 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 - `r` repeats the execution of the second parameter
- `w` waits in milliseconds - `w` waits in milliseconds
- `k` writes a single keystroke - `k` writes a single keystroke
- `e` writes an event
- `m` holds a modifier while executing the second parameter - `m` holds a modifier while executing the second parameter
- `h` executes the parameter as long as the key is pressed down - `h` executes the parameter as long as the key is pressed down
- `.` executes two actions behind each other - `.` 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 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 delay of 10ms between key-down, key-up and at the end. See
[Configuration Files](#configuration-files) for more info. [Configuration Files](#configuration-files) for more info.

@ -547,10 +547,10 @@ class TestKeycodeMapper(unittest.TestCase):
# 6 keycodes written, with down and up events # 6 keycodes written, with down and up events
self.assertEqual(len(history), 12) self.assertEqual(len(history), 12)
self.assertIn((code_a, 1), history) self.assertIn((EV_KEY, code_a, 1), history)
self.assertIn((code_a, 0), history) self.assertIn((EV_KEY, code_a, 0), history)
self.assertIn((code_b, 1), history) self.assertIn((EV_KEY, code_b, 1), history)
self.assertIn((code_b, 0), history) self.assertIn((EV_KEY, code_b, 0), history)
# releasing stuff # releasing stuff
self.assertIn((EV_KEY, 1), unreleased) self.assertIn((EV_KEY, 1), unreleased)
@ -611,14 +611,14 @@ class TestKeycodeMapper(unittest.TestCase):
self.assertGreater(len(history), events * 0.9) self.assertGreater(len(history), events * 0.9)
self.assertLess(len(history), events * 1.1) self.assertLess(len(history), events * 1.1)
self.assertIn((code_a, 1), history) self.assertIn((EV_KEY, code_a, 1), history)
self.assertIn((code_a, 0), history) self.assertIn((EV_KEY, code_a, 0), history)
self.assertIn((code_b, 1), history) self.assertIn((EV_KEY, code_b, 1), history)
self.assertIn((code_b, 0), history) self.assertIn((EV_KEY, code_b, 0), history)
self.assertIn((code_c, 1), history) self.assertIn((EV_KEY, code_c, 1), history)
self.assertIn((code_c, 0), history) self.assertIn((EV_KEY, code_c, 0), history)
self.assertGreater(history.count((code_b, 1)), 1) self.assertGreater(history.count((EV_KEY, code_b, 1)), 1)
self.assertGreater(history.count((code_b, 0)), 1) self.assertGreater(history.count((EV_KEY, code_b, 0)), 1)
# it's stopped and won't write stuff anymore # it's stopped and won't write stuff anymore
count_before = len(history) count_before = len(history)
@ -667,8 +667,8 @@ class TestKeycodeMapper(unittest.TestCase):
loop.run_until_complete(asyncio.sleep(0.1)) loop.run_until_complete(asyncio.sleep(0.1))
# starting code_c written # starting code_c written
self.assertEqual(history.count((code_c, 1)), 1) self.assertEqual(history.count((EV_KEY, code_c, 1)), 1)
self.assertEqual(history.count((code_c, 0)), 1) self.assertEqual(history.count((EV_KEY, code_c, 0)), 1)
# spam garbage events # spam garbage events
for _ in range(5): 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 # there should only be one code_c in the events, because no key
# up event was ever done so the hold just continued # up event was ever done so the hold just continued
self.assertEqual(history.count((code_c, 1)), 1) self.assertEqual(history.count((EV_KEY, code_c, 1)), 1)
self.assertEqual(history.count((code_c, 0)), 1) self.assertEqual(history.count((EV_KEY, code_c, 0)), 1)
# without an key up event on 2, it won't write code_d # without an key up event on 2, it won't write code_d
self.assertNotIn((code_d, 1), history) self.assertNotIn((code_d, 1), history)
self.assertNotIn((code_d, 0), history) self.assertNotIn((code_d, 0), history)
@ -695,17 +695,17 @@ class TestKeycodeMapper(unittest.TestCase):
loop.run_until_complete(asyncio.sleep(0.1)) loop.run_until_complete(asyncio.sleep(0.1))
# it stopped and didn't restart, so the count stays at 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((EV_KEY, code_c, 1)), 1)
self.assertEqual(history.count((code_c, 0)), 1) self.assertEqual(history.count((EV_KEY, code_c, 0)), 1)
# and the trailing d was written # and the trailing d was written
self.assertEqual(history.count((code_d, 1)), 1) self.assertEqual(history.count((EV_KEY, code_d, 1)), 1)
self.assertEqual(history.count((code_d, 0)), 1) self.assertEqual(history.count((EV_KEY, code_d, 0)), 1)
# it's stopped and won't write stuff anymore # 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) self.assertGreater(count_before, 1)
loop.run_until_complete(asyncio.sleep(0.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) self.assertEqual(count_before, count_after)
"""restart macro 2""" """restart macro 2"""
@ -714,8 +714,8 @@ class TestKeycodeMapper(unittest.TestCase):
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1)) keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 1))
loop.run_until_complete(asyncio.sleep(0.1)) loop.run_until_complete(asyncio.sleep(0.1))
self.assertEqual(history.count((code_c, 1)), 1) self.assertEqual(history.count((EV_KEY, code_c, 1)), 1)
self.assertEqual(history.count((code_c, 0)), 1) self.assertEqual(history.count((EV_KEY, code_c, 0)), 1)
# spam garbage events again, this time key-up events on all other # spam garbage events again, this time key-up events on all other
# macros # macros
@ -734,11 +734,11 @@ class TestKeycodeMapper(unittest.TestCase):
keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 0)) keycode_mapper.handle_keycode(new_event(EV_KEY, 2, 0))
loop.run_until_complete(asyncio.sleep(0.1)) loop.run_until_complete(asyncio.sleep(0.1))
# was started only once # was started only once
self.assertEqual(history.count((code_c, 1)), 1) self.assertEqual(history.count((EV_KEY, code_c, 1)), 1)
self.assertEqual(history.count((code_c, 0)), 1) self.assertEqual(history.count((EV_KEY, code_c, 0)), 1)
# and the trailing d was also written only once # and the trailing d was also written only once
self.assertEqual(history.count((code_d, 1)), 1) self.assertEqual(history.count((EV_KEY, code_d, 1)), 1)
self.assertEqual(history.count((code_d, 0)), 1) self.assertEqual(history.count((EV_KEY, code_d, 0)), 1)
# stop all macros # stop all macros
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0)) 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)) loop.run_until_complete(asyncio.sleep(0.05))
# duplicate key down events don't do anything # duplicate key down events don't do anything
self.assertEqual(history.count((code_a, 1)), 1) self.assertEqual(history.count((EV_KEY, code_a, 1)), 1)
self.assertEqual(history.count((code_a, 0)), 1) self.assertEqual(history.count((EV_KEY, code_a, 0)), 1)
self.assertEqual(history.count((code_c, 1)), 0) self.assertEqual(history.count((EV_KEY, code_c, 1)), 0)
self.assertEqual(history.count((code_c, 0)), 0) self.assertEqual(history.count((EV_KEY, code_c, 0)), 0)
# stop # stop
keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0)) keycode_mapper.handle_keycode(new_event(EV_KEY, 1, 0))
loop.run_until_complete(asyncio.sleep(0.1)) loop.run_until_complete(asyncio.sleep(0.1))
self.assertEqual(history.count((code_a, 1)), 1) self.assertEqual(history.count((EV_KEY, code_a, 1)), 1)
self.assertEqual(history.count((code_a, 0)), 1) self.assertEqual(history.count((EV_KEY, code_a, 0)), 1)
self.assertEqual(history.count((code_c, 1)), 1) self.assertEqual(history.count((EV_KEY, code_c, 1)), 1)
self.assertEqual(history.count((code_c, 0)), 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)].is_holding())
self.assertFalse(active_macros[(EV_KEY, 1)].running) self.assertFalse(active_macros[(EV_KEY, 1)].running)
@ -925,22 +925,22 @@ class TestKeycodeMapper(unittest.TestCase):
self.assertGreater(len(history), events * 0.9) self.assertGreater(len(history), events * 0.9)
self.assertLess(len(history), events * 1.1) self.assertLess(len(history), events * 1.1)
self.assertIn((code_a, 1), history) self.assertIn((EV_KEY, code_a, 1), history)
self.assertIn((code_a, 0), history) self.assertIn((EV_KEY, code_a, 0), history)
self.assertIn((code_b, 1), history) self.assertIn((EV_KEY, code_b, 1), history)
self.assertIn((code_b, 0), history) self.assertIn((EV_KEY, code_b, 0), history)
self.assertIn((code_c, 1), history) self.assertIn((EV_KEY, code_c, 1), history)
self.assertIn((code_c, 0), history) self.assertIn((EV_KEY, code_c, 0), history)
self.assertIn((code_1, 1), history) self.assertIn((EV_KEY, code_1, 1), history)
self.assertIn((code_1, 0), history) self.assertIn((EV_KEY, code_1, 0), history)
self.assertIn((code_2, 1), history) self.assertIn((EV_KEY, code_2, 1), history)
self.assertIn((code_2, 0), history) self.assertIn((EV_KEY, code_2, 0), history)
self.assertIn((code_3, 1), history) self.assertIn((EV_KEY, code_3, 1), history)
self.assertIn((code_3, 0), history) self.assertIn((EV_KEY, code_3, 0), history)
self.assertGreater(history.count((code_b, 1)), 1) self.assertGreater(history.count((EV_KEY, code_b, 1)), 1)
self.assertGreater(history.count((code_b, 0)), 1) self.assertGreater(history.count((EV_KEY, code_b, 0)), 1)
self.assertGreater(history.count((code_2, 1)), 1) self.assertGreater(history.count((EV_KEY, code_2, 1)), 1)
self.assertGreater(history.count((code_2, 0)), 1) self.assertGreater(history.count((EV_KEY, code_2, 0)), 1)
# it's stopped and won't write stuff anymore # it's stopped and won't write stuff anymore
count_before = len(history) count_before = len(history)
@ -994,10 +994,10 @@ class TestKeycodeMapper(unittest.TestCase):
sleeptime = config.get('macros.keystroke_sleep_ms') / 1000 sleeptime = config.get('macros.keystroke_sleep_ms') / 1000
loop.run_until_complete(asyncio.sleep(1.1 * repeats * 2 * sleeptime)) loop.run_until_complete(asyncio.sleep(1.1 * repeats * 2 * sleeptime))
self.assertEqual(history.count((code_1, 1)), 10) self.assertEqual(history.count((EV_KEY, code_1, 1)), 10)
self.assertEqual(history.count((code_1, 0)), 10) self.assertEqual(history.count((EV_KEY, code_1, 0)), 10)
self.assertEqual(history.count((code_2, 1)), 10) self.assertEqual(history.count((EV_KEY, code_2, 1)), 10)
self.assertEqual(history.count((code_2, 0)), 10) self.assertEqual(history.count((EV_KEY, code_2, 0)), 10)
self.assertEqual(len(history), repeats * 4) self.assertEqual(len(history), repeats * 4)
def test_filter_trigger_spam(self): def test_filter_trigger_spam(self):
@ -1223,7 +1223,7 @@ class TestKeycodeMapper(unittest.TestCase):
self.assertEqual(len(uinput_write_history), 0) self.assertEqual(len(uinput_write_history), 0)
self.assertGreater(len(macro_history), 1) self.assertGreater(len(macro_history), 1)
self.assertIn(down_1[:2], unreleased) self.assertIn(down_1[:2], unreleased)
self.assertIn((92, 1), macro_history) self.assertIn((EV_KEY, 92, 1), macro_history)
# combination triggered # combination triggered
keycode_mapper.handle_keycode(new_event(*down_2)) keycode_mapper.handle_keycode(new_event(*down_2))

@ -23,6 +23,8 @@ import time
import unittest import unittest
import asyncio 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, \ from keymapper.injection.macros import parse, _Macro, _extract_params, \
is_this_a_macro, _parse_recurse, handle_plus_syntax is_this_a_macro, _parse_recurse, handle_plus_syntax
from keymapper.config import config from keymapper.config import config
@ -43,9 +45,10 @@ class TestMacros(unittest.TestCase):
self.mapping.clear_config() self.mapping.clear_config()
quick_cleanup() quick_cleanup()
def handler(self, code, value): def handler(self, ev_type, code, value):
"""Where macros should write codes to.""" """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): def test_is_this_a_macro(self):
self.assertTrue(is_this_a_macro('k(1)')) self.assertTrue(is_this_a_macro('k(1)'))
@ -79,7 +82,7 @@ class TestMacros(unittest.TestCase):
def test_run_plus_syntax(self): def test_run_plus_syntax(self):
macro = parse('a + b + c + d', self.mapping) 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('a'),
system_mapping.get('b'), system_mapping.get('b'),
system_mapping.get('c'), system_mapping.get('c'),
@ -92,19 +95,19 @@ class TestMacros(unittest.TestCase):
self.assertTrue(macro.is_holding()) self.assertTrue(macro.is_holding())
# starting from the left, presses each one down # starting from the left, presses each one down
self.assertEqual(self.result[0], (system_mapping.get('a'), 1)) self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('a'), 1))
self.assertEqual(self.result[1], (system_mapping.get('b'), 1)) self.assertEqual(self.result[1], (EV_KEY, system_mapping.get('b'), 1))
self.assertEqual(self.result[2], (system_mapping.get('c'), 1)) self.assertEqual(self.result[2], (EV_KEY, system_mapping.get('c'), 1))
self.assertEqual(self.result[3], (system_mapping.get('d'), 1)) self.assertEqual(self.result[3], (EV_KEY, system_mapping.get('d'), 1))
# and then releases starting with the previously pressed key # and then releases starting with the previously pressed key
macro.release_key() macro.release_key()
self.loop.run_until_complete(asyncio.sleep(0.2)) self.loop.run_until_complete(asyncio.sleep(0.2))
self.assertFalse(macro.is_holding()) self.assertFalse(macro.is_holding())
self.assertEqual(self.result[4], (system_mapping.get('d'), 0)) self.assertEqual(self.result[4], (EV_KEY, system_mapping.get('d'), 0))
self.assertEqual(self.result[5], (system_mapping.get('c'), 0)) self.assertEqual(self.result[5], (EV_KEY, system_mapping.get('c'), 0))
self.assertEqual(self.result[6], (system_mapping.get('b'), 0)) self.assertEqual(self.result[6], (EV_KEY, system_mapping.get('b'), 0))
self.assertEqual(self.result[7], (system_mapping.get('a'), 0)) self.assertEqual(self.result[7], (EV_KEY, system_mapping.get('a'), 0))
def test_extract_params(self): def test_extract_params(self):
def expect(raw, expectation): def expect(raw, expectation):
@ -142,15 +145,16 @@ class TestMacros(unittest.TestCase):
def test_0(self): def test_0(self):
macro = parse('k(1)', self.mapping) macro = parse('k(1)', self.mapping)
one_code = system_mapping.get('1') 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.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) self.assertEqual(len(macro.child_macros), 0)
def test_1(self): def test_1(self):
macro = parse('k(1).k(a).k(3)', self.mapping) 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('1'),
system_mapping.get('a'), system_mapping.get('a'),
system_mapping.get('3') system_mapping.get('3')
@ -158,9 +162,9 @@ class TestMacros(unittest.TestCase):
self.loop.run_until_complete(macro.run(self.handler)) self.loop.run_until_complete(macro.run(self.handler))
self.assertListEqual(self.result, [ self.assertListEqual(self.result, [
(system_mapping.get('1'), 1), (system_mapping.get('1'), 0), (EV_KEY, system_mapping.get('1'), 1), (EV_KEY, system_mapping.get('1'), 0),
(system_mapping.get('a'), 1), (system_mapping.get('a'), 0), (EV_KEY, system_mapping.get('a'), 1), (EV_KEY, system_mapping.get('a'), 0),
(system_mapping.get('3'), 1), (system_mapping.get('3'), 0), (EV_KEY, system_mapping.get('3'), 1), (EV_KEY, system_mapping.get('3'), 0),
]) ])
self.assertEqual(len(macro.child_macros), 0) self.assertEqual(len(macro.child_macros), 0)
@ -196,7 +200,7 @@ class TestMacros(unittest.TestCase):
def test_hold(self): def test_hold(self):
macro = parse('k(1).h(k(a)).k(3)', self.mapping) 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('1'),
system_mapping.get('a'), system_mapping.get('a'),
system_mapping.get('3') system_mapping.get('3')
@ -212,17 +216,17 @@ class TestMacros(unittest.TestCase):
self.loop.run_until_complete(asyncio.sleep(0.05)) self.loop.run_until_complete(asyncio.sleep(0.05))
self.assertFalse(macro.is_holding()) self.assertFalse(macro.is_holding())
self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1))
self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0))
code_a = system_mapping.get('a') 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) self.assertEqual(len(macro.child_macros), 1)
def test_dont_hold(self): def test_dont_hold(self):
macro = parse('k(1).h(k(a)).k(3)', self.mapping) 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('1'),
system_mapping.get('a'), system_mapping.get('a'),
system_mapping.get('3') system_mapping.get('3')
@ -235,14 +239,14 @@ class TestMacros(unittest.TestCase):
# and the child macro of hold is never called. # and the child macro of hold is never called.
self.assertEqual(len(self.result), 4) self.assertEqual(len(self.result), 4)
self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1))
self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0))
self.assertEqual(len(macro.child_macros), 1) self.assertEqual(len(macro.child_macros), 1)
def test_just_hold(self): def test_just_hold(self):
macro = parse('k(1).h().k(3)', self.mapping) 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('1'),
system_mapping.get('3') system_mapping.get('3')
}) })
@ -261,14 +265,14 @@ class TestMacros(unittest.TestCase):
self.assertFalse(macro.is_holding()) self.assertFalse(macro.is_holding())
self.assertEqual(len(self.result), 4) self.assertEqual(len(self.result), 4)
self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1))
self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0))
self.assertEqual(len(macro.child_macros), 0) self.assertEqual(len(macro.child_macros), 0)
def test_dont_just_hold(self): def test_dont_just_hold(self):
macro = parse('k(1).h().k(3)', self.mapping) 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('1'),
system_mapping.get('3') system_mapping.get('3')
}) })
@ -280,8 +284,8 @@ class TestMacros(unittest.TestCase):
# completely # completely
self.assertEqual(len(self.result), 4) self.assertEqual(len(self.result), 4)
self.assertEqual(self.result[0], (system_mapping.get('1'), 1)) self.assertEqual(self.result[0], (EV_KEY, system_mapping.get('1'), 1))
self.assertEqual(self.result[-1], (system_mapping.get('3'), 0)) self.assertEqual(self.result[-1], (EV_KEY, system_mapping.get('3'), 0))
self.assertEqual(len(macro.child_macros), 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) macro = parse(f'r({repeats}, k(k)).r(1, k(k))', self.mapping)
k_code = system_mapping.get('k') 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)) self.loop.run_until_complete(macro.run(self.handler))
keystroke_sleep = self.mapping.get('macros.keystroke_sleep_ms') keystroke_sleep = self.mapping.get('macros.keystroke_sleep_ms')
@ -301,7 +305,7 @@ class TestMacros(unittest.TestCase):
self.assertListEqual( self.assertListEqual(
self.result, 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) self.assertEqual(len(macro.child_macros), 2)
@ -311,7 +315,7 @@ class TestMacros(unittest.TestCase):
start = time.time() start = time.time()
macro = parse('r(3, k(m).w(100))', self.mapping) macro = parse('r(3, k(m).w(100))', self.mapping)
m_code = system_mapping.get('m') 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)) self.loop.run_until_complete(macro.run(self.handler))
keystroke_time = 6 * self.mapping.get('macros.keystroke_sleep_ms') 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.assertGreater(time.time() - start, total_time * 0.9)
self.assertLess(time.time() - start, total_time * 1.1) self.assertLess(time.time() - start, total_time * 1.1)
self.assertListEqual(self.result, [ self.assertListEqual(self.result, [
(m_code, 1), (m_code, 0), (EV_KEY, m_code, 1), (EV_KEY, m_code, 0),
(m_code, 1), (m_code, 0), (EV_KEY, m_code, 1), (EV_KEY, m_code, 0),
(m_code, 1), (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), 1)
self.assertEqual(len(macro.child_macros[0].child_macros), 0) self.assertEqual(len(macro.child_macros[0].child_macros), 0)
@ -335,15 +339,15 @@ class TestMacros(unittest.TestCase):
minus = system_mapping.get('minus') minus = system_mapping.get('minus')
m = system_mapping.get('m') 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.loop.run_until_complete(macro.run(self.handler))
self.assertListEqual(self.result, [ self.assertListEqual(self.result, [
(r, 1), (r, 0), (EV_KEY, r, 1), (EV_KEY, r, 0),
(minus, 1), (minus, 0), (EV_KEY, minus, 1), (EV_KEY, minus, 0),
(r, 1), (r, 0), (EV_KEY, r, 1), (EV_KEY, r, 0),
(minus, 1), (minus, 0), (EV_KEY, minus, 1), (EV_KEY, minus, 0),
(m, 1), (m, 0), (EV_KEY, m, 1), (EV_KEY, m, 0),
]) ])
self.assertEqual(len(macro.child_macros), 1) self.assertEqual(len(macro.child_macros), 1)
self.assertEqual(len(macro.child_macros[0].child_macros), 0) self.assertEqual(len(macro.child_macros[0].child_macros), 0)
@ -359,7 +363,7 @@ class TestMacros(unittest.TestCase):
left = system_mapping.get('bTn_lEfT') left = system_mapping.get('bTn_lEfT')
k = system_mapping.get('k') 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)) 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.assertLess(time.time() - start, total_time * 1.1)
self.assertGreater(time.time() - start, total_time * 0.9) self.assertGreater(time.time() - start, total_time * 0.9)
expected = [(w, 1)] expected = [(EV_KEY, w, 1)]
expected += [(left, 1), (left, 0)] * 2 expected += [(EV_KEY, left, 1), (EV_KEY, left, 0)] * 2
expected += [(w, 0)] expected += [(EV_KEY, w, 0)]
expected += [(k, 1), (k, 0)] expected += [(EV_KEY, k, 1), (EV_KEY, k, 0)]
expected *= 2 expected *= 2
self.assertListEqual(self.result, expected) self.assertListEqual(self.result, expected)
@ -431,9 +435,9 @@ class TestMacros(unittest.TestCase):
self.assertFalse(macro.is_holding()) self.assertFalse(macro.is_holding())
expected = [ expected = [
(a, 1), (a, 0), (EV_KEY, a, 1), (EV_KEY, a, 0),
(b, 1), (b, 0), (EV_KEY, b, 1), (EV_KEY, b, 0),
(c, 1), (c, 0), (EV_KEY, c, 1), (EV_KEY, c, 0),
] ]
self.assertListEqual(self.result, expected) self.assertListEqual(self.result, expected)
@ -448,12 +452,57 @@ class TestMacros(unittest.TestCase):
self.assertFalse(macro.is_holding()) self.assertFalse(macro.is_holding())
expected = [ expected = [
(a, 1), (a, 0), (EV_KEY, a, 1), (EV_KEY, a, 0),
(b, 1), (b, 0), (EV_KEY, b, 1), (EV_KEY, b, 0),
(c, 1), (c, 0), (EV_KEY, c, 1), (EV_KEY, c, 0),
] * 2 ] * 2
self.assertListEqual(self.result, expected) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

Loading…
Cancel
Save