Beispiel #1
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)
Beispiel #2
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))
Beispiel #3
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)
Beispiel #4
0
    def test_autoload_3(self):
        # based on a bug
        preset = 'preset7'
        group = groups.find(key='Foo Device 2')

        mapping = Mapping()
        mapping.change(Key(3, 2, 1), 'a')
        mapping.save(group.get_preset_path(preset))

        config.set_autoload_preset(group.key, preset)
        config.save_config()

        self.daemon = Daemon()
        self.daemon.set_config_dir(get_config_path())
        groups.set_groups([])  # caused the bug
        self.assertIsNone(groups.find(key='Foo Device 2'))
        self.daemon.autoload()

        # it should try to refresh the groups because all the
        # group_keys are unknown at the moment
        history = self.daemon.autoload_history._autoload_history
        self.assertEqual(history[group.key][1], preset)
        self.assertEqual(self.daemon.get_state(group.key), STARTING)
        self.assertIsNotNone(groups.find(key='Foo Device 2'))
Beispiel #5
0
    def test_autoload_2(self):
        self.daemon = Daemon()
        history = self.daemon.autoload_history._autoload_history

        # existing device
        preset = 'preset7'
        group = groups.find(key='Foo Device 2')
        mapping = Mapping()
        mapping.change(Key(3, 2, 1), 'a')
        mapping.save(group.get_preset_path(preset))
        config.set_autoload_preset(group.key, preset)

        # ignored, won't cause problems:
        config.set_autoload_preset('non-existant-key', 'foo')

        # daemon is missing the config directory yet
        self.daemon.autoload()
        self.assertEqual(len(history), 0)

        config.save_config()
        self.daemon.set_config_dir(get_config_path())
        self.daemon.autoload()
        self.assertEqual(len(history), 1)
        self.assertEqual(history[group.key][1], preset)
Beispiel #6
0
    def test_injector(self):
        # the tests in test_keycode_mapper.py test this stuff in detail

        numlock_before = is_numlock_on()

        combination = Key((EV_KEY, 8, 1), (EV_KEY, 9, 1))
        custom_mapping.change(combination, 'k(KEY_Q).k(w)')
        custom_mapping.change(Key(EV_ABS, ABS_HAT0X, -1), 'a')
        # one mapping that is unknown in the system_mapping on purpose
        input_b = 10
        custom_mapping.change(Key(EV_KEY, input_b, 1), 'b')

        # stuff the custom_mapping outputs (except for the unknown b)
        system_mapping.clear()
        code_a = 100
        code_q = 101
        code_w = 102
        system_mapping._set('a', code_a)
        system_mapping._set('key_q', code_q)
        system_mapping._set('w', code_w)

        push_events(
            'Bar Device',
            [
                # should execute a macro...
                new_event(EV_KEY, 8, 1),
                new_event(EV_KEY, 9, 1),  # ...now
                new_event(EV_KEY, 8, 0),
                new_event(EV_KEY, 9, 0),
                # gamepad stuff. trigger a combination
                new_event(EV_ABS, ABS_HAT0X, -1),
                new_event(EV_ABS, ABS_HAT0X, 0),
                # just pass those over without modifying
                new_event(EV_KEY, 10, 1),
                new_event(EV_KEY, 10, 0),
                new_event(3124, 3564, 6542),
            ])

        self.injector = Injector(groups.find(name='Bar Device'),
                                 custom_mapping)
        self.assertEqual(self.injector.get_state(), UNKNOWN)
        self.injector.start()
        self.assertEqual(self.injector.get_state(), STARTING)

        uinput_write_history_pipe[0].poll(timeout=1)
        self.assertEqual(self.injector.get_state(), RUNNING)
        time.sleep(EVENT_READ_TIMEOUT * 10)

        # sending anything arbitrary does not stop the process
        # (is_alive checked later after some time)
        self.injector._msg_pipe[1].send(1234)

        # convert the write history to some easier to manage list
        history = read_write_history_pipe()

        # 1 event before the combination was triggered (+1 for release)
        # 4 events for the macro
        # 2 for mapped keys
        # 3 for forwarded events
        self.assertEqual(len(history), 11)

        # since the macro takes a little bit of time to execute, its
        # keystrokes are all over the place.
        # just check if they are there and if so, remove them from the list.
        self.assertIn((EV_KEY, 8, 1), history)
        self.assertIn((EV_KEY, code_q, 1), history)
        self.assertIn((EV_KEY, code_q, 1), history)
        self.assertIn((EV_KEY, code_q, 0), history)
        self.assertIn((EV_KEY, code_w, 1), history)
        self.assertIn((EV_KEY, code_w, 0), history)
        index_q_1 = history.index((EV_KEY, code_q, 1))
        index_q_0 = history.index((EV_KEY, code_q, 0))
        index_w_1 = history.index((EV_KEY, code_w, 1))
        index_w_0 = history.index((EV_KEY, code_w, 0))
        self.assertGreater(index_q_0, index_q_1)
        self.assertGreater(index_w_1, index_q_0)
        self.assertGreater(index_w_0, index_w_1)
        del history[index_q_1]
        index_q_0 = history.index((EV_KEY, code_q, 0))
        del history[index_q_0]
        index_w_1 = history.index((EV_KEY, code_w, 1))
        del history[index_w_1]
        index_w_0 = history.index((EV_KEY, code_w, 0))
        del history[index_w_0]

        # the rest should be in order.
        # first the incomplete combination key that wasn't mapped to anything
        # and just forwarded. The input event that triggered the macro
        # won't appear here.
        self.assertEqual(history[0], (EV_KEY, 8, 1))
        self.assertEqual(history[1], (EV_KEY, 8, 0))
        # value should be 1, even if the input event was -1.
        # Injected keycodes should always be either 0 or 1
        self.assertEqual(history[2], (EV_KEY, code_a, 1))
        self.assertEqual(history[3], (EV_KEY, code_a, 0))
        self.assertEqual(history[4], (EV_KEY, input_b, 1))
        self.assertEqual(history[5], (EV_KEY, input_b, 0))
        self.assertEqual(history[6], (3124, 3564, 6542))

        time.sleep(0.1)
        self.assertTrue(self.injector.is_alive())

        numlock_after = is_numlock_on()
        self.assertEqual(numlock_before, numlock_after)
        self.assertEqual(self.injector.get_state(), RUNNING)
Beispiel #7
0
    def test_start_stop(self):
        group = groups.find(key='Foo Device 2')
        preset = 'preset8'

        daemon = Daemon()
        self.daemon = daemon

        mapping = Mapping()
        mapping.change(Key(3, 2, 1), 'a')
        mapping.save(group.get_preset_path(preset))

        # the daemon needs set_config_dir first before doing anything
        daemon.start_injecting(group.key, preset)
        self.assertNotIn(group.key, daemon.autoload_history._autoload_history)
        self.assertNotIn(group.key, daemon.injectors)
        self.assertTrue(daemon.autoload_history.may_autoload(
            group.key, preset))

        # start
        config.save_config()
        daemon.set_config_dir(get_config_path())
        daemon.start_injecting(group.key, preset)
        # explicit start, not autoload, so the history stays empty
        self.assertNotIn(group.key, daemon.autoload_history._autoload_history)
        self.assertTrue(daemon.autoload_history.may_autoload(
            group.key, preset))
        # path got translated to the device name
        self.assertIn(group.key, daemon.injectors)

        # start again
        previous_injector = daemon.injectors[group.key]
        self.assertNotEqual(previous_injector.get_state(), STOPPED)
        daemon.start_injecting(group.key, preset)
        self.assertNotIn(group.key, daemon.autoload_history._autoload_history)
        self.assertTrue(daemon.autoload_history.may_autoload(
            group.key, preset))
        self.assertIn(group.key, daemon.injectors)
        self.assertEqual(previous_injector.get_state(), STOPPED)
        # a different injetor is now running
        self.assertNotEqual(previous_injector, daemon.injectors[group.key])
        self.assertNotEqual(daemon.injectors[group.key].get_state(), STOPPED)

        # trying to inject a non existing preset keeps the previous inejction
        # alive
        injector = daemon.injectors[group.key]
        daemon.start_injecting(group.key, 'qux')
        self.assertEqual(injector, daemon.injectors[group.key])
        self.assertNotEqual(daemon.injectors[group.key].get_state(), STOPPED)

        # trying to start injecting for an unknown device also just does
        # nothing
        daemon.start_injecting('quux', 'qux')
        self.assertNotEqual(daemon.injectors[group.key].get_state(), STOPPED)

        # after all that stuff autoload_history is still unharmed
        self.assertNotIn(group.key, daemon.autoload_history._autoload_history)
        self.assertTrue(daemon.autoload_history.may_autoload(
            group.key, preset))

        # stop
        daemon.stop_injecting(group.key)
        self.assertNotIn(group.key, daemon.autoload_history._autoload_history)
        self.assertEqual(daemon.injectors[group.key].get_state(), STOPPED)
        self.assertTrue(daemon.autoload_history.may_autoload(
            group.key, preset))
Beispiel #8
0
    def test_daemon(self):
        # remove the existing system mapping to force our own into it
        if os.path.exists(get_config_path('xmodmap.json')):
            os.remove(get_config_path('xmodmap.json'))

        ev_1 = (EV_KEY, 9)
        ev_2 = (EV_ABS, 12)
        keycode_to_1 = 100
        keycode_to_2 = 101

        group = groups.find(name='Bar Device')

        # unrelated group that shouldn't be affected at all
        group2 = groups.find(name='gamepad')

        custom_mapping.change(Key(*ev_1, 1), 'a')
        custom_mapping.change(Key(*ev_2, -1), 'b')

        system_mapping.clear()
        # since this is in the same memory as the daemon, there is no need
        # to save it to disk
        system_mapping._set('a', keycode_to_1)
        system_mapping._set('b', keycode_to_2)

        preset = 'foo'

        custom_mapping.save(group.get_preset_path(preset))
        config.set_autoload_preset(group.key, preset)
        """injection 1"""

        # should forward the event unchanged
        push_events(group.key, [new_event(EV_KEY, 13, 1)])

        self.daemon = Daemon()
        self.daemon.set_config_dir(get_config_path())

        self.assertFalse(uinput_write_history_pipe[0].poll())
        self.daemon.start_injecting(group.key, preset)

        self.assertEqual(self.daemon.get_state(group.key), STARTING)
        self.assertEqual(self.daemon.get_state(group2.key), UNKNOWN)

        event = uinput_write_history_pipe[0].recv()
        self.assertEqual(self.daemon.get_state(group.key), RUNNING)
        self.assertEqual(event.type, EV_KEY)
        self.assertEqual(event.code, 13)
        self.assertEqual(event.value, 1)

        self.daemon.stop_injecting(group.key)
        self.assertEqual(self.daemon.get_state(group.key), STOPPED)

        time.sleep(0.1)
        try:
            self.assertFalse(uinput_write_history_pipe[0].poll())
        except AssertionError:
            print('Unexpected', uinput_write_history_pipe[0].recv())
            # possibly a duplicate write!
            raise
        """injection 2"""

        # -1234 will be normalized to -1 by the injector
        push_events(group.key, [new_event(*ev_2, -1234)])

        self.daemon.start_injecting(group.key, preset)

        time.sleep(0.1)
        self.assertTrue(uinput_write_history_pipe[0].poll())

        # the written key is a key-down event, not the original
        # event value of -1234
        event = uinput_write_history_pipe[0].recv()

        self.assertEqual(event.type, EV_KEY)
        self.assertEqual(event.code, keycode_to_2)
        self.assertEqual(event.value, 1)
Beispiel #9
0
    def test_raises(self):
        self.assertRaises(ValueError, lambda: Key(1))
        self.assertRaises(ValueError, lambda: Key(None))
        self.assertRaises(ValueError, lambda: Key([1]))
        self.assertRaises(ValueError, lambda: Key((1, )))
        self.assertRaises(ValueError, lambda: Key((1, 2)))
        self.assertRaises(ValueError, lambda: Key(('1', '2', '3')))
        self.assertRaises(ValueError, lambda: Key('1'))
        self.assertRaises(ValueError, lambda: Key('(1,2,3)'))
        self.assertRaises(ValueError, lambda: Key((1, 2, 3), (1, 2, '3')))
        self.assertRaises(ValueError, lambda: Key((1, 2, 3), (1, 2, 3), None))

        # those don't raise errors
        Key((1, 2, 3), (1, 2, 3))
        Key((1, 2, 3))
Beispiel #10
0
    def test_prepare_device_non_existing(self):
        custom_mapping.change(Key(EV_ABS, ABS_HAT0X, 1), 'a')
        self.injector = KeycodeInjector('foobar', custom_mapping)

        _prepare_device = self.injector._prepare_device
        self.assertIsNone(_prepare_device('/dev/input/event1234')[0])
Beispiel #11
0
    def test_get_permutations(self):
        key_1 = Key((1, 3, 1))
        self.assertEqual(len(key_1.get_permutations()), 1)
        self.assertEqual(key_1.get_permutations()[0], key_1)

        key_2 = Key((1, 3, 1), (1, 5, 1))
        self.assertEqual(len(key_2.get_permutations()), 1)
        self.assertEqual(key_2.get_permutations()[0], key_2)

        key_3 = Key((1, 3, 1), (1, 5, 1), (1, 7, 1))
        self.assertEqual(len(key_3.get_permutations()), 2)
        self.assertEqual(key_3.get_permutations()[0],
                         Key((1, 3, 1), (1, 5, 1), (1, 7, 1)))
        self.assertEqual(key_3.get_permutations()[1],
                         ((1, 5, 1), (1, 3, 1), (1, 7, 1)))
Beispiel #12
0
    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())
Beispiel #13
0
    def test_daemon(self):
        ev_1 = (EV_KEY, 9)
        ev_2 = (EV_ABS, 12)
        keycode_to_1 = 100
        keycode_to_2 = 101

        device = 'device 2'

        custom_mapping.change(Key(*ev_1, 1), 'a')
        custom_mapping.change(Key(*ev_2, -1), 'b')

        system_mapping.clear()
        system_mapping._set('a', keycode_to_1)
        system_mapping._set('b', keycode_to_2)

        preset = 'foo'

        custom_mapping.save(get_preset_path(device, preset))
        config.set_autoload_preset(device, preset)
        """injection 1"""

        # should forward the event unchanged
        pending_events[device] = [InputEvent(EV_KEY, 13, 1)]

        self.daemon = Daemon()
        preset_path = get_preset_path(device, preset)

        self.assertFalse(uinput_write_history_pipe[0].poll())
        self.daemon.start_injecting(device, preset_path)

        self.assertTrue(self.daemon.is_injecting(device))
        self.assertFalse(self.daemon.is_injecting('device 1'))

        event = uinput_write_history_pipe[0].recv()
        self.assertEqual(event.type, EV_KEY)
        self.assertEqual(event.code, 13)
        self.assertEqual(event.value, 1)

        self.daemon.stop_injecting(device)
        self.assertFalse(self.daemon.is_injecting(device))

        time.sleep(0.2)
        try:
            self.assertFalse(uinput_write_history_pipe[0].poll())
        except AssertionError:
            print(uinput_write_history_pipe[0].recv())
            raise
        """injection 2"""

        # -1234 will be normalized to -1 by the injector
        pending_events[device] = [InputEvent(*ev_2, -1234)]

        path = get_preset_path(device, preset)
        self.daemon.start_injecting(device, path)

        # the written key is a key-down event, not the original
        # event value of -5678
        event = uinput_write_history_pipe[0].recv()
        self.assertEqual(event.type, EV_KEY)
        self.assertEqual(event.code, keycode_to_2)
        self.assertEqual(event.value, 1)
Beispiel #14
0
    def read(self):
        """Get the newest key as Key object

        If the timing of two recent events is very close, prioritize
        key events over abs events.
        """
        if self._pipe is None:
            self.fail_counter += 1
            if self.fail_counter % 10 == 0:
                # spam less
                logger.debug('No pipe available to read from')
            return None

        newest_event = self.newest_event
        newest_time = (0 if newest_event is None else newest_event.sec +
                       newest_event.usec / 1000000)

        while self._pipe[0].poll():
            event = self._pipe[0].recv()
            without_value = (event.type, event.code)

            if event.value == 0:
                if without_value in self._unreleased:
                    del self._unreleased[without_value]
                continue

            if without_value in self._unreleased:
                # no duplicate down events (gamepad triggers)
                continue

            time = event.sec + event.usec / 1000000
            delta = time - newest_time

            if delta < FILTER_THRESHOLD:
                if prioritize([newest_event, event]) != event:
                    # two events happened very close, probably some weird
                    # spam from the device. The wacom intuos 5 adds an
                    # ABS_MISC event to every button press, filter that out
                    logger.spam('Ignoring event (%s, %s, %s)', event.type,
                                event.code, event.value)
                    continue

                # the previous event is ignored
                previous_without_value = (newest_event.type, newest_event.code)
                if previous_without_value in self._unreleased:
                    del self._unreleased[previous_without_value]

            self._unreleased[without_value] = (event.type, event.code,
                                               event.value)

            newest_event = event
            newest_time = time

        if newest_event == self.newest_event:
            # don't return the same event twice
            return None

        self.newest_event = newest_event

        if len(self._unreleased) > 0:
            return Key(*self._unreleased.values())

        # nothing
        return None
Beispiel #15
0
    def test_any_funky_event_as_button(self):
        # as long as should_map_as_btn says it should be a button,
        # it will be.
        EV_TYPE = 4531
        CODE_1 = 754
        CODE_2 = 4139

        w_down = (EV_TYPE, CODE_1, -1)
        w_up = (EV_TYPE, CODE_1, 0)

        d_down = (EV_TYPE, CODE_2, 1)
        d_up = (EV_TYPE, CODE_2, 0)

        custom_mapping.change(Key(*w_down[:2], -1), 'w')
        custom_mapping.change(Key(*d_down[:2], 1), 'k(d)')

        system_mapping.clear()
        code_w = 71
        code_d = 74
        system_mapping._set('w', code_w)
        system_mapping._set('d', code_d)

        def do_stuff():
            if self.injector is not None:
                # discard the previous injector
                self.injector.stop_injecting()
                time.sleep(0.1)
                while uinput_write_history_pipe[0].poll():
                    uinput_write_history_pipe[0].recv()

            push_events('gamepad', [
                new_event(*w_down),
                new_event(*d_down),
                new_event(*w_up),
                new_event(*d_up),
            ])

            self.injector = Injector(groups.find(name='gamepad'),
                                     custom_mapping)

            # the injector will otherwise skip the device because
            # the capabilities don't contain EV_TYPE
            input = InputDevice('/dev/input/event30')
            self.injector._grab_device = lambda *args: input

            self.injector.start()
            uinput_write_history_pipe[0].poll(timeout=1)
            time.sleep(EVENT_READ_TIMEOUT * 10)
            return read_write_history_pipe()

        """no"""

        history = do_stuff()
        self.assertEqual(history.count((EV_KEY, code_w, 1)), 0)
        self.assertEqual(history.count((EV_KEY, code_d, 1)), 0)
        self.assertEqual(history.count((EV_KEY, code_w, 0)), 0)
        self.assertEqual(history.count((EV_KEY, code_d, 0)), 0)
        """yes"""

        with mock.patch('keymapper.utils.should_map_as_btn', lambda *_: True):
            history = do_stuff()
            self.assertEqual(history.count((EV_KEY, code_w, 1)), 1)
            self.assertEqual(history.count((EV_KEY, code_d, 1)), 1)
            self.assertEqual(history.count((EV_KEY, code_w, 0)), 1)
            self.assertEqual(history.count((EV_KEY, code_d, 0)), 1)
Beispiel #16
0
    def test_is_problematic(self):
        key_1 = Key((1, KEY_LEFTSHIFT, 1), (1, 5, 1))
        self.assertTrue(key_1.is_problematic())

        key_2 = Key((1, KEY_RIGHTALT, 1), (1, 5, 1))
        self.assertTrue(key_2.is_problematic())

        key_3 = Key((1, 3, 1), (1, KEY_LEFTCTRL, 1))
        self.assertTrue(key_3.is_problematic())

        key_4 = Key(1, 3, 1)
        self.assertFalse(key_4.is_problematic())

        key_5 = Key((1, 3, 1), (1, 5, 1))
        self.assertFalse(key_5.is_problematic())
Beispiel #17
0
    def test_wheel(self):
        # this tests both keycode_mapper and event_producer, and it seems
        # to test stuff not covered in test_keycode_mapper, so it's a quite
        # important one.

        # wheel release events are made up with a debouncer

        # map those two to stuff
        w_up = (EV_REL, REL_WHEEL, -1)
        hw_right = (EV_REL, REL_HWHEEL, 1)

        # should be forwarded and present in the capabilities
        hw_left = (EV_REL, REL_HWHEEL, -1)

        custom_mapping.change(Key(*hw_right), 'k(b)')
        custom_mapping.change(Key(*w_up), 'c')

        system_mapping.clear()
        code_b = 91
        code_c = 92
        system_mapping._set('b', code_b)
        system_mapping._set('c', code_c)

        group_key = 'Foo Device 2'
        push_events(group_key, [
            new_event(*w_up),
        ] * 10 + [
            new_event(*hw_right),
            new_event(*w_up),
        ] * 5 + [new_event(*hw_left)])

        group = groups.find(key=group_key)
        self.injector = Injector(group, custom_mapping)

        device = InputDevice('/dev/input/event11')
        # make sure this test uses a device that has the needed capabilities
        # for the injector to grab it
        self.assertIn(EV_REL, device.capabilities())
        self.assertIn(REL_WHEEL, device.capabilities()[EV_REL])
        self.assertIn(REL_HWHEEL, device.capabilities()[EV_REL])
        self.assertIn(device.path, group.paths)

        self.injector.start()

        # wait for the first injected key down event
        uinput_write_history_pipe[0].poll(timeout=1)
        self.assertTrue(uinput_write_history_pipe[0].poll())
        event = uinput_write_history_pipe[0].recv()
        self.assertEqual(event.t, (EV_KEY, code_c, 1))

        time.sleep(EVENT_READ_TIMEOUT * 5)
        # in 5 more read-loop ticks, nothing new should have happened
        self.assertFalse(uinput_write_history_pipe[0].poll())

        time.sleep(EVENT_READ_TIMEOUT * 6)
        # 5 more and it should be within the second phase in which
        # the horizontal wheel is used. add some tolerance
        self.assertTrue(uinput_write_history_pipe[0].poll())
        event = uinput_write_history_pipe[0].recv()
        self.assertEqual(event.t, (EV_KEY, code_b, 1))

        time.sleep(EVENT_READ_TIMEOUT * 10 + 5 / 60)
        # after 21 read-loop ticks all events should be consumed, wait for
        # at least 3 (=5) producer-ticks so that the debouncers are triggered.
        # Key-up events for both wheel events should be written now that no
        # new key-down event arrived.
        events = read_write_history_pipe()
        self.assertEqual(events.count((EV_KEY, code_b, 0)), 1)
        self.assertEqual(events.count((EV_KEY, code_c, 0)), 1)
        self.assertEqual(events.count(hw_left), 1)  # the unmapped wheel

        # the unmapped wheel won't get a debounced release command, it's
        # forwarded as is
        self.assertNotIn((EV_REL, REL_HWHEEL, 0), events)

        self.assertEqual(len(events), 3)
Beispiel #18
0
    def read(self):
        """Get the newest key/combination as Key object.

        Only reports keys from down-events.

        On key-down events the pipe returns changed combinations. Release
        events won't cause that and the reader will return None as in
        "nothing new to report". So In order to change a combination, one
        of its keys has to be released and then a different one pressed.

        Otherwise making combinations wouldn't be possible. Because at
        some point the keys have to be released, and that shouldn't cause
        the combination to get trimmed.
        """
        # this is in some ways similar to the keycode_mapper and
        # event_producer, but its much simpler because it doesn't
        # have to trigger anything, manage any macros and only
        # reports key-down events. This function is called periodically
        # by the window.

        # remember the previous down-event from the pipe in order to
        # be able to tell if the reader should return the updated combination
        previous_event = self.previous_event
        key_down_received = False

        self._debounce_tick()

        while self._results.poll():
            message = self._results.recv()
            event = self._get_event(message)
            if event is None:
                continue

            gamepad = GAMEPAD in self.group.types
            if not utils.should_map_as_btn(event, custom_mapping, gamepad):
                continue

            event_tuple = (event.type, event.code, event.value)

            type_code = (event.type, event.code)

            if event.value == 0:
                logger.key_spam(event_tuple, 'release')
                self._release(type_code)
                continue

            if self._unreleased.get(type_code) == event_tuple:
                logger.key_spam(event_tuple, 'duplicate key down')
                self._debounce_start(event_tuple)
                continue

            # to keep track of combinations.
            # "I have got this release event, what was this for?" A release
            # event for a D-Pad axis might be any direction, hence this maps
            # from release to input in order to remember it. Since all release
            # events have value 0, the value is not used in the key.
            key_down_received = True
            logger.key_spam(event_tuple, 'down')
            self._unreleased[type_code] = event_tuple
            self._debounce_start(event_tuple)
            previous_event = event

        if not key_down_received:
            # This prevents writing a subset of the combination into
            # result after keys were released. In order to control the gui,
            # they have to be released.
            return None

        self.previous_event = previous_event

        if len(self._unreleased) > 0:
            result = Key(*self._unreleased.values())
            if result == self.previous_result:
                # don't return the same stuff twice
                return None

            self.previous_result = result
            logger.key_spam(result.keys, 'read result')

            return result

        return None