diff --git a/keymapper/dev/injector.py b/keymapper/dev/injector.py index 1e6e2ee1..c96dbd13 100644 --- a/keymapper/dev/injector.py +++ b/keymapper/dev/injector.py @@ -98,15 +98,14 @@ def ensure_numlock(func): def is_in_capabilities(key, capabilities): - """Are this key or all of its sub keys in the capabilities?""" - if isinstance(key[0], tuple): - # it's a key combination - for sub_key in key: - if is_in_capabilities(sub_key, capabilities): - return True - else: - ev_type, code, _ = key - if code in capabilities.get(ev_type, []): + """Are this key or one of its sub keys in the capabilities? + + Parameters + ---------- + key : Key + """ + for sub_key in key: + if sub_key[1] in capabilities.get(sub_key[0], []): return True return False @@ -285,7 +284,7 @@ class KeycodeInjector: if ecodes.BTN_MOUSE not in capabilities[EV_KEY]: # to be able to move the cursor, this key capability is # needed - capabilities[EV_KEY] = [ecodes.BTN_MOUSE] + capabilities[EV_KEY].append(ecodes.BTN_MOUSE) # just like what python-evdev does in from_device if ecodes.EV_SYN in capabilities: diff --git a/keymapper/dev/reader.py b/keymapper/dev/reader.py index 86260098..c097cd30 100644 --- a/keymapper/dev/reader.py +++ b/keymapper/dev/reader.py @@ -223,7 +223,10 @@ class _KeycodeReader: if event.value == 0: if without_value in self._unreleased: del self._unreleased[without_value] + continue + if without_value in self._unreleased: + # no duplicate down events (gamepad triggers) continue self._unreleased[without_value] = ( diff --git a/keymapper/gtk/row.py b/keymapper/gtk/row.py index 7afc7f43..97e97d68 100644 --- a/keymapper/gtk/row.py +++ b/keymapper/gtk/row.py @@ -253,10 +253,11 @@ class Row(Gtk.ListBoxRow): self.keycode_input.set_label(label) # make the child label widget break lines, important for # long combinations - self.keycode_input.get_child().set_line_wrap(True) - self.keycode_input.get_child().set_line_wrap_mode(2) - self.keycode_input.get_child().set_max_width_chars(15) - self.keycode_input.get_child().set_justify(Gtk.Justification.CENTER) + label = self.keycode_input.get_child() + label.set_line_wrap(True) + label.set_line_wrap_mode(2) + label.set_max_width_chars(13) + label.set_justify(Gtk.Justification.CENTER) def put_together(self, character): """Create all child GTK widgets and connect their signals.""" diff --git a/keymapper/gtk/window.py b/keymapper/gtk/window.py index 24ce2b37..e850fccc 100755 --- a/keymapper/gtk/window.py +++ b/keymapper/gtk/window.py @@ -348,7 +348,12 @@ class Window: # the "event" event of Gtk.Window wouldn't trigger on gamepad # events, so it became a GLib timeout to periodically check kernel # events. + + # letting go of one of the keys of a combination won't just make + # it return the leftover key, it will continue to return None because + # they have already been read. key = keycode_reader.read() + key and print(key) if isinstance(focused, Gtk.ToggleButton): if not keycode_reader.are_keys_pressed(): diff --git a/keymapper/key.py b/keymapper/key.py index 7475b897..32f7b85a 100644 --- a/keymapper/key.py +++ b/keymapper/key.py @@ -98,6 +98,7 @@ class Key: return hash(self.keys) def __eq__(self, other): + print(self, 'eq', other) if isinstance(other, tuple): if isinstance(other[0], tuple): # a combination ((1, 5, 1), (1, 3, 1)) diff --git a/tests/testcases/test_injector.py b/tests/testcases/test_injector.py index 1949a59f..9506e360 100644 --- a/tests/testcases/test_injector.py +++ b/tests/testcases/test_injector.py @@ -95,23 +95,34 @@ class TestInjector(unittest.TestCase): self.injector = KeycodeInjector('foo', mapping) fake_device = FakeDevice() - capabilities = self.injector._modify_capabilities( + capabilities_1 = self.injector._modify_capabilities( {60: macro}, fake_device, abs_to_rel=False ) - self.assertIn(EV_KEY, capabilities) - keys = capabilities[EV_KEY] + self.assertIn(EV_KEY, capabilities_1) + keys = capabilities_1[EV_KEY] self.assertIn(a, keys) self.assertIn(one, keys) self.assertIn(two, keys) self.assertIn(shift_l, keys) - self.assertNotIn(evdev.ecodes.EV_SYN, capabilities) - self.assertNotIn(evdev.ecodes.EV_FF, capabilities) - self.assertNotIn(evdev.ecodes.EV_REL, capabilities) - self.assertNotIn(evdev.ecodes.EV_ABS, capabilities) + self.assertNotIn(evdev.ecodes.EV_SYN, capabilities_1) + self.assertNotIn(evdev.ecodes.EV_FF, capabilities_1) + self.assertNotIn(evdev.ecodes.EV_REL, capabilities_1) + self.assertNotIn(evdev.ecodes.EV_ABS, capabilities_1) + + capabilities_2 = self.injector._modify_capabilities( + {60: macro}, + fake_device, + abs_to_rel=True + ) + keys = capabilities_2[EV_KEY] + self.assertIn(a, keys) + self.assertIn(one, keys) + self.assertIn(two, keys) + self.assertIn(shift_l, keys) def test_grab(self): # path is from the fixtures @@ -485,13 +496,13 @@ class TestInjector(unittest.TestCase): self.assertEqual(len(injector._key_to_code), 3) def test_is_in_capabilities(self): - key = (1, 2, 1) + key = Key(1, 2, 1) capabilities = { 1: [9, 2, 5] } self.assertTrue(is_in_capabilities(key, capabilities)) - key = ((1, 2, 1), (1, 3, 1)) + key = Key((1, 2, 1), (1, 3, 1)) capabilities = { 1: [9, 2, 5] } @@ -500,7 +511,7 @@ class TestInjector(unittest.TestCase): # that make up one hardware device self.assertTrue(is_in_capabilities(key, capabilities)) - key = ((1, 2, 1), (1, 5, 1)) + key = Key((1, 2, 1), (1, 5, 1)) capabilities = { 1: [9, 2, 5] } diff --git a/tests/testcases/test_reader.py b/tests/testcases/test_reader.py index 11b42796..627f4681 100644 --- a/tests/testcases/test_reader.py +++ b/tests/testcases/test_reader.py @@ -21,9 +21,10 @@ import unittest import time +import multiprocessing from evdev.ecodes import EV_KEY, EV_ABS, ABS_HAT0X, ABS_HAT0Y, KEY_COMMA, \ - BTN_LEFT, BTN_TOOL_DOUBLETAP + BTN_LEFT, BTN_TOOL_DOUBLETAP, ABS_Z from keymapper.dev.reader import keycode_reader @@ -127,6 +128,20 @@ class TestReader(unittest.TestCase): self.assertEqual(keycode_reader.read(), None) self.assertEqual(len(keycode_reader._unreleased), 1) + def test_reading_ignore_duplicate_down(self): + pipe = multiprocessing.Pipe() + pipe[1].send(InputEvent(EV_ABS, ABS_Z, 1, 10)) + keycode_reader._pipe = pipe + + self.assertEqual(keycode_reader.read(), (EV_ABS, ABS_Z, 1)) + self.assertEqual(keycode_reader.read(), None) + + pipe[1].send(InputEvent(EV_ABS, ABS_Z, 1, 10)) + # still none + self.assertEqual(keycode_reader.read(), None) + + self.assertEqual(len(keycode_reader._unreleased), 1) + def test_wrong_device(self): pending_events['device 1'] = [ InputEvent(EV_KEY, CODE_1, 1),