markusressel/barcode-server

View on GitHub
barcode_server/keyevent_reader.py

Summary

Maintainability
C
1 day
Test Coverage
import logging

from evdev import KeyEvent, InputDevice, categorize

LOGGER = logging.getLogger(__name__)

US_EN_UPPER_DICT = {
    "`": "~",
    "1": "!",
    "2": "@",
    "3": "#",
    "4": "$",
    "5": "%",
    "6": "^",
    "7": "&",
    "8": "*",
    "9": "(",
    "0": ")",
    "-": "_",
    "=": "+",
    ",": "<",
    ".": ">",
    "/": "?",
    ";": ":",
    "'": "\"",
    "\\": "|",
    "[": "{",
    "]": "}"
}


class KeyEventReader:
    """
    Class used to convert a sequence of KeyEvents to text
    """

    def __init__(self):
        self._shift = False
        self._caps = False
        self._alt = False
        self._unicode_number_input_buffer = ""

        self._line = ""

    def read_line(self, input_device: InputDevice) -> str:
        """
        Reads a line
        :param input_device: the device to read from
        :return: line
        """
        self._line = ""
        # While there is a function called async_read_loop, it tends
        # to skip input events, so we use the non-async read-loop here.

        # async for event in input_device.async_read_loop():
        for event in input_device.read_loop():
            try:
                event = categorize(event)

                if hasattr(event, "event"):
                    if not hasattr(event, "keystate") and hasattr(event.event, "keystate"):
                        event.keystate = event.event.keystate

                if not hasattr(event, "keystate") or not hasattr(event, "keycode"):
                    continue

                keycode = event.keycode
                keystate = event.keystate

                if isinstance(event, KeyEvent):
                    if self._on_key_event(keycode, keystate):
                        return self._line
                elif hasattr(event, "event") and event.event.type == 1:
                    if self._on_key_event(keycode, keystate):
                        return self._line
            except Exception as ex:
                LOGGER.exception(ex)

    def _on_key_event(self, code: str, state: int) -> bool:
        if code in ["KEY_ENTER", "KEY_KPENTER"]:
            if state == KeyEvent.key_up:
                # line is finished
                self._reset_modifiers()
                return True
        elif code in ["KEY_RIGHTSHIFT", "KEY_LEFTSHIFT"]:
            if state in [KeyEvent.key_down, KeyEvent.key_hold]:
                self._shift = True
            else:
                self._shift = False
        elif code in ["KEY_LEFTALT", "KEY_RIGHTALT"]:
            if state in [KeyEvent.key_down, KeyEvent.key_hold]:
                self._alt = True
            else:
                self._alt = False

                character = self._unicode_numbers_to_character(self._unicode_number_input_buffer)
                self._unicode_number_input_buffer = ""

                if character is not None:
                    self._line += character

        elif code == "KEY_BACKSPACE":
            self._line = self._line[:-1]
        elif state == KeyEvent.key_down:
            character = self._code_to_character(code)
            if self._alt:
                self._unicode_number_input_buffer += character
            else:
                if character is not None and not self._alt:
                    # append the current character
                    self._line += character

        return False

    def _code_to_character(self, code: str) -> chr or None:
        character = None

        if len(code) == 5:
            character = code[-1]
        elif code.startswith("KEY_KP") and len(code) == 7:
            character = code[-1]

        elif code in ["KEY_DOWN"]:
            character = '\n'
        elif code in ["KEY_SPACE"]:
            character = ' '
        elif code in ["KEY_ASTERISK", "KEY_KPASTERISK"]:
            character = '*'
        elif code in ["KEY_MINUS", "KEY_KPMINUS"]:
            character = '-'
        elif code in ["KEY_PLUS", "KEY_KPPLUS"]:
            character = '+'
        elif code in ["KEY_QUESTION"]:
            character = '?'
        elif code in ["KEY_COMMA", "KEY_KPCOMMA"]:
            character = ','
        elif code in ["KEY_DOT", "KEY_KPDOT"]:
            character = '.'
        elif code in ["KEY_EQUAL", "KEY_KPEQUAL"]:
            character = '='
        elif code in ["KEY_LEFTPAREN", "KEY_KPLEFTPAREN"]:
            character = '('
        elif code in ["KEY_PLUSMINUS", "KEY_KPPLUSMINUS"]:
            character = '+-'
        elif code in ["KEY_RIGHTPAREN", "KEY_KPRIGHTPAREN"]:
            character = ')'
        elif code in ["KEY_RIGHTBRACE"]:
            character = ']'
        elif code in ["KEY_LEFTBRACE"]:
            character = '['
        elif code in ["KEY_SLASH", "KEY_KPSLASH"]:
            character = '/'
        elif code in ["KEY_BACKSLASH"]:
            character = '\\'
        elif code in ["KEY_COLON"]:
            character = ';'
        elif code in ["KEY_SEMICOLON"]:
            character = ';'
        elif code in ["KEY_APOSTROPHE"]:
            character = '\''
        elif code in ["KEY_GRAVE"]:
            character = '`'

        if character is None:
            character = code[4:]
            if len(character) > 1:
                LOGGER.warning(f"Unhandled Keycode: {code}")

        if self._shift or self._caps:
            character = character.upper()
            if character in US_EN_UPPER_DICT.keys():
                character = US_EN_UPPER_DICT[character]
        else:
            character = character.lower()

        return character

    @staticmethod
    def _unicode_numbers_to_character(code: str) -> chr or None:
        if code is None or len(code) <= 0:
            return None

        try:
            # convert to hex
            i = int(code)
            h = hex(i)
            s = f"{h}"

            return bytearray.fromhex(s[2:]).decode('utf-8')
        except Exception as ex:
            LOGGER.exception(ex)
            return None

    def _reset_modifiers(self):
        self._alt = False
        self._unicode_number_input_buffer = ""
        self._shift = False
        self._caps = False