diff --git a/plover_machine_hid.py b/plover_machine_hid.py index 8d6ffec..c63ce54 100644 --- a/plover_machine_hid.py +++ b/plover_machine_hid.py @@ -10,6 +10,7 @@ The order of the buttons (from left to right) is the same as in `KEYS_LAYOUT`. Most buttons have the same names as in GeminiPR, except for the extra buttons which are called X1-X26. ''' +from plover.engine import StenoEngine from plover.machine.base import ThreadedStenotypeBase from plover import log @@ -18,6 +19,7 @@ import hid import platform import threading import time +from typing import Optional # This is a hack to not open the hid device in exclusive mode on # darwin, if the version of hidapi installed is current enough @@ -27,7 +29,7 @@ if platform.system() == "Darwin": hid.hidapi.hid_darwin_set_open_exclusive.argtypes = (ctypes.c_int, ) hid.hidapi.hid_darwin_set_open_exclusive.restype = None hid.hidapi.hid_darwin_set_open_exclusive(0) - except AttributeError as e: + except AttributeError: log.error("hidapi < 0.12 in use, plover-hid will not work correctly") USAGE_PAGE: int = 0xFF60 @@ -40,12 +42,35 @@ N_LEVERS: int = 80 SIMPLE_REPORT_TYPE: int = 0x62 SIMPLE_REPORT_LEN: int = 32 + class InvalidReport(Exception): pass + STENO_KEY_CHART = tuple(f"X{i}" for i in range(1, N_LEVERS + 1)) print('steno key chart', len(STENO_KEY_CHART)) + + +class UnicodeSender: + def __init__(self, engine: StenoEngine): + self._engine = engine + + def start(self): + global unicode_sender + unicode_sender = self + + def stop(self): + global unicode_sender + unicode_sender = None + + def send_string(self, string: str): + self._engine.keyboard_emulation.send_string(string) + + +unicode_sender: Optional[UnicodeSender] = None + + class HidMachine(ThreadedStenotypeBase): KEYS_LAYOUT: str = ''' X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 @@ -57,17 +82,26 @@ class HidMachine(ThreadedStenotypeBase): X61 X62 X63 X64 X65 X66 X67 X68 X69 X70 X71 X72 X73 X74 X75 X76 X77 X78 X79 X80 ''' + def __init__(self, params): super().__init__() self._params = params self._hid = None - def _parse(self, report): + def _parse(self, report: bytes) -> Optional[BitString]: if len(report) != SIMPLE_REPORT_LEN: raise InvalidReport() - if report[:3] != b"STN": + if report[:3] == b"STN": + return BitString(bytes=report[3:13]) + elif report[:3] == b"UNI": + try: + s = report[3:].decode("UTF-8").strip('\0') + if unicode_sender is not None: + unicode_sender.send_string(s) + except UnicodeDecodeError: + raise InvalidReport() + else: raise InvalidReport() - return BitString(bytes=report[3:13]) def _ping_thread(self): while not self.finished.wait(0): @@ -89,6 +123,8 @@ class HidMachine(ThreadedStenotypeBase): continue try: report = self._parse(report) + if report is None: + continue except InvalidReport: continue keystate |= report @@ -109,14 +145,15 @@ class HidMachine(ThreadedStenotypeBase): devices = [ device["path"] for device in hid.enumerate() - if device["usage_page"] == USAGE_PAGE and device["usage"] == USAGE + if device["usage_page"] == USAGE_PAGE + and device["usage"] == USAGE ] if not devices: self._error() return # FIXME: if multiple compatible devices are found we should either - # let the end user configure which one they want, or support reading - # from all connected plover hid devices at the same time. + # let the end user configure which one they want, or support + # reading from all connected plover hid devices at the same time. self._hid = hid.Device(path=devices[0]) except hid.HIDException: self._error() diff --git a/setup.cfg b/setup.cfg index a87a26f..129b1d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,3 +16,4 @@ py_modules = [options.entry_points] plover.machine = Plover HID = plover_machine_hid:HidMachine +plover.extension = plover_machine_hid:UnicodeSender \ No newline at end of file