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
git clone https://github.com/sezanzeb/key-mapper.git
cd key-mapper && ./scripts/build.sh
sudo dpkg -i ./dist/key-mapper-0.4.0.deb
sudo apt -f install # fixes missing dependency
sudo dpkg -i ./dist/key-mapper-0.4.1.deb
sudo apt -f install
```
##### pip

@ -335,19 +335,6 @@ class KeycodeInjector:
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
for path in paths:
source, abs_to_rel = self._prepare_device(path)

@ -22,6 +22,7 @@
"""Inject a keycode based on the mapping."""
import itertools
import asyncio
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
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):
"""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
# going to be False.
combination = tuple([value[1] for value in unreleased.values()] + [key])
if combination in macros or combination in key_to_code:
key = combination
# find any triggered combination. macros and key_to_code contain
# 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"""
active_macro = active_macros.get(type_code)
if is_key_up(event):
if active_macro is not None and active_macro.holding:
# 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
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.dev.macros import parse
from keymapper.config import config
@ -86,6 +86,21 @@ class TestKeycodeMapper(unittest.TestCase):
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):
ev_1 = (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)
_key_to_code = {
ev_1: 51,
ev_2: 52,
ev_4: 54,
ev_5: 55,
(ev_1,): 51,
(ev_2,): 52,
(ev_4,): 54,
(ev_5,): 55,
}
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_4), uinput)
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_4[:2]), ((EV_KEY, _key_to_code[ev_4]), ev_4))
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))
# release all of them
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_5), uinput)
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_5[:2]), ((EV_KEY, _key_to_code[ev_5]), ev_5))
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))
# release all of them again
handle_keycode(_key_to_code, {}, InputEvent(*ev_3), uinput)
@ -150,7 +165,7 @@ class TestKeycodeMapper(unittest.TestCase):
_key_to_code = {
(ev_1, ev_2): 51,
ev_2: 52,
(ev_2,): 52,
}
uinput = UInput()
@ -188,8 +203,8 @@ class TestKeycodeMapper(unittest.TestCase):
def test_handle_keycode(self):
_key_to_code = {
(EV_KEY, 1, 1): 101,
(EV_KEY, 2, 1): 102
((EV_KEY, 1, 1),): 101,
((EV_KEY, 2, 1),): 102
}
uinput = UInput()
@ -243,6 +258,8 @@ class TestKeycodeMapper(unittest.TestCase):
(EV_KEY, 4, 1)
)
combination_2 = (
# should not be triggered, combination_1 should be prioritized
# when all of its keys are down
(EV_KEY, 2, 1),
(EV_KEY, 3, 1),
(EV_KEY, 4, 1)
@ -255,35 +272,41 @@ class TestKeycodeMapper(unittest.TestCase):
_key_to_code = {
combination_1: 101,
combination_2: 102,
down_5: 103
(down_5,): 103
}
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[1]), 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)
self.assertEqual(len(uinput_write_history), 4)
self.assertEqual(len(uinput_write_history), 6)
# 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, 2, 1))
self.assertEqual(uinput_write_history[2].t, (EV_KEY, 3, 1))
self.assertEqual(uinput_write_history[3].t, (EV_KEY, 101, 1))
self.assertEqual(uinput_write_history[1].t, (EV_KEY, 1, 1))
self.assertEqual(uinput_write_history[2].t, (EV_KEY, 2, 1))
self.assertEqual(uinput_write_history[3].t, (EV_KEY, 3, 1))
self.assertEqual(uinput_write_history[5].t, (EV_KEY, 101, 1))
# while the combination is down, another unrelated key can be used
handle_keycode(_key_to_code, {}, InputEvent(*down_5), uinput)
self.assertEqual(len(uinput_write_history), 5)
self.assertEqual(uinput_write_history[4].t, (EV_KEY, 103, 1))
# the keycode_mapper searches for subsets of the current held-down
# 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
# the unrelated key
handle_keycode(_key_to_code, {}, InputEvent(*up_4), 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[6].t, (EV_KEY, 103, 0))
self.assertEqual(uinput_write_history[7].t, (EV_KEY, 101, 0))
self.assertEqual(uinput_write_history[8].t, (EV_KEY, 103, 0))
def test_handle_keycode_macro(self):
history = []
@ -295,12 +318,12 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('b', code_b)
macro_mapping = {
(EV_KEY, 1, 1): parse('k(a)', self.mapping),
(EV_KEY, 2, 1): parse('r(5, k(b))', self.mapping)
((EV_KEY, 1, 1),): parse('k(a)', 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, 2, 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))
handle_keycode({}, macro_mapping, InputEvent(EV_KEY, 1, 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)
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):
history.append(args)
macro_mapping[(EV_KEY, 1, 1)].set_handler(handler)
macro_mapping[((EV_KEY, 1, 1),)].set_handler(handler)
"""start macro"""
@ -395,9 +418,9 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('d', code_d)
macro_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, 3, 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, 3, 1),): parse('h(k(b))', self.mapping)
}
history = []
@ -405,9 +428,9 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args):
history.append(args)
macro_mapping[(EV_KEY, 1, 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, 1, 1),)].set_handler(handler)
macro_mapping[((EV_KEY, 2, 1),)].set_handler(handler)
macro_mapping[((EV_KEY, 3, 1),)].set_handler(handler)
"""start macro 2"""
@ -518,7 +541,7 @@ class TestKeycodeMapper(unittest.TestCase):
system_mapping._set('c', code_c)
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 = []
@ -526,7 +549,7 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*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)
loop = asyncio.get_event_loop()
@ -591,14 +614,14 @@ class TestKeycodeMapper(unittest.TestCase):
macro_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):
history.append(args)
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()
@ -690,8 +713,8 @@ class TestKeycodeMapper(unittest.TestCase):
repeats = 10
macro_mapping = {
down_1: parse(f'r({repeats}, k(1))', self.mapping),
down_2: parse(f'r({repeats}, k(2))', self.mapping)
(down_1,): parse(f'r({repeats}, k(1))', self.mapping),
(down_2,): parse(f'r({repeats}, k(2))', self.mapping)
}
history = []
@ -699,8 +722,8 @@ class TestKeycodeMapper(unittest.TestCase):
def handler(*args):
history.append(args)
macro_mapping[down_1].set_handler(handler)
macro_mapping[down_2].set_handler(handler)
macro_mapping[(down_1,)].set_handler(handler)
macro_mapping[(down_2,)].set_handler(handler)
handle_keycode({}, macro_mapping, InputEvent(*down_1), None)
handle_keycode({}, macro_mapping, InputEvent(*down_2), None)
@ -730,8 +753,8 @@ class TestKeycodeMapper(unittest.TestCase):
ev_4 = (*key_2, 0)
_key_to_code = {
(*key_1, 1): 41,
(*key_2, -1): 42
((*key_1, 1),): 41,
((*key_2, -1),): 42
}
uinput = UInput()
@ -750,8 +773,8 @@ class TestKeycodeMapper(unittest.TestCase):
trigger = (EV_KEY, BTN_TL)
_key_to_code = {
(*trigger, 1): 51,
(*trigger, -1): 52
((*trigger, 1),): 51,
((*trigger, -1),): 52
}
uinput = UInput()
@ -786,7 +809,7 @@ class TestKeycodeMapper(unittest.TestCase):
ev_3 = (*key, 0)
_key_to_code = {
(*key, 1): 21,
((*key, 1),): 21,
}
uinput = UInput()

Loading…
Cancel
Save