def test_hat0x(self): # it should be possible to add all of them ev_1 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1) ev_2 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, 1) ev_3 = Key(EV_ABS, evdev.ecodes.ABS_HAT0Y, -1) ev_4 = Key(EV_ABS, evdev.ecodes.ABS_HAT0Y, 1) self.change_empty_row(ev_1, 'a') self.change_empty_row(ev_2, 'b') self.change_empty_row(ev_3, 'c') self.change_empty_row(ev_4, 'd') self.assertEqual(custom_mapping.get_character(ev_1), 'a') self.assertEqual(custom_mapping.get_character(ev_2), 'b') self.assertEqual(custom_mapping.get_character(ev_3), 'c') self.assertEqual(custom_mapping.get_character(ev_4), 'd') self.assertTrue(custom_mapping.changed) # and trying to add them as duplicate rows will be ignored for each # of them self.change_empty_row(ev_1, 'e', expect_success=False) self.change_empty_row(ev_2, 'f', expect_success=False) self.change_empty_row(ev_3, 'g', expect_success=False) self.change_empty_row(ev_4, 'h', expect_success=False) self.assertEqual(custom_mapping.get_character(ev_1), 'a') self.assertEqual(custom_mapping.get_character(ev_2), 'b') self.assertEqual(custom_mapping.get_character(ev_3), 'c') self.assertEqual(custom_mapping.get_character(ev_4), 'd') self.assertTrue(custom_mapping.changed)
def test_rows(self): """Comprehensive test for rows.""" # how many rows there should be in the end num_rows_target = 3 ev_1 = Key(EV_KEY, 10, 1) ev_2 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1) """edit""" # add two rows by modifiying the one empty row that exists self.change_empty_row(ev_1, 'a', code_first=False) self.change_empty_row(ev_2, 'k(b).k(c)') # one empty row added automatically again time.sleep(0.1) gtk_iteration() self.assertEqual(len(self.get_rows()), num_rows_target) self.assertEqual(custom_mapping.get_character(ev_1), 'a') self.assertEqual(custom_mapping.get_character(ev_2), 'k(b).k(c)') self.assertTrue(custom_mapping.changed) """save""" self.window.on_save_preset_clicked(None) for row in self.get_rows(): css_classes = row.get_style_context().list_classes() self.assertNotIn('changed', css_classes) self.assertFalse(custom_mapping.changed) """edit first row""" # 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:]: css_classes = row.get_style_context().list_classes() self.assertNotIn('changed', css_classes) self.assertEqual(custom_mapping.get_character(ev_1), 'c') self.assertEqual(custom_mapping.get_character(ev_2), 'k(b).k(c)') self.assertTrue(custom_mapping.changed) """add duplicate""" # try to add a duplicate keycode, it should be ignored self.change_empty_row(ev_2, 'd', expect_success=False) self.assertEqual(custom_mapping.get_character(ev_2), 'k(b).k(c)') # and the number of rows shouldn't change self.assertEqual(len(self.get_rows()), num_rows_target)
def show_injection_result(self): """Show if the injection was successfully started.""" state = self.dbus.get_state(self.selected_device) if state == RUNNING: msg = f'Applied preset "{self.selected_preset}"' if custom_mapping.get_character(Key.btn_left()): msg += ', CTRL + DEL to stop' self.show_status(CTX_APPLY, msg) self.show_device_mapping_status() return False if state == FAILED: self.show_status( CTX_ERROR, f'Failed to apply preset "{self.selected_preset}"') return False if state == NO_GRAB: self.show_status( CTX_ERROR, 'The device was not grabbed', 'Either another application is already grabbing it or ' 'your preset doesn\'t contain anything that is sent by the ' 'device.') return False # keep the timeout running return True
def test_remove_row(self): """Comprehensive test for rows 2.""" # sleeps are added to be able to visually follow and debug the test. # add two rows by modifiying the one empty row that exists row_1 = self.change_empty_row(Key(EV_KEY, 10, 1), 'a') row_2 = self.change_empty_row(Key(EV_KEY, 11, 1), 'b') row_3 = self.change_empty_row(None, 'c') # no empty row added because one is unfinished time.sleep(0.2) gtk_iteration() self.assertEqual(len(self.get_rows()), 3) self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 11, 1)), 'b') def remove(row, code, char, num_rows_after): """Remove a row by clicking the delete button. Parameters ---------- row : Row code : int or None keycode of the mapping that is displayed by this row char : string or None ouptut of the mapping that is displayed by this row num_rows_after : int after deleting, how many rows are expected to still be there """ if code is not None and char is not None: self.assertEqual( custom_mapping.get_character(Key(EV_KEY, code, 1)), char) self.assertEqual(row.get_character(), char) if code is None: self.assertIsNone(row.get_key()) else: self.assertEqual(row.get_key(), Key(EV_KEY, code, 1)) row.on_delete_button_clicked() time.sleep(0.2) gtk_iteration() # if a reference to the row is held somewhere and it is # accidentally used again, make sure to not provide any outdated # information that is supposed to be deleted self.assertIsNone(row.get_key()) self.assertIsNone(row.get_character()) if code is not None: self.assertIsNone( custom_mapping.get_character(Key(EV_KEY, code, 1))) self.assertEqual(len(self.get_rows()), num_rows_after) remove(row_1, 10, 'a', 2) remove(row_2, 11, 'b', 1) # there is no empty row at the moment, so after removing that one, # which is the only row, one empty row will be there. So the number # of rows won't change. remove(row_3, None, 'c', 1)
def test_rename_and_save(self): custom_mapping.change(Key(EV_KEY, 14, 1), 'a', None) self.assertEqual(self.window.selected_preset, 'new preset') self.window.on_save_preset_clicked(None) self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'a') custom_mapping.change(Key(EV_KEY, 14, 1), 'b', None) self.window.get('preset_name_input').set_text('asdf') self.window.on_save_preset_clicked(None) self.assertEqual(self.window.selected_preset, 'asdf') self.assertTrue( os.path.exists(f'{CONFIG_PATH}/presets/device 1/asdf.json')) self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'b') error_icon = self.window.get('error_status_icon') status = self.window.get('status_bar') tooltip = status.get_tooltip_text().lower() self.assertIn('saved', tooltip) self.assertFalse(error_icon.get_visible())
def set_new_key(self, new_key): """Check if a keycode has been pressed and if so, display it. Parameters ---------- new_key : Key """ if new_key is not None and not isinstance(new_key, Key): raise TypeError('Expected new_key to be a Key object') # the newest_keycode is populated since the ui regularly polls it # in order to display it in the status bar. previous_key = self.get_key() # no input if new_key is None: return # it might end up being a key combination self.state = HOLDING # keycode didn't change, do nothing if new_key == previous_key: return # keycode is already set by some other row existing = custom_mapping.get_character(new_key) if existing is not None: msg = f'"{to_string(new_key)}" already mapped to "{existing}"' logger.info(msg) self.window.show_status(CTX_KEYCODE, msg) return # it's legal to display the keycode # always ask for get_child to set the label, otherwise line breaking # has to be configured again. self.set_keycode_input_label(to_string(new_key)) self.key = new_key self.highlight() character = self.get_character() # the character is empty and therefore the mapping is not complete if character is None: return # else, the keycode has changed, the character is set, all good custom_mapping.change(new_key=new_key, character=character, previous_key=previous_key)
def remove(row, code, char, num_rows_after): """Remove a row by clicking the delete button. Parameters ---------- row : Row code : int or None keycode of the mapping that is displayed by this row char : string or None ouptut of the mapping that is displayed by this row num_rows_after : int after deleting, how many rows are expected to still be there """ if code is not None and char is not None: self.assertEqual( custom_mapping.get_character(Key(EV_KEY, code, 1)), char) self.assertEqual(row.get_character(), char) if code is None: self.assertIsNone(row.get_key()) else: self.assertEqual(row.get_key(), Key(EV_KEY, code, 1)) row.on_delete_button_clicked() time.sleep(0.2) gtk_iteration() # if a reference to the row is held somewhere and it is # accidentally used again, make sure to not provide any outdated # information that is supposed to be deleted self.assertIsNone(row.get_key()) self.assertIsNone(row.get_character()) if code is not None: self.assertIsNone( custom_mapping.get_character(Key(EV_KEY, code, 1))) self.assertEqual(len(self.get_rows()), num_rows_after)
def test_check_macro_syntax(self): status = self.window.get('status_bar') error_icon = self.window.get('error_status_icon') warning_icon = self.window.get('warning_status_icon') custom_mapping.change(Key(EV_KEY, 9, 1), 'k(1))', None) self.window.on_save_preset_clicked(None) tooltip = status.get_tooltip_text().lower() self.assertIn('brackets', tooltip) self.assertTrue(error_icon.get_visible()) self.assertFalse(warning_icon.get_visible()) custom_mapping.change(Key(EV_KEY, 9, 1), 'k(1)', None) self.window.on_save_preset_clicked(None) tooltip = status.get_tooltip_text().lower() self.assertNotIn('brackets', tooltip) self.assertIn('saved', tooltip) self.assertFalse(error_icon.get_visible()) self.assertFalse(warning_icon.get_visible()) self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 9, 1)), 'k(1)')
def test_row_simple(self): rows = self.window.get('key_list').get_children() self.assertEqual(len(rows), 1) row = rows[0] row.set_new_key(None) self.assertIsNone(row.get_key()) self.assertEqual(len(custom_mapping), 0) self.assertEqual(row.keycode_input.get_label(), 'click here') row.set_new_key(Key(EV_KEY, 30, 1)) self.assertEqual(len(custom_mapping), 0) self.assertEqual(row.get_key(), (EV_KEY, 30, 1)) # this is KEY_A in linux/input-event-codes.h, # but KEY_ is removed from the text self.assertEqual(row.keycode_input.get_label(), 'A') row.set_new_key(Key(EV_KEY, 30, 1)) self.assertEqual(len(custom_mapping), 0) self.assertEqual(row.get_key(), (EV_KEY, 30, 1)) time.sleep(0.1) gtk_iteration() self.assertEqual(len(self.window.get('key_list').get_children()), 1) row.character_input.set_text('Shift_L') self.assertEqual(len(custom_mapping), 1) time.sleep(0.1) gtk_iteration() self.assertEqual(len(self.window.get('key_list').get_children()), 2) self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 30, 1)), 'Shift_L') self.assertEqual(row.get_character(), 'Shift_L') self.assertEqual(row.get_key(), (EV_KEY, 30, 1))
def test_combination(self): # it should be possible to write a key combination ev_1 = Key(EV_KEY, evdev.ecodes.KEY_A, 1) ev_2 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, 1) ev_3 = Key(EV_KEY, evdev.ecodes.KEY_C, 1) ev_4 = Key(EV_ABS, evdev.ecodes.ABS_HAT0X, -1) combination_1 = Key(ev_1, ev_2, ev_3) combination_2 = Key(ev_2, ev_1, ev_3) # same as 1, but different D-Pad direction combination_3 = Key(ev_1, ev_4, ev_3) combination_4 = Key(ev_4, ev_1, ev_3) # same as 1, but the last key is different combination_5 = Key(ev_1, ev_3, ev_2) combination_6 = Key(ev_3, ev_1, ev_2) self.change_empty_row(combination_1, 'a') self.assertEqual(custom_mapping.get_character(combination_1), 'a') self.assertEqual(custom_mapping.get_character(combination_2), 'a') self.assertIsNone(custom_mapping.get_character(combination_3)) self.assertIsNone(custom_mapping.get_character(combination_4)) self.assertIsNone(custom_mapping.get_character(combination_5)) self.assertIsNone(custom_mapping.get_character(combination_6)) # it won't write the same combination again, even if the # first two events are in a different order self.change_empty_row(combination_2, 'b', expect_success=False) self.assertEqual(custom_mapping.get_character(combination_1), 'a') self.assertEqual(custom_mapping.get_character(combination_2), 'a') self.assertIsNone(custom_mapping.get_character(combination_3)) self.assertIsNone(custom_mapping.get_character(combination_4)) self.assertIsNone(custom_mapping.get_character(combination_5)) self.assertIsNone(custom_mapping.get_character(combination_6)) self.change_empty_row(combination_3, 'c') self.assertEqual(custom_mapping.get_character(combination_1), 'a') self.assertEqual(custom_mapping.get_character(combination_2), 'a') self.assertEqual(custom_mapping.get_character(combination_3), 'c') self.assertEqual(custom_mapping.get_character(combination_4), 'c') self.assertIsNone(custom_mapping.get_character(combination_5)) self.assertIsNone(custom_mapping.get_character(combination_6)) # same as with combination_2, the existing combination_3 blocks # combination_4 because they have the same keys and end in the # same key. self.change_empty_row(combination_4, 'd', expect_success=False) self.assertEqual(custom_mapping.get_character(combination_1), 'a') self.assertEqual(custom_mapping.get_character(combination_2), 'a') self.assertEqual(custom_mapping.get_character(combination_3), 'c') self.assertEqual(custom_mapping.get_character(combination_4), 'c') self.assertIsNone(custom_mapping.get_character(combination_5)) self.assertIsNone(custom_mapping.get_character(combination_6)) self.change_empty_row(combination_5, 'e') self.assertEqual(custom_mapping.get_character(combination_1), 'a') self.assertEqual(custom_mapping.get_character(combination_2), 'a') self.assertEqual(custom_mapping.get_character(combination_3), 'c') self.assertEqual(custom_mapping.get_character(combination_4), 'c') self.assertEqual(custom_mapping.get_character(combination_5), 'e') self.assertEqual(custom_mapping.get_character(combination_6), 'e') error_icon = self.window.get('error_status_icon') warning_icon = self.window.get('warning_status_icon') self.assertFalse(error_icon.get_visible()) self.assertFalse(warning_icon.get_visible())