199 lines
6.0 KiB
Python
199 lines
6.0 KiB
Python
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
|