示例#1
0
    def test_modify_capabilities(self):
        class FakeDevice:
            def capabilities(self, absinfo=True):
                assert absinfo is False
                return {
                    evdev.ecodes.EV_SYN: [1, 2, 3],
                    evdev.ecodes.EV_FF: [1, 2, 3],
                    evdev.ecodes.EV_ABS: [1, 2, 3]
                }

        mapping = Mapping()
        mapping.change(Key(EV_KEY, 80, 1), 'a')
        mapping.change(Key(EV_KEY, 81, 1), DISABLE_NAME)

        macro_code = 'r(2, m(sHiFt_l, r(2, k(1).k(2))))'
        macro = parse(macro_code, mapping)

        mapping.change(Key(EV_KEY, 60, 111), macro_code)

        # going to be ignored, because EV_REL cannot be mapped, that's
        # mouse movements.
        mapping.change(Key(EV_REL, 1234, 3), 'b')

        a = system_mapping.get('a')
        shift_l = system_mapping.get('ShIfT_L')
        one = system_mapping.get(1)
        two = system_mapping.get('2')
        btn_left = system_mapping.get('BtN_lEfT')

        self.injector = KeycodeInjector('foo', mapping)
        fake_device = FakeDevice()
        capabilities_1 = self.injector._modify_capabilities({60: macro},
                                                            fake_device,
                                                            abs_to_rel=False)

        self.assertIn(EV_KEY, capabilities_1)
        keys = capabilities_1[EV_KEY]
        self.assertIn(a, keys)
        self.assertIn(one, keys)
        self.assertIn(two, keys)
        self.assertIn(shift_l, keys)
        self.assertNotIn(DISABLE_CODE, keys)
        # abs_to_rel is false, so mouse capabilities are not needed
        self.assertNotIn(btn_left, keys)

        self.assertNotIn(evdev.ecodes.EV_SYN, capabilities_1)
        self.assertNotIn(evdev.ecodes.EV_FF, capabilities_1)
        self.assertNotIn(evdev.ecodes.EV_REL, capabilities_1)
        self.assertNotIn(evdev.ecodes.EV_ABS, capabilities_1)

        # abs_to_rel makes sure that BTN_LEFT is present
        capabilities_2 = self.injector._modify_capabilities({60: macro},
                                                            fake_device,
                                                            abs_to_rel=True)
        keys = capabilities_2[EV_KEY]
        self.assertIn(a, keys)
        self.assertIn(one, keys)
        self.assertIn(two, keys)
        self.assertIn(shift_l, keys)
        self.assertIn(btn_left, keys)
示例#2
0
    def test_store_permutations_for_macros(self):
        mapping = Mapping()
        ev_1 = (EV_KEY, 41, 1)
        ev_2 = (EV_KEY, 42, 1)
        ev_3 = (EV_KEY, 43, 1)
        # a combination
        mapping.change(Key(ev_1, ev_2, ev_3), 'k(a)')
        self.injector = KeycodeInjector('device 1', mapping)

        history = []

        class Stop(Exception):
            pass

        def _modify_capabilities(*args):
            history.append(args)
            # avoid going into any mainloop
            raise Stop()

        self.injector._modify_capabilities = _modify_capabilities
        try:
            self.injector._start_injecting()
        except Stop:
            pass

        # one call
        self.assertEqual(len(history), 1)
        # first argument of the first call
        self.assertEqual(len(history[0][0]), 2)
        self.assertEqual(history[0][0][(ev_1, ev_2, ev_3)].code, 'k(a)')
        self.assertEqual(history[0][0][(ev_2, ev_1, ev_3)].code, 'k(a)')
示例#3
0
        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()

            pending_events['gamepad'] = [
                InputEvent(*w_down),
                InputEvent(*d_down),
                InputEvent(*w_up),
                InputEvent(*d_up),
            ]

            self.injector = KeycodeInjector('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._prepare_device = lambda *args: (input, False)

            self.injector.start_injecting()
            uinput_write_history_pipe[0].poll(timeout=1)
            time.sleep(EVENT_READ_TIMEOUT * 10)
            return read_write_history_pipe()
示例#4
0
    def test_prepare_device_1(self):
        # according to the fixtures, /dev/input/event30 can do ABS_HAT0X
        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/event10')[0])
        self.assertIsNotNone(_prepare_device('/dev/input/event30')[0])
示例#5
0
 def test_skip_unused_device(self):
     # skips a device because its capabilities are not used in the mapping
     custom_mapping.change(Key(EV_KEY, 10, 1), 'a')
     self.injector = KeycodeInjector('device 1', custom_mapping)
     path = '/dev/input/event11'
     device, abs_to_rel = self.injector._prepare_device(path)
     self.assertFalse(abs_to_rel)
     self.assertEqual(self.failed, 0)
     self.assertIsNone(device)
示例#6
0
    def start_injecting(self, device, path, xmodmap_path=None):
        """Start injecting the preset for the device.

        Returns True on success.

        Parameters
        ----------
        device : string
            The name of the device
        path : string
            Path to the preset. The daemon, if started via systemctl, has no
            knowledge of the user and their home path, so the complete
            absolute path needs to be provided here.
        xmodmap_path : string, None
            Path to a dump of the xkb mappings, to provide more human
            readable keys in the correct keyboard layout to the service.
            The service cannot use `xmodmap -pke` because it's running via
            systemd.
        """
        if device not in get_devices():
            logger.debug('Devices possibly outdated, refreshing')
            refresh_devices()

        # reload the config, since it may have been changed
        config.load_config()
        if self.injectors.get(device) is not None:
            self.injectors[device].stop_injecting()

        mapping = Mapping()
        try:
            mapping.load(path)
        except FileNotFoundError as error:
            logger.error(str(error))
            return False

        if xmodmap_path is not None:
            try:
                with open(xmodmap_path, 'r') as file:
                    xmodmap = json.load(file)
                    logger.debug('Using keycodes from "%s"', xmodmap_path)
                    system_mapping.update(xmodmap)
                    # the service now has process wide knowledge of xmodmap
                    # keys of the users session
            except FileNotFoundError:
                logger.error('Could not find "%s"', xmodmap_path)

        try:
            injector = KeycodeInjector(device, mapping)
            injector.start_injecting()
            self.injectors[device] = injector
        except OSError:
            return False

        return True
示例#7
0
    def test_abs_to_rel(self):
        # maps gamepad joystick events to mouse events
        config.set('gamepad.joystick.non_linearity', 1)
        pointer_speed = 80
        config.set('gamepad.joystick.pointer_speed', pointer_speed)

        rel_x = evdev.ecodes.REL_X
        rel_y = evdev.ecodes.REL_Y

        # they need to sum up before something is written
        divisor = 10
        x = MAX_ABS / pointer_speed / divisor
        y = MAX_ABS / pointer_speed / divisor
        pending_events['gamepad'] = [
            InputEvent(EV_ABS, rel_x, x),
            InputEvent(EV_ABS, rel_y, y),
            InputEvent(EV_ABS, rel_x, -x),
            InputEvent(EV_ABS, rel_y, -y),
        ]

        self.injector = KeycodeInjector('gamepad', custom_mapping)
        self.injector.start_injecting()

        # wait for the injector to start sending, at most 1s
        uinput_write_history_pipe[0].poll(1)

        # wait a bit more for it to sum up
        sleep = 0.5
        time.sleep(sleep)

        # convert the write history to some easier to manage list
        history = []
        while uinput_write_history_pipe[0].poll():
            event = uinput_write_history_pipe[0].recv()
            history.append((event.type, event.code, event.value))

        if history[0][0] == EV_ABS:
            raise AssertionError(
                'The injector probably just forwarded them unchanged')

        # movement is written at 60hz and it takes `divisor` steps to
        # move 1px. take it times 2 for both x and y events.
        self.assertGreater(len(history), 60 * sleep * 0.9 * 2 / divisor)
        self.assertLess(len(history), 60 * sleep * 1.1 * 2 / divisor)

        # those may be in arbitrary order, the injector happens to write
        # y first
        self.assertEqual(history[-1][0], EV_REL)
        self.assertEqual(history[-1][1], rel_x)
        self.assertAlmostEqual(history[-1][2], -1)
        self.assertEqual(history[-2][0], EV_REL)
        self.assertEqual(history[-2][1], rel_y)
        self.assertAlmostEqual(history[-2][2], -1)
示例#8
0
    def test_grab(self):
        # path is from the fixtures
        custom_mapping.change(Key(EV_KEY, 10, 1), 'a')

        self.injector = KeycodeInjector('device 1', custom_mapping)
        path = '/dev/input/event10'
        # this test needs to pass around all other constraints of
        # _prepare_device
        device, abs_to_rel = self.injector._prepare_device(path)
        self.assertFalse(abs_to_rel)
        self.assertEqual(self.failed, 2)
        # success on the third try
        device.name = fixtures[path]['name']
示例#9
0
    def test_skip_unknown_device(self):
        # skips a device because its capabilities are not used in the mapping
        self.injector = KeycodeInjector('device 1', custom_mapping)
        path = '/dev/input/event11'
        device, _ = self.injector._prepare_device(path)

        # make sure the test uses a fixture without interesting capabilities
        capabilities = evdev.InputDevice(path).capabilities()
        self.assertEqual(len(capabilities.get(EV_KEY, [])), 0)
        self.assertEqual(len(capabilities.get(EV_ABS, [])), 0)

        # skips the device alltogether, so no grab attempts fail
        self.assertEqual(self.failed, 0)
        self.assertIsNone(device)
示例#10
0
    def test_adds_ev_key(self):
        # for some reason, having any EV_KEY capability is needed to
        # be able to control the mouse. it probably wants the mouse click.
        self.injector = KeycodeInjector('gamepad 2', custom_mapping)
        """gamepad without any existing key capability"""

        path = self.new_gamepad
        gamepad_template = copy.deepcopy(fixtures['/dev/input/event30'])
        fixtures[path] = {
            'name': 'gamepad 2',
            'phys': 'abcd',
            'info': '1234',
            'capabilities': gamepad_template['capabilities']
        }
        del fixtures[path]['capabilities'][EV_KEY]
        device, abs_to_rel = self.injector._prepare_device(path)
        self.assertNotIn(EV_KEY, device.capabilities())
        capabilities = self.injector._modify_capabilities({}, device,
                                                          abs_to_rel)
        self.assertIn(EV_KEY, capabilities)
        self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
        """gamepad with a btn_mouse capability existing"""

        path = self.new_gamepad
        gamepad_template = copy.deepcopy(fixtures['/dev/input/event30'])
        fixtures[path] = {
            'name': 'gamepad 3',
            'phys': 'abcd',
            'info': '1234',
            'capabilities': gamepad_template['capabilities']
        }
        fixtures[path]['capabilities'][EV_KEY].append(BTN_LEFT)
        fixtures[path]['capabilities'][EV_KEY].append(KEY_A)
        device, abs_to_rel = self.injector._prepare_device(path)
        capabilities = self.injector._modify_capabilities({}, device,
                                                          abs_to_rel)
        self.assertIn(EV_KEY, capabilities)
        self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
        self.assertIn(evdev.ecodes.KEY_A, capabilities[EV_KEY])
        """gamepad with existing key capabilities, but not btn_mouse"""

        path = '/dev/input/event30'
        device, abs_to_rel = self.injector._prepare_device(path)
        self.assertIn(EV_KEY, device.capabilities())
        self.assertNotIn(evdev.ecodes.BTN_MOUSE, device.capabilities()[EV_KEY])
        capabilities = self.injector._modify_capabilities({}, device,
                                                          abs_to_rel)
        self.assertIn(EV_KEY, capabilities)
        self.assertGreater(len(capabilities), 1)
        self.assertIn(evdev.ecodes.BTN_MOUSE, capabilities[EV_KEY])
示例#11
0
    def test_fail_grab(self):
        self.make_it_fail = 10
        custom_mapping.change(Key(EV_KEY, 10, 1), 'a')

        self.injector = KeycodeInjector('device 1', custom_mapping)
        path = '/dev/input/event10'
        device, abs_to_rel = self.injector._prepare_device(path)
        self.assertFalse(abs_to_rel)
        self.assertGreaterEqual(self.failed, 1)
        self.assertIsNone(device)

        self.injector.start_injecting()
        # since none can be grabbed, the process will terminate. But that
        # actually takes quite some time.
        time.sleep(1.5)
        self.assertFalse(self.injector._process.is_alive())
示例#12
0
    def test_gamepad_capabilities(self):
        self.injector = KeycodeInjector('gamepad', custom_mapping)

        path = '/dev/input/event30'
        device, abs_to_rel = self.injector._prepare_device(path)
        self.assertTrue(abs_to_rel)

        capabilities = self.injector._modify_capabilities({}, device,
                                                          abs_to_rel)
        self.assertNotIn(evdev.ecodes.EV_ABS, capabilities)
        self.assertIn(evdev.ecodes.EV_REL, capabilities)

        self.assertIn(evdev.ecodes.REL_X, capabilities.get(EV_REL))
        self.assertIn(evdev.ecodes.REL_Y, capabilities.get(EV_REL))
        self.assertIn(evdev.ecodes.REL_WHEEL, capabilities.get(EV_REL))
        self.assertIn(evdev.ecodes.REL_HWHEEL, capabilities.get(EV_REL))

        self.assertIn(EV_KEY, capabilities)
        self.assertIn(evdev.ecodes.BTN_LEFT, capabilities[EV_KEY])
示例#13
0
    def test_key_to_code(self):
        mapping = Mapping()
        ev_1 = (EV_KEY, 41, 1)
        ev_2 = (EV_KEY, 42, 1)
        ev_3 = (EV_KEY, 43, 1)
        ev_4 = (EV_KEY, 44, 1)
        mapping.change(Key(ev_1), 'a')
        # a combination
        mapping.change(Key(ev_2, ev_3, ev_4), 'b')
        self.assertEqual(mapping.get_character(Key(ev_2, ev_3, ev_4)), 'b')

        system_mapping.clear()
        system_mapping._set('a', 51)
        system_mapping._set('b', 52)

        injector = KeycodeInjector('device 1', mapping)
        self.assertEqual(injector._key_to_code.get((ev_1, )), 51)
        # permutations to make matching combinations easier
        self.assertEqual(injector._key_to_code.get((ev_2, ev_3, ev_4)), 52)
        self.assertEqual(injector._key_to_code.get((ev_3, ev_2, ev_4)), 52)
        self.assertEqual(len(injector._key_to_code), 3)
示例#14
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)

        pending_events['device 2'] = [
            # should execute a macro...
            InputEvent(EV_KEY, 8, 1),
            InputEvent(EV_KEY, 9, 1),  # ...now
            InputEvent(EV_KEY, 8, 0),
            InputEvent(EV_KEY, 9, 0),
            # gamepad stuff. trigger a combination
            InputEvent(EV_ABS, ABS_HAT0X, -1),
            InputEvent(EV_ABS, ABS_HAT0X, 0),
            # just pass those over without modifying
            InputEvent(EV_KEY, 10, 1),
            InputEvent(EV_KEY, 10, 0),
            InputEvent(3124, 3564, 6542),
        ]

        self.injector = KeycodeInjector('device 2', custom_mapping)
        self.injector.start_injecting()

        uinput_write_history_pipe[0].poll(timeout=1)
        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._process.is_alive())

        numlock_after = is_numlock_on()
        self.assertEqual(numlock_before, numlock_after)
示例#15
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])