def test_autoload(self): devices = ['device 1234', 'device 2345'] presets = ['preset', 'bar'] paths = [ get_preset_path(devices[0], presets[0]), get_preset_path(devices[1], presets[1]) ] xmodmap = 'a/xmodmap.json' Mapping().save(paths[0]) Mapping().save(paths[1]) daemon = Daemon() start_history = [] stop_history = [] daemon.start_injecting = lambda *args: start_history.append(args) daemon.stop = lambda *args: stop_history.append(args) config.set_autoload_preset(devices[0], presets[0]) config.set_autoload_preset(devices[1], presets[1]) control(options('autoload', None, None, False, False), daemon, xmodmap) self.assertEqual(len(start_history), 2) self.assertEqual(len(stop_history), 1) self.assertEqual(start_history[0], (devices[0], os.path.expanduser(paths[0]), xmodmap)) self.assertEqual(start_history[1], (devices[1], os.path.abspath(paths[1]), xmodmap))
def test_get_available_preset_name(self): # no filename conflict self.assertEqual(get_available_preset_name('_', 'qux 2'), 'qux 2') touch(get_preset_path('_', 'qux 5')) self.assertEqual(get_available_preset_name('_', 'qux 5'), 'qux 6') touch(get_preset_path('_', 'qux')) self.assertEqual(get_available_preset_name('_', 'qux'), 'qux 2') touch(get_preset_path('_', 'qux1')) self.assertEqual(get_available_preset_name('_', 'qux1'), 'qux1 2') touch(get_preset_path('_', 'qux 2 3')) self.assertEqual(get_available_preset_name('_', 'qux 2 3'), 'qux 2 4') touch(get_preset_path('_', 'qux 5')) self.assertEqual(get_available_preset_name('_', 'qux 5', True), 'qux 5 copy') touch(get_preset_path('_', 'qux 5 copy')) self.assertEqual(get_available_preset_name('_', 'qux 5', True), 'qux 5 copy 2') touch(get_preset_path('_', 'qux 5 copy 2')) self.assertEqual(get_available_preset_name('_', 'qux 5', True), 'qux 5 copy 3') touch(get_preset_path('_', 'qux 5copy')) self.assertEqual(get_available_preset_name('_', 'qux 5copy', True), 'qux 5copy copy') touch(get_preset_path('_', 'qux 5copy 2')) self.assertEqual(get_available_preset_name('_', 'qux 5copy 2', True), 'qux 5copy 2 copy') touch(get_preset_path('_', 'qux 5copy 2 copy')) self.assertEqual( get_available_preset_name('_', 'qux 5copy 2 copy', True), 'qux 5copy 2 copy 2')
def get_available_preset_name(group_name, preset='new preset', copy=False): """Increment the preset name until it is available.""" if group_name is None: # endless loop otherwise raise ValueError('group_name may not be None') preset = preset.strip() if copy and not re.match(r'^.+\scopy( \d+)?$', preset): preset = f'{preset} copy' # find a name that is not already taken if os.path.exists(get_preset_path(group_name, preset)): # if there already is a trailing number, increment it instead of # adding another one match = re.match(r'^(.+) (\d+)$', preset) if match: preset = match[1] i = int(match[2]) + 1 else: i = 2 while os.path.exists(get_preset_path(group_name, f'{preset} {i}')): i += 1 return f'{preset} {i}' return preset
def get_available_preset_name(device, preset='new preset'): """Increment the preset name until it is available.""" preset = preset.strip() # find a name that is not already taken if os.path.exists(get_preset_path(device, preset)): i = 2 while os.path.exists(get_preset_path(device, f'{preset} {i}')): i += 1 return f'{preset} {i}' return preset
def rename_preset(device, old_preset_name, new_preset_name): """Rename one of the users presets while avoiding name conflicts.""" if new_preset_name == old_preset_name: return None new_preset_name = get_available_preset_name(device, new_preset_name) logger.info('Moving "%s" to "%s"', old_preset_name, new_preset_name) os.rename(get_preset_path(device, old_preset_name), get_preset_path(device, new_preset_name)) # set the modification date to now now = time.time() os.utime(get_preset_path(device, new_preset_name), (now, now)) return new_preset_name
def delete_preset(device, preset): """Delete one of the users presets.""" preset_path = get_preset_path(device, preset) if not os.path.exists(preset_path): logger.debug('Cannot remove non existing path "%s"', preset_path) return logger.info('Removing "%s"', preset_path) os.remove(preset_path) device_path = get_preset_path(device) if os.path.exists(device_path) and len(os.listdir(device_path)) == 0: logger.debug('Removing empty dir "%s"', device_path) os.rmdir(device_path)
def test_xmodmap_file(self): from_keycode = evdev.ecodes.KEY_A to_name = 'qux' to_keycode = 100 event = (EV_KEY, from_keycode, 1) device = 'device 2' preset = 'foo' path = get_preset_path(device, preset) custom_mapping.change(Key(event), to_name) custom_mapping.save(path) system_mapping.clear() config.set_autoload_preset(device, preset) pending_events[device] = [InputEvent(*event)] xmodmap_path = os.path.join(tmp, 'foobar.json') with open(xmodmap_path, 'w') as file: file.write(f'{{"{to_name}":{to_keycode}}}') self.daemon = Daemon() self.daemon.start_injecting(device, path, xmodmap_path) event = uinput_write_history_pipe[0].recv() self.assertEqual(event.type, EV_KEY) self.assertEqual(event.code, to_keycode) self.assertEqual(event.value, 1)
def get_preset_path(self, preset=None): """Get a path to the stored preset, or to store a preset to. This path is unique per device-model, not per group. Groups of the same model share the same preset paths. """ return get_preset_path(self.name, preset)
def save_preset(self, *_): """Write changes to presets to disk.""" if not custom_mapping.changed: return try: path = get_preset_path(self.selected_device, self.selected_preset) custom_mapping.save(path) custom_mapping.changed = False # after saving the config, its modification date will be the # newest, so populate_presets will automatically select the # right one again. self.populate_presets() except PermissionError as error: error = str(error) self.show_status(CTX_ERROR, 'Permission denied!', error) logger.error(error) for _, character in custom_mapping: if is_this_a_macro(character): continue if system_mapping.get(character) is None: self.show_status(CTX_MAPPING, f'Unknown mapping "{character}"') break else: # no broken mappings found self.show_status(CTX_MAPPING, None) # checking macros is probably a bit more expensive, do that if # the regular mappings are allright self.check_macro_syntax()
def populate_presets(self): """Show the available presets for the selected device. This will destroy unsaved changes in the custom_mapping. """ device = self.selected_device presets = get_presets(device) if len(presets) == 0: new_preset = get_available_preset_name(self.selected_device) custom_mapping.empty() path = get_preset_path(self.selected_device, new_preset) custom_mapping.save(path) presets = [new_preset] else: logger.debug('"%s" presets: "%s"', device, '", "'.join(presets)) preset_selection = self.get('preset_selection') with HandlerDisabled(preset_selection, self.on_select_preset): # otherwise the handler is called with None for each preset preset_selection.remove_all() for preset in presets: preset_selection.append(preset, preset) # and select the newest one (on the top). triggers on_select_preset preset_selection.set_active(0)
def test_save_load_2(self): # loads mappings with only (type, code) as the key by using 1 as value, # loads combinations chained with + path = os.path.join(tmp, 'presets', 'device 1', 'test.json') os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, 'w') as file: json.dump( { 'mapping': { f'{EV_KEY},3': 'a', f'{EV_ABS},{ABS_HAT0X},-1': 'b', f'{EV_ABS},1,1+{EV_ABS},2,-1+{EV_ABS},3,1': 'c', # ignored because broken f'3,1,1,2': 'e', f'3': 'e', f',,+3,1,2': 'g', f'': 'h', } }, file) loaded = Mapping() self.assertEqual(loaded.num_saved_keys, 0) loaded.load(get_preset_path('device 1', 'test')) self.assertEqual(len(loaded), 3) self.assertEqual(loaded.num_saved_keys, 3) self.assertEqual(loaded.get_character(Key(EV_KEY, 3, 1)), 'a') self.assertEqual(loaded.get_character(Key(EV_ABS, ABS_HAT0X, -1)), 'b') self.assertEqual( loaded.get_character( Key((EV_ABS, 1, 1), (EV_ABS, 2, -1), Key(EV_ABS, 3, 1))), 'c')
def find_newest_preset(device=None): """Get a tuple of (device, preset) that was most recently modified in the users home directory. If no device has been configured yet, return an arbitrary device. Parameters ---------- device : string If set, will return the newest preset for the device or None """ # sort the oldest files to the front in order to use pop to get the newest if device is None: paths = sorted( glob.glob(os.path.join(get_preset_path(), '*/*.json')), key=os.path.getmtime ) else: paths = sorted( glob.glob(os.path.join(get_preset_path(device), '*.json')), key=os.path.getmtime ) if len(paths) == 0: logger.debug('No presets found') return get_any_preset() online_devices = get_devices().keys() newest_path = None while len(paths) > 0: # take the newest path path = paths.pop() preset = os.path.split(path)[1] device = os.path.split(os.path.split(path)[0])[1] if device in online_devices: newest_path = path break if newest_path is None: logger.debug('None of the configured devices is currently online') return get_any_preset() preset = os.path.splitext(preset)[0] logger.debug('The newest preset is "%s", "%s"', device, preset) return device, preset
def migrate_path(): """Migrate the folder structure from < 0.4.0. Move existing presets into the new subfolder "presets" """ new_preset_folder = os.path.join(CONFIG_PATH, 'presets') if not os.path.exists(get_preset_path()) and os.path.exists(CONFIG_PATH): logger.info('Migrating presets from < 0.4.0...') devices = os.listdir(CONFIG_PATH) mkdir(get_preset_path()) for device in devices: path = os.path.join(CONFIG_PATH, device) if os.path.isdir(path): target = path.replace(CONFIG_PATH, new_preset_folder) logger.info('Moving "%s" to "%s"', path, target) os.rename(path, target) logger.info('done')
def test_refresh_on_start(self): if os.path.exists(get_config_path('xmodmap.json')): os.remove(get_config_path('xmodmap.json')) ev = (EV_KEY, 9) keycode_to = 100 group_name = '9876 name' # expected key of the group group_key = group_name group = groups.find(name=group_name) # this test only makes sense if this device is unknown yet self.assertIsNone(group) custom_mapping.change(Key(*ev, 1), 'a') system_mapping.clear() system_mapping._set('a', keycode_to) # make the daemon load the file instead with open(get_config_path('xmodmap.json'), 'w') as file: json.dump(system_mapping._mapping, file, indent=4) system_mapping.clear() preset = 'foo' custom_mapping.save(get_preset_path(group_name, preset)) config.set_autoload_preset(group_key, preset) push_events(group_key, [new_event(*ev, 1)]) self.daemon = Daemon() # make sure the devices are populated groups.refresh() # the daemon is supposed to find this device by calling refresh fixtures[self.new_fixture_path] = { 'capabilities': { evdev.ecodes.EV_KEY: [ev[1]] }, 'phys': '9876 phys', 'info': evdev.device.DeviceInfo(4, 5, 6, 7), 'name': group_name } self.daemon.set_config_dir(get_config_path()) self.daemon.start_injecting(group_key, preset) # test if the injector called groups.refresh successfully group = groups.find(key=group_key) self.assertEqual(group.name, group_name) self.assertEqual(group.key, group_key) time.sleep(0.1) self.assertTrue(uinput_write_history_pipe[0].poll()) event = uinput_write_history_pipe[0].recv() self.assertEqual(event.t, (EV_KEY, keycode_to, 1)) self.daemon.stop_injecting(group_key) self.assertEqual(self.daemon.get_state(group_key), STOPPED)
def save_preset(self): """Write changes to presets to disk.""" logger.info('Updating configs for "%s", "%s"', self.selected_device, self.selected_preset) path = get_preset_path(self.selected_device, self.selected_preset) custom_mapping.save(path) custom_mapping.changed = False self.unhighlight_all_rows()
def test_config(self): self.mapping.save(get_preset_path('foo', 'bar2')) self.assertEqual(self.mapping.get('a'), None) self.assertFalse(self.mapping.changed) self.mapping.set('a', 1) self.assertEqual(self.mapping.get('a'), 1) self.assertTrue(self.mapping.changed) self.mapping.remove('a') self.mapping.set('a.b', 2) self.assertEqual(self.mapping.get('a.b'), 2) self.assertEqual(self.mapping._config['a']['b'], 2) self.mapping.remove('a.b') self.mapping.set('a.b.c', 3) self.assertEqual(self.mapping.get('a.b.c'), 3) self.assertEqual(self.mapping._config['a']['b']['c'], 3) # setting mapping.whatever does not overwrite the mapping # after saving. It should be ignored. self.mapping.change(Key(EV_KEY, 81, 1), ' a ') self.mapping.set('mapping.a', 2) self.assertEqual(self.mapping.num_saved_keys, 0) self.mapping.save(get_preset_path('foo', 'bar')) self.assertEqual(self.mapping.num_saved_keys, len(self.mapping)) self.assertFalse(self.mapping.changed) self.mapping.load(get_preset_path('foo', 'bar')) self.assertEqual(self.mapping.get_character(Key(EV_KEY, 81, 1)), 'a') self.assertIsNone(self.mapping.get('mapping.a')) self.assertFalse(self.mapping.changed) # loading a different preset also removes the configs from memory self.mapping.remove('a') self.assertTrue(self.mapping.changed) self.mapping.set('a.b.c', 6) self.mapping.load(get_preset_path('foo', 'bar2')) self.assertIsNone(self.mapping.get('a.b.c'))
def test_refresh_devices_on_start(self): ev = (EV_KEY, 9) keycode_to = 100 device = '9876 name' # this test only makes sense if this device is unknown yet self.assertIsNone(get_devices().get(device)) custom_mapping.change(Key(*ev, 1), 'a') system_mapping.clear() system_mapping._set('a', keycode_to) preset = 'foo' custom_mapping.save(get_preset_path(device, preset)) config.set_autoload_preset(device, preset) pending_events[device] = [InputEvent(*ev, 1)] self.daemon = Daemon() preset_path = get_preset_path(device, preset) # make sure the devices are populated get_devices() fixtures[self.new_fixture] = { 'capabilities': { evdev.ecodes.EV_KEY: [ev[1]] }, 'phys': '9876 phys', 'info': 'abcd', 'name': device } self.daemon.start_injecting(device, preset_path) # test if the injector called refresh_devices successfully self.assertIsNotNone(get_devices().get(device)) event = uinput_write_history_pipe[0].recv() self.assertEqual(event.type, EV_KEY) self.assertEqual(event.code, keycode_to) self.assertEqual(event.value, 1) self.daemon.stop_injecting(device) self.assertFalse(self.daemon.is_injecting(device))
def test_autoload(self): device = 'device 1' preset = 'preset7' path = '/dev/input/event11' daemon = Daemon() self.daemon = daemon self.daemon.set_config_dir(get_config_path()) mapping = Mapping() mapping.change(Key(3, 2, 1), 'a') mapping.save(get_preset_path(device, preset)) # no autoloading is configured yet self.daemon._autoload(device) self.daemon._autoload(path) self.assertNotIn(device, daemon.autoload_history._autoload_history) self.assertTrue(daemon.autoload_history.may_autoload(device, preset)) config.set_autoload_preset(device, preset) config.save_config() self.daemon.set_config_dir(get_config_path()) len_before = len(self.daemon.autoload_history._autoload_history) self.daemon._autoload(path) len_after = len(self.daemon.autoload_history._autoload_history) self.assertEqual(daemon.autoload_history._autoload_history[device][1], preset) self.assertFalse(daemon.autoload_history.may_autoload(device, preset)) injector = daemon.injectors[device] self.assertEqual(len_before + 1, len_after) # calling duplicate _autoload does nothing self.daemon._autoload(path) self.assertEqual(daemon.autoload_history._autoload_history[device][1], preset) self.assertEqual(injector, daemon.injectors[device]) self.assertFalse(daemon.autoload_history.may_autoload(device, preset)) # explicit start_injecting clears the autoload history self.daemon.start_injecting(device, preset) self.assertTrue(daemon.autoload_history.may_autoload(device, preset)) # calling autoload for (yet) unknown devices does nothing len_before = len(self.daemon.autoload_history._autoload_history) self.daemon._autoload('/dev/input/qux') len_after = len(self.daemon.autoload_history._autoload_history) self.assertEqual(len_before, len_after) # autoloading key-mapper devices does nothing len_before = len(self.daemon.autoload_history._autoload_history) self.daemon.autoload_single('/dev/input/event40') len_after = len(self.daemon.autoload_history._autoload_history) self.assertEqual(len_before, len_after)
def test_save_load(self): one = Key(EV_KEY, 10, 1) two = Key(EV_KEY, 11, 1) three = Key(EV_KEY, 12, 1) self.mapping.change(one, '1') self.mapping.change(two, '2') self.mapping.change(Key(two, three), '3') self.mapping._config['foo'] = 'bar' self.mapping.save(get_preset_path('device 1', 'test')) path = os.path.join(tmp, 'presets', 'device 1', 'test.json') self.assertTrue(os.path.exists(path)) loaded = Mapping() self.assertEqual(len(loaded), 0) loaded.load(get_preset_path('device 1', 'test')) self.assertEqual(len(loaded), 3) self.assertEqual(loaded.get_character(one), '1') self.assertEqual(loaded.get_character(two), '2') self.assertEqual(loaded.get_character(Key(two, three)), '3') self.assertEqual(loaded._config['foo'], 'bar')
def on_create_preset_clicked(self, _): """Create a new preset and select it.""" if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK: return try: new_preset = get_available_preset_name(self.selected_device) custom_mapping.empty() path = get_preset_path(self.selected_device, new_preset) custom_mapping.save(path) self.get('preset_selection').append(new_preset, new_preset) self.get('preset_selection').set_active_id(new_preset) except PermissionError as error: error = str(error) self.show_status(CTX_ERROR, 'Error: Permission denied!', error) logger.error(error)
def test_select_device(self): # creates a new empty preset when no preset exists for the device self.window.on_select_device(FakeDropdown('device 1')) custom_mapping.change(Key(EV_KEY, 50, 1), 'q') custom_mapping.change(Key(EV_KEY, 51, 1), 'u') custom_mapping.change(Key(EV_KEY, 52, 1), 'x') self.assertEqual(len(custom_mapping), 3) self.window.on_select_device(FakeDropdown('device 2')) self.assertEqual(len(custom_mapping), 0) # it creates the file for that right away. It may have been possible # to write it such that it doesn't (its empty anyway), but it does, # so use that to test it in more detail. path = get_preset_path('device 2', 'new preset') self.assertTrue(os.path.exists(path)) with open(path, 'r') as file: preset = json.load(file) self.assertEqual(len(preset['mapping']), 0)
def get_presets(device): """Get all presets for the device and user, starting with the newest. Parameters ---------- device : string """ device_folder = get_preset_path(device) mkdir(device_folder) paths = glob.glob(os.path.join(device_folder, '*.json')) presets = [ os.path.splitext(os.path.basename(path))[0] for path in sorted(paths, key=os.path.getmtime) ] # the highest timestamp to the front presets.reverse() return presets
def on_select_preset(self, dropdown): """Show the mappings of the preset.""" # beware in tests that this function won't be called at all if the # active_id stays the same self.save_preset() if dropdown.get_active_id() == self.selected_preset: return self.clear_mapping_table() preset = dropdown.get_active_text() if preset is None: return logger.debug('Selecting preset "%s"', preset) self.selected_preset = preset custom_mapping.load(get_preset_path(self.selected_device, preset)) key_list = self.get('key_list') for key, output in custom_mapping: single_key_mapping = Row(window=self, delete_callback=self.on_row_removed, key=key, character=output) key_list.insert(single_key_mapping, -1) autoload_switch = self.get('preset_autoload_switch') with HandlerDisabled(autoload_switch, self.on_autoload_switch): autoload_switch.set_active( config.is_autoloaded(self.selected_device, self.selected_preset)) self.get('preset_name_input').set_text('') self.add_empty() self.initialize_gamepad_config() custom_mapping.changed = False
def create_preset(self, copy=False): """Create a new preset and select it.""" self.save_preset() try: if copy: new_preset = get_available_preset_name(self.selected_device, self.selected_preset, copy) else: new_preset = get_available_preset_name(self.selected_device) custom_mapping.empty() path = get_preset_path(self.selected_device, new_preset) custom_mapping.save(path) self.get('preset_selection').append(new_preset, new_preset) self.get('preset_selection').set_active_id(new_preset) except PermissionError as error: error = str(error) self.show_status(CTX_ERROR, 'Permission denied!', error) logger.error(error)
def test_stop_injecting(self): keycode_from = 16 keycode_to = 90 self.change_empty_row(Key(EV_KEY, keycode_from, 1), 't') system_mapping.clear() system_mapping._set('t', keycode_to) # not all of those events should be processed, since that takes some # time due to time.sleep in the fakes and the injection is stopped. pending_events['device 2'] = [InputEvent(1, keycode_from, 1)] * 100 custom_mapping.save(get_preset_path('device 2', 'foo preset')) self.window.selected_device = 'device 2' self.window.selected_preset = 'foo preset' self.window.on_apply_preset_clicked(None) pipe = uinput_write_history_pipe[0] # block until the first event is available, indicating that # the injector is ready write_history = [pipe.recv()] # stop self.window.on_apply_system_layout_clicked(None) # try to receive a few of the events time.sleep(0.2) while pipe.poll(): write_history.append(pipe.recv()) len_before = len(write_history) self.assertLess(len(write_history), 50) # since the injector should not be running anymore, no more events # should be received after waiting even more time time.sleep(0.2) while pipe.poll(): write_history.append(pipe.recv()) self.assertEqual(len(write_history), len_before)
def test_start_injecting(self): keycode_from = 9 keycode_to = 200 self.change_empty_row(Key(EV_KEY, keycode_from, 1), 'a') system_mapping.clear() system_mapping._set('a', keycode_to) pending_events['device 2'] = [ InputEvent(evdev.events.EV_KEY, keycode_from, 1), InputEvent(evdev.events.EV_KEY, keycode_from, 0) ] custom_mapping.save(get_preset_path('device 2', 'foo preset')) # use only the manipulated system_mapping os.remove(os.path.join(tmp, XMODMAP_FILENAME)) self.window.selected_device = 'device 2' self.window.selected_preset = 'foo preset' self.window.on_apply_preset_clicked(None) # the integration tests will cause the injection to be started as # processes, as intended. Luckily, recv will block until the events # are handled and pushed. # Note, that pushing events to pending_events won't work anymore # from here on because the injector processes memory cannot be # modified from here. event = uinput_write_history_pipe[0].recv() self.assertEqual(event.type, evdev.events.EV_KEY) self.assertEqual(event.code, keycode_to) self.assertEqual(event.value, 1) event = uinput_write_history_pipe[0].recv() self.assertEqual(event.type, evdev.events.EV_KEY) self.assertEqual(event.code, keycode_to) self.assertEqual(event.value, 0)
def on_select_preset(self, dropdown): """Show the mappings of the preset.""" if dropdown.get_active_id() == self.selected_preset: return if custom_mapping.changed and unsaved_changes_dialog() == GO_BACK: dropdown.set_active_id(self.selected_preset) return self.clear_mapping_table() preset = dropdown.get_active_text() logger.debug('Selecting preset "%s"', preset) self.selected_preset = preset custom_mapping.load( get_preset_path(self.selected_device, self.selected_preset)) key_list = self.get('key_list') for key, output in custom_mapping: single_key_mapping = Row(window=self, delete_callback=self.on_row_removed, key=key, character=output) key_list.insert(single_key_mapping, -1) autoload_switch = self.get('preset_autoload_switch') with HandlerDisabled(autoload_switch, self.on_autoload_switch): autoload_switch.set_active( config.is_autoloaded(self.selected_device, self.selected_preset)) self.get('preset_name_input').set_text('') self.add_empty() self.initialize_gamepad_config() custom_mapping.changed = False
def on_apply_preset_clicked(self, _): """Apply a preset without saving changes.""" preset = self.selected_preset device = self.selected_device logger.info('Applying preset "%s" for "%s"', preset, device) if custom_mapping.changed: self.show_status(CTX_WARNING, f'Applied outdated preset "{preset}"', 'Click "Save" first for changes to take effect') else: self.show_status(CTX_APPLY, f'Applied preset "{preset}"') path = get_preset_path(device, preset) xmodmap = get_config_path(XMODMAP_FILENAME) success = self.dbus.start_injecting(device, path, xmodmap) if not success: self.show_status(CTX_ERROR, 'Error: Could not grab devices!') GLib.timeout_add(10, self.show_device_mapping_status)
def test_start_stop(self): device = 'device 1' preset = 'preset8' path = '/dev/input/event11' daemon = Daemon() self.daemon = daemon mapping = Mapping() mapping.change(Key(3, 2, 1), 'a') mapping.save(get_preset_path(device, preset)) # the daemon needs set_config_dir first before doing anything daemon.start_injecting(device, preset) self.assertNotIn(device, daemon.autoload_history._autoload_history) self.assertNotIn(device, daemon.injectors) self.assertTrue(daemon.autoload_history.may_autoload(device, preset)) # start config.save_config() daemon.set_config_dir(get_config_path()) daemon.start_injecting(path, preset) # explicit start, not autoload, so the history stays empty self.assertNotIn(device, daemon.autoload_history._autoload_history) self.assertTrue(daemon.autoload_history.may_autoload(device, preset)) # path got translated to the device name self.assertIn(device, daemon.injectors) # start again previous_injector = daemon.injectors[device] self.assertNotEqual(previous_injector.get_state(), STOPPED) daemon.start_injecting(device, preset) self.assertNotIn(device, daemon.autoload_history._autoload_history) self.assertTrue(daemon.autoload_history.may_autoload(device, preset)) self.assertIn(device, daemon.injectors) self.assertEqual(previous_injector.get_state(), STOPPED) # a different injetor is now running self.assertNotEqual(previous_injector, daemon.injectors[device]) self.assertNotEqual(daemon.injectors[device].get_state(), STOPPED) # trying to inject a non existing preset keeps the previous inejction # alive injector = daemon.injectors[device] daemon.start_injecting(device, 'qux') self.assertEqual(injector, daemon.injectors[device]) self.assertNotEqual(daemon.injectors[device].get_state(), STOPPED) # trying to start injecting for an unknown device also just does # nothing daemon.start_injecting('quux', 'qux') self.assertNotEqual(daemon.injectors[device].get_state(), STOPPED) # after all that stuff autoload_history is still unharmed self.assertNotIn(device, daemon.autoload_history._autoload_history) self.assertTrue(daemon.autoload_history.may_autoload(device, preset)) # stop daemon.stop_injecting(device) self.assertNotIn(device, daemon.autoload_history._autoload_history) self.assertEqual(daemon.injectors[device].get_state(), STOPPED) self.assertTrue(daemon.autoload_history.may_autoload(device, preset))
def test_autoload(self): devices = ['device 1', 'device 2'] presets = ['bar0', 'bar', 'bar2'] paths = [ get_preset_path(devices[0], presets[0]), get_preset_path(devices[1], presets[1]), get_preset_path(devices[1], presets[2]) ] Mapping().save(paths[0]) Mapping().save(paths[1]) Mapping().save(paths[2]) daemon = Daemon() start_history = [] stop_counter = 0 # using an actual injector is not within the scope of this test class Injector: def stop_injecting(self, *args, **kwargs): nonlocal stop_counter stop_counter += 1 def start_injecting(device, preset): print(f'\033[90mstart_injecting\033[0m') start_history.append((device, preset)) daemon.injectors[device] = Injector() daemon.start_injecting = start_injecting config.set_autoload_preset(devices[0], presets[0]) config.set_autoload_preset(devices[1], presets[1]) config.save_config() control(options('autoload', None, None, None, False, False, False), daemon) self.assertEqual(len(start_history), 2) self.assertEqual(start_history[0], (devices[0], presets[0])) self.assertEqual(start_history[1], (devices[1], presets[1])) self.assertIn(devices[0], daemon.injectors) self.assertIn(devices[1], daemon.injectors) self.assertFalse( daemon.autoload_history.may_autoload(devices[0], presets[0])) self.assertFalse( daemon.autoload_history.may_autoload(devices[1], presets[1])) # calling autoload again doesn't load redundantly control(options('autoload', None, None, None, False, False, False), daemon) self.assertEqual(len(start_history), 2) self.assertEqual(stop_counter, 0) self.assertFalse( daemon.autoload_history.may_autoload(devices[0], presets[0])) self.assertFalse( daemon.autoload_history.may_autoload(devices[1], presets[1])) # unless the injection in question ist stopped control(options('stop', None, None, devices[0], False, False, False), daemon) self.assertEqual(stop_counter, 1) self.assertTrue( daemon.autoload_history.may_autoload(devices[0], presets[0])) self.assertFalse( daemon.autoload_history.may_autoload(devices[1], presets[1])) control(options('autoload', None, None, None, False, False, False), daemon) self.assertEqual(len(start_history), 3) self.assertEqual(start_history[2], (devices[0], presets[0])) self.assertFalse( daemon.autoload_history.may_autoload(devices[0], presets[0])) self.assertFalse( daemon.autoload_history.may_autoload(devices[1], presets[1])) # if a device name is passed, will only start injecting for that one control(options('stop-all', None, None, None, False, False, False), daemon) self.assertTrue( daemon.autoload_history.may_autoload(devices[0], presets[0])) self.assertTrue( daemon.autoload_history.may_autoload(devices[1], presets[1])) self.assertEqual(stop_counter, 3) config.set_autoload_preset(devices[1], presets[2]) config.save_config() control( options('autoload', None, None, devices[1], False, False, False), daemon) self.assertEqual(len(start_history), 4) self.assertEqual(start_history[3], (devices[1], presets[2])) self.assertTrue( daemon.autoload_history.may_autoload(devices[0], presets[0])) self.assertFalse( daemon.autoload_history.may_autoload(devices[1], presets[2])) # autoloading for the same device again redundantly will not autoload # again control( options('autoload', None, None, devices[1], False, False, False), daemon) self.assertEqual(len(start_history), 4) self.assertEqual(stop_counter, 3) self.assertFalse( daemon.autoload_history.may_autoload(devices[1], presets[2])) # any other arbitrary preset may be autoloaded self.assertTrue( daemon.autoload_history.may_autoload(devices[1], 'quuuux')) # after 15 seconds it may be autoloaded again daemon.autoload_history._autoload_history[devices[1]] = (time.time() - 16, presets[2]) self.assertTrue( daemon.autoload_history.may_autoload(devices[1], presets[2]))