Exemplo n.º 1
0
    async def _event_consumer(self, source, forward_to):
        """Reads input events to inject keycodes or talk to the event_producer.

        Can be stopped by stopping the asyncio loop. This loop
        reads events from a single device only. Other devnodes may be
        present for the hardware device, in which case this needs to be
        started multiple times.

        Parameters
        ----------
        source : evdev.InputDevice
            where to read keycodes from
        forward_to : evdev.UInput
            where to write keycodes to that were not mapped to anything.
            Should be an UInput with capabilities that work for all forwarded
            events, so ideally they should be copied from source.
        """
        logger.debug(
            'Started consumer to inject to %s, fd %s',
            source.path, source.fd
        )

        gamepad = classify(source) == GAMEPAD

        keycode_handler = KeycodeMapper(self.context, source, forward_to)

        async for event in source.async_read_loop():
            if self._event_producer.is_handled(event):
                # the event_producer will take care of it
                self._event_producer.notify(event)
                continue

            # for mapped stuff
            if utils.should_map_as_btn(event, self.context.mapping, gamepad):
                will_report_key_up = utils.will_report_key_up(event)

                keycode_handler.handle_keycode(event)

                if not will_report_key_up:
                    # simulate a key-up event if no down event arrives anymore.
                    # this may release macros, combinations or keycodes.
                    release = evdev.InputEvent(0, 0, event.type, event.code, 0)
                    self._event_producer.debounce(
                        debounce_id=(event.type, event.code, event.value),
                        func=keycode_handler.handle_keycode,
                        args=(release, False),
                        ticks=3,
                    )

                continue

            # forward the rest
            forward_to.write(event.type, event.code, event.value)
            # this already includes SYN events, so need to syn here again

        # This happens all the time in tests because the async_read_loop
        # stops when there is nothing to read anymore. Otherwise tests
        # would block.
        logger.error('The consumer for "%s" stopped early', source.path)
Exemplo n.º 2
0
 def do(gamepad, event):
     return utils.should_map_as_btn(event, mapping, gamepad)
Exemplo n.º 3
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