using get_keycode instead of evdev

This commit is contained in:
sezanzeb 2020-11-15 03:01:11 +01:00
parent 6ceaab2488
commit c2abf2c24f
4 changed files with 64 additions and 85 deletions

View File

@ -55,24 +55,26 @@ class Row(Gtk.ListBoxRow):
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 start_watching_keycodes(self, *args): def highlight(self):
"""Start to periodically check if a keycode has been pressed. """Mark this row as changed."""
self.get_style_context().add_class('changed')
This is different from just listening for text input events def unhighlight(self):
(as in Gtk.Entry), since keys may not write characters into the form """Mark this row as unchanged."""
because they are not mapped. Furthermore their keycode is needed, self.get_style_context().remove_class('changed')
not their mapped character."""
keycode_reader.clear()
def iterate(): def on_character_input_change(self, entry):
self.check_newest_keycode() keycode = self.get_keycode()
return self.keycode.is_focus() and self.window.window.is_active() character = self.get_character()
GLib.timeout_add(1000 / 30, iterate) self.highlight()
def check_newest_keycode(self): if keycode is not None:
custom_mapping.change(None, keycode, character)
def on_key_pressed(self, button, event):
"""Check if a keycode has been pressed and if so, display it.""" """Check if a keycode has been pressed and if so, display it."""
new_keycode = keycode_reader.read() new_keycode = event.get_keycode()[1]
previous_keycode = self.get_keycode() previous_keycode = self.get_keycode()
character = self.get_character() character = self.get_character()
@ -106,25 +108,8 @@ class Row(Gtk.ListBoxRow):
# 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(previous_keycode, new_keycode, character) custom_mapping.change(previous_keycode, new_keycode, character)
def highlight(self):
"""Mark this row as changed."""
self.get_style_context().add_class('changed')
def unhighlight(self):
"""Mark this row as unchanged."""
self.get_style_context().remove_class('changed')
def on_character_input_change(self, entry):
keycode = self.get_keycode()
character = self.get_character()
self.highlight()
if keycode is not None:
custom_mapping.change(None, keycode, character)
def put_together(self, keycode, character): def put_together(self, keycode, character):
"""Create all GTK widgets.""" """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(
'window-close', 'window-close',
@ -141,8 +126,8 @@ class Row(Gtk.ListBoxRow):
if keycode is not None: if keycode is not None:
keycode_input.set_label(str(keycode)) keycode_input.set_label(str(keycode))
keycode_input.connect( keycode_input.connect(
'focus-in-event', 'key-press-event',
self.start_watching_keycodes self.on_key_pressed
) )
# 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

View File

@ -106,7 +106,7 @@ class Window:
self.select_newest_preset() self.select_newest_preset()
GLib.timeout_add(100, self.check_add_row) self.timeout = GLib.timeout_add(100, self.check_add_row)
def get(self, name): def get(self, name):
"""Get a widget from the window""" """Get a widget from the window"""
@ -114,6 +114,7 @@ class Window:
def on_close(self, *_): def on_close(self, *_):
"""Safely close the application.""" """Safely close the application."""
GLib.source_remove(self.timeout)
Gtk.main_quit() Gtk.main_quit()
def check_add_row(self): def check_add_row(self):

View File

@ -47,6 +47,8 @@ def can_grab(path):
class KeycodeReader: class KeycodeReader:
"""Keeps reading keycodes in the background for the UI to use. """Keeps reading keycodes in the background for the UI to use.
This was written before I figured out there is get_keycode in GLib.
A new arriving keycode indicates that a button was pressed, so the A new arriving keycode indicates that a button was pressed, so the
UI can keep checking for a new keycode on this object and act like the UI can keep checking for a new keycode on this object and act like the
keycode went right to the input box. keycode went right to the input box.
@ -96,9 +98,6 @@ class KeycodeReader:
# value: 1 for down, 0 for up, 2 for hold. # value: 1 for down, 0 for up, 2 for hold.
# this happens to report key codes that are 8 lower # this happens to report key codes that are 8 lower
# than the ones reported by xev # than the ones reported by xev
# TODO check if 280 and above works on wayland and
# if not, prevent anything > 255. adjust
# the maximum of keycodes before trying
newest_keycode = event.code + 8 newest_keycode = event.code + 8
return newest_keycode return newest_keycode

View File

@ -83,8 +83,6 @@ class Integration(unittest.TestCase):
self.window = launch() self.window = launch()
def tearDown(self): def tearDown(self):
# before calling destroy to break everything (happened with
# check_add_row), make an iteration to clear all pending events.
gtk_iteration() gtk_iteration()
self.window.on_close() self.window.on_close()
self.window.window.destroy() self.window.window.destroy()
@ -111,12 +109,14 @@ class Integration(unittest.TestCase):
def test_rows(self): def test_rows(self):
"""Comprehensive test for rows.""" """Comprehensive test for rows."""
def read(): class FakeEvent:
"""Always return a different keycode for each row.""" def __init__(self, keycode):
# + 7 because keycodes usually start at 8 self.keycode = keycode
return len(self.window.get('key_list').get_children()) + 7
def change_empty_row(character): def get_keycode(self):
return [False, self.keycode]
def change_empty_row(keycode, character):
"""Modify the one empty row that always exists.""" """Modify the one empty row that always exists."""
# 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.2) time.sleep(0.2)
@ -129,14 +129,9 @@ class Integration(unittest.TestCase):
self.assertIsNone(row.keycode.get_label()) self.assertIsNone(row.keycode.get_label())
self.assertEqual(row.character_input.get_text(), '') self.assertEqual(row.character_input.get_text(), '')
# focus the keycode to trigger reading the fake keycode row.on_key_pressed(None, FakeEvent(keycode))
self.window.window.set_focus(row.keycode)
time.sleep(0.2)
gtk_iteration()
# it should be filled using the `read` patch self.assertEqual(int(row.keycode.get_label()), keycode)
self.assertEqual(int(row.keycode.get_label()), len(rows) + 7)
self.window.window.set_focus(None)
# set the character to make the new row complete # set the character to make the new row complete
row.character_input.set_text(character) row.character_input.set_text(character)
@ -145,45 +140,44 @@ class Integration(unittest.TestCase):
return row return row
with patch.object(keycode_reader, 'read', read): # add two rows by modifiying the one empty row that exists
# add two rows by modifiying the one empty row that exists change_empty_row(10, 'a')
change_empty_row('a') change_empty_row(11, 'b')
change_empty_row('b')
# one empty row added automatically again # one empty row added automatically again
time.sleep(0.2) time.sleep(0.2)
gtk_iteration() gtk_iteration()
# sleep one more time because it's funny to watch the ui # sleep one more time because it's funny to watch the ui
# during the test, how rows turn blue and stuff # during the test, how rows turn blue and stuff
time.sleep(0.2) time.sleep(0.2)
self.assertEqual(len(self.get_rows()), 3) self.assertEqual(len(self.get_rows()), 3)
self.assertEqual(custom_mapping.get(8), 'a') self.assertEqual(custom_mapping.get(10), 'a')
self.assertEqual(custom_mapping.get(9), 'b') self.assertEqual(custom_mapping.get(11), 'b')
self.assertTrue(custom_mapping.changed) self.assertTrue(custom_mapping.changed)
self.window.on_save_preset_clicked(None) self.window.on_save_preset_clicked(None)
for row in self.get_rows(): for row in self.get_rows():
self.assertNotIn( self.assertNotIn(
'changed', 'changed',
row.get_style_context().list_classes() row.get_style_context().list_classes()
) )
self.assertFalse(custom_mapping.changed) self.assertFalse(custom_mapping.changed)
# now change the first row and it should turn blue, # now change the first row and it should turn blue,
# but the other should remain unhighlighted # but the other should remain unhighlighted
row = self.get_rows()[0] row = self.get_rows()[0]
row.character_input.set_text('c') row.character_input.set_text('c')
self.assertIn('changed', row.get_style_context().list_classes()) self.assertIn('changed', row.get_style_context().list_classes())
for row in self.get_rows()[1:]: for row in self.get_rows()[1:]:
self.assertNotIn( self.assertNotIn(
'changed', 'changed',
row.get_style_context().list_classes() row.get_style_context().list_classes()
) )
self.assertEqual(custom_mapping.get(8), 'c') self.assertEqual(custom_mapping.get(10), 'c')
self.assertEqual(custom_mapping.get(9), 'b') self.assertEqual(custom_mapping.get(11), 'b')
self.assertTrue(custom_mapping.changed) self.assertTrue(custom_mapping.changed)
def test_rename_and_save(self): def test_rename_and_save(self):
custom_mapping.change(None, 14, 'a') custom_mapping.change(None, 14, 'a')