From 78692f40ebe646954be831f64ada1d849ec1514f Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Wed, 2 Dec 2020 20:08:55 +0100 Subject: [PATCH] more work for event type to mapping key --- keymapper/dev/reader.py | 10 +++--- keymapper/gtk/row.py | 54 +++++++++++++++++++---------- keymapper/gtk/window.py | 15 ++++---- keymapper/mapping.py | 3 +- tests/testcases/test_integration.py | 23 +++++++----- tests/testcases/test_reader.py | 45 ++++++++++++------------ 6 files changed, 89 insertions(+), 61 deletions(-) diff --git a/keymapper/dev/reader.py b/keymapper/dev/reader.py index e8b94ccd..db954f8c 100644 --- a/keymapper/dev/reader.py +++ b/keymapper/dev/reader.py @@ -113,7 +113,7 @@ class _KeycodeReader: 'got code:%s value:%s', event.code + KEYCODE_OFFSET, event.value ) - self._pipe[1].send(event.code + KEYCODE_OFFSET) + self._pipe[1].send((event.type, event.code + KEYCODE_OFFSET)) def _read_worker(self): """Process that reads keycodes and buffers them into a pipe.""" @@ -145,16 +145,16 @@ class _KeycodeReader: del rlist[fd] def read(self): - """Get the newest keycode or None if none was pressed.""" + """Get the newest tuple of event type, keycode or None.""" if self._pipe is None: logger.debug('No pipe available to read from') return None - newest_keycode = None + newest_event = (None, None) while self._pipe[0].poll(): - newest_keycode = self._pipe[0].recv() + newest_event = self._pipe[0].recv() - return newest_keycode + return newest_event keycode_reader = _KeycodeReader() diff --git a/keymapper/gtk/row.py b/keymapper/gtk/row.py index 73e36678..129500e2 100644 --- a/keymapper/gtk/row.py +++ b/keymapper/gtk/row.py @@ -40,7 +40,10 @@ class Row(Gtk.ListBoxRow): """A single, configurable key mapping.""" __gtype_name__ = 'ListBoxRow' - def __init__(self, delete_callback, window, keycode=None, character=None): + def __init__( + self, delete_callback, window, ev_type=None, keycode=None, + character=None + ): """Construct a row widget.""" super().__init__() self.device = window.selected_device @@ -50,19 +53,26 @@ class Row(Gtk.ListBoxRow): self.character_input = None self.keycode_input = None - self.put_together(keycode, character) + self.put_together(ev_type, keycode, character) def get_keycode(self): - """Get the integer keycode from the left column.""" + """Get a tuple of event_type and keycode from the left column. + + Or None if no codes are mapped on this row. + """ keycode = self.keycode_input.get_label() - return int(keycode) if keycode else None + if not keycode: + return None + + ev_type, keycode = keycode.split(',') + return int(ev_type), int(keycode) def get_character(self): """Get the assigned character from the middle column.""" character = self.character_input.get_text() return character if character else None - def set_new_keycode(self, new_keycode): + def set_new_keycode(self, ev_type, new_keycode): """Check if a keycode has been pressed and if so, display it.""" # the newest_keycode is populated since the ui regularly polls it # in order to display it in the status bar. @@ -78,7 +88,7 @@ class Row(Gtk.ListBoxRow): return # keycode is already set by some other row - if custom_mapping.get_character(EV_KEY, new_keycode) is not None: + if custom_mapping.get_character(ev_type, new_keycode) is not None: msg = f'Keycode {new_keycode} is already mapped' logger.info(msg) self.window.get('status_bar').push(CTX_KEYCODE, msg) @@ -86,7 +96,7 @@ class Row(Gtk.ListBoxRow): # it's legal to display the keycode self.window.get('status_bar').remove_all(CTX_KEYCODE) - self.keycode_input.set_label(str(new_keycode)) + self.keycode_input.set_label(f'{ev_type},{new_keycode}') # switch to the character, don't require mouse input because # that would overwrite the key with the mouse-button key if # the current device is a mouse. idle_add this so that the @@ -100,7 +110,12 @@ class Row(Gtk.ListBoxRow): return # else, the keycode has changed, the character is set, all good - custom_mapping.change(EV_KEY, new_keycode, character, previous_keycode) + custom_mapping.change( + ev_type=ev_type, + new_keycode=new_keycode, + character=character, + previous_keycode=previous_keycode + ) def highlight(self): """Mark this row as changed.""" @@ -112,19 +127,21 @@ class Row(Gtk.ListBoxRow): def on_character_input_change(self, _): """When the output character for that keycode is typed in.""" - keycode = self.get_keycode() + key = self.get_keycode() character = self.get_character() self.highlight() - if keycode is not None: - custom_mapping.change(EV_KEY, - previous_keycode=None, + if key is not None: + ev_type, keycode = key + custom_mapping.change( + ev_type=ev_type, new_keycode=keycode, - character=character + character=character, + previous_keycode=None ) - def put_together(self, keycode, character): + def put_together(self, ev_type, keycode, character): """Create all child GTK widgets and connect their signals.""" delete_button = Gtk.EventBox() delete_button.add(Gtk.Image.new_from_icon_name( @@ -141,7 +158,7 @@ class Row(Gtk.ListBoxRow): keycode_input.set_size_request(50, -1) if keycode is not None: - keycode_input.set_label(str(keycode)) + keycode_input.set_label(f'{ev_type},{keycode})') # make the togglebutton go back to its normal state when doing # something else in the UI @@ -178,9 +195,10 @@ class Row(Gtk.ListBoxRow): def on_delete_button_clicked(self, *args): """Destroy the row and remove it from the config.""" - keycode = self.get_keycode() - if keycode is not None: - custom_mapping.clear(EV_KEY, keycode) + key = self.get_keycode() + if key is not None: + ev_type, keycode = key + custom_mapping.clear(ev_type, keycode) self.character_input.set_text('') self.keycode_input.set_label('') self.delete_callback(self) diff --git a/keymapper/gtk/window.py b/keymapper/gtk/window.py index a20c605e..548f52cb 100755 --- a/keymapper/gtk/window.py +++ b/keymapper/gtk/window.py @@ -22,6 +22,8 @@ """User Interface.""" +from evdev.ecodes import EV_KEY + import gi gi.require_version('Gtk', '3.0') gi.require_version('GLib', '2.0') @@ -213,24 +215,24 @@ class Window: """To capture events from keyboard, mice and gamepads.""" # the "event" event of Gtk.Window wouldn't trigger on gamepad # events, so it became a GLib timeout - keycode = keycode_reader.read() + ev_type, keycode = keycode_reader.read() - if keycode is None: + if keycode is None or ev_type is None: return True - if keycode in [280, 333]: + if ev_type == EV_KEY and keycode in [280, 333]: # disable mapping the left mouse button because it would break # the mouse. Also it is emitted right when focusing the row # which breaks the current workflow. return True - self.get('keycode').set_text(str(keycode)) + self.get('keycode').set_text(f'{ev_type},{keycode}') # inform the currently selected row about the new keycode focused = self.window.get_focus() row = focused.get_parent().get_parent() if isinstance(focused, Gtk.ToggleButton) and isinstance(row, Row): - row.set_new_keycode(keycode) + row.set_new_keycode(ev_type, keycode) return True @@ -374,10 +376,11 @@ class Window: custom_mapping.load(self.selected_device, self.selected_preset) key_list = self.get('key_list') - for (_, keycode), output in custom_mapping: + for (ev_type, keycode), output in custom_mapping: single_key_mapping = Row( window=self, delete_callback=self.on_row_removed, + ev_type=ev_type, keycode=keycode, character=output ) diff --git a/keymapper/mapping.py b/keymapper/mapping.py index 191eaf3f..05d60e2d 100644 --- a/keymapper/mapping.py +++ b/keymapper/mapping.py @@ -59,7 +59,8 @@ class Mapping: Parameters ---------- ev_type : int - one of evdev.events. The original event + one of evdev.events, taken from the original source event. + Everything will be mapped to EV_KEY. new_keycode : int The source keycode, what the mouse would report without any modification. xkb keycode. diff --git a/tests/testcases/test_integration.py b/tests/testcases/test_integration.py index a62e722e..454324a0 100644 --- a/tests/testcases/test_integration.py +++ b/tests/testcases/test_integration.py @@ -182,15 +182,15 @@ class TestIntegration(unittest.TestCase): row = rows[0] - row.set_new_keycode(None) + row.set_new_keycode(None, None) self.assertIsNone(row.get_keycode()) self.assertEqual(len(custom_mapping), 0) - row.set_new_keycode(30) + row.set_new_keycode(EV_KEY, 30) self.assertEqual(len(custom_mapping), 0) - self.assertEqual(row.get_keycode(), 30) - row.set_new_keycode(30) + self.assertEqual(row.get_keycode(), (EV_KEY, 30)) + row.set_new_keycode(EV_KEY, 30) self.assertEqual(len(custom_mapping), 0) - self.assertEqual(row.get_keycode(), 30) + self.assertEqual(row.get_keycode(), (EV_KEY, 30)) time.sleep(0.1) gtk_iteration() @@ -205,10 +205,11 @@ class TestIntegration(unittest.TestCase): self.assertEqual(custom_mapping.get_character(EV_KEY, 30), 'Shift_L') self.assertEqual(row.get_character(), 'Shift_L') - self.assertEqual(row.get_keycode(), 30) + self.assertEqual(row.get_keycode(), (EV_KEY, 30)) def change_empty_row(self, code, char, code_first=True, success=True): """Modify the one empty row that always exists.""" + # this is not a test, it's a utility function for other tests. # wait for the window to create a new empty row if needed time.sleep(0.1) gtk_iteration() @@ -231,11 +232,11 @@ class TestIntegration(unittest.TestCase): if code: # modifies the keycode in the row not by writing into the input, # but by sending an event - keycode_reader._pipe[1].send(code) + keycode_reader._pipe[1].send((EV_KEY, code)) time.sleep(0.1) gtk_iteration() if success: - self.assertEqual(row.get_keycode(), code) + self.assertEqual(row.get_keycode(), (EV_KEY, code)) self.assertIn( 'changed', row.get_style_context().list_classes() @@ -322,8 +323,12 @@ class TestIntegration(unittest.TestCase): def remove(row, code, char, num_rows_after): if code is not None and char is not None: self.assertEqual(custom_mapping.get_character(EV_KEY, code), char) + self.assertEqual(row.get_character(), char) - self.assertEqual(row.get_keycode(), code) + if code is None: + self.assertIsNone(row.get_keycode()) + else: + self.assertEqual(row.get_keycode(), (EV_KEY, code)) row.on_delete_button_clicked() time.sleep(0.2) gtk_iteration() diff --git a/tests/testcases/test_reader.py b/tests/testcases/test_reader.py index 758df44a..88817f4f 100644 --- a/tests/testcases/test_reader.py +++ b/tests/testcases/test_reader.py @@ -22,6 +22,7 @@ import unittest import evdev +from evdev.events import EV_KEY import time from keymapper.dev.reader import keycode_reader @@ -47,28 +48,28 @@ class TestReader(unittest.TestCase): def test_reading(self): pending_events['device 1'] = [ - Event(evdev.events.EV_KEY, CODE_1, 1), - Event(evdev.events.EV_KEY, CODE_2, 1), - Event(evdev.events.EV_KEY, CODE_3, 1) + Event(EV_KEY, CODE_1, 1), + Event(EV_KEY, CODE_2, 1), + Event(EV_KEY, CODE_3, 1) ] keycode_reader.start_reading('device 1') # sending anything arbitrary does not stop the pipe - keycode_reader._pipe[0].send(1234) + keycode_reader._pipe[0].send((EV_KEY, 1234)) time.sleep(EVENT_READ_TIMEOUT * 5) - self.assertEqual(keycode_reader.read(), CODE_3 + 8) - self.assertIsNone(keycode_reader.read()) + self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3 + 8)) + self.assertEqual(keycode_reader.read(), (None, None)) def test_wrong_device(self): pending_events['device 1'] = [ - Event(evdev.events.EV_KEY, CODE_1, 1), - Event(evdev.events.EV_KEY, CODE_2, 1), - Event(evdev.events.EV_KEY, CODE_3, 1) + Event(EV_KEY, CODE_1, 1), + Event(EV_KEY, CODE_2, 1), + Event(EV_KEY, CODE_3, 1) ] keycode_reader.start_reading('device 2') time.sleep(EVENT_READ_TIMEOUT * 5) - self.assertIsNone(keycode_reader.read()) + self.assertEqual(keycode_reader.read(), (None, None)) def test_keymapper_devices(self): # Don't read from keymapper devices, their keycodes are not @@ -76,28 +77,28 @@ class TestReader(unittest.TestCase): # intentionally programmed it won't even do that. But it was at some # point. pending_events['key-mapper device 2'] = [ - Event(evdev.events.EV_KEY, CODE_1, 1), - Event(evdev.events.EV_KEY, CODE_2, 1), - Event(evdev.events.EV_KEY, CODE_3, 1) + Event(EV_KEY, CODE_1, 1), + Event(EV_KEY, CODE_2, 1), + Event(EV_KEY, CODE_3, 1) ] keycode_reader.start_reading('device 2') time.sleep(EVENT_READ_TIMEOUT * 5) - self.assertIsNone(keycode_reader.read()) + self.assertEqual(keycode_reader.read(), (None, None)) def test_clear(self): pending_events['device 1'] = [ - Event(evdev.events.EV_KEY, CODE_1, 1), - Event(evdev.events.EV_KEY, CODE_2, 1), - Event(evdev.events.EV_KEY, CODE_3, 1) + Event(EV_KEY, CODE_1, 1), + Event(EV_KEY, CODE_2, 1), + Event(EV_KEY, CODE_3, 1) ] keycode_reader.start_reading('device 1') time.sleep(EVENT_READ_TIMEOUT * 5) keycode_reader.clear() - self.assertIsNone(keycode_reader.read()) + self.assertEqual(keycode_reader.read(), (None, None)) def test_switch_device(self): - pending_events['device 2'] = [Event(evdev.events.EV_KEY, CODE_1, 1)] - pending_events['device 1'] = [Event(evdev.events.EV_KEY, CODE_3, 1)] + pending_events['device 2'] = [Event(EV_KEY, CODE_1, 1)] + pending_events['device 1'] = [Event(EV_KEY, CODE_3, 1)] keycode_reader.start_reading('device 2') time.sleep(EVENT_READ_TIMEOUT * 5) @@ -105,8 +106,8 @@ class TestReader(unittest.TestCase): keycode_reader.start_reading('device 1') time.sleep(EVENT_READ_TIMEOUT * 5) - self.assertEqual(keycode_reader.read(), CODE_3 + 8) - self.assertIsNone(keycode_reader.read()) + self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3 + 8)) + self.assertEqual(keycode_reader.read(), (None, None)) if __name__ == "__main__":