Ejemplo n.º 1
0
    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))
Ejemplo n.º 2
0
    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')
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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()
Ejemplo n.º 10
0
    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)
Ejemplo n.º 11
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')
Ejemplo n.º 12
0
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
Ejemplo n.º 13
0
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')
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
    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()
Ejemplo n.º 16
0
    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'))
Ejemplo n.º 17
0
    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))
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
    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')
Ejemplo n.º 20
0
    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)
Ejemplo n.º 21
0
 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)
Ejemplo n.º 22
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
Ejemplo n.º 23
0
    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
Ejemplo n.º 24
0
    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)
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
    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)
Ejemplo n.º 27
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
Ejemplo n.º 28
0
    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)
Ejemplo n.º 29
0
    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))
Ejemplo n.º 30
0
    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]))