improved combination detection

pull/14/head
sezanzeb 4 years ago
parent 27a3598172
commit be2701ea45

@ -25,8 +25,8 @@ pacaur -S key-mapper-git
sudo apt install git python3-setuptools sudo apt install git python3-setuptools
git clone https://github.com/sezanzeb/key-mapper.git git clone https://github.com/sezanzeb/key-mapper.git
cd key-mapper && ./scripts/build.sh cd key-mapper && ./scripts/build.sh
sudo dpkg -i ./dist/key-mapper-0.4.0.deb sudo dpkg -i ./dist/key-mapper-0.4.1.deb
sudo apt -f install # fixes missing dependency sudo apt -f install
``` ```
##### pip ##### pip

@ -335,19 +335,6 @@ class KeycodeInjector:
paths = get_devices()[self.device]['paths'] paths = get_devices()[self.device]['paths']
"""# in order to map a combination of shift + a to xf86audiomute,
# key-mapper has to find a way around the systems xkb configs,
# because X11 won't do shift + xf86audiomute.
# This special input can write every possible EV_KEY character
# below 272 (which is mouse-left) without being bothered by
# modifiers. It has its own special xkb symbols and keycodes files
# and
self.special_uinput = evdev.UInput(
name=f'{DEV_NAME} special {self.device}',
phys=DEV_NAME,
events=self._modify_capabilities(macros, source, abs_to_rel)
)"""
# Watch over each one of the potentially multiple devices per hardware # Watch over each one of the potentially multiple devices per hardware
for path in paths: for path in paths:
source, abs_to_rel = self._prepare_device(path) source, abs_to_rel = self._prepare_device(path)

@ -22,6 +22,7 @@
"""Inject a keycode based on the mapping.""" """Inject a keycode based on the mapping."""
import itertools
import asyncio import asyncio
from evdev.ecodes import EV_KEY, EV_ABS from evdev.ecodes import EV_KEY, EV_ABS
@ -97,6 +98,26 @@ COMBINATION_INCOMPLETE = 1 # not all keys of the combination are pressed
NOT_COMBINED = 2 # this key is not part of a combination NOT_COMBINED = 2 # this key is not part of a combination
def subsets(combination):
"""Return a list of subsets of the combination.
If combination is only one element long it returns an empty list,
because it's not a combination and there is no reason to iterate.
Parameters
-----------
combination : tuple
tuple of 3-tuples, each being int, int, int (type, code, value)
"""
combination = list(combination)
lengths = list(range(2, len(combination) + 1))
lengths.reverse()
return list(itertools.chain.from_iterable(
itertools.combinations(combination, length)
for length in lengths
))
def handle_keycode(key_to_code, macros, event, uinput): def handle_keycode(key_to_code, macros, event, uinput):
"""Write mapped keycodes, forward unmapped ones and manage macros. """Write mapped keycodes, forward unmapped ones and manage macros.
@ -136,12 +157,27 @@ def handle_keycode(key_to_code, macros, event, uinput):
# Do not check if key in macros and such, if it is an up event. It's # Do not check if key in macros and such, if it is an up event. It's
# going to be False. # going to be False.
combination = tuple([value[1] for value in unreleased.values()] + [key]) combination = tuple([value[1] for value in unreleased.values()] + [key])
if combination in macros or combination in key_to_code: # find any triggered combination. macros and key_to_code contain
key = combination # every possible equivalent permutation of possible macros. The last
# key in the combination needs to remain the newest key though.
for subset in subsets(combination):
if subset[-1] != key:
# only combinations that are completed and triggered by the
# newest input are of interest
continue
if subset in macros or subset in key_to_code:
key = subset
break
else:
# no subset found, just use the key. all indices are tuples of tuples,
# both for combinations and single keys.
key = (key,)
active_macro = active_macros.get(type_code)
"""Releasing keys and macros""" """Releasing keys and macros"""
active_macro = active_macros.get(type_code)
if is_key_up(event): if is_key_up(event):
if active_macro is not None and active_macro.holding: if active_macro is not None and active_macro.holding:
# Tell the macro for that keycode that the key is released and # Tell the macro for that keycode that the key is released and

@ -28,7 +28,7 @@ from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, ABS_HAT0Y, KEY_A, ABS_X, \
EV_REL, REL_X, BTN_TL EV_REL, REL_X, BTN_TL
from keymapper.dev.keycode_mapper import should_map_event_as_btn, \ from keymapper.dev.keycode_mapper import should_map_event_as_btn, \
active_macros, handle_keycode, unreleased active_macros, handle_keycode, unreleased, subsets
from keymapper.state import system_mapping from keymapper.state import system_mapping
from keymapper.dev.macros import parse from keymapper.dev.macros import parse
from keymapper.config import config from keymapper.config import config
@ -86,6 +86,21 @@ class TestKeycodeMapper(unittest.TestCase):
cleanup() cleanup()
def test_subsets(self):
a = subsets(((1,), (2,), (3,)))
self.assertIn(((1,), (2,)), a)
self.assertIn(((2,), (3,)), a)
self.assertIn(((1,), (3,)), a)
self.assertIn(((1,), (2,), (3,)), a)
self.assertEqual(len(a), 4)
b = subsets(((1,), (2,)))
self.assertIn(((1,), (2,)), b)
self.assertEqual(len(b), 1)
c = subsets(((1,),))
self.assertEqual(len(c), 0)
def test_d_pad(self): def test_d_pad(self):
ev_1 = (EV_ABS, ABS_HAT0X, 1) ev_1 = (EV_ABS, ABS_HAT0X, 1)
ev_2 = (EV_ABS, ABS_HAT0X, -1) ev_2 = (EV_ABS, ABS_HAT0X, -1)
@ -96,10 +111,10 @@ class TestKeycodeMapper(unittest.TestCase):
ev_6 = (EV_ABS, ABS_HAT0Y, 0) ev_6 = (EV_ABS, ABS_HAT0Y, 0)
_key_to_code = { _key_to_code = {
ev_1: 51, (ev_1,): 51,
ev_2: 52, (ev_2,): 52,
ev_4: 54, (ev_4,): 54,
ev_5: 55, (ev_5,): 55,
} }
uinput = UInput() uinput = UInput()
@ -107,8 +122,8 @@ class TestKeycodeMapper(unittest.TestCase):
handle_keycode(_key_to_code, {}, InputEvent(*ev_1), uinput) handle_keycode(_key_to_code, {}, InputEvent(*ev_1), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*ev_4), uinput) handle_keycode(_key_to_code, {}, InputEvent(*ev_4), uinput)
self.assertEqual(len(unreleased), 2) self.assertEqual(len(unreleased), 2)
self.assertEqual(unreleased.get(ev_1[:2]), ((EV_KEY, _key_to_code[ev_1]), ev_1)) self.assertEqual(unreleased.get(ev_1[:2]), ((EV_KEY, _key_to_code[(ev_1,)]), ev_1))
self.assertEqual(unreleased.get(ev_4[:2]), ((EV_KEY, _key_to_code[ev_4]), ev_4)) self.assertEqual(unreleased.get(ev_4[:2]), ((EV_KEY, _key_to_code[(ev_4,)]), ev_4))
# release all of them # release all of them
handle_keycode(_key_to_code, {}, InputEvent(*ev_3), uinput) handle_keycode(_key_to_code, {}, InputEvent(*ev_3), uinput)
@ -119,8 +134,8 @@ class TestKeycodeMapper(unittest.TestCase):
handle_keycode(_key_to_code, {}, InputEvent(*ev_2), uinput) handle_keycode(_key_to_code, {}, InputEvent(*ev_2), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*ev_5), uinput) handle_keycode(_key_to_code, {}, InputEvent(*ev_5), uinput)
self.assertEqual(len(unreleased), 2) self.assertEqual(len(unreleased), 2)
self.assertEqual(unreleased.get(ev_2[:2]), ((EV_KEY, _key_to_code[ev_2]), ev_2)) self.assertEqual(unreleased.get(ev_2[:2]), ((EV_KEY, _key_to_code[(ev_2,)]), ev_2))
self.assertEqual(unreleased.get(ev_5[:2]), ((EV_KEY, _key_to_code[ev_5]), ev_5)) self.assertEqual(unreleased.get(ev_5[:2]), ((EV_KEY, _key_to_code[(ev_5,)]), ev_5))
# release all of them again # release all of them again
handle_keycode(_key_to_code, {}, InputEvent(*ev_3), uinput) handle_keycode(_key_to_code, {}, InputEvent(*ev_3), uinput)
@ -150,7 +165,7 @@ class TestKeycodeMapper(unittest.TestCase):
_key_to_code = { _key_to_code = {
(ev_1, ev_2): 51, (ev_1, ev_2): 51,
ev_2: 52, (ev_2,): 52,
} }
uinput = UInput() uinput = UInput()
@ -188,8 +203,8 @@ class TestKeycodeMapper(unittest.TestCase):
def test_handle_keycode(self): def test_handle_keycode(self):
_key_to_code = { _key_to_code = {
(EV_KEY, 1, 1): 101, ((EV_KEY, 1, 1),): 101,
(EV_KEY, 2, 1): 102 ((EV_KEY, 2, 1),): 102
} }
uinput = UInput() uinput = UInput()
@ -243,6 +258,8 @@ class TestKeycodeMapper(unittest.TestCase):
(EV_KEY, 4, 1) (EV_KEY, 4, 1)
) )
combination_2 = ( combination_2 = (
# should not be triggered, combination_1 should be prioritized
# when all of its keys are down
(EV_KEY, 2, 1), (EV_KEY, 2, 1),
(EV_KEY, 3, 1), (EV_KEY, 3, 1),
(EV_KEY, 4, 1) (EV_KEY, 4, 1)
@ -255,35 +272,41 @@ class TestKeycodeMapper(unittest.TestCase):
_key_to_code = { _key_to_code = {
combination_1: 101, combination_1: 101,
combination_2: 102, combination_2: 102,
down_5: 103 (down_5,): 103
} }
uinput = UInput() uinput = UInput()
# 10 and 11: more key-down events than needed
handle_keycode(_key_to_code, {}, InputEvent(EV_KEY, 10, 1), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*combination_1[0]), uinput) handle_keycode(_key_to_code, {}, InputEvent(*combination_1[0]), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*combination_1[1]), uinput) handle_keycode(_key_to_code, {}, InputEvent(*combination_1[1]), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*combination_1[2]), uinput) handle_keycode(_key_to_code, {}, InputEvent(*combination_1[2]), uinput)
handle_keycode(_key_to_code, {}, InputEvent(EV_KEY, 11, 1), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*combination_1[3]), uinput) handle_keycode(_key_to_code, {}, InputEvent(*combination_1[3]), uinput)
self.assertEqual(len(uinput_write_history), 4) self.assertEqual(len(uinput_write_history), 6)
# the first event is written and then the triggered combination # the first event is written and then the triggered combination
self.assertEqual(uinput_write_history[0].t, (EV_KEY, 1, 1)) self.assertEqual(uinput_write_history[1].t, (EV_KEY, 1, 1))
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 2, 1)) self.assertEqual(uinput_write_history[2].t, (EV_KEY, 2, 1))
self.assertEqual(uinput_write_history[2].t, (EV_KEY, 3, 1)) self.assertEqual(uinput_write_history[3].t, (EV_KEY, 3, 1))
self.assertEqual(uinput_write_history[3].t, (EV_KEY, 101, 1)) self.assertEqual(uinput_write_history[5].t, (EV_KEY, 101, 1))
# while the combination is down, another unrelated key can be used # while the combination is down, another unrelated key can be used
handle_keycode(_key_to_code, {}, InputEvent(*down_5), uinput) handle_keycode(_key_to_code, {}, InputEvent(*down_5), uinput)
self.assertEqual(len(uinput_write_history), 5) # the keycode_mapper searches for subsets of the current held-down
self.assertEqual(uinput_write_history[4].t, (EV_KEY, 103, 1)) # keys to activate combinations, down_5 should not trigger them
# again.
self.assertEqual(len(uinput_write_history), 7)
self.assertEqual(uinput_write_history[6].t, (EV_KEY, 103, 1))
# release the combination by releasing the last key, and release # release the combination by releasing the last key, and release
# the unrelated key # the unrelated key
handle_keycode(_key_to_code, {}, InputEvent(*up_4), uinput) handle_keycode(_key_to_code, {}, InputEvent(*up_4), uinput)
handle_keycode(_key_to_code, {}, InputEvent(*up_5), uinput) handle_keycode(_key_to_code, {}, InputEvent(*up_5), uinput)
self.assertEqual(len(uinput_write_history), 7) self.assertEqual(len(uinput_write_history), 9)
self.assertEqual(uinput_write_history[5].t, (EV_KEY, 101, 0)) self.assertEqual(uinput_write_history[7].t, (EV_KEY, 101, 0))
self.assertEqual(uinput_write_history[6].t, (EV_KEY, 103, 0)) self.assertEqual(uinput_write_history[8].t, (EV_KEY, 103, 0))
def test_handle_keycode_macro(self): def test_handle_keycode_macro(self):
history = [] history = []
@ -295,12 +318,12 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('b', code_b) system_mapping._set('b', code_b)
macro_mapping = { macro_mapping = {
(EV_KEY, 1, 1): parse('k(a)', self.mapping), ((EV_KEY, 1, 1),): parse('k(a)', self.mapping),
(EV_KEY, 2, 1): parse('r(5, k(b))', self.mapping) ((EV_KEY, 2, 1),): parse('r(5, k(b))', self.mapping)
} }
macro_mapping[(EV_KEY, 1, 1)].set_handler(lambda *args: history.append(args)) macro_mapping[((EV_KEY, 1, 1),)].set_handler(lambda *args: history.append(args))
macro_mapping[(EV_KEY, 2, 1)].set_handler(lambda *args: history.append(args)) macro_mapping[((EV_KEY, 2, 1),)].set_handler(lambda *args: history.append(args))
handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 1, 1), None) handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 1, 1), None)
handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 2, 1), None) handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 2, 1), None)
@ -331,13 +354,13 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('c', code_c) system_mapping._set('c', code_c)
macro_mapping = { macro_mapping = {
(EV_KEY, 1, 1): parse('k(a).h(k(b)).k(c)', self.mapping) ((EV_KEY, 1, 1),): parse('k(a).h(k(b)).k(c)', self.mapping)
} }
def handler(*args): def handler(*args):
history.append(args) history.append(args)
macro_mapping[(EV_KEY, 1, 1)].set_handler(handler) macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
"""start macro""" """start macro"""
@ -395,9 +418,9 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('d', code_d) system_mapping._set('d', code_d)
macro_mapping = { macro_mapping = {
(EV_KEY, 1, 1): parse('h(k(b))', self.mapping), ((EV_KEY, 1, 1),): parse('h(k(b))', self.mapping),
(EV_KEY, 2, 1): parse('k(c).r(1, r(1, r(1, h(k(a))))).k(d)', self.mapping), ((EV_KEY, 2, 1),): parse('k(c).r(1, r(1, r(1, h(k(a))))).k(d)', self.mapping),
(EV_KEY, 3, 1): parse('h(k(b))', self.mapping) ((EV_KEY, 3, 1),): parse('h(k(b))', self.mapping)
} }
history = [] history = []
@ -405,9 +428,9 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args): def handler(*args):
history.append(args) history.append(args)
macro_mapping[(EV_KEY, 1, 1)].set_handler(handler) macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
macro_mapping[(EV_KEY, 2, 1)].set_handler(handler) macro_mapping[((EV_KEY, 2, 1),)].set_handler(handler)
macro_mapping[(EV_KEY, 3, 1)].set_handler(handler) macro_mapping[((EV_KEY, 3, 1),)].set_handler(handler)
"""start macro 2""" """start macro 2"""
@ -518,7 +541,7 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('c', code_c) system_mapping._set('c', code_c)
macro_mapping = { macro_mapping = {
(EV_KEY, 1, 1): parse('k(a).h(k(b)).k(c)', self.mapping), ((EV_KEY, 1, 1),): parse('k(a).h(k(b)).k(c)', self.mapping),
} }
history = [] history = []
@ -526,7 +549,7 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args): def handler(*args):
history.append(args) history.append(args)
macro_mapping[(EV_KEY, 1, 1)].set_handler(handler) macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 1, 1), None) handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 1, 1), None)
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -591,14 +614,14 @@ class TestKeycodeMapper(unittest.TestCase):
macro_mapping = { macro_mapping = {
(down_0, down_1): parse('k(1).h(k(2)).k(3)', self.mapping), (down_0, down_1): parse('k(1).h(k(2)).k(3)', self.mapping),
down_2: parse('k(a).h(k(b)).k(c)', self.mapping) (down_2,): parse('k(a).h(k(b)).k(c)', self.mapping)
} }
def handler(*args): def handler(*args):
history.append(args) history.append(args)
macro_mapping[(down_0, down_1)].set_handler(handler) macro_mapping[(down_0, down_1)].set_handler(handler)
macro_mapping[down_2].set_handler(handler) macro_mapping[(down_2,)].set_handler(handler)
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -690,8 +713,8 @@ class TestKeycodeMapper(unittest.TestCase):
repeats = 10 repeats = 10
macro_mapping = { macro_mapping = {
down_1: parse(f'r({repeats}, k(1))', self.mapping), (down_1,): parse(f'r({repeats}, k(1))', self.mapping),
down_2: parse(f'r({repeats}, k(2))', self.mapping) (down_2,): parse(f'r({repeats}, k(2))', self.mapping)
} }
history = [] history = []
@ -699,8 +722,8 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args): def handler(*args):
history.append(args) history.append(args)
macro_mapping[down_1].set_handler(handler) macro_mapping[(down_1,)].set_handler(handler)
macro_mapping[down_2].set_handler(handler) macro_mapping[(down_2,)].set_handler(handler)
handle_keycode({}, macro_mapping, InputEvent(*down_1), None) handle_keycode({}, macro_mapping, InputEvent(*down_1), None)
handle_keycode({}, macro_mapping, InputEvent(*down_2), None) handle_keycode({}, macro_mapping, InputEvent(*down_2), None)
@ -730,8 +753,8 @@ class TestKeycodeMapper(unittest.TestCase):
ev_4 = (*key_2, 0) ev_4 = (*key_2, 0)
_key_to_code = { _key_to_code = {
(*key_1, 1): 41, ((*key_1, 1),): 41,
(*key_2, -1): 42 ((*key_2, -1),): 42
} }
uinput = UInput() uinput = UInput()
@ -750,8 +773,8 @@ class TestKeycodeMapper(unittest.TestCase):
trigger = (EV_KEY, BTN_TL) trigger = (EV_KEY, BTN_TL)
_key_to_code = { _key_to_code = {
(*trigger, 1): 51, ((*trigger, 1),): 51,
(*trigger, -1): 52 ((*trigger, -1),): 52
} }
uinput = UInput() uinput = UInput()
@ -786,7 +809,7 @@ class TestKeycodeMapper(unittest.TestCase):
ev_3 = (*key, 0) ev_3 = (*key, 0)
_key_to_code = { _key_to_code = {
(*key, 1): 21, ((*key, 1),): 21,
} }
uinput = UInput() uinput = UInput()

Loading…
Cancel
Save