using get_keycode instead of evdev

first
sezanzeb 4 years ago
parent 6ceaab2488
commit c2abf2c24f

@ -55,24 +55,26 @@ class Row(Gtk.ListBoxRow):
character = self.character_input.get_text()
return character if character else None
def start_watching_keycodes(self, *args):
"""Start to periodically check if a keycode has been pressed.
def highlight(self):
"""Mark this row as changed."""
self.get_style_context().add_class('changed')
This is different from just listening for text input events
(as in Gtk.Entry), since keys may not write characters into the form
because they are not mapped. Furthermore their keycode is needed,
not their mapped character."""
keycode_reader.clear()
def unhighlight(self):
"""Mark this row as unchanged."""
self.get_style_context().remove_class('changed')
def iterate():
self.check_newest_keycode()
return self.keycode.is_focus() and self.window.window.is_active()
def on_character_input_change(self, entry):
keycode = self.get_keycode()
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."""
new_keycode = keycode_reader.read()
new_keycode = event.get_keycode()[1]
previous_keycode = self.get_keycode()
character = self.get_character()
@ -106,25 +108,8 @@ class Row(Gtk.ListBoxRow):
# else, the keycode has changed, the character is set, all good
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):
"""Create all GTK widgets."""
"""Create all child GTK widgets and connect their signals."""
delete_button = Gtk.EventBox()
delete_button.add(Gtk.Image.new_from_icon_name(
'window-close',
@ -141,8 +126,8 @@ class Row(Gtk.ListBoxRow):
if keycode is not None:
keycode_input.set_label(str(keycode))
keycode_input.connect(
'focus-in-event',
self.start_watching_keycodes
'key-press-event',
self.on_key_pressed
)
# make the togglebutton go back to its normal state when doing
# something else in the UI

@ -106,7 +106,7 @@ class Window:
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):
"""Get a widget from the window"""
@ -114,6 +114,7 @@ class Window:
def on_close(self, *_):
"""Safely close the application."""
GLib.source_remove(self.timeout)
Gtk.main_quit()
def check_add_row(self):

@ -47,6 +47,8 @@ def can_grab(path):
class KeycodeReader:
"""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
UI can keep checking for a new keycode on this object and act like the
keycode went right to the input box.
@ -96,9 +98,6 @@ class KeycodeReader:
# value: 1 for down, 0 for up, 2 for hold.
# this happens to report key codes that are 8 lower
# 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
return newest_keycode

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

Loading…
Cancel
Save