barcode_server/keyevent_reader.py
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