more work for event type to mapping key

This commit is contained in:
sezanzeb 2020-12-02 20:08:55 +01:00
parent 4b5c9e3143
commit 861ae868b2
6 changed files with 89 additions and 61 deletions

View File

@ -113,7 +113,7 @@ class _KeycodeReader:
'got code:%s value:%s', 'got code:%s value:%s',
event.code + KEYCODE_OFFSET, event.value 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): def _read_worker(self):
"""Process that reads keycodes and buffers them into a pipe.""" """Process that reads keycodes and buffers them into a pipe."""
@ -145,16 +145,16 @@ class _KeycodeReader:
del rlist[fd] del rlist[fd]
def read(self): 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: if self._pipe is None:
logger.debug('No pipe available to read from') logger.debug('No pipe available to read from')
return None return None
newest_keycode = None newest_event = (None, None)
while self._pipe[0].poll(): 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() keycode_reader = _KeycodeReader()

View File

@ -40,7 +40,10 @@ class Row(Gtk.ListBoxRow):
"""A single, configurable key mapping.""" """A single, configurable key mapping."""
__gtype_name__ = 'ListBoxRow' __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.""" """Construct a row widget."""
super().__init__() super().__init__()
self.device = window.selected_device self.device = window.selected_device
@ -50,19 +53,26 @@ class Row(Gtk.ListBoxRow):
self.character_input = None self.character_input = None
self.keycode_input = None self.keycode_input = None
self.put_together(keycode, character) self.put_together(ev_type, keycode, character)
def get_keycode(self): 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() 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): def get_character(self):
"""Get the assigned character from the middle column.""" """Get the assigned character from the middle column."""
character = self.character_input.get_text() character = self.character_input.get_text()
return character if character else None 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.""" """Check if a keycode has been pressed and if so, display it."""
# the newest_keycode is populated since the ui regularly polls it # the newest_keycode is populated since the ui regularly polls it
# in order to display it in the status bar. # in order to display it in the status bar.
@ -78,7 +88,7 @@ class Row(Gtk.ListBoxRow):
return return
# keycode is already set by some other row # 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' msg = f'Keycode {new_keycode} is already mapped'
logger.info(msg) logger.info(msg)
self.window.get('status_bar').push(CTX_KEYCODE, 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 # it's legal to display the keycode
self.window.get('status_bar').remove_all(CTX_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 # switch to the character, don't require mouse input because
# that would overwrite the key with the mouse-button key if # that would overwrite the key with the mouse-button key if
# the current device is a mouse. idle_add this so that the # the current device is a mouse. idle_add this so that the
@ -100,7 +110,12 @@ class Row(Gtk.ListBoxRow):
return return
# else, the keycode has changed, the character is set, all good # 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): def highlight(self):
"""Mark this row as changed.""" """Mark this row as changed."""
@ -112,19 +127,21 @@ class Row(Gtk.ListBoxRow):
def on_character_input_change(self, _): def on_character_input_change(self, _):
"""When the output character for that keycode is typed in.""" """When the output character for that keycode is typed in."""
keycode = self.get_keycode() key = self.get_keycode()
character = self.get_character() character = self.get_character()
self.highlight() self.highlight()
if keycode is not None: if key is not None:
custom_mapping.change(EV_KEY, ev_type, keycode = key
previous_keycode=None, custom_mapping.change(
ev_type=ev_type,
new_keycode=keycode, 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.""" """Create all child GTK widgets and connect their signals."""
delete_button = Gtk.EventBox() delete_button = Gtk.EventBox()
delete_button.add(Gtk.Image.new_from_icon_name( delete_button.add(Gtk.Image.new_from_icon_name(
@ -141,7 +158,7 @@ class Row(Gtk.ListBoxRow):
keycode_input.set_size_request(50, -1) keycode_input.set_size_request(50, -1)
if keycode is not None: 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 # make the togglebutton go back to its normal state when doing
# something else in the UI # something else in the UI
@ -178,9 +195,10 @@ class Row(Gtk.ListBoxRow):
def on_delete_button_clicked(self, *args): def on_delete_button_clicked(self, *args):
"""Destroy the row and remove it from the config.""" """Destroy the row and remove it from the config."""
keycode = self.get_keycode() key = self.get_keycode()
if keycode is not None: if key is not None:
custom_mapping.clear(EV_KEY, keycode) ev_type, keycode = key
custom_mapping.clear(ev_type, keycode)
self.character_input.set_text('') self.character_input.set_text('')
self.keycode_input.set_label('') self.keycode_input.set_label('')
self.delete_callback(self) self.delete_callback(self)

View File

@ -22,6 +22,8 @@
"""User Interface.""" """User Interface."""
from evdev.ecodes import EV_KEY
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
gi.require_version('GLib', '2.0') gi.require_version('GLib', '2.0')
@ -213,24 +215,24 @@ class Window:
"""To capture events from keyboard, mice and gamepads.""" """To capture events from keyboard, mice and gamepads."""
# the "event" event of Gtk.Window wouldn't trigger on gamepad # the "event" event of Gtk.Window wouldn't trigger on gamepad
# events, so it became a GLib timeout # 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 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 # disable mapping the left mouse button because it would break
# the mouse. Also it is emitted right when focusing the row # the mouse. Also it is emitted right when focusing the row
# which breaks the current workflow. # which breaks the current workflow.
return True 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 # inform the currently selected row about the new keycode
focused = self.window.get_focus() focused = self.window.get_focus()
row = focused.get_parent().get_parent() row = focused.get_parent().get_parent()
if isinstance(focused, Gtk.ToggleButton) and isinstance(row, Row): if isinstance(focused, Gtk.ToggleButton) and isinstance(row, Row):
row.set_new_keycode(keycode) row.set_new_keycode(ev_type, keycode)
return True return True
@ -374,10 +376,11 @@ class Window:
custom_mapping.load(self.selected_device, self.selected_preset) custom_mapping.load(self.selected_device, self.selected_preset)
key_list = self.get('key_list') key_list = self.get('key_list')
for (_, keycode), output in custom_mapping: for (ev_type, keycode), output in custom_mapping:
single_key_mapping = Row( single_key_mapping = Row(
window=self, window=self,
delete_callback=self.on_row_removed, delete_callback=self.on_row_removed,
ev_type=ev_type,
keycode=keycode, keycode=keycode,
character=output character=output
) )

View File

@ -59,7 +59,8 @@ class Mapping:
Parameters Parameters
---------- ----------
ev_type : int 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 new_keycode : int
The source keycode, what the mouse would report without any The source keycode, what the mouse would report without any
modification. xkb keycode. modification. xkb keycode.

View File

@ -182,15 +182,15 @@ class TestIntegration(unittest.TestCase):
row = rows[0] row = rows[0]
row.set_new_keycode(None) row.set_new_keycode(None, None)
self.assertIsNone(row.get_keycode()) self.assertIsNone(row.get_keycode())
self.assertEqual(len(custom_mapping), 0) 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(len(custom_mapping), 0)
self.assertEqual(row.get_keycode(), 30) self.assertEqual(row.get_keycode(), (EV_KEY, 30))
row.set_new_keycode(30) row.set_new_keycode(EV_KEY, 30)
self.assertEqual(len(custom_mapping), 0) self.assertEqual(len(custom_mapping), 0)
self.assertEqual(row.get_keycode(), 30) self.assertEqual(row.get_keycode(), (EV_KEY, 30))
time.sleep(0.1) time.sleep(0.1)
gtk_iteration() gtk_iteration()
@ -205,10 +205,11 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(custom_mapping.get_character(EV_KEY, 30), 'Shift_L') self.assertEqual(custom_mapping.get_character(EV_KEY, 30), 'Shift_L')
self.assertEqual(row.get_character(), '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): def change_empty_row(self, code, char, code_first=True, success=True):
"""Modify the one empty row that always exists.""" """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 # wait for the window to create a new empty row if needed
time.sleep(0.1) time.sleep(0.1)
gtk_iteration() gtk_iteration()
@ -231,11 +232,11 @@ class TestIntegration(unittest.TestCase):
if code: if code:
# modifies the keycode in the row not by writing into the input, # modifies the keycode in the row not by writing into the input,
# but by sending an event # but by sending an event
keycode_reader._pipe[1].send(code) keycode_reader._pipe[1].send((EV_KEY, code))
time.sleep(0.1) time.sleep(0.1)
gtk_iteration() gtk_iteration()
if success: if success:
self.assertEqual(row.get_keycode(), code) self.assertEqual(row.get_keycode(), (EV_KEY, code))
self.assertIn( self.assertIn(
'changed', 'changed',
row.get_style_context().list_classes() row.get_style_context().list_classes()
@ -322,8 +323,12 @@ class TestIntegration(unittest.TestCase):
def remove(row, code, char, num_rows_after): def remove(row, code, char, num_rows_after):
if code is not None and char is not None: if code is not None and char is not None:
self.assertEqual(custom_mapping.get_character(EV_KEY, code), char) self.assertEqual(custom_mapping.get_character(EV_KEY, code), char)
self.assertEqual(row.get_character(), 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() row.on_delete_button_clicked()
time.sleep(0.2) time.sleep(0.2)
gtk_iteration() gtk_iteration()

View File

@ -22,6 +22,7 @@
import unittest import unittest
import evdev import evdev
from evdev.events import EV_KEY
import time import time
from keymapper.dev.reader import keycode_reader from keymapper.dev.reader import keycode_reader
@ -47,28 +48,28 @@ class TestReader(unittest.TestCase):
def test_reading(self): def test_reading(self):
pending_events['device 1'] = [ pending_events['device 1'] = [
Event(evdev.events.EV_KEY, CODE_1, 1), Event(EV_KEY, CODE_1, 1),
Event(evdev.events.EV_KEY, CODE_2, 1), Event(EV_KEY, CODE_2, 1),
Event(evdev.events.EV_KEY, CODE_3, 1) Event(EV_KEY, CODE_3, 1)
] ]
keycode_reader.start_reading('device 1') keycode_reader.start_reading('device 1')
# sending anything arbitrary does not stop the pipe # 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) time.sleep(EVENT_READ_TIMEOUT * 5)
self.assertEqual(keycode_reader.read(), CODE_3 + 8) self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3 + 8))
self.assertIsNone(keycode_reader.read()) self.assertEqual(keycode_reader.read(), (None, None))
def test_wrong_device(self): def test_wrong_device(self):
pending_events['device 1'] = [ pending_events['device 1'] = [
Event(evdev.events.EV_KEY, CODE_1, 1), Event(EV_KEY, CODE_1, 1),
Event(evdev.events.EV_KEY, CODE_2, 1), Event(EV_KEY, CODE_2, 1),
Event(evdev.events.EV_KEY, CODE_3, 1) Event(EV_KEY, CODE_3, 1)
] ]
keycode_reader.start_reading('device 2') keycode_reader.start_reading('device 2')
time.sleep(EVENT_READ_TIMEOUT * 5) time.sleep(EVENT_READ_TIMEOUT * 5)
self.assertIsNone(keycode_reader.read()) self.assertEqual(keycode_reader.read(), (None, None))
def test_keymapper_devices(self): def test_keymapper_devices(self):
# Don't read from keymapper devices, their keycodes are not # 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 # intentionally programmed it won't even do that. But it was at some
# point. # point.
pending_events['key-mapper device 2'] = [ pending_events['key-mapper device 2'] = [
Event(evdev.events.EV_KEY, CODE_1, 1), Event(EV_KEY, CODE_1, 1),
Event(evdev.events.EV_KEY, CODE_2, 1), Event(EV_KEY, CODE_2, 1),
Event(evdev.events.EV_KEY, CODE_3, 1) Event(EV_KEY, CODE_3, 1)
] ]
keycode_reader.start_reading('device 2') keycode_reader.start_reading('device 2')
time.sleep(EVENT_READ_TIMEOUT * 5) time.sleep(EVENT_READ_TIMEOUT * 5)
self.assertIsNone(keycode_reader.read()) self.assertEqual(keycode_reader.read(), (None, None))
def test_clear(self): def test_clear(self):
pending_events['device 1'] = [ pending_events['device 1'] = [
Event(evdev.events.EV_KEY, CODE_1, 1), Event(EV_KEY, CODE_1, 1),
Event(evdev.events.EV_KEY, CODE_2, 1), Event(EV_KEY, CODE_2, 1),
Event(evdev.events.EV_KEY, CODE_3, 1) Event(EV_KEY, CODE_3, 1)
] ]
keycode_reader.start_reading('device 1') keycode_reader.start_reading('device 1')
time.sleep(EVENT_READ_TIMEOUT * 5) time.sleep(EVENT_READ_TIMEOUT * 5)
keycode_reader.clear() keycode_reader.clear()
self.assertIsNone(keycode_reader.read()) self.assertEqual(keycode_reader.read(), (None, None))
def test_switch_device(self): def test_switch_device(self):
pending_events['device 2'] = [Event(evdev.events.EV_KEY, CODE_1, 1)] pending_events['device 2'] = [Event(EV_KEY, CODE_1, 1)]
pending_events['device 1'] = [Event(evdev.events.EV_KEY, CODE_3, 1)] pending_events['device 1'] = [Event(EV_KEY, CODE_3, 1)]
keycode_reader.start_reading('device 2') keycode_reader.start_reading('device 2')
time.sleep(EVENT_READ_TIMEOUT * 5) time.sleep(EVENT_READ_TIMEOUT * 5)
@ -105,8 +106,8 @@ class TestReader(unittest.TestCase):
keycode_reader.start_reading('device 1') keycode_reader.start_reading('device 1')
time.sleep(EVENT_READ_TIMEOUT * 5) time.sleep(EVENT_READ_TIMEOUT * 5)
self.assertEqual(keycode_reader.read(), CODE_3 + 8) self.assertEqual(keycode_reader.read(), (EV_KEY, CODE_3 + 8))
self.assertIsNone(keycode_reader.read()) self.assertEqual(keycode_reader.read(), (None, None))
if __name__ == "__main__": if __name__ == "__main__":