macro improvements

This commit is contained in:
sezanzeb 2020-11-28 23:44:40 +01:00
parent e159aff995
commit 845b7c2397
3 changed files with 38 additions and 21 deletions

View File

@ -40,7 +40,8 @@ Documentation:
- `m` holds a modifier while executing the second parameter - `m` holds a modifier while executing the second parameter
- `.` executes two actions behind each other - `.` executes two actions behind each other
For a list of supported keystrokes and their names, check the output of `xmodmap -pke` For a list of supported keystrokes and their names, check the output of
`xmodmap -pke`
Maybe you shouldn't use this feature in online PVP though. Might even get Maybe you shouldn't use this feature in online PVP though. Might even get
detected by the game. detected by the game.

View File

@ -42,9 +42,18 @@ from keymapper.logger import logger
from keymapper.config import config from keymapper.config import config
# for debugging purposes
MODIFIER = 1
CHILD_MACRO = 2
SLEEP = 3
REPEAT = 4
KEYSTROKE = 5
DEBUG = 6
class _Macro: class _Macro:
"""Supports chaining and preparing actions.""" """Supports chaining and preparing actions."""
def __init__(self, handler): def __init__(self, handler, depth):
"""Create a macro instance that can be populated with tasks. """Create a macro instance that can be populated with tasks.
Parameters Parameters
@ -53,13 +62,17 @@ class _Macro:
A function that accepts keycodes as the first parameter and the A function that accepts keycodes as the first parameter and the
key-press state as the second. 1 for down and 0 for up. The key-press state as the second. 1 for down and 0 for up. The
macro will write to this function once executed with `.run()`. macro will write to this function once executed with `.run()`.
depth : int
0 for the outermost parent macro, 1 or greater for child macros,
like the second argument of repeat.
""" """
self.tasks = [] self.tasks = []
self.handler = handler self.handler = handler
self.depth = depth
async def run(self): async def run(self):
"""Run the macro.""" """Run the macro."""
for task in self.tasks: for i, (_, task) in enumerate(self.tasks):
coroutine = task() coroutine = task()
if asyncio.iscoroutine(coroutine): if asyncio.iscoroutine(coroutine):
await coroutine await coroutine
@ -76,11 +89,11 @@ class _Macro:
modifier : str modifier : str
macro : _Macro macro : _Macro
""" """
self.tasks.append(lambda: self.handler(modifier, 1)) self.tasks.append((MODIFIER, lambda: self.handler(modifier, 1)))
self.add_keycode_pause() self.add_keycode_pause()
self.tasks.append(macro.run) self.tasks.append((CHILD_MACRO, macro.run))
self.add_keycode_pause() self.add_keycode_pause()
self.tasks.append(lambda: self.handler(modifier, 0)) self.tasks.append((MODIFIER, lambda: self.handler(modifier, 0)))
self.add_keycode_pause() self.add_keycode_pause()
return self return self
@ -93,7 +106,7 @@ class _Macro:
macro : _Macro macro : _Macro
""" """
for _ in range(repeats): for _ in range(repeats):
self.tasks.append(macro.run) self.tasks.append((CHILD_MACRO, macro.run))
return self return self
def add_keycode_pause(self): def add_keycode_pause(self):
@ -103,17 +116,17 @@ class _Macro:
async def sleep(): async def sleep():
await asyncio.sleep(sleeptime) await asyncio.sleep(sleeptime)
self.tasks.append(sleep) self.tasks.append((SLEEP, sleep))
def keycode(self, character): def keycode(self, character):
"""Write the character.""" """Write the character."""
self.tasks.append(lambda: self.handler(character, 1)) self.tasks.append((KEYSTROKE, lambda: self.handler(character, 1)))
self.tasks.append(lambda: logger.spam( self.tasks.append((DEBUG, lambda: logger.spam(
'macro writes character %s', 'macro writes character %s',
character character
)) )))
self.add_keycode_pause() self.add_keycode_pause()
self.tasks.append(lambda: self.handler(character, 0)) self.tasks.append((KEYSTROKE, lambda: self.handler(character, 0)))
self.add_keycode_pause() self.add_keycode_pause()
return self return self
@ -124,7 +137,7 @@ class _Macro:
async def sleep(): async def sleep():
await asyncio.sleep(sleeptime) await asyncio.sleep(sleeptime)
self.tasks.append(sleep) self.tasks.append((SLEEP, sleep))
return self return self
@ -171,7 +184,6 @@ def _parse_recurse(macro, handler, macro_instance=None, depth=0):
macro_instance : _Macro or None macro_instance : _Macro or None
A macro instance to add tasks to A macro instance to add tasks to
depth : int depth : int
For logging and debugging purposes
""" """
# to anyone who knows better about compilers and thinks this is horrible: # to anyone who knows better about compilers and thinks this is horrible:
# please make a pull request. Because it probably is. # please make a pull request. Because it probably is.
@ -184,7 +196,7 @@ def _parse_recurse(macro, handler, macro_instance=None, depth=0):
assert isinstance(depth, int) assert isinstance(depth, int)
if macro_instance is None: if macro_instance is None:
macro_instance = _Macro(handler) macro_instance = _Macro(handler, depth)
else: else:
assert isinstance(macro_instance, _Macro) assert isinstance(macro_instance, _Macro)
@ -192,7 +204,7 @@ def _parse_recurse(macro, handler, macro_instance=None, depth=0):
space = ' ' * depth space = ' ' * depth
# is it another macro? # is it another macro?
call_match = re.match(r'^(\w+)\(.+?', macro) call_match = re.match(r'^(\w+)\(', macro)
call = call_match[1] if call_match else None call = call_match[1] if call_match else None
if call is not None: if call is not None:
# available functions in the macro # available functions in the macro
@ -255,6 +267,7 @@ def _parse_recurse(macro, handler, macro_instance=None, depth=0):
macro = int(macro) macro = int(macro)
except ValueError: except ValueError:
pass pass
logger.spam('%s%s %s', space, type(macro), macro)
return macro return macro
@ -272,8 +285,11 @@ def parse(macro, handler):
key-press state as the second. 1 for down and 0 for up. The key-press state as the second. 1 for down and 0 for up. The
macro will write to this function once executed with `.run()`. macro will write to this function once executed with `.run()`.
""" """
# whitespaces, tabs, newlines and such don't serve a purpose. make
# the log output clearer.
macro = re.sub(r'\s', '', macro)
logger.spam('preparing macro %s for later execution', macro)
try: try:
logger.spam('input %s', macro)
return _parse_recurse(macro, handler) return _parse_recurse(macro, handler)
except Exception as e: except Exception as e:
logger.error('Failed to parse macro "%s": %s', macro, e) logger.error('Failed to parse macro "%s": %s', macro, e)

View File

@ -41,10 +41,10 @@ class TestMacros(unittest.TestCase):
self.assertListEqual(self.result, [(1, 1), (1, 0)]) self.assertListEqual(self.result, [(1, 1), (1, 0)])
def test_1(self): def test_1(self):
macro = 'k(1 2).k(a).k(3)' macro = 'k(1).k(a).k(3)'
self.loop.run_until_complete(parse(macro, self.handler).run()) self.loop.run_until_complete(parse(macro, self.handler).run())
self.assertListEqual(self.result, [ self.assertListEqual(self.result, [
('1 2', 1), ('1 2', 0), (1, 1), (1, 0),
('a', 1), ('a', 0), ('a', 1), ('a', 0),
(3, 1), (3, 0), (3, 1), (3, 0),
]) ])
@ -77,7 +77,7 @@ class TestMacros(unittest.TestCase):
]) ])
def test_4(self): def test_4(self):
macro = ' r(2,\nk(\rr ).k(-\n )).k(m) ' macro = ' r(2,\nk(\nr ).k(-\n )).k(m) '
self.loop.run_until_complete(parse(macro, self.handler).run()) self.loop.run_until_complete(parse(macro, self.handler).run())
self.assertListEqual(self.result, [ self.assertListEqual(self.result, [
('r', 1), ('r', 0), ('r', 1), ('r', 0),
@ -89,7 +89,7 @@ class TestMacros(unittest.TestCase):
def test_5(self): def test_5(self):
start = time.time() start = time.time()
macro = 'w(200).r(2,m(w,\rr(2,\tk(r))).w(10).k(k))' macro = 'w(200).r(2,m(w,\nr(2,\tk(r))).w(10).k(k))'
self.loop.run_until_complete(parse(macro, self.handler).run()) self.loop.run_until_complete(parse(macro, self.handler).run())
num_pauses = 8 + 6 + 4 num_pauses = 8 + 6 + 4