more work for event type to mapping key

xkb
sezanzeb 4 years ago committed by sezanzeb
parent edf71e82e0
commit 78692f40eb

@ -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()

@ -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)

@ -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
)

@ -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.

@ -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()

@ -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__":

Loading…
Cancel
Save