import ctypes import ctypes.util import operator import os from enum import auto, IntEnum, IntFlag from ctypes import (c_uint8, c_uint16, c_uint32, c_size_t, c_void_p, c_char_p, Structure, Union, POINTER, CFUNCTYPE) from typing import Any, Tuple CP = c_uint16 USV = c_uint32 VirtualKey = c_uint16 libkbp = ctypes.cdll.LoadLibrary(ctypes.util.find_library('kmnkbp0')) # Error handling # ============== class StatusCode(IntEnum): OK = 0 NO_MEM = auto() IO_ERROR = auto() INVALID_ARGUMENT = auto() KEY_ERROR = auto() OS_ERROR = 0x80000000 Status = c_uint32 def __map_oserror(code: Status) -> str: code = StatusCode.OS_ERROR ^ code msg = '{0!s}: OS error code (' + code + '): ' + os.strerror(code) return msg, lambda m: OSError(code, m) __exceptions_map = [ (None, '{0!s}: Success'), (MemoryError, '{0!s}: memory allocation failed in: {1!s}'), (RuntimeError, '{0!s}: IO Error: {2!s}'), (ValueError, '{0!s}: Invalid argument passed to: {1!s}'), (LookupError, '{0!s}: Item does not exist in: {2!s}'), (OSError, __map_oserror)] def status_code(code: Status, func, args): if code == StatusCode.OK: return args exc, msg = __exceptions_map[code] if callable(msg): msg = msg(code) raise exc(msg.format(libkbp._name, func._name_, *args)) def null_check(code, func, args): if code is not None: return args raise KeyError(args[1] + ': Not found in collection') class Dir(IntEnum): IN = auto() OUT = auto() OPT = auto() def __method(iface: str, method: str, result, *args: Tuple[Any, Dir, str], **kwds): proto = CFUNCTYPE(result, *map(operator.itemgetter(0), args)) params = [a[1:] for a in args] c_name = iface+'_'+method if iface else method f = proto(('km_kbp_' + c_name, libkbp), params) if 'errcheck' in kwds: f.errcheck = kwds.get('errcheck') globals()[c_name] = f # Context processing # ================== Context_p = c_void_p class ContextType(IntEnum): END = 0 CHAR = auto() MARKER = auto() class ContextItem(Structure): class __ContextValue(Union): _fields_ = (('character', c_char_p), ('marker', c_char_p)) _anonymous_ = ('value',) _fields_ = (('type', c_uint8), ('value', __ContextValue)) __method('context_items', 'from_utf16', Status, (POINTER(CP), Dir.IN, 'text'), (POINTER(ContextItem), Dir.OUT, 'context_items'), errcheck=status_code) __method('context_items', 'to_utf16', c_size_t, (POINTER(ContextItem), Dir.IN, 'context_items'), (POINTER(CP), Dir.OUT | Dir.OPT, 'buffer'), (c_size_t, Dir.OPT, 'buffer_size')) __method('context_items', 'dispose', None, (POINTER(ContextItem), Dir.IN, 'context_items')) __method('context', 'set', Status, (Context_p, Dir.IN, 'context'), (POINTER(ContextItem), Dir.IN, 'context_items'), errcheck=status_code) __method('context', 'get', POINTER(ContextItem), (Context_p, Dir.IN, 'context')) __method('context', 'clear', None, (Context_p, Dir.IN, 'context')) __method('context', 'length', c_size_t, (Context_p, Dir.IN, 'context')) __method('context', 'append', None, (Context_p, Dir.IN, 'context'), (POINTER(ContextItem), Dir.IN, 'context_items')) __method('context', 'shrink', None, (Context_p, Dir.IN, 'context'), (c_size_t, Dir.IN, 'num'), (POINTER(ContextItem), Dir.IN, 'prefix')) class ActionItem(Structure): class __ActionItem(Union): _fields_ = (('marker', c_size_t), ('option', c_char_p), ('character', USV), ('vkey', VirtualKey)) _anonymous_ = ('data',) _fields_ = (('type', c_uint8), ('reserved', c_uint8*3), ('data', __ActionItem)) class ActionType(IntEnum): END = 0 # Marks end of action items list. CHAR = 1 MARKER = 2 # correlates to kmn's "deadkey" markers ALERT = 3 BACK = 4 PERSIST_OPT = 5 RESET_OPT = 6 VKEYDOWN = 7 VKEYUP = 8 VSHIFTDOWN = 9 VSHIFTUP = 10 MAX_TYPE_ID = auto() # Option processing # ================= OptionSet_p = c_void_p class OptionScope(IntEnum): UNKNOWN = auto() KEYBOARD = auto() ENVIRONMENT = auto() class Option(Structure): _fields_ = (('key', c_char_p), ('value', c_char_p), ('scope', c_uint8)) Option.END = Option(None, None) __method('option_set', 'length', c_size_t, (OptionSet_p, Dir.IN, 'opts')) __method('option_set', 'lookup', POINTER(Option), (OptionSet_p, Dir.IN, 'opts'), (c_char_p, Dir.IN, 'key'), errcheck=null_check) __method('option_set', 'update', Status, (OptionSet_p, Dir.IN, 'opts'), (POINTER(Option), Dir.IN, 'new_opts'), errcheck=status_code) __method('option_set', 'to_json', Status, (OptionSet_p, Dir.IN, 'opts'), (c_char_p, Dir.OUT | Dir.OPT, 'buffer'), (c_size_t, Dir.IN | Dir.OUT, 'space'), errcheck=status_code) # Keyboards # ========= Keyboard_p = c_void_p class KeyboardAttrs(Structure): _fields_ = (('version_string', c_char_p), ('id', c_char_p), ('folder_path', c_char_p), ('default_options', OptionSet_p)) __method('keyboard', 'load', Status, (c_char_p, Dir.IN, 'path'), (POINTER(Keyboard_p), Dir.OUT, 'kb'), errcheck=status_code) __method('keyboard', 'dispose', None, (Keyboard_p, Dir.IN, 'kb')) __method('keyboard', 'get_attrs', POINTER(KeyboardAttrs), (Keyboard_p, Dir.IN, 'keyboard')) # State processing # ================ State_p = c_void_p __method('state', 'create', Status, (Keyboard_p, Dir.IN, 'keyboard'), (POINTER(Option), Dir.IN, 'env',) (POINTER(State_p), Dir.OUT, 'out'), errcheck=status_code) __method('state', 'clone', Status, (State_p, Dir.IN, 'state'), (POINTER(State_p), Dir.OUT, 'out'), errcheck=status_code) __method('state', 'dispose', None, (State_p, Dir.IN, 'state')) __method('state', 'context', Context_p, (State_p, Dir.IN, 'state')) __method('state', 'options', OptionSet_p, (State_p, Dir.IN, 'state')) __method('state', 'action_items', POINTER(ActionItem), (State_p, Dir.IN, 'state'), (POINTER(c_size_t), Dir.OUT, 'num_items')) __method('state', 'to_json', Status, (State_p, Dir.IN, 'state'), (c_char_p, Dir.OUT | Dir.OPT, 'buffer'), (c_size_t, Dir.IN | Dir.OUT, 'space'), errcheck=status_code) # Processor # ========= class Attributes(Structure): _fields_ = (('max_context', c_size_t), ('current', c_uint16), ('revision', c_uint16), ('age', c_uint16), ('technology', c_uint16), ('vendor', c_char_p)) class Tech(IntFlag): UNSPECIFIED = 0 KMN = 1 LDML = 2 __method(None, 'get_engine_attrs', POINTER(Attributes)) __method(None, 'process_events', Status, (State_p, Dir.IN, 'state'), (VirtualKey, Dir.IN, 'vkey'), (c_uint16, Dir.IN, 'modifier_state')) class Modifier(IntFlag): LCTRL = 1 << 0 RCTRL = 1 << 1 LALT = 1 << 2 RALT = 1 << 3 SHIFT = 1 << 4 CTRL = 1 << 5 ALT = 1 << 6 CAPS = 1 << 7 NOCAPS = 1 << 8 NUMLOCK = 1 << 9 NONUMLOCK = 1 << 10 SCROLLOCK = 1 << 11 NOSCROLLOCK = 1 << 12 VIRTUALKEY = 1 << 13 class ModifierMask(IntFlag): ALL = 0x7f ALT_GR_SIM = Modifier.LCTRL | Modifier.LALT CHIRAL = 0x1f IS_CHIRAL = 0x0f NON_CHIRAL = 0x7f CAPS = 0x0300 NUMLOCK = 0x0C00 SCROLLLOCK = 0x3000 class VKey(IntEnum): _00 = auto() LBUTTON = auto() RBUTTON = auto() CANCEL = auto() MBUTTON = auto() _05 = auto() _06 = auto() _07 = auto() BKSP = auto() KTAB = auto() _0A = auto() _0B = auto() KP5 = auto() ENTER = auto() _0E = auto() _0F = auto() SHIFT = auto() CONTROL = auto() ALT = auto() PAUSE = auto() CAPS = auto() _15 = auto() _16 = auto() _17 = auto() _18 = auto() _19 = auto() _1A = auto() ESC = auto() _1C = auto() _1D = auto() _1E = auto() _1F = auto() SPACE = auto() PGUP = auto() PGDN = auto() END = auto() HOME = auto() LEFT = auto() UP = auto() RIGHT = auto() DOWN = auto() SEL = auto() PRINT = auto() EXEC = auto() PRTSCN = auto() INS = auto() DEL = auto() HELP = auto() K0 = auto() K1 = auto() K2 = auto() K3 = auto() K4 = auto() K5 = auto() K6 = auto() K7 = auto() K8 = auto() K9 = auto() _3A = auto() _3B = auto() _3C = auto() _3D = auto() _3E = auto() _3F = auto() _40 = auto() KA = auto() KB = auto() KC = auto() KD = auto() KE = auto() KF = auto() KG = auto() KH = auto() KI = auto() KJ = auto() KK = auto() KL = auto() KM = auto() KN = auto() KO = auto() KP = auto() KQ = auto() KR = auto() KS = auto() KT = auto() KU = auto() KV = auto() KW = auto() KX = auto() KY = auto() KZ = auto() _5B = auto() _5C = auto() _5D = auto() _5E = auto() _5F = auto() NP0 = auto() NP1 = auto() NP2 = auto() NP3 = auto() NP4 = auto() NP5 = auto() NP6 = auto() NP7 = auto() NP8 = auto() NP9 = auto() NPSTAR = auto() NPPLUS = auto() SEPARATOR = auto() NPMINUS = auto() NPDOT = auto() NPSLASH = auto() F1 = auto() F2 = auto() F3 = auto() F4 = auto() F5 = auto() F6 = auto() F7 = auto() F8 = auto() F9 = auto() F10 = auto() F11 = auto() F12 = auto() F13 = auto() F14 = auto() F15 = auto() F16 = auto() F17 = auto() F18 = auto() F19 = auto() F20 = auto() F21 = auto() F22 = auto() F23 = auto() F24 = auto() _88 = auto() _89 = auto() _8A = auto() _8B = auto() _8C = auto() _8D = auto() _8E = auto() _8F = auto() NUMLOCK = auto() SCROLL = auto() _92 = auto() _93 = auto() _94 = auto() _95 = auto() _96 = auto() _97 = auto() _98 = auto() _99 = auto() _9A = auto() _9B = auto() _9C = auto() _9D = auto() _9E = auto() _9F = auto() _A0 = auto() _A1 = auto() _A2 = auto() _A3 = auto() _A4 = auto() _A5 = auto() _A6 = auto() _A7 = auto() _A8 = auto() _A9 = auto() _AA = auto() _AB = auto() _AC = auto() _AD = auto() _AE = auto() _AF = auto() _B0 = auto() _B1 = auto() _B2 = auto() _B3 = auto() _B4 = auto() _B5 = auto() _B6 = auto() _B7 = auto() _B8 = auto() _B9 = auto() COLON = auto() EQUAL = auto() COMMA = auto() HYPHEN = auto() PERIOD = auto() SLASH = auto() BKQUOTE = auto() _C1 = auto() _C2 = auto() _C3 = auto() _C4 = auto() _C5 = auto() _C6 = auto() _C7 = auto() _C8 = auto() _C9 = auto() _CA = auto() _CB = auto() _CC = auto() _CD = auto() _CE = auto() _CF = auto() _D0 = auto() _D1 = auto() _D2 = auto() _D3 = auto() _D4 = auto() _D5 = auto() _D6 = auto() _D7 = auto() _D8 = auto() _D9 = auto() _DA = auto() LBRKT = auto() BKSLASH = auto() RBRKT = auto() QUOTE = auto() oDF = auto() oE0 = auto() oE1 = auto() oE2 = auto() oE3 = auto() oE4 = auto() _E5 = auto() oE6 = auto() _E7 = auto() _E8 = auto() oE9 = auto() oEA = auto() oEB = auto() oEC = auto() oED = auto() oEE = auto() oEF = auto() oF0 = auto() oF1 = auto() oF2 = auto() oF3 = auto() oF4 = auto() oF5 = auto() _F6 = auto() _F7 = auto() _F8 = auto() _F9 = auto() _FA = auto() _FB = auto() _FC = auto() _FD = auto() _FE = auto() _FF = auto()