improved macro tests

pull/14/head
sezanzeb 4 years ago
parent f920c2d031
commit 53677b4a21

@ -33,17 +33,20 @@ It is possible to write timed macros into the center column:
- `k(1).w(10).k(2)` 12 - `k(1).w(10).k(2)` 12
- `r(3, k(a).w(10))` aaa - `r(3, k(a).w(10))` aaa
- `r(2, k(a).k(-)).k(b)` a-a-b - `r(2, k(a).k(-)).k(b)` a-a-b
- `w(1000).m(SHIFT_L, r(2, k(a))).w(10, 20).k(b)` AAb - `w(1000).m(SHIFT_L, r(2, k(a))).w(10).k(b)` AAb
Documentation: Documentation:
- `r` repeats the execution of the second parameter - `r` repeats the execution of the second parameter
- `w` waits in milliseconds (randomly with 2 parameters) - `w` waits in milliseconds
- `k` writes a single keystroke - `k` writes a single keystroke
- `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
detected by the game.
## Git Installation ## Git Installation
```bash ```bash

@ -33,9 +33,13 @@ from keymapper.logger import logger
CONFIG_PATH = os.path.join(CONFIG, 'config') CONFIG_PATH = os.path.join(CONFIG, 'config')
# an empty config with basic expected substructures
INITIAL_CONFIG = { INITIAL_CONFIG = {
'autoload': {} 'autoload': {},
'macros': {
# some time between keystrokes might be required for them to be
# detected properly in software.
'keystroke_sleep_ms': 10
}
} }
@ -54,6 +58,11 @@ class _Config:
elif self._config['autoload'].get(device) is not None: elif self._config['autoload'].get(device) is not None:
del self._config['autoload'][device] del self._config['autoload'][device]
def get_keystroke_sleep(self):
"""Get the seconds of sleep between key down and up events."""
macros = self._config.get('macros', {})
return macros.get('keystroke_sleep_ms', 10)
def iterate_autoload_presets(self): def iterate_autoload_presets(self):
"""Get tuples of (device, preset).""" """Get tuples of (device, preset)."""
return self._config.get('autoload', {}).items() return self._config.get('autoload', {}).items()

@ -22,7 +22,6 @@
"""Keeps injecting keycodes in the background based on the mapping.""" """Keeps injecting keycodes in the background based on the mapping."""
import os
import re import re
import asyncio import asyncio
import time import time
@ -220,11 +219,7 @@ class KeycodeInjector:
def _write(self, device, keycode, value): def _write(self, device, keycode, value):
"""Actually inject.""" """Actually inject."""
device.write( device.write(evdev.ecodes.EV_KEY, keycode - KEYCODE_OFFSET, value)
evdev.ecodes.EV_KEY,
keycode - KEYCODE_OFFSET,
value
)
device.syn() device.syn()
async def _injection_loop(self, device, keymapper_device): async def _injection_loop(self, device, keymapper_device):
@ -237,6 +232,23 @@ class KeycodeInjector:
keymapper_device : evdev.UInput keymapper_device : evdev.UInput
where to write keycodes to where to write keycodes to
""" """
# Parse all macros beforehand
logger.debug('Parsing macros')
macros = {}
for keycode, output in self.mapping:
if '(' in output and ')' in output and len(output) > 4:
# probably a macro
macros[keycode] = parse(
output,
lambda char, value: (
self._write(
keymapper_device,
system_mapping.get_keycode(char),
value
)
)
)
logger.debug( logger.debug(
'Started injecting into %s, fd %s', 'Started injecting into %s, fd %s',
keymapper_device.device.path, keymapper_device.fd keymapper_device.device.path, keymapper_device.fd
@ -261,24 +273,20 @@ class KeycodeInjector:
target_keycode = input_keycode target_keycode = input_keycode
elif '(' in character: elif '(' in character:
# must be a macro # must be a macro
if event.value == 0:
continue
logger.spam( logger.spam(
'got code:%s value:%s, maps to macro %s', 'got code:%s value:%s, maps to macro %s',
event.code + KEYCODE_OFFSET, event.code + KEYCODE_OFFSET,
event.value, event.value,
character character
) )
# TODO prepare this beforehand, not on each keystroke macro = macros.get(input_keycode)
# TODO test if m(SHIFT_L, k(a)) prints A in injector tests # TODO test if m(SHIFT_L, k(a)) prints A in injector tests
parse( if macro is not None:
character, asyncio.ensure_future(macro.run())
handler=lambda keycode, value: ( continue
self._write(
keymapper_device,
keycode,
value
)
)
).run()
else: else:
target_keycode = system_mapping.get_keycode(character) target_keycode = system_mapping.get_keycode(character)
if target_keycode is None: if target_keycode is None:

@ -37,9 +37,9 @@ 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 random
from keymapper.logger import logger from keymapper.logger import logger
from keymapper.config import config
class _Macro: class _Macro:
@ -77,8 +77,11 @@ class _Macro:
macro : _Macro macro : _Macro
""" """
self.tasks.append(lambda: self.handler(modifier, 1)) self.tasks.append(lambda: self.handler(modifier, 1))
self.add_keycode_pause()
self.tasks.append(macro.run) self.tasks.append(macro.run)
self.add_keycode_pause()
self.tasks.append(lambda: self.handler(modifier, 0)) self.tasks.append(lambda: self.handler(modifier, 0))
self.add_keycode_pause()
return self return self
def repeat(self, repeats, macro): def repeat(self, repeats, macro):
@ -93,21 +96,33 @@ class _Macro:
self.tasks.append(macro.run) self.tasks.append(macro.run)
return self return self
def add_keycode_pause(self):
"""To add a pause between keystrokes."""
sleeptime = config.get_keystroke_sleep() / 1000
async def sleep():
await asyncio.sleep(sleeptime)
self.tasks.append(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(lambda: self.handler(character, 1))
self.tasks.append(lambda: logger.spam(
'macro writes character %s',
character
))
self.add_keycode_pause()
self.tasks.append(lambda: self.handler(character, 0)) self.tasks.append(lambda: self.handler(character, 0))
self.add_keycode_pause()
return self return self
def wait(self, min_time, max_time=None): def wait(self, sleeptime):
"""Wait a random time in milliseconds""" """Wait time in milliseconds."""
async def sleep(): sleeptime /= 1000
if max_time is None:
sleeptime = min_time
else:
sleeptime = random.random() * (max_time - min_time) + min_time
await asyncio.sleep(sleeptime / 1000) async def sleep():
await asyncio.sleep(sleeptime)
self.tasks.append(sleep) self.tasks.append(sleep)
return self return self

@ -24,6 +24,7 @@ import unittest
import asyncio import asyncio
from keymapper.dev.macros import parse, _Macro from keymapper.dev.macros import parse, _Macro
from keymapper.config import config
class TestMacros(unittest.TestCase): class TestMacros(unittest.TestCase):
@ -50,19 +51,25 @@ class TestMacros(unittest.TestCase):
def test_2(self): def test_2(self):
start = time.time() start = time.time()
macro = 'r(1, k(k))' repeats = 20
macro = f'r({repeats}, k(k))'
self.loop.run_until_complete(parse(macro, self.handler).run()) self.loop.run_until_complete(parse(macro, self.handler).run())
self.assertLess(time.time() - start, 0.1) sleep_time = 2 * repeats * config.get_keystroke_sleep() / 1000
self.assertListEqual(self.result, [ self.assertGreater(time.time() - start, sleep_time * 0.9)
('k', 1), ('k', 0), self.assertLess(time.time() - start, sleep_time * 1.1)
]) self.assertListEqual(self.result, [('k', 1), ('k', 0)] * repeats)
def test_3(self): def test_3(self):
start = time.time() start = time.time()
macro = 'r(3, k(m).w(100, 200))' macro = 'r(3, k(m).w(100))'
self.loop.run_until_complete(parse(macro, self.handler).run()) self.loop.run_until_complete(parse(macro, self.handler).run())
self.assertGreater(time.time() - start, 0.1 * 3)
self.assertLess(time.time() - start, 0.21 * 3) keystroke_time = 6 * config.get_keystroke_sleep()
total_time = keystroke_time + 300
total_time /= 1000
self.assertGreater(time.time() - start, total_time * 0.9)
self.assertLess(time.time() - start, total_time * 1.1)
self.assertListEqual(self.result, [ self.assertListEqual(self.result, [
('m', 1), ('m', 0), ('m', 1), ('m', 0),
('m', 1), ('m', 0), ('m', 1), ('m', 0),
@ -84,8 +91,14 @@ class TestMacros(unittest.TestCase):
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,\rr(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())
self.assertLess(time.time() - start, 0.23)
self.assertGreater(time.time() - start, 0.21) num_pauses = 8 + 6 + 4
keystroke_time = num_pauses * config.get_keystroke_sleep()
wait_time = 220
total_time = (keystroke_time + wait_time) / 1000
self.assertLess(time.time() - start, total_time * 1.1)
self.assertGreater(time.time() - start, total_time * 0.9)
expected = [('w', 1)] expected = [('w', 1)]
expected += [('r', 1), ('r', 0)] * 2 expected += [('r', 1), ('r', 0)] * 2
expected += [('w', 0)] expected += [('w', 0)]

Loading…
Cancel
Save