From ebbd55ee177c3315d3df113cfacfa69f4e934f50 Mon Sep 17 00:00:00 2001 From: Maxim Devaev Date: Thu, 1 May 2025 03:03:25 +0300 Subject: [PATCH] using evdev instead of string constants --- PKGBUILD | 1 + genmap.py | 3 + keymap.csv | 224 +++++----- kvmd/apps/kvmd/api/hid.py | 30 +- kvmd/apps/kvmd/snapshoter.py | 8 +- kvmd/apps/vnc/rfb/__init__.py | 26 +- kvmd/apps/vnc/server.py | 76 ++-- kvmd/clients/kvmd.py | 18 +- kvmd/keyboard/keysym.py | 22 +- kvmd/keyboard/mappings.py | 589 +++++++++++++++----------- kvmd/keyboard/mappings.py.mako | 41 +- kvmd/keyboard/printer.py | 27 +- kvmd/mouse.py | 12 + kvmd/plugins/hid/__init__.py | 19 +- kvmd/plugins/hid/_mcu/__init__.py | 4 +- kvmd/plugins/hid/_mcu/proto.py | 27 +- kvmd/plugins/hid/bt/__init__.py | 4 +- kvmd/plugins/hid/ch9329/__init__.py | 4 +- kvmd/plugins/hid/ch9329/keyboard.py | 2 +- kvmd/plugins/hid/ch9329/mouse.py | 14 +- kvmd/plugins/hid/otg/__init__.py | 4 +- kvmd/plugins/hid/otg/events.py | 24 +- kvmd/plugins/hid/otg/keyboard.py | 2 +- kvmd/plugins/hid/otg/mouse.py | 2 +- kvmd/validators/hid.py | 7 +- testenv/Dockerfile | 1 + testenv/linters/vulture-wl.py | 1 + testenv/tests/keyboard/test_keymap.py | 35 -- testenv/tests/validators/test_hid.py | 4 +- 29 files changed, 692 insertions(+), 539 deletions(-) delete mode 100644 testenv/tests/keyboard/test_keymap.py diff --git a/PKGBUILD b/PKGBUILD index cfe0b258..20f7018c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -82,6 +82,7 @@ depends=( python-luma-oled python-pyusb python-pyudev + python-evdev "libgpiod>=2.1" freetype2 "v4l-utils>=1.22.1-1" diff --git a/genmap.py b/genmap.py index 61952c9e..6ddc3fc0 100755 --- a/genmap.py +++ b/genmap.py @@ -69,6 +69,7 @@ class _X11Key: @dataclasses.dataclass(frozen=True) class _KeyMapping: web_name: str + evdev_name: str mcu_code: int usb_key: _UsbKey ps2_key: _Ps2Key @@ -122,6 +123,7 @@ def _read_keymap_csv(path: str) -> list[_KeyMapping]: if len(row) >= 6: keymap.append(_KeyMapping( web_name=row["web_name"], + evdev_name=row["evdev_name"], mcu_code=int(row["mcu_code"]), usb_key=_parse_usb_key(row["usb_key"]), ps2_key=_parse_ps2_key(row["ps2_key"]), @@ -150,6 +152,7 @@ def main() -> None: # Fields list: # - Web + # - Linux/evdev # - MCU code # - USB code (^ for the modifier mask) # - PS/2 key diff --git a/keymap.csv b/keymap.csv index daf33c24..3691d9ca 100644 --- a/keymap.csv +++ b/keymap.csv @@ -1,112 +1,112 @@ -web_name,mcu_code,usb_key,ps2_key,at1_code,x11_names -KeyA,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a" -KeyB,2,0x05,reg:0x32,0x30,"^XK_B,XK_b" -KeyC,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c" -KeyD,4,0x07,reg:0x23,0x20,"^XK_D,XK_d" -KeyE,5,0x08,reg:0x24,0x12,"^XK_E,XK_e" -KeyF,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f" -KeyG,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g" -KeyH,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h" -KeyI,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i" -KeyJ,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j" -KeyK,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k" -KeyL,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l" -KeyM,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m" -KeyN,14,0x11,reg:0x31,0x31,"^XK_N,XK_n" -KeyO,15,0x12,reg:0x44,0x18,"^XK_O,XK_o" -KeyP,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p" -KeyQ,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q" -KeyR,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r" -KeyS,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s" -KeyT,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t" -KeyU,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u" -KeyV,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v" -KeyW,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w" -KeyX,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x" -KeyY,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y" -KeyZ,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z" -Digit1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam" -Digit2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at" -Digit3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign" -Digit4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar" -Digit5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent" -Digit6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum" -Digit7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand" -Digit8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk" -Digit9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft" -Digit0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright" -Enter,37,0x28,reg:0x5a,0x1c,XK_Return -Escape,38,0x29,reg:0x76,0x01,XK_Escape -Backspace,39,0x2a,reg:0x66,0x0e,XK_BackSpace -Tab,40,0x2b,reg:0x0d,0x0f,XK_Tab -Space,41,0x2c,reg:0x29,0x39,XK_space -Minus,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore" -Equal,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus" -BracketLeft,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft" -BracketRight,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright" -Backslash,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar" -Semicolon,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon" -Quote,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl" -Backquote,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde" -Comma,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less" -Period,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater" -Slash,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question" -CapsLock,53,0x39,reg:0x58,0x3a,XK_Caps_Lock -F1,54,0x3a,reg:0x05,0x3b,XK_F1 -F2,55,0x3b,reg:0x06,0x3c,XK_F2 -F3,56,0x3c,reg:0x04,0x3d,XK_F3 -F4,57,0x3d,reg:0x0c,0x3e,XK_F4 -F5,58,0x3e,reg:0x03,0x3f,XK_F5 -F6,59,0x3f,reg:0x0b,0x40,XK_F6 -F7,60,0x40,reg:0x83,0x41,XK_F7 -F8,61,0x41,reg:0x0a,0x42,XK_F8 -F9,62,0x42,reg:0x01,0x43,XK_F9 -F10,63,0x43,reg:0x09,0x44,XK_F10 -F11,64,0x44,reg:0x78,0x57,XK_F11 -F12,65,0x45,reg:0x07,0x58,XK_F12 -PrintScreen,66,0x46,print:0xff,0x54,XK_Sys_Req -Insert,67,0x49,spec:0x70,0xe052,XK_Insert -Home,68,0x4a,spec:0x6c,0xe047,XK_Home -PageUp,69,0x4b,spec:0x7d,0xe049,XK_Page_Up -Delete,70,0x4c,spec:0x71,0xe053,XK_Delete -End,71,0x4d,spec:0x69,0xe04f,XK_End -PageDown,72,0x4e,spec:0x7a,0xe051,XK_Page_Down -ArrowRight,73,0x4f,spec:0x74,0xe04d,XK_Right -ArrowLeft,74,0x50,spec:0x6b,0xe04b,XK_Left -ArrowDown,75,0x51,spec:0x72,0xe050,XK_Down -ArrowUp,76,0x52,spec:0x75,0xe048,XK_Up -ControlLeft,77,^0x01,reg:0x14,0x1d,XK_Control_L -ShiftLeft,78,^0x02,reg:0x12,0x2a,XK_Shift_L -AltLeft,79,^0x04,reg:0x11,0x38,XK_Alt_L -MetaLeft,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L" -ControlRight,81,^0x10,spec:0x14,0xe01d,XK_Control_R -ShiftRight,82,^0x20,reg:0x59,0x36,XK_Shift_R -AltRight,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift" -MetaRight,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R" -Pause,85,0x48,pause:0xff,0xe046,XK_Pause -ScrollLock,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock -NumLock,87,0x53,reg:0x77,0x45,XK_Num_Lock -ContextMenu,88,0x65,spec:0x2f,0xe05d,XK_Menu -NumpadDivide,89,0x54,spec:0x4a,0xe035,XK_KP_Divide -NumpadMultiply,90,0x55,reg:0x7c,0x37,XK_multiply -NumpadSubtract,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract -NumpadAdd,92,0x57,reg:0x79,0x4e,XK_KP_Add -NumpadEnter,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter -Numpad1,94,0x59,reg:0x69,0x4f,XK_KP_1 -Numpad2,95,0x5a,reg:0x72,0x50,XK_KP_2 -Numpad3,96,0x5b,reg:0x7a,0x51,XK_KP_3 -Numpad4,97,0x5c,reg:0x6b,0x4b,XK_KP_4 -Numpad5,98,0x5d,reg:0x73,0x4c,XK_KP_5 -Numpad6,99,0x5e,reg:0x74,0x4d,XK_KP_6 -Numpad7,100,0x5f,reg:0x6c,0x47,XK_KP_7 -Numpad8,101,0x60,reg:0x75,0x48,XK_KP_8 -Numpad9,102,0x61,reg:0x7d,0x49,XK_KP_9 -Numpad0,103,0x62,reg:0x70,0x52,XK_KP_0 -NumpadDecimal,104,0x63,reg:0x71,0x53,XK_KP_Decimal -Power,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep -IntlBackslash,106,0x64,reg:0x61,0x56,"" -IntlYen,107,0x89,reg:0x6a,0x7d,"" -IntlRo,108,0x87,reg:0x51,0x73,"" -KanaMode,109,0x88,reg:0x13,0x70,"" -Convert,110,0x8a,reg:0x64,0x79,"" -NonConvert,111,0x8b,reg:0x67,0x7b,"" +web_name,evdev_name,mcu_code,usb_key,ps2_key,at1_code,x11_names +KeyA,KEY_A,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a" +KeyB,KEY_B,2,0x05,reg:0x32,0x30,"^XK_B,XK_b" +KeyC,KEY_C,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c" +KeyD,KEY_D,4,0x07,reg:0x23,0x20,"^XK_D,XK_d" +KeyE,KEY_E,5,0x08,reg:0x24,0x12,"^XK_E,XK_e" +KeyF,KEY_F,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f" +KeyG,KEY_G,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g" +KeyH,KEY_H,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h" +KeyI,KEY_I,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i" +KeyJ,KEY_J,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j" +KeyK,KEY_K,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k" +KeyL,KEY_L,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l" +KeyM,KEY_M,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m" +KeyN,KEY_N,14,0x11,reg:0x31,0x31,"^XK_N,XK_n" +KeyO,KEY_O,15,0x12,reg:0x44,0x18,"^XK_O,XK_o" +KeyP,KEY_P,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p" +KeyQ,KEY_Q,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q" +KeyR,KEY_R,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r" +KeyS,KEY_S,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s" +KeyT,KEY_T,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t" +KeyU,KEY_U,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u" +KeyV,KEY_V,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v" +KeyW,KEY_W,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w" +KeyX,KEY_X,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x" +KeyY,KEY_Y,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y" +KeyZ,KEY_Z,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z" +Digit1,KEY_1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam" +Digit2,KEY_2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at" +Digit3,KEY_3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign" +Digit4,KEY_4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar" +Digit5,KEY_5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent" +Digit6,KEY_6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum" +Digit7,KEY_7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand" +Digit8,KEY_8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk" +Digit9,KEY_9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft" +Digit0,KEY_0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright" +Enter,KEY_ENTER,37,0x28,reg:0x5a,0x1c,XK_Return +Escape,KEY_ESC,38,0x29,reg:0x76,0x01,XK_Escape +Backspace,KEY_BACKSPACE,39,0x2a,reg:0x66,0x0e,XK_BackSpace +Tab,KEY_TAB,40,0x2b,reg:0x0d,0x0f,XK_Tab +Space,KEY_SPACE,41,0x2c,reg:0x29,0x39,XK_space +Minus,KEY_MINUS,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore" +Equal,KEY_EQUAL,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus" +BracketLeft,KEY_LEFTBRACE,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft" +BracketRight,KEY_RIGHTBRACE,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright" +Backslash,KEY_BACKSLASH,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar" +Semicolon,KEY_SEMICOLON,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon" +Quote,KEY_APOSTROPHE,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl" +Backquote,KEY_GRAVE,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde" +Comma,KEY_COMMA,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less" +Period,KEY_DOT,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater" +Slash,KEY_SLASH,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question" +CapsLock,KEY_CAPSLOCK,53,0x39,reg:0x58,0x3a,XK_Caps_Lock +F1,KEY_F1,54,0x3a,reg:0x05,0x3b,XK_F1 +F2,KEY_F2,55,0x3b,reg:0x06,0x3c,XK_F2 +F3,KEY_F3,56,0x3c,reg:0x04,0x3d,XK_F3 +F4,KEY_F4,57,0x3d,reg:0x0c,0x3e,XK_F4 +F5,KEY_F5,58,0x3e,reg:0x03,0x3f,XK_F5 +F6,KEY_F6,59,0x3f,reg:0x0b,0x40,XK_F6 +F7,KEY_F7,60,0x40,reg:0x83,0x41,XK_F7 +F8,KEY_F8,61,0x41,reg:0x0a,0x42,XK_F8 +F9,KEY_F9,62,0x42,reg:0x01,0x43,XK_F9 +F10,KEY_F10,63,0x43,reg:0x09,0x44,XK_F10 +F11,KEY_F11,64,0x44,reg:0x78,0x57,XK_F11 +F12,KEY_F12,65,0x45,reg:0x07,0x58,XK_F12 +PrintScreen,KEY_SYSRQ,66,0x46,print:0xff,0x54,XK_Sys_Req +Insert,KEY_INSERT,67,0x49,spec:0x70,0xe052,XK_Insert +Home,KEY_HOME,68,0x4a,spec:0x6c,0xe047,XK_Home +PageUp,KEY_PAGEUP,69,0x4b,spec:0x7d,0xe049,XK_Page_Up +Delete,KEY_DELETE,70,0x4c,spec:0x71,0xe053,XK_Delete +End,KEY_END,71,0x4d,spec:0x69,0xe04f,XK_End +PageDown,KEY_PAGEDOWN,72,0x4e,spec:0x7a,0xe051,XK_Page_Down +ArrowRight,KEY_RIGHT,73,0x4f,spec:0x74,0xe04d,XK_Right +ArrowLeft,KEY_LEFT,74,0x50,spec:0x6b,0xe04b,XK_Left +ArrowDown,KEY_DOWN,75,0x51,spec:0x72,0xe050,XK_Down +ArrowUp,KEY_UP,76,0x52,spec:0x75,0xe048,XK_Up +ControlLeft,KEY_LEFTCTRL,77,^0x01,reg:0x14,0x1d,XK_Control_L +ShiftLeft,KEY_LEFTSHIFT,78,^0x02,reg:0x12,0x2a,XK_Shift_L +AltLeft,KEY_LEFTALT,79,^0x04,reg:0x11,0x38,XK_Alt_L +MetaLeft,KEY_LEFTMETA,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L" +ControlRight,KEY_RIGHTCTRL,81,^0x10,spec:0x14,0xe01d,XK_Control_R +ShiftRight,KEY_RIGHTSHIFT,82,^0x20,reg:0x59,0x36,XK_Shift_R +AltRight,KEY_RIGHTALT,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift" +MetaRight,KEY_RIGHTMETA,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R" +Pause,KEY_PAUSE,85,0x48,pause:0xff,0xe046,XK_Pause +ScrollLock,KEY_SCROLLLOCK,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock +NumLock,KEY_NUMLOCK,87,0x53,reg:0x77,0x45,XK_Num_Lock +ContextMenu,KEY_CONTEXT_MENU,88,0x65,spec:0x2f,0xe05d,XK_Menu +NumpadDivide,KEY_KPSLASH,89,0x54,spec:0x4a,0xe035,XK_KP_Divide +NumpadMultiply,KEY_KPASTERISK,90,0x55,reg:0x7c,0x37,XK_multiply +NumpadSubtract,KEY_KPMINUS,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract +NumpadAdd,KEY_KPPLUS,92,0x57,reg:0x79,0x4e,XK_KP_Add +NumpadEnter,KEY_KPENTER,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter +Numpad1,KEY_KP1,94,0x59,reg:0x69,0x4f,XK_KP_1 +Numpad2,KEY_KP2,95,0x5a,reg:0x72,0x50,XK_KP_2 +Numpad3,KEY_KP3,96,0x5b,reg:0x7a,0x51,XK_KP_3 +Numpad4,KEY_KP4,97,0x5c,reg:0x6b,0x4b,XK_KP_4 +Numpad5,KEY_KP5,98,0x5d,reg:0x73,0x4c,XK_KP_5 +Numpad6,KEY_KP6,99,0x5e,reg:0x74,0x4d,XK_KP_6 +Numpad7,KEY_KP7,100,0x5f,reg:0x6c,0x47,XK_KP_7 +Numpad8,KEY_KP8,101,0x60,reg:0x75,0x48,XK_KP_8 +Numpad9,KEY_KP9,102,0x61,reg:0x7d,0x49,XK_KP_9 +Numpad0,KEY_KP0,103,0x62,reg:0x70,0x52,XK_KP_0 +NumpadDecimal,KEY_KPDOT,104,0x63,reg:0x71,0x53,XK_KP_Decimal +Power,KEY_POWER,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep +IntlBackslash,KEY_102ND,106,0x64,reg:0x61,0x56, +IntlYen,KEY_YEN,107,0x89,reg:0x6a,0x7d, +IntlRo,KEY_RO,108,0x87,reg:0x51,0x73, +KanaMode,KEY_KATAKANA,109,0x88,reg:0x13,0x70, +Convert,KEY_HENKAN,110,0x8a,reg:0x64,0x79, +NonConvert,KEY_MUHENKAN,111,0x8b,reg:0x67,0x7b, diff --git a/kvmd/apps/kvmd/api/hid.py b/kvmd/apps/kvmd/api/hid.py index 98b96313..d67fd0ff 100644 --- a/kvmd/apps/kvmd/api/hid.py +++ b/kvmd/apps/kvmd/api/hid.py @@ -31,8 +31,11 @@ from typing import Callable from aiohttp.web import Request from aiohttp.web import Response +from ....keyboard.mappings import WEB_TO_EVDEV from ....keyboard.keysym import build_symmap -from ....keyboard.printer import text_to_web_keys +from ....keyboard.printer import text_to_evdev_keys + +from ....mouse import MOUSE_TO_EVDEV from ....htserver import exposed_http from ....htserver import exposed_ws @@ -124,10 +127,10 @@ class HidApi: text = text[:limit] symmap = self.__ensure_symmap(req.query.get("keymap", self.__default_keymap_name)) slow = valid_bool(req.query.get("slow", False)) - await self.__hid.send_key_events(text_to_web_keys(text, symmap), no_ignore_keys=True, slow=slow) + await self.__hid.send_key_events(text_to_evdev_keys(text, symmap), no_ignore_keys=True, slow=slow) return make_json_response() - def __ensure_symmap(self, keymap_name: str) -> dict[int, dict[int, str]]: + def __ensure_symmap(self, keymap_name: str) -> dict[int, dict[int, int]]: keymap_name = valid_printable_filename(keymap_name, "keymap") path = os.path.join(self.__keymaps_dir_path, keymap_name) try: @@ -139,7 +142,7 @@ class HidApi: return self.__inner_ensure_symmap(path, st.st_mtime) @functools.lru_cache(maxsize=10) - def __inner_ensure_symmap(self, path: str, mod_ts: int) -> dict[int, dict[int, str]]: + def __inner_ensure_symmap(self, path: str, mod_ts: int) -> dict[int, dict[int, int]]: _ = mod_ts # For LRU return build_symmap(path) @@ -148,9 +151,12 @@ class HidApi: @exposed_ws(1) async def __ws_bin_key_handler(self, _: WsSession, data: bytes) -> None: try: - key = valid_hid_key(data[1:].decode("ascii")) state = bool(data[0] & 0b01) finish = bool(data[0] & 0b10) + if data[0] & 0b10000000: + key = struct.unpack(">H", data[1:])[0] + else: + key = WEB_TO_EVDEV[valid_hid_key(data[1:33].decode("ascii"))] except Exception: return self.__hid.send_key_event(key, state, finish) @@ -158,7 +164,11 @@ class HidApi: @exposed_ws(2) async def __ws_bin_mouse_button_handler(self, _: WsSession, data: bytes) -> None: try: - button = valid_hid_mouse_button(data[1:].decode("ascii")) + state = bool(data[0] & 0b01) + if data[0] & 0b10000000: + button = struct.unpack(">H", data[1:])[0] + else: + button = MOUSE_TO_EVDEV[valid_hid_mouse_button(data[1:33].decode("ascii"))] state = bool(data[0] & 0b01) except Exception: return @@ -199,7 +209,7 @@ class HidApi: @exposed_ws("key") async def __ws_key_handler(self, _: WsSession, event: dict) -> None: try: - key = valid_hid_key(event["key"]) + key = WEB_TO_EVDEV[valid_hid_key(event["key"])] state = valid_bool(event["state"]) finish = valid_bool(event.get("finish", False)) except Exception: @@ -209,7 +219,7 @@ class HidApi: @exposed_ws("mouse_button") async def __ws_mouse_button_handler(self, _: WsSession, event: dict) -> None: try: - button = valid_hid_mouse_button(event["button"]) + button = MOUSE_TO_EVDEV[valid_hid_mouse_button(event["button"])] state = valid_bool(event["state"]) except Exception: return @@ -248,7 +258,7 @@ class HidApi: @exposed_http("POST", "/hid/events/send_key") async def __events_send_key_handler(self, req: Request) -> Response: - key = valid_hid_key(req.query.get("key")) + key = WEB_TO_EVDEV[valid_hid_key(req.query.get("key"))] if "state" in req.query: state = valid_bool(req.query["state"]) finish = valid_bool(req.query.get("finish", False)) @@ -259,7 +269,7 @@ class HidApi: @exposed_http("POST", "/hid/events/send_mouse_button") async def __events_send_mouse_button_handler(self, req: Request) -> Response: - button = valid_hid_mouse_button(req.query.get("button")) + button = MOUSE_TO_EVDEV[valid_hid_mouse_button(req.query.get("button"))] if "state" in req.query: state = valid_bool(req.query["state"]) self.__hid.send_mouse_button_event(button, state) diff --git a/kvmd/apps/kvmd/snapshoter.py b/kvmd/apps/kvmd/snapshoter.py index e9391306..3799b281 100644 --- a/kvmd/apps/kvmd/snapshoter.py +++ b/kvmd/apps/kvmd/snapshoter.py @@ -31,6 +31,8 @@ from ... import aiotools from ...plugins.hid import BaseHid +from ...keyboard.mappings import WEB_TO_EVDEV + from .streamer import Streamer @@ -63,7 +65,7 @@ class Snapshoter: # pylint: disable=too-many-instance-attributes else: self.__idle_interval = self.__live_interval = 0.0 - self.__wakeup_key = wakeup_key + self.__wakeup_key = WEB_TO_EVDEV.get(wakeup_key, 0) self.__wakeup_move = wakeup_move self.__online_delay = online_delay @@ -121,8 +123,8 @@ class Snapshoter: # pylint: disable=too-many-instance-attributes async def __wakeup(self) -> None: logger = get_logger(0) - if self.__wakeup_key: - logger.info("Waking up using key %r ...", self.__wakeup_key) + if self.__wakeup_key > 0: + logger.info("Waking up using keyboard ...") await self.__hid.send_key_events( keys=[(self.__wakeup_key, True), (self.__wakeup_key, False)], no_ignore_keys=True, diff --git a/kvmd/apps/vnc/rfb/__init__.py b/kvmd/apps/vnc/rfb/__init__.py index 5966243f..64350341 100644 --- a/kvmd/apps/vnc/rfb/__init__.py +++ b/kvmd/apps/vnc/rfb/__init__.py @@ -159,7 +159,7 @@ class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attribute async def _on_ext_key_event(self, code: int, state: bool) -> None: raise NotImplementedError - async def _on_pointer_event(self, buttons: dict[str, bool], wheel: dict[str, int], move: dict[str, int]) -> None: + async def _on_pointer_event(self, buttons: dict[str, bool], wheel: tuple[int, int], move: tuple[int, int]) -> None: raise NotImplementedError async def _on_cut_event(self, text: str) -> None: @@ -498,20 +498,20 @@ class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attribute sr = self.__scroll_rate await self._on_pointer_event( buttons={ - "left": bool(buttons & 0x1), - "right": bool(buttons & 0x4), + "left": bool(buttons & 0x1), + "right": bool(buttons & 0x4), "middle": bool(buttons & 0x2), - "up": bool(ext_buttons & 0x2), - "down": bool(ext_buttons & 0x1), - }, - wheel={ - "x": (-sr if buttons & 0x40 else (sr if buttons & 0x20 else 0)), - "y": (-sr if buttons & 0x10 else (sr if buttons & 0x8 else 0)), - }, - move={ - "x": tools.remap(to_x, 0, self._width, *MouseRange.RANGE), - "y": tools.remap(to_y, 0, self._height, *MouseRange.RANGE), + "up": bool(ext_buttons & 0x2), + "down": bool(ext_buttons & 0x1), }, + wheel=( + (-sr if buttons & 0x40 else (sr if buttons & 0x20 else 0)), + (-sr if buttons & 0x10 else (sr if buttons & 0x8 else 0)), + ), + move=( + tools.remap(to_x, 0, self._width, *MouseRange.RANGE), + tools.remap(to_y, 0, self._height, *MouseRange.RANGE), + ), ) async def __handle_client_cut_text(self) -> None: diff --git a/kvmd/apps/vnc/server.py b/kvmd/apps/vnc/server.py index d7757ea0..d330a4ed 100644 --- a/kvmd/apps/vnc/server.py +++ b/kvmd/apps/vnc/server.py @@ -32,9 +32,11 @@ from ...logging import get_logger from ...keyboard.keysym import SymmapModifiers from ...keyboard.keysym import build_symmap -from ...keyboard.mappings import WebModifiers +from ...keyboard.mappings import EvdevModifiers from ...keyboard.mappings import X11Modifiers -from ...keyboard.mappings import AT1_TO_WEB +from ...keyboard.mappings import AT1_TO_EVDEV + +from ...mouse import MOUSE_TO_EVDEV from ...clients.kvmd import KvmdClientWs from ...clients.kvmd import KvmdClientSession @@ -80,7 +82,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes desired_fps: int, mouse_output: str, keymap_name: str, - symmap: dict[int, dict[int, str]], + symmap: dict[int, dict[int, int]], scroll_rate: int, allow_cut_after: float, @@ -132,8 +134,8 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes # Эти состояния шарить не обязательно - бекенд исключает дублирующиеся события. # Все это нужно только чтобы не посылать лишние жсоны в сокет KVMD - self.__mouse_buttons: dict[str, (bool | None)] = dict.fromkeys(["left", "right", "middle", "up", "down"], None) - self.__mouse_move = {"x": -1, "y": -1} + self.__mouse_buttons: dict[str, (bool | None)] = dict.fromkeys(MOUSE_TO_EVDEV, None) + self.__mouse_move = (-1, -1) # (X, Y) self.__modifiers = 0 @@ -337,45 +339,45 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes # ===== async def _on_key_event(self, code: int, state: bool) -> None: - is_modifier = self.__switch_modifiers(code, state) + is_modifier = self.__switch_modifiers_x11(code, state) variants = self.__symmap.get(code) fake_shift = False if variants: if is_modifier: - web_key = variants.get(0) + key = variants.get(0) else: - web_key = variants.get(self.__modifiers) - if web_key is None: - web_key = variants.get(0) + key = variants.get(self.__modifiers) + if key is None: + key = variants.get(0) - if web_key is None and self.__modifiers == 0 and SymmapModifiers.SHIFT in variants: + if key is None and self.__modifiers == 0 and SymmapModifiers.SHIFT in variants: # JUMP doesn't send shift events: # - https://github.com/pikvm/pikvm/issues/820 - web_key = variants[SymmapModifiers.SHIFT] + key = variants[SymmapModifiers.SHIFT] fake_shift = True - if web_key and self.__kvmd_ws: + if key and self.__kvmd_ws: if fake_shift: - await self.__kvmd_ws.send_key_event(WebModifiers.SHIFT_LEFT, True) - await self.__kvmd_ws.send_key_event(web_key, state) + await self.__kvmd_ws.send_key_event(EvdevModifiers.SHIFT_LEFT, True) + await self.__kvmd_ws.send_key_event(key, state) if fake_shift: - await self.__kvmd_ws.send_key_event(WebModifiers.SHIFT_LEFT, False) + await self.__kvmd_ws.send_key_event(EvdevModifiers.SHIFT_LEFT, False) async def _on_ext_key_event(self, code: int, state: bool) -> None: - web_key = AT1_TO_WEB.get(code) - if web_key: - self.__switch_modifiers(web_key, state) # Предполагаем, что модификаторы всегда известны + key = AT1_TO_EVDEV.get(code, 0) + if key: + self.__switch_modifiers_evdev(key, state) # Предполагаем, что модификаторы всегда известны if self.__kvmd_ws: - await self.__kvmd_ws.send_key_event(web_key, state) + await self.__kvmd_ws.send_key_event(key, state) - def __switch_modifiers(self, key: (int | str), state: bool) -> bool: + def __switch_modifiers_x11(self, key: int, state: bool) -> bool: mod = 0 - if key in X11Modifiers.SHIFTS or key in WebModifiers.SHIFTS: + if key in X11Modifiers.SHIFTS: mod = SymmapModifiers.SHIFT - elif key == X11Modifiers.ALTGR or key == WebModifiers.ALT_RIGHT: + elif key == X11Modifiers.ALTGR: mod = SymmapModifiers.ALTGR - elif key in X11Modifiers.CTRLS or key in WebModifiers.CTRLS: + elif key in X11Modifiers.CTRLS: mod = SymmapModifiers.CTRL if mod == 0: return False @@ -385,18 +387,34 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes self.__modifiers &= ~mod return True - async def _on_pointer_event(self, buttons: dict[str, bool], wheel: dict[str, int], move: dict[str, int]) -> None: + def __switch_modifiers_evdev(self, key: int, state: bool) -> bool: + mod = 0 + if key in EvdevModifiers.SHIFTS: + mod = SymmapModifiers.SHIFT + elif key == EvdevModifiers.ALT_RIGHT: + mod = SymmapModifiers.ALTGR + elif key in EvdevModifiers.CTRLS: + mod = SymmapModifiers.CTRL + if mod == 0: + return False + if state: + self.__modifiers |= mod + else: + self.__modifiers &= ~mod + return True + + async def _on_pointer_event(self, buttons: dict[str, bool], wheel: tuple[int, int], move: tuple[int, int]) -> None: if self.__kvmd_ws: - if wheel["x"] or wheel["y"]: - await self.__kvmd_ws.send_mouse_wheel_event(wheel["x"], wheel["y"]) + if wheel[0] or wheel[1]: + await self.__kvmd_ws.send_mouse_wheel_event(*wheel) if self.__mouse_move != move: - await self.__kvmd_ws.send_mouse_move_event(move["x"], move["y"]) + await self.__kvmd_ws.send_mouse_move_event(*move) self.__mouse_move = move for (button, state) in buttons.items(): if self.__mouse_buttons[button] != state: - await self.__kvmd_ws.send_mouse_button_event(button, state) + await self.__kvmd_ws.send_mouse_button_event(MOUSE_TO_EVDEV[button], state) self.__mouse_buttons[button] = state async def _on_cut_event(self, text: str) -> None: diff --git a/kvmd/clients/kvmd.py b/kvmd/clients/kvmd.py index f8f9c07e..5600c28f 100644 --- a/kvmd/clients/kvmd.py +++ b/kvmd/clients/kvmd.py @@ -182,22 +182,22 @@ class KvmdClientWs: finally: self.__communicated = False - async def send_key_event(self, key: str, state: bool) -> None: - mask = (0b01 if state else 0) - await self.__writer_queue.put(bytes([1, mask]) + key.encode("ascii")) + async def send_key_event(self, key: int, state: bool) -> None: + mask = (0b10000000 | int(bool(state))) + await self.__writer_queue.put(struct.pack(">BBH", 1, mask, key)) - async def send_mouse_button_event(self, button: str, state: bool) -> None: - mask = (0b01 if state else 0) - await self.__writer_queue.put(bytes([2, mask]) + button.encode("ascii")) + async def send_mouse_button_event(self, button: int, state: bool) -> None: + mask = (0b10000000 | int(bool(state))) + await self.__writer_queue.put(struct.pack(">BBH", 2, mask, button)) async def send_mouse_move_event(self, to_x: int, to_y: int) -> None: - await self.__writer_queue.put(struct.pack(">bhh", 3, to_x, to_y)) + await self.__writer_queue.put(struct.pack(">Bhh", 3, to_x, to_y)) async def send_mouse_relative_event(self, delta_x: int, delta_y: int) -> None: - await self.__writer_queue.put(struct.pack(">bbbb", 4, 0, delta_x, delta_y)) + await self.__writer_queue.put(struct.pack(">BBbb", 4, 0, delta_x, delta_y)) async def send_mouse_wheel_event(self, delta_x: int, delta_y: int) -> None: - await self.__writer_queue.put(struct.pack(">bbbb", 5, 0, delta_x, delta_y)) + await self.__writer_queue.put(struct.pack(">BBbb", 5, 0, delta_x, delta_y)) class KvmdClientSession(BaseHttpClientSession): diff --git a/kvmd/keyboard/keysym.py b/kvmd/keyboard/keysym.py index 2896ca6e..46165512 100644 --- a/kvmd/keyboard/keysym.py +++ b/kvmd/keyboard/keysym.py @@ -30,9 +30,9 @@ import Xlib.keysymdef from ..logging import get_logger from .mappings import At1Key -from .mappings import WebModifiers +from .mappings import EvdevModifiers from .mappings import X11_TO_AT1 -from .mappings import AT1_TO_WEB +from .mappings import AT1_TO_EVDEV # ===== @@ -42,11 +42,11 @@ class SymmapModifiers: CTRL: int = 0x4 -def build_symmap(path: str) -> dict[int, dict[int, str]]: # x11 keysym -> [(modifiers, webkey), ...] +def build_symmap(path: str) -> dict[int, dict[int, int]]: # x11 keysym -> [(symmap_modifiers, evdev_code), ...] # https://github.com/qemu/qemu/blob/95a9457fd44ad97c518858a4e1586a5498f9773c/ui/keymaps.c logger = get_logger() - symmap: dict[int, dict[int, str]] = {} + symmap: dict[int, dict[int, int]] = {} for (src, items) in [ (path, list(_read_keyboard_layout(path).items())), ("", list(X11_TO_AT1.items())), @@ -57,14 +57,14 @@ def build_symmap(path: str) -> dict[int, dict[int, str]]: # x11 keysym -> [(mod for (code, keys) in items: for key in keys: - web_name = AT1_TO_WEB.get(key.code) - if web_name is not None: + evdev_code = AT1_TO_EVDEV.get(key.code) + if evdev_code is not None: if ( - (web_name in WebModifiers.SHIFTS and key.shift) # pylint: disable=too-many-boolean-expressions - or (web_name in WebModifiers.ALTS and key.altgr) - or (web_name in WebModifiers.CTRLS and key.ctrl) + (evdev_code in EvdevModifiers.SHIFTS and key.shift) # pylint: disable=too-many-boolean-expressions + or (evdev_code in EvdevModifiers.ALTS and key.altgr) + or (evdev_code in EvdevModifiers.CTRLS and key.ctrl) ): - logger.error("Invalid modifier key at mapping %s: %s / %s", src, web_name, key) + logger.error("Invalid modifier key at mapping %s: %s / %s", src, evdev_code, key) continue modifiers = ( @@ -75,7 +75,7 @@ def build_symmap(path: str) -> dict[int, dict[int, str]]: # x11 keysym -> [(mod ) if code not in symmap: symmap[code] = {} - symmap[code].setdefault(modifiers, web_name) + symmap[code].setdefault(modifiers, evdev_code) return symmap diff --git a/kvmd/keyboard/mappings.py b/kvmd/keyboard/mappings.py index 57744cea..0be56d7d 100644 --- a/kvmd/keyboard/mappings.py +++ b/kvmd/keyboard/mappings.py @@ -22,6 +22,8 @@ import dataclasses +from evdev import ecodes + # ===== @dataclasses.dataclass(frozen=True) @@ -31,7 +33,7 @@ class McuKey: @dataclasses.dataclass(frozen=True) class UsbKey: - code: int + code: int is_modifier: bool @@ -41,137 +43,252 @@ class Key: usb: UsbKey -KEYMAP: dict[str, Key] = { - "KeyA": Key(mcu=McuKey(code=1), usb=UsbKey(code=4, is_modifier=False)), - "KeyB": Key(mcu=McuKey(code=2), usb=UsbKey(code=5, is_modifier=False)), - "KeyC": Key(mcu=McuKey(code=3), usb=UsbKey(code=6, is_modifier=False)), - "KeyD": Key(mcu=McuKey(code=4), usb=UsbKey(code=7, is_modifier=False)), - "KeyE": Key(mcu=McuKey(code=5), usb=UsbKey(code=8, is_modifier=False)), - "KeyF": Key(mcu=McuKey(code=6), usb=UsbKey(code=9, is_modifier=False)), - "KeyG": Key(mcu=McuKey(code=7), usb=UsbKey(code=10, is_modifier=False)), - "KeyH": Key(mcu=McuKey(code=8), usb=UsbKey(code=11, is_modifier=False)), - "KeyI": Key(mcu=McuKey(code=9), usb=UsbKey(code=12, is_modifier=False)), - "KeyJ": Key(mcu=McuKey(code=10), usb=UsbKey(code=13, is_modifier=False)), - "KeyK": Key(mcu=McuKey(code=11), usb=UsbKey(code=14, is_modifier=False)), - "KeyL": Key(mcu=McuKey(code=12), usb=UsbKey(code=15, is_modifier=False)), - "KeyM": Key(mcu=McuKey(code=13), usb=UsbKey(code=16, is_modifier=False)), - "KeyN": Key(mcu=McuKey(code=14), usb=UsbKey(code=17, is_modifier=False)), - "KeyO": Key(mcu=McuKey(code=15), usb=UsbKey(code=18, is_modifier=False)), - "KeyP": Key(mcu=McuKey(code=16), usb=UsbKey(code=19, is_modifier=False)), - "KeyQ": Key(mcu=McuKey(code=17), usb=UsbKey(code=20, is_modifier=False)), - "KeyR": Key(mcu=McuKey(code=18), usb=UsbKey(code=21, is_modifier=False)), - "KeyS": Key(mcu=McuKey(code=19), usb=UsbKey(code=22, is_modifier=False)), - "KeyT": Key(mcu=McuKey(code=20), usb=UsbKey(code=23, is_modifier=False)), - "KeyU": Key(mcu=McuKey(code=21), usb=UsbKey(code=24, is_modifier=False)), - "KeyV": Key(mcu=McuKey(code=22), usb=UsbKey(code=25, is_modifier=False)), - "KeyW": Key(mcu=McuKey(code=23), usb=UsbKey(code=26, is_modifier=False)), - "KeyX": Key(mcu=McuKey(code=24), usb=UsbKey(code=27, is_modifier=False)), - "KeyY": Key(mcu=McuKey(code=25), usb=UsbKey(code=28, is_modifier=False)), - "KeyZ": Key(mcu=McuKey(code=26), usb=UsbKey(code=29, is_modifier=False)), - "Digit1": Key(mcu=McuKey(code=27), usb=UsbKey(code=30, is_modifier=False)), - "Digit2": Key(mcu=McuKey(code=28), usb=UsbKey(code=31, is_modifier=False)), - "Digit3": Key(mcu=McuKey(code=29), usb=UsbKey(code=32, is_modifier=False)), - "Digit4": Key(mcu=McuKey(code=30), usb=UsbKey(code=33, is_modifier=False)), - "Digit5": Key(mcu=McuKey(code=31), usb=UsbKey(code=34, is_modifier=False)), - "Digit6": Key(mcu=McuKey(code=32), usb=UsbKey(code=35, is_modifier=False)), - "Digit7": Key(mcu=McuKey(code=33), usb=UsbKey(code=36, is_modifier=False)), - "Digit8": Key(mcu=McuKey(code=34), usb=UsbKey(code=37, is_modifier=False)), - "Digit9": Key(mcu=McuKey(code=35), usb=UsbKey(code=38, is_modifier=False)), - "Digit0": Key(mcu=McuKey(code=36), usb=UsbKey(code=39, is_modifier=False)), - "Enter": Key(mcu=McuKey(code=37), usb=UsbKey(code=40, is_modifier=False)), - "Escape": Key(mcu=McuKey(code=38), usb=UsbKey(code=41, is_modifier=False)), - "Backspace": Key(mcu=McuKey(code=39), usb=UsbKey(code=42, is_modifier=False)), - "Tab": Key(mcu=McuKey(code=40), usb=UsbKey(code=43, is_modifier=False)), - "Space": Key(mcu=McuKey(code=41), usb=UsbKey(code=44, is_modifier=False)), - "Minus": Key(mcu=McuKey(code=42), usb=UsbKey(code=45, is_modifier=False)), - "Equal": Key(mcu=McuKey(code=43), usb=UsbKey(code=46, is_modifier=False)), - "BracketLeft": Key(mcu=McuKey(code=44), usb=UsbKey(code=47, is_modifier=False)), - "BracketRight": Key(mcu=McuKey(code=45), usb=UsbKey(code=48, is_modifier=False)), - "Backslash": Key(mcu=McuKey(code=46), usb=UsbKey(code=49, is_modifier=False)), - "Semicolon": Key(mcu=McuKey(code=47), usb=UsbKey(code=51, is_modifier=False)), - "Quote": Key(mcu=McuKey(code=48), usb=UsbKey(code=52, is_modifier=False)), - "Backquote": Key(mcu=McuKey(code=49), usb=UsbKey(code=53, is_modifier=False)), - "Comma": Key(mcu=McuKey(code=50), usb=UsbKey(code=54, is_modifier=False)), - "Period": Key(mcu=McuKey(code=51), usb=UsbKey(code=55, is_modifier=False)), - "Slash": Key(mcu=McuKey(code=52), usb=UsbKey(code=56, is_modifier=False)), - "CapsLock": Key(mcu=McuKey(code=53), usb=UsbKey(code=57, is_modifier=False)), - "F1": Key(mcu=McuKey(code=54), usb=UsbKey(code=58, is_modifier=False)), - "F2": Key(mcu=McuKey(code=55), usb=UsbKey(code=59, is_modifier=False)), - "F3": Key(mcu=McuKey(code=56), usb=UsbKey(code=60, is_modifier=False)), - "F4": Key(mcu=McuKey(code=57), usb=UsbKey(code=61, is_modifier=False)), - "F5": Key(mcu=McuKey(code=58), usb=UsbKey(code=62, is_modifier=False)), - "F6": Key(mcu=McuKey(code=59), usb=UsbKey(code=63, is_modifier=False)), - "F7": Key(mcu=McuKey(code=60), usb=UsbKey(code=64, is_modifier=False)), - "F8": Key(mcu=McuKey(code=61), usb=UsbKey(code=65, is_modifier=False)), - "F9": Key(mcu=McuKey(code=62), usb=UsbKey(code=66, is_modifier=False)), - "F10": Key(mcu=McuKey(code=63), usb=UsbKey(code=67, is_modifier=False)), - "F11": Key(mcu=McuKey(code=64), usb=UsbKey(code=68, is_modifier=False)), - "F12": Key(mcu=McuKey(code=65), usb=UsbKey(code=69, is_modifier=False)), - "PrintScreen": Key(mcu=McuKey(code=66), usb=UsbKey(code=70, is_modifier=False)), - "Insert": Key(mcu=McuKey(code=67), usb=UsbKey(code=73, is_modifier=False)), - "Home": Key(mcu=McuKey(code=68), usb=UsbKey(code=74, is_modifier=False)), - "PageUp": Key(mcu=McuKey(code=69), usb=UsbKey(code=75, is_modifier=False)), - "Delete": Key(mcu=McuKey(code=70), usb=UsbKey(code=76, is_modifier=False)), - "End": Key(mcu=McuKey(code=71), usb=UsbKey(code=77, is_modifier=False)), - "PageDown": Key(mcu=McuKey(code=72), usb=UsbKey(code=78, is_modifier=False)), - "ArrowRight": Key(mcu=McuKey(code=73), usb=UsbKey(code=79, is_modifier=False)), - "ArrowLeft": Key(mcu=McuKey(code=74), usb=UsbKey(code=80, is_modifier=False)), - "ArrowDown": Key(mcu=McuKey(code=75), usb=UsbKey(code=81, is_modifier=False)), - "ArrowUp": Key(mcu=McuKey(code=76), usb=UsbKey(code=82, is_modifier=False)), - "ControlLeft": Key(mcu=McuKey(code=77), usb=UsbKey(code=1, is_modifier=True)), - "ShiftLeft": Key(mcu=McuKey(code=78), usb=UsbKey(code=2, is_modifier=True)), - "AltLeft": Key(mcu=McuKey(code=79), usb=UsbKey(code=4, is_modifier=True)), - "MetaLeft": Key(mcu=McuKey(code=80), usb=UsbKey(code=8, is_modifier=True)), - "ControlRight": Key(mcu=McuKey(code=81), usb=UsbKey(code=16, is_modifier=True)), - "ShiftRight": Key(mcu=McuKey(code=82), usb=UsbKey(code=32, is_modifier=True)), - "AltRight": Key(mcu=McuKey(code=83), usb=UsbKey(code=64, is_modifier=True)), - "MetaRight": Key(mcu=McuKey(code=84), usb=UsbKey(code=128, is_modifier=True)), - "Pause": Key(mcu=McuKey(code=85), usb=UsbKey(code=72, is_modifier=False)), - "ScrollLock": Key(mcu=McuKey(code=86), usb=UsbKey(code=71, is_modifier=False)), - "NumLock": Key(mcu=McuKey(code=87), usb=UsbKey(code=83, is_modifier=False)), - "ContextMenu": Key(mcu=McuKey(code=88), usb=UsbKey(code=101, is_modifier=False)), - "NumpadDivide": Key(mcu=McuKey(code=89), usb=UsbKey(code=84, is_modifier=False)), - "NumpadMultiply": Key(mcu=McuKey(code=90), usb=UsbKey(code=85, is_modifier=False)), - "NumpadSubtract": Key(mcu=McuKey(code=91), usb=UsbKey(code=86, is_modifier=False)), - "NumpadAdd": Key(mcu=McuKey(code=92), usb=UsbKey(code=87, is_modifier=False)), - "NumpadEnter": Key(mcu=McuKey(code=93), usb=UsbKey(code=88, is_modifier=False)), - "Numpad1": Key(mcu=McuKey(code=94), usb=UsbKey(code=89, is_modifier=False)), - "Numpad2": Key(mcu=McuKey(code=95), usb=UsbKey(code=90, is_modifier=False)), - "Numpad3": Key(mcu=McuKey(code=96), usb=UsbKey(code=91, is_modifier=False)), - "Numpad4": Key(mcu=McuKey(code=97), usb=UsbKey(code=92, is_modifier=False)), - "Numpad5": Key(mcu=McuKey(code=98), usb=UsbKey(code=93, is_modifier=False)), - "Numpad6": Key(mcu=McuKey(code=99), usb=UsbKey(code=94, is_modifier=False)), - "Numpad7": Key(mcu=McuKey(code=100), usb=UsbKey(code=95, is_modifier=False)), - "Numpad8": Key(mcu=McuKey(code=101), usb=UsbKey(code=96, is_modifier=False)), - "Numpad9": Key(mcu=McuKey(code=102), usb=UsbKey(code=97, is_modifier=False)), - "Numpad0": Key(mcu=McuKey(code=103), usb=UsbKey(code=98, is_modifier=False)), - "NumpadDecimal": Key(mcu=McuKey(code=104), usb=UsbKey(code=99, is_modifier=False)), - "Power": Key(mcu=McuKey(code=105), usb=UsbKey(code=102, is_modifier=False)), - "IntlBackslash": Key(mcu=McuKey(code=106), usb=UsbKey(code=100, is_modifier=False)), - "IntlYen": Key(mcu=McuKey(code=107), usb=UsbKey(code=137, is_modifier=False)), - "IntlRo": Key(mcu=McuKey(code=108), usb=UsbKey(code=135, is_modifier=False)), - "KanaMode": Key(mcu=McuKey(code=109), usb=UsbKey(code=136, is_modifier=False)), - "Convert": Key(mcu=McuKey(code=110), usb=UsbKey(code=138, is_modifier=False)), - "NonConvert": Key(mcu=McuKey(code=111), usb=UsbKey(code=139, is_modifier=False)), +KEYMAP: dict[int, Key] = { + ecodes.KEY_A: Key(mcu=McuKey(code=1), usb=UsbKey(code=4, is_modifier=False)), + ecodes.KEY_B: Key(mcu=McuKey(code=2), usb=UsbKey(code=5, is_modifier=False)), + ecodes.KEY_C: Key(mcu=McuKey(code=3), usb=UsbKey(code=6, is_modifier=False)), + ecodes.KEY_D: Key(mcu=McuKey(code=4), usb=UsbKey(code=7, is_modifier=False)), + ecodes.KEY_E: Key(mcu=McuKey(code=5), usb=UsbKey(code=8, is_modifier=False)), + ecodes.KEY_F: Key(mcu=McuKey(code=6), usb=UsbKey(code=9, is_modifier=False)), + ecodes.KEY_G: Key(mcu=McuKey(code=7), usb=UsbKey(code=10, is_modifier=False)), + ecodes.KEY_H: Key(mcu=McuKey(code=8), usb=UsbKey(code=11, is_modifier=False)), + ecodes.KEY_I: Key(mcu=McuKey(code=9), usb=UsbKey(code=12, is_modifier=False)), + ecodes.KEY_J: Key(mcu=McuKey(code=10), usb=UsbKey(code=13, is_modifier=False)), + ecodes.KEY_K: Key(mcu=McuKey(code=11), usb=UsbKey(code=14, is_modifier=False)), + ecodes.KEY_L: Key(mcu=McuKey(code=12), usb=UsbKey(code=15, is_modifier=False)), + ecodes.KEY_M: Key(mcu=McuKey(code=13), usb=UsbKey(code=16, is_modifier=False)), + ecodes.KEY_N: Key(mcu=McuKey(code=14), usb=UsbKey(code=17, is_modifier=False)), + ecodes.KEY_O: Key(mcu=McuKey(code=15), usb=UsbKey(code=18, is_modifier=False)), + ecodes.KEY_P: Key(mcu=McuKey(code=16), usb=UsbKey(code=19, is_modifier=False)), + ecodes.KEY_Q: Key(mcu=McuKey(code=17), usb=UsbKey(code=20, is_modifier=False)), + ecodes.KEY_R: Key(mcu=McuKey(code=18), usb=UsbKey(code=21, is_modifier=False)), + ecodes.KEY_S: Key(mcu=McuKey(code=19), usb=UsbKey(code=22, is_modifier=False)), + ecodes.KEY_T: Key(mcu=McuKey(code=20), usb=UsbKey(code=23, is_modifier=False)), + ecodes.KEY_U: Key(mcu=McuKey(code=21), usb=UsbKey(code=24, is_modifier=False)), + ecodes.KEY_V: Key(mcu=McuKey(code=22), usb=UsbKey(code=25, is_modifier=False)), + ecodes.KEY_W: Key(mcu=McuKey(code=23), usb=UsbKey(code=26, is_modifier=False)), + ecodes.KEY_X: Key(mcu=McuKey(code=24), usb=UsbKey(code=27, is_modifier=False)), + ecodes.KEY_Y: Key(mcu=McuKey(code=25), usb=UsbKey(code=28, is_modifier=False)), + ecodes.KEY_Z: Key(mcu=McuKey(code=26), usb=UsbKey(code=29, is_modifier=False)), + ecodes.KEY_1: Key(mcu=McuKey(code=27), usb=UsbKey(code=30, is_modifier=False)), + ecodes.KEY_2: Key(mcu=McuKey(code=28), usb=UsbKey(code=31, is_modifier=False)), + ecodes.KEY_3: Key(mcu=McuKey(code=29), usb=UsbKey(code=32, is_modifier=False)), + ecodes.KEY_4: Key(mcu=McuKey(code=30), usb=UsbKey(code=33, is_modifier=False)), + ecodes.KEY_5: Key(mcu=McuKey(code=31), usb=UsbKey(code=34, is_modifier=False)), + ecodes.KEY_6: Key(mcu=McuKey(code=32), usb=UsbKey(code=35, is_modifier=False)), + ecodes.KEY_7: Key(mcu=McuKey(code=33), usb=UsbKey(code=36, is_modifier=False)), + ecodes.KEY_8: Key(mcu=McuKey(code=34), usb=UsbKey(code=37, is_modifier=False)), + ecodes.KEY_9: Key(mcu=McuKey(code=35), usb=UsbKey(code=38, is_modifier=False)), + ecodes.KEY_0: Key(mcu=McuKey(code=36), usb=UsbKey(code=39, is_modifier=False)), + ecodes.KEY_ENTER: Key(mcu=McuKey(code=37), usb=UsbKey(code=40, is_modifier=False)), + ecodes.KEY_ESC: Key(mcu=McuKey(code=38), usb=UsbKey(code=41, is_modifier=False)), + ecodes.KEY_BACKSPACE: Key(mcu=McuKey(code=39), usb=UsbKey(code=42, is_modifier=False)), + ecodes.KEY_TAB: Key(mcu=McuKey(code=40), usb=UsbKey(code=43, is_modifier=False)), + ecodes.KEY_SPACE: Key(mcu=McuKey(code=41), usb=UsbKey(code=44, is_modifier=False)), + ecodes.KEY_MINUS: Key(mcu=McuKey(code=42), usb=UsbKey(code=45, is_modifier=False)), + ecodes.KEY_EQUAL: Key(mcu=McuKey(code=43), usb=UsbKey(code=46, is_modifier=False)), + ecodes.KEY_LEFTBRACE: Key(mcu=McuKey(code=44), usb=UsbKey(code=47, is_modifier=False)), + ecodes.KEY_RIGHTBRACE: Key(mcu=McuKey(code=45), usb=UsbKey(code=48, is_modifier=False)), + ecodes.KEY_BACKSLASH: Key(mcu=McuKey(code=46), usb=UsbKey(code=49, is_modifier=False)), + ecodes.KEY_SEMICOLON: Key(mcu=McuKey(code=47), usb=UsbKey(code=51, is_modifier=False)), + ecodes.KEY_APOSTROPHE: Key(mcu=McuKey(code=48), usb=UsbKey(code=52, is_modifier=False)), + ecodes.KEY_GRAVE: Key(mcu=McuKey(code=49), usb=UsbKey(code=53, is_modifier=False)), + ecodes.KEY_COMMA: Key(mcu=McuKey(code=50), usb=UsbKey(code=54, is_modifier=False)), + ecodes.KEY_DOT: Key(mcu=McuKey(code=51), usb=UsbKey(code=55, is_modifier=False)), + ecodes.KEY_SLASH: Key(mcu=McuKey(code=52), usb=UsbKey(code=56, is_modifier=False)), + ecodes.KEY_CAPSLOCK: Key(mcu=McuKey(code=53), usb=UsbKey(code=57, is_modifier=False)), + ecodes.KEY_F1: Key(mcu=McuKey(code=54), usb=UsbKey(code=58, is_modifier=False)), + ecodes.KEY_F2: Key(mcu=McuKey(code=55), usb=UsbKey(code=59, is_modifier=False)), + ecodes.KEY_F3: Key(mcu=McuKey(code=56), usb=UsbKey(code=60, is_modifier=False)), + ecodes.KEY_F4: Key(mcu=McuKey(code=57), usb=UsbKey(code=61, is_modifier=False)), + ecodes.KEY_F5: Key(mcu=McuKey(code=58), usb=UsbKey(code=62, is_modifier=False)), + ecodes.KEY_F6: Key(mcu=McuKey(code=59), usb=UsbKey(code=63, is_modifier=False)), + ecodes.KEY_F7: Key(mcu=McuKey(code=60), usb=UsbKey(code=64, is_modifier=False)), + ecodes.KEY_F8: Key(mcu=McuKey(code=61), usb=UsbKey(code=65, is_modifier=False)), + ecodes.KEY_F9: Key(mcu=McuKey(code=62), usb=UsbKey(code=66, is_modifier=False)), + ecodes.KEY_F10: Key(mcu=McuKey(code=63), usb=UsbKey(code=67, is_modifier=False)), + ecodes.KEY_F11: Key(mcu=McuKey(code=64), usb=UsbKey(code=68, is_modifier=False)), + ecodes.KEY_F12: Key(mcu=McuKey(code=65), usb=UsbKey(code=69, is_modifier=False)), + ecodes.KEY_SYSRQ: Key(mcu=McuKey(code=66), usb=UsbKey(code=70, is_modifier=False)), + ecodes.KEY_INSERT: Key(mcu=McuKey(code=67), usb=UsbKey(code=73, is_modifier=False)), + ecodes.KEY_HOME: Key(mcu=McuKey(code=68), usb=UsbKey(code=74, is_modifier=False)), + ecodes.KEY_PAGEUP: Key(mcu=McuKey(code=69), usb=UsbKey(code=75, is_modifier=False)), + ecodes.KEY_DELETE: Key(mcu=McuKey(code=70), usb=UsbKey(code=76, is_modifier=False)), + ecodes.KEY_END: Key(mcu=McuKey(code=71), usb=UsbKey(code=77, is_modifier=False)), + ecodes.KEY_PAGEDOWN: Key(mcu=McuKey(code=72), usb=UsbKey(code=78, is_modifier=False)), + ecodes.KEY_RIGHT: Key(mcu=McuKey(code=73), usb=UsbKey(code=79, is_modifier=False)), + ecodes.KEY_LEFT: Key(mcu=McuKey(code=74), usb=UsbKey(code=80, is_modifier=False)), + ecodes.KEY_DOWN: Key(mcu=McuKey(code=75), usb=UsbKey(code=81, is_modifier=False)), + ecodes.KEY_UP: Key(mcu=McuKey(code=76), usb=UsbKey(code=82, is_modifier=False)), + ecodes.KEY_LEFTCTRL: Key(mcu=McuKey(code=77), usb=UsbKey(code=1, is_modifier=True)), + ecodes.KEY_LEFTSHIFT: Key(mcu=McuKey(code=78), usb=UsbKey(code=2, is_modifier=True)), + ecodes.KEY_LEFTALT: Key(mcu=McuKey(code=79), usb=UsbKey(code=4, is_modifier=True)), + ecodes.KEY_LEFTMETA: Key(mcu=McuKey(code=80), usb=UsbKey(code=8, is_modifier=True)), + ecodes.KEY_RIGHTCTRL: Key(mcu=McuKey(code=81), usb=UsbKey(code=16, is_modifier=True)), + ecodes.KEY_RIGHTSHIFT: Key(mcu=McuKey(code=82), usb=UsbKey(code=32, is_modifier=True)), + ecodes.KEY_RIGHTALT: Key(mcu=McuKey(code=83), usb=UsbKey(code=64, is_modifier=True)), + ecodes.KEY_RIGHTMETA: Key(mcu=McuKey(code=84), usb=UsbKey(code=128, is_modifier=True)), + ecodes.KEY_PAUSE: Key(mcu=McuKey(code=85), usb=UsbKey(code=72, is_modifier=False)), + ecodes.KEY_SCROLLLOCK: Key(mcu=McuKey(code=86), usb=UsbKey(code=71, is_modifier=False)), + ecodes.KEY_NUMLOCK: Key(mcu=McuKey(code=87), usb=UsbKey(code=83, is_modifier=False)), + ecodes.KEY_CONTEXT_MENU: Key(mcu=McuKey(code=88), usb=UsbKey(code=101, is_modifier=False)), + ecodes.KEY_KPSLASH: Key(mcu=McuKey(code=89), usb=UsbKey(code=84, is_modifier=False)), + ecodes.KEY_KPASTERISK: Key(mcu=McuKey(code=90), usb=UsbKey(code=85, is_modifier=False)), + ecodes.KEY_KPMINUS: Key(mcu=McuKey(code=91), usb=UsbKey(code=86, is_modifier=False)), + ecodes.KEY_KPPLUS: Key(mcu=McuKey(code=92), usb=UsbKey(code=87, is_modifier=False)), + ecodes.KEY_KPENTER: Key(mcu=McuKey(code=93), usb=UsbKey(code=88, is_modifier=False)), + ecodes.KEY_KP1: Key(mcu=McuKey(code=94), usb=UsbKey(code=89, is_modifier=False)), + ecodes.KEY_KP2: Key(mcu=McuKey(code=95), usb=UsbKey(code=90, is_modifier=False)), + ecodes.KEY_KP3: Key(mcu=McuKey(code=96), usb=UsbKey(code=91, is_modifier=False)), + ecodes.KEY_KP4: Key(mcu=McuKey(code=97), usb=UsbKey(code=92, is_modifier=False)), + ecodes.KEY_KP5: Key(mcu=McuKey(code=98), usb=UsbKey(code=93, is_modifier=False)), + ecodes.KEY_KP6: Key(mcu=McuKey(code=99), usb=UsbKey(code=94, is_modifier=False)), + ecodes.KEY_KP7: Key(mcu=McuKey(code=100), usb=UsbKey(code=95, is_modifier=False)), + ecodes.KEY_KP8: Key(mcu=McuKey(code=101), usb=UsbKey(code=96, is_modifier=False)), + ecodes.KEY_KP9: Key(mcu=McuKey(code=102), usb=UsbKey(code=97, is_modifier=False)), + ecodes.KEY_KP0: Key(mcu=McuKey(code=103), usb=UsbKey(code=98, is_modifier=False)), + ecodes.KEY_KPDOT: Key(mcu=McuKey(code=104), usb=UsbKey(code=99, is_modifier=False)), + ecodes.KEY_POWER: Key(mcu=McuKey(code=105), usb=UsbKey(code=102, is_modifier=False)), + ecodes.KEY_102ND: Key(mcu=McuKey(code=106), usb=UsbKey(code=100, is_modifier=False)), + ecodes.KEY_YEN: Key(mcu=McuKey(code=107), usb=UsbKey(code=137, is_modifier=False)), + ecodes.KEY_RO: Key(mcu=McuKey(code=108), usb=UsbKey(code=135, is_modifier=False)), + ecodes.KEY_KATAKANA: Key(mcu=McuKey(code=109), usb=UsbKey(code=136, is_modifier=False)), + ecodes.KEY_HENKAN: Key(mcu=McuKey(code=110), usb=UsbKey(code=138, is_modifier=False)), + ecodes.KEY_MUHENKAN: Key(mcu=McuKey(code=111), usb=UsbKey(code=139, is_modifier=False)), +} + + +WEB_TO_EVDEV = { + "KeyA": ecodes.KEY_A, + "KeyB": ecodes.KEY_B, + "KeyC": ecodes.KEY_C, + "KeyD": ecodes.KEY_D, + "KeyE": ecodes.KEY_E, + "KeyF": ecodes.KEY_F, + "KeyG": ecodes.KEY_G, + "KeyH": ecodes.KEY_H, + "KeyI": ecodes.KEY_I, + "KeyJ": ecodes.KEY_J, + "KeyK": ecodes.KEY_K, + "KeyL": ecodes.KEY_L, + "KeyM": ecodes.KEY_M, + "KeyN": ecodes.KEY_N, + "KeyO": ecodes.KEY_O, + "KeyP": ecodes.KEY_P, + "KeyQ": ecodes.KEY_Q, + "KeyR": ecodes.KEY_R, + "KeyS": ecodes.KEY_S, + "KeyT": ecodes.KEY_T, + "KeyU": ecodes.KEY_U, + "KeyV": ecodes.KEY_V, + "KeyW": ecodes.KEY_W, + "KeyX": ecodes.KEY_X, + "KeyY": ecodes.KEY_Y, + "KeyZ": ecodes.KEY_Z, + "Digit1": ecodes.KEY_1, + "Digit2": ecodes.KEY_2, + "Digit3": ecodes.KEY_3, + "Digit4": ecodes.KEY_4, + "Digit5": ecodes.KEY_5, + "Digit6": ecodes.KEY_6, + "Digit7": ecodes.KEY_7, + "Digit8": ecodes.KEY_8, + "Digit9": ecodes.KEY_9, + "Digit0": ecodes.KEY_0, + "Enter": ecodes.KEY_ENTER, + "Escape": ecodes.KEY_ESC, + "Backspace": ecodes.KEY_BACKSPACE, + "Tab": ecodes.KEY_TAB, + "Space": ecodes.KEY_SPACE, + "Minus": ecodes.KEY_MINUS, + "Equal": ecodes.KEY_EQUAL, + "BracketLeft": ecodes.KEY_LEFTBRACE, + "BracketRight": ecodes.KEY_RIGHTBRACE, + "Backslash": ecodes.KEY_BACKSLASH, + "Semicolon": ecodes.KEY_SEMICOLON, + "Quote": ecodes.KEY_APOSTROPHE, + "Backquote": ecodes.KEY_GRAVE, + "Comma": ecodes.KEY_COMMA, + "Period": ecodes.KEY_DOT, + "Slash": ecodes.KEY_SLASH, + "CapsLock": ecodes.KEY_CAPSLOCK, + "F1": ecodes.KEY_F1, + "F2": ecodes.KEY_F2, + "F3": ecodes.KEY_F3, + "F4": ecodes.KEY_F4, + "F5": ecodes.KEY_F5, + "F6": ecodes.KEY_F6, + "F7": ecodes.KEY_F7, + "F8": ecodes.KEY_F8, + "F9": ecodes.KEY_F9, + "F10": ecodes.KEY_F10, + "F11": ecodes.KEY_F11, + "F12": ecodes.KEY_F12, + "PrintScreen": ecodes.KEY_SYSRQ, + "Insert": ecodes.KEY_INSERT, + "Home": ecodes.KEY_HOME, + "PageUp": ecodes.KEY_PAGEUP, + "Delete": ecodes.KEY_DELETE, + "End": ecodes.KEY_END, + "PageDown": ecodes.KEY_PAGEDOWN, + "ArrowRight": ecodes.KEY_RIGHT, + "ArrowLeft": ecodes.KEY_LEFT, + "ArrowDown": ecodes.KEY_DOWN, + "ArrowUp": ecodes.KEY_UP, + "ControlLeft": ecodes.KEY_LEFTCTRL, + "ShiftLeft": ecodes.KEY_LEFTSHIFT, + "AltLeft": ecodes.KEY_LEFTALT, + "MetaLeft": ecodes.KEY_LEFTMETA, + "ControlRight": ecodes.KEY_RIGHTCTRL, + "ShiftRight": ecodes.KEY_RIGHTSHIFT, + "AltRight": ecodes.KEY_RIGHTALT, + "MetaRight": ecodes.KEY_RIGHTMETA, + "Pause": ecodes.KEY_PAUSE, + "ScrollLock": ecodes.KEY_SCROLLLOCK, + "NumLock": ecodes.KEY_NUMLOCK, + "ContextMenu": ecodes.KEY_CONTEXT_MENU, + "NumpadDivide": ecodes.KEY_KPSLASH, + "NumpadMultiply": ecodes.KEY_KPASTERISK, + "NumpadSubtract": ecodes.KEY_KPMINUS, + "NumpadAdd": ecodes.KEY_KPPLUS, + "NumpadEnter": ecodes.KEY_KPENTER, + "Numpad1": ecodes.KEY_KP1, + "Numpad2": ecodes.KEY_KP2, + "Numpad3": ecodes.KEY_KP3, + "Numpad4": ecodes.KEY_KP4, + "Numpad5": ecodes.KEY_KP5, + "Numpad6": ecodes.KEY_KP6, + "Numpad7": ecodes.KEY_KP7, + "Numpad8": ecodes.KEY_KP8, + "Numpad9": ecodes.KEY_KP9, + "Numpad0": ecodes.KEY_KP0, + "NumpadDecimal": ecodes.KEY_KPDOT, + "Power": ecodes.KEY_POWER, + "IntlBackslash": ecodes.KEY_102ND, + "IntlYen": ecodes.KEY_YEN, + "IntlRo": ecodes.KEY_RO, + "KanaMode": ecodes.KEY_KATAKANA, + "Convert": ecodes.KEY_HENKAN, + "NonConvert": ecodes.KEY_MUHENKAN, } # ===== -class WebModifiers: - SHIFT_LEFT = "ShiftLeft" - SHIFT_RIGHT = "ShiftRight" +class EvdevModifiers: + SHIFT_LEFT = ecodes.KEY_LEFTSHIFT + SHIFT_RIGHT = ecodes.KEY_RIGHTSHIFT SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) - ALT_LEFT = "AltLeft" - ALT_RIGHT = "AltRight" + ALT_LEFT = ecodes.KEY_LEFTALT + ALT_RIGHT = ecodes.KEY_RIGHTALT ALTS = set([ALT_LEFT, ALT_RIGHT]) - CTRL_LEFT = "ControlLeft" - CTRL_RIGHT = "ControlRight" + CTRL_LEFT = ecodes.KEY_LEFTCTRL + CTRL_RIGHT = ecodes.KEY_RIGHTCTRL CTRLS = set([CTRL_LEFT, CTRL_RIGHT]) - META_LEFT = "MetaLeft" - META_RIGHT = "MetaRight" + META_LEFT = ecodes.KEY_LEFTMETA + META_RIGHT = ecodes.KEY_RIGHTMETA METAS = set([META_LEFT, META_RIGHT]) ALL = (SHIFTS | ALTS | CTRLS | METAS) @@ -192,10 +309,10 @@ class X11Modifiers: # ===== @dataclasses.dataclass(frozen=True) class At1Key: - code: int + code: int shift: bool altgr: bool = False - ctrl: bool = False + ctrl: bool = False X11_TO_AT1 = { @@ -357,116 +474,116 @@ X11_TO_AT1 = { } -AT1_TO_WEB = { - 1: "Escape", - 2: "Digit1", - 3: "Digit2", - 4: "Digit3", - 5: "Digit4", - 6: "Digit5", - 7: "Digit6", - 8: "Digit7", - 9: "Digit8", - 10: "Digit9", - 11: "Digit0", - 12: "Minus", - 13: "Equal", - 14: "Backspace", - 15: "Tab", - 16: "KeyQ", - 17: "KeyW", - 18: "KeyE", - 19: "KeyR", - 20: "KeyT", - 21: "KeyY", - 22: "KeyU", - 23: "KeyI", - 24: "KeyO", - 25: "KeyP", - 26: "BracketLeft", - 27: "BracketRight", - 28: "Enter", - 29: "ControlLeft", - 30: "KeyA", - 31: "KeyS", - 32: "KeyD", - 33: "KeyF", - 34: "KeyG", - 35: "KeyH", - 36: "KeyJ", - 37: "KeyK", - 38: "KeyL", - 39: "Semicolon", - 40: "Quote", - 41: "Backquote", - 42: "ShiftLeft", - 43: "Backslash", - 44: "KeyZ", - 45: "KeyX", - 46: "KeyC", - 47: "KeyV", - 48: "KeyB", - 49: "KeyN", - 50: "KeyM", - 51: "Comma", - 52: "Period", - 53: "Slash", - 54: "ShiftRight", - 55: "NumpadMultiply", - 56: "AltLeft", - 57: "Space", - 58: "CapsLock", - 59: "F1", - 60: "F2", - 61: "F3", - 62: "F4", - 63: "F5", - 64: "F6", - 65: "F7", - 66: "F8", - 67: "F9", - 68: "F10", - 69: "NumLock", - 70: "ScrollLock", - 71: "Numpad7", - 72: "Numpad8", - 73: "Numpad9", - 74: "NumpadSubtract", - 75: "Numpad4", - 76: "Numpad5", - 77: "Numpad6", - 78: "NumpadAdd", - 79: "Numpad1", - 80: "Numpad2", - 81: "Numpad3", - 82: "Numpad0", - 83: "NumpadDecimal", - 84: "PrintScreen", - 86: "IntlBackslash", - 87: "F11", - 88: "F12", - 112: "KanaMode", - 115: "IntlRo", - 121: "Convert", - 123: "NonConvert", - 125: "IntlYen", - 57372: "NumpadEnter", - 57373: "ControlRight", - 57397: "NumpadDivide", - 57400: "AltRight", - 57414: "Pause", - 57415: "Home", - 57416: "ArrowUp", - 57417: "PageUp", - 57419: "ArrowLeft", - 57421: "ArrowRight", - 57423: "End", - 57424: "ArrowDown", - 57425: "PageDown", - 57426: "Insert", - 57427: "Delete", - 57435: "MetaLeft", - 57436: "MetaRight", - 57437: "ContextMenu", - 57438: "Power", +AT1_TO_EVDEV = { + 1: ecodes.KEY_ESC, + 2: ecodes.KEY_1, + 3: ecodes.KEY_2, + 4: ecodes.KEY_3, + 5: ecodes.KEY_4, + 6: ecodes.KEY_5, + 7: ecodes.KEY_6, + 8: ecodes.KEY_7, + 9: ecodes.KEY_8, + 10: ecodes.KEY_9, + 11: ecodes.KEY_0, + 12: ecodes.KEY_MINUS, + 13: ecodes.KEY_EQUAL, + 14: ecodes.KEY_BACKSPACE, + 15: ecodes.KEY_TAB, + 16: ecodes.KEY_Q, + 17: ecodes.KEY_W, + 18: ecodes.KEY_E, + 19: ecodes.KEY_R, + 20: ecodes.KEY_T, + 21: ecodes.KEY_Y, + 22: ecodes.KEY_U, + 23: ecodes.KEY_I, + 24: ecodes.KEY_O, + 25: ecodes.KEY_P, + 26: ecodes.KEY_LEFTBRACE, + 27: ecodes.KEY_RIGHTBRACE, + 28: ecodes.KEY_ENTER, + 29: ecodes.KEY_LEFTCTRL, + 30: ecodes.KEY_A, + 31: ecodes.KEY_S, + 32: ecodes.KEY_D, + 33: ecodes.KEY_F, + 34: ecodes.KEY_G, + 35: ecodes.KEY_H, + 36: ecodes.KEY_J, + 37: ecodes.KEY_K, + 38: ecodes.KEY_L, + 39: ecodes.KEY_SEMICOLON, + 40: ecodes.KEY_APOSTROPHE, + 41: ecodes.KEY_GRAVE, + 42: ecodes.KEY_LEFTSHIFT, + 43: ecodes.KEY_BACKSLASH, + 44: ecodes.KEY_Z, + 45: ecodes.KEY_X, + 46: ecodes.KEY_C, + 47: ecodes.KEY_V, + 48: ecodes.KEY_B, + 49: ecodes.KEY_N, + 50: ecodes.KEY_M, + 51: ecodes.KEY_COMMA, + 52: ecodes.KEY_DOT, + 53: ecodes.KEY_SLASH, + 54: ecodes.KEY_RIGHTSHIFT, + 55: ecodes.KEY_KPASTERISK, + 56: ecodes.KEY_LEFTALT, + 57: ecodes.KEY_SPACE, + 58: ecodes.KEY_CAPSLOCK, + 59: ecodes.KEY_F1, + 60: ecodes.KEY_F2, + 61: ecodes.KEY_F3, + 62: ecodes.KEY_F4, + 63: ecodes.KEY_F5, + 64: ecodes.KEY_F6, + 65: ecodes.KEY_F7, + 66: ecodes.KEY_F8, + 67: ecodes.KEY_F9, + 68: ecodes.KEY_F10, + 69: ecodes.KEY_NUMLOCK, + 70: ecodes.KEY_SCROLLLOCK, + 71: ecodes.KEY_KP7, + 72: ecodes.KEY_KP8, + 73: ecodes.KEY_KP9, + 74: ecodes.KEY_KPMINUS, + 75: ecodes.KEY_KP4, + 76: ecodes.KEY_KP5, + 77: ecodes.KEY_KP6, + 78: ecodes.KEY_KPPLUS, + 79: ecodes.KEY_KP1, + 80: ecodes.KEY_KP2, + 81: ecodes.KEY_KP3, + 82: ecodes.KEY_KP0, + 83: ecodes.KEY_KPDOT, + 84: ecodes.KEY_SYSRQ, + 86: ecodes.KEY_102ND, + 87: ecodes.KEY_F11, + 88: ecodes.KEY_F12, + 112: ecodes.KEY_KATAKANA, + 115: ecodes.KEY_RO, + 121: ecodes.KEY_HENKAN, + 123: ecodes.KEY_MUHENKAN, + 125: ecodes.KEY_YEN, + 57372: ecodes.KEY_KPENTER, + 57373: ecodes.KEY_RIGHTCTRL, + 57397: ecodes.KEY_KPSLASH, + 57400: ecodes.KEY_RIGHTALT, + 57414: ecodes.KEY_PAUSE, + 57415: ecodes.KEY_HOME, + 57416: ecodes.KEY_UP, + 57417: ecodes.KEY_PAGEUP, + 57419: ecodes.KEY_LEFT, + 57421: ecodes.KEY_RIGHT, + 57423: ecodes.KEY_END, + 57424: ecodes.KEY_DOWN, + 57425: ecodes.KEY_PAGEDOWN, + 57426: ecodes.KEY_INSERT, + 57427: ecodes.KEY_DELETE, + 57435: ecodes.KEY_LEFTMETA, + 57436: ecodes.KEY_RIGHTMETA, + 57437: ecodes.KEY_CONTEXT_MENU, + 57438: ecodes.KEY_POWER, } diff --git a/kvmd/keyboard/mappings.py.mako b/kvmd/keyboard/mappings.py.mako index 1be41854..30397891 100644 --- a/kvmd/keyboard/mappings.py.mako +++ b/kvmd/keyboard/mappings.py.mako @@ -22,6 +22,8 @@ import dataclasses +from evdev import ecodes + # ===== @dataclasses.dataclass(frozen=True) @@ -31,7 +33,7 @@ class McuKey: @dataclasses.dataclass(frozen=True) class UsbKey: - code: int + code: int is_modifier: bool @@ -41,29 +43,36 @@ class Key: usb: UsbKey <%! import operator %> -KEYMAP: dict[str, Key] = { +KEYMAP: dict[int, Key] = { % for km in sorted(keymap, key=operator.attrgetter("mcu_code")): - "${km.web_name}": Key(mcu=McuKey(code=${km.mcu_code}), usb=UsbKey(code=${km.usb_key.code}, is_modifier=${km.usb_key.is_modifier})), + ecodes.${km.evdev_name}: Key(mcu=McuKey(code=${km.mcu_code}), usb=UsbKey(code=${km.usb_key.code}, is_modifier=${km.usb_key.is_modifier})), +% endfor +} + + +WEB_TO_EVDEV = { +% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): + "${km.web_name}": ecodes.${km.evdev_name}, % endfor } # ===== -class WebModifiers: - SHIFT_LEFT = "ShiftLeft" - SHIFT_RIGHT = "ShiftRight" +class EvdevModifiers: + SHIFT_LEFT = ecodes.KEY_LEFTSHIFT + SHIFT_RIGHT = ecodes.KEY_RIGHTSHIFT SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) - ALT_LEFT = "AltLeft" - ALT_RIGHT = "AltRight" + ALT_LEFT = ecodes.KEY_LEFTALT + ALT_RIGHT = ecodes.KEY_RIGHTALT ALTS = set([ALT_LEFT, ALT_RIGHT]) - CTRL_LEFT = "ControlLeft" - CTRL_RIGHT = "ControlRight" + CTRL_LEFT = ecodes.KEY_LEFTCTRL + CTRL_RIGHT = ecodes.KEY_RIGHTCTRL CTRLS = set([CTRL_LEFT, CTRL_RIGHT]) - META_LEFT = "MetaLeft" - META_RIGHT = "MetaRight" + META_LEFT = ecodes.KEY_LEFTMETA + META_RIGHT = ecodes.KEY_RIGHTMETA METAS = set([META_LEFT, META_RIGHT]) ALL = (SHIFTS | ALTS | CTRLS | METAS) @@ -84,10 +93,10 @@ class X11Modifiers: # ===== @dataclasses.dataclass(frozen=True) class At1Key: - code: int + code: int shift: bool altgr: bool = False - ctrl: bool = False + ctrl: bool = False X11_TO_AT1 = { @@ -99,8 +108,8 @@ X11_TO_AT1 = { } -AT1_TO_WEB = { +AT1_TO_EVDEV = { % for km in sorted(keymap, key=operator.attrgetter("at1_code")): - ${km.at1_code}: "${km.web_name}", + ${km.at1_code}: ecodes.${km.evdev_name}, % endfor } diff --git a/kvmd/keyboard/printer.py b/kvmd/keyboard/printer.py index efee6d44..35814264 100644 --- a/kvmd/keyboard/printer.py +++ b/kvmd/keyboard/printer.py @@ -25,8 +25,9 @@ import ctypes.util from typing import Generator +from evdev import ecodes + from .keysym import SymmapModifiers -from .mappings import WebModifiers # ===== @@ -56,10 +57,10 @@ def _ch_to_keysym(ch: str) -> int: # ===== -def text_to_web_keys( # pylint: disable=too-many-branches +def text_to_evdev_keys( # pylint: disable=too-many-branches text: str, - symmap: dict[int, dict[int, str]], -) -> Generator[tuple[str, bool], None, None]: + symmap: dict[int, dict[int, int]], +) -> Generator[tuple[int, bool], None, None]: shift = False altgr = False @@ -68,11 +69,11 @@ def text_to_web_keys( # pylint: disable=too-many-branches # https://stackoverflow.com/questions/12343987/convert-ascii-character-to-x11-keycode # https://www.ascii-code.com if ch == "\n": - keys = {0: "Enter"} + keys = {0: ecodes.KEY_ENTER} elif ch == "\t": - keys = {0: "Tab"} + keys = {0: ecodes.KEY_TAB} elif ch == " ": - keys = {0: "Space"} + keys = {0: ecodes.KEY_SPACE} else: if ch in ["‚", "‘", "’"]: ch = "'" @@ -95,17 +96,17 @@ def text_to_web_keys( # pylint: disable=too-many-branches continue if modifiers & SymmapModifiers.SHIFT and not shift: - yield (WebModifiers.SHIFT_LEFT, True) + yield (ecodes.KEY_LEFTSHIFT, True) shift = True elif not (modifiers & SymmapModifiers.SHIFT) and shift: - yield (WebModifiers.SHIFT_LEFT, False) + yield (ecodes.KEY_LEFTSHIFT, False) shift = False if modifiers & SymmapModifiers.ALTGR and not altgr: - yield (WebModifiers.ALT_RIGHT, True) + yield (ecodes.KEY_RIGHTALT, True) altgr = True elif not (modifiers & SymmapModifiers.ALTGR) and altgr: - yield (WebModifiers.ALT_RIGHT, False) + yield (ecodes.KEY_RIGHTALT, False) altgr = False yield (key, True) @@ -113,6 +114,6 @@ def text_to_web_keys( # pylint: disable=too-many-branches break if shift: - yield (WebModifiers.SHIFT_LEFT, False) + yield (ecodes.KEY_LEFTSHIFT, False) if altgr: - yield (WebModifiers.ALT_RIGHT, False) + yield (ecodes.KEY_RIGHTALT, False) diff --git a/kvmd/mouse.py b/kvmd/mouse.py index 399c6a33..c02ed1c6 100644 --- a/kvmd/mouse.py +++ b/kvmd/mouse.py @@ -20,6 +20,8 @@ # ========================================================================== # +from evdev import ecodes + from . import tools @@ -46,3 +48,13 @@ class MouseDelta: @classmethod def normalize(cls, value: int) -> int: return min(max(cls.MIN, value), cls.MAX) + + +# ===== +MOUSE_TO_EVDEV = { + "left": ecodes.BTN_LEFT, + "right": ecodes.BTN_RIGHT, + "middle": ecodes.BTN_MIDDLE, + "up": ecodes.BTN_BACK, + "down": ecodes.BTN_FORWARD, +} diff --git a/kvmd/plugins/hid/__init__.py b/kvmd/plugins/hid/__init__.py index a385023a..80397019 100644 --- a/kvmd/plugins/hid/__init__.py +++ b/kvmd/plugins/hid/__init__.py @@ -29,6 +29,8 @@ from typing import Callable from typing import AsyncGenerator from typing import Any +from evdev import ecodes + from ...yamlconf import Option from ...validators.basic import valid_bool @@ -37,7 +39,8 @@ from ...validators.basic import valid_string_list from ...validators.hid import valid_hid_key from ...validators.hid import valid_hid_mouse_move -from ...keyboard.mappings import WebModifiers +from ...keyboard.mappings import WEB_TO_EVDEV +from ...keyboard.mappings import EvdevModifiers from ...mouse import MouseRange from .. import BasePlugin @@ -60,7 +63,7 @@ class BaseHid(BasePlugin): # pylint: disable=too-many-instance-attributes jiggler_interval: int, ) -> None: - self.__ignore_keys = ignore_keys + self.__ignore_keys = [WEB_TO_EVDEV[key] for key in ignore_keys] self.__mouse_x_range = (mouse_x_min, mouse_x_max) self.__mouse_y_range = (mouse_y_min, mouse_y_max) @@ -142,7 +145,7 @@ class BaseHid(BasePlugin): # pylint: disable=too-many-instance-attributes async def send_key_events( self, - keys: Iterable[tuple[str, bool]], + keys: Iterable[tuple[int, bool]], no_ignore_keys: bool=False, slow: bool=False, ) -> None: @@ -153,24 +156,24 @@ class BaseHid(BasePlugin): # pylint: disable=too-many-instance-attributes await asyncio.sleep(0.02) self.send_key_event(key, state, False) - def send_key_event(self, key: str, state: bool, finish: bool) -> None: + def send_key_event(self, key: int, state: bool, finish: bool) -> None: self._send_key_event(key, state) - if state and finish and (key not in WebModifiers.ALL and key != "PrintScreen"): + if state and finish and (key not in EvdevModifiers.ALL and key != ecodes.KEY_SYSRQ): # Считаем что PrintScreen это модификатор для Alt+SysRq+... # По-хорошему надо учитывать факт нажатия на Alt, но можно и забить. self._send_key_event(key, False) self.__bump_activity() - def _send_key_event(self, key: str, state: bool) -> None: + def _send_key_event(self, key: int, state: bool) -> None: raise NotImplementedError # ===== - def send_mouse_button_event(self, button: str, state: bool) -> None: + def send_mouse_button_event(self, button: int, state: bool) -> None: self._send_mouse_button_event(button, state) self.__bump_activity() - def _send_mouse_button_event(self, button: str, state: bool) -> None: + def _send_mouse_button_event(self, button: int, state: bool) -> None: raise NotImplementedError # ===== diff --git a/kvmd/plugins/hid/_mcu/__init__.py b/kvmd/plugins/hid/_mcu/__init__.py index d6f04f76..dc681b68 100644 --- a/kvmd/plugins/hid/_mcu/__init__.py +++ b/kvmd/plugins/hid/_mcu/__init__.py @@ -285,10 +285,10 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many- def set_connected(self, connected: bool) -> None: self.__queue_event(SetConnectedEvent(connected), clear=True) - def _send_key_event(self, key: str, state: bool) -> None: + def _send_key_event(self, key: int, state: bool) -> None: self.__queue_event(KeyEvent(key, state)) - def _send_mouse_button_event(self, button: str, state: bool) -> None: + def _send_mouse_button_event(self, button: int, state: bool) -> None: self.__queue_event(MouseButtonEvent(button, state)) def _send_mouse_move_event(self, to_x: int, to_y: int) -> None: diff --git a/kvmd/plugins/hid/_mcu/proto.py b/kvmd/plugins/hid/_mcu/proto.py index 315c1f89..2ae03b83 100644 --- a/kvmd/plugins/hid/_mcu/proto.py +++ b/kvmd/plugins/hid/_mcu/proto.py @@ -23,6 +23,8 @@ import dataclasses import struct +from evdev import ecodes + from ....keyboard.mappings import KEYMAP from ....mouse import MouseRange @@ -106,33 +108,36 @@ class ClearEvent(BaseEvent): @dataclasses.dataclass(frozen=True) class KeyEvent(BaseEvent): - name: str + code: int state: bool def __post_init__(self) -> None: - assert self.name in KEYMAP + assert self.code in KEYMAP def make_request(self) -> bytes: - code = KEYMAP[self.name].mcu.code + code = KEYMAP[self.code].mcu.code return _make_request(struct.pack(">BBBxx", 0x11, code, int(self.state))) @dataclasses.dataclass(frozen=True) class MouseButtonEvent(BaseEvent): - name: str + code: int state: bool def __post_init__(self) -> None: - assert self.name in ["left", "right", "middle", "up", "down"] + assert self.code in [ + ecodes.BTN_LEFT, ecodes.BTN_RIGHT, ecodes.BTN_MIDDLE, + ecodes.BTN_BACK, ecodes.BTN_FORWARD, + ] def make_request(self) -> bytes: (code, state_pressed, is_main) = { - "left": (0b10000000, 0b00001000, True), - "right": (0b01000000, 0b00000100, True), - "middle": (0b00100000, 0b00000010, True), - "up": (0b10000000, 0b00001000, False), # Back - "down": (0b01000000, 0b00000100, False), # Forward - }[self.name] + ecodes.BTN_LEFT: (0b10000000, 0b00001000, True), + ecodes.BTN_RIGHT: (0b01000000, 0b00000100, True), + ecodes.BTN_MIDDLE: (0b00100000, 0b00000010, True), + ecodes.BTN_BACK: (0b10000000, 0b00001000, False), # Up + ecodes.BTN_FORWARD: (0b01000000, 0b00000100, False), # Down + }[self.code] if self.state: code |= state_pressed if is_main: diff --git a/kvmd/plugins/hid/bt/__init__.py b/kvmd/plugins/hid/bt/__init__.py index 0c95a6d5..246e3a8a 100644 --- a/kvmd/plugins/hid/bt/__init__.py +++ b/kvmd/plugins/hid/bt/__init__.py @@ -203,10 +203,10 @@ class Plugin(BaseHid): # pylint: disable=too-many-instance-attributes self._set_jiggler_active(jiggler) self.__notifier.notify() - def _send_key_event(self, key: str, state: bool) -> None: + def _send_key_event(self, key: int, state: bool) -> None: self.__server.queue_event(make_keyboard_event(key, state)) - def _send_mouse_button_event(self, button: str, state: bool) -> None: + def _send_mouse_button_event(self, button: int, state: bool) -> None: self.__server.queue_event(MouseButtonEvent(button, state)) def _send_mouse_relative_event(self, delta_x: int, delta_y: int) -> None: diff --git a/kvmd/plugins/hid/ch9329/__init__.py b/kvmd/plugins/hid/ch9329/__init__.py index 1b235090..ed3f5dcf 100644 --- a/kvmd/plugins/hid/ch9329/__init__.py +++ b/kvmd/plugins/hid/ch9329/__init__.py @@ -168,10 +168,10 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst self._set_jiggler_active(jiggler) self.__notifier.notify() - def _send_key_event(self, key: str, state: bool) -> None: + def _send_key_event(self, key: int, state: bool) -> None: self.__queue_cmd(self.__keyboard.process_key(key, state)) - def _send_mouse_button_event(self, button: str, state: bool) -> None: + def _send_mouse_button_event(self, button: int, state: bool) -> None: self.__queue_cmd(self.__mouse.process_button(button, state)) def _send_mouse_move_event(self, to_x: int, to_y: int) -> None: diff --git a/kvmd/plugins/hid/ch9329/keyboard.py b/kvmd/plugins/hid/ch9329/keyboard.py index 9aa16d9d..6d56aab6 100644 --- a/kvmd/plugins/hid/ch9329/keyboard.py +++ b/kvmd/plugins/hid/ch9329/keyboard.py @@ -46,7 +46,7 @@ class Keyboard: async def get_leds(self) -> dict[str, bool]: return (await self.__leds.get()) - def process_key(self, key: str, state: bool) -> bytes: + def process_key(self, key: int, state: bool) -> bytes: code = KEYMAP[key].usb.code is_modifier = KEYMAP[key].usb.is_modifier if state: diff --git a/kvmd/plugins/hid/ch9329/mouse.py b/kvmd/plugins/hid/ch9329/mouse.py index d61bab4e..fb620f8a 100644 --- a/kvmd/plugins/hid/ch9329/mouse.py +++ b/kvmd/plugins/hid/ch9329/mouse.py @@ -22,6 +22,8 @@ import math +from evdev import ecodes + from ....mouse import MouseRange from ....mouse import MouseDelta @@ -43,18 +45,18 @@ class Mouse: # pylint: disable=too-many-instance-attributes def is_absolute(self) -> bool: return self.__absolute - def process_button(self, button: str, state: bool) -> bytes: + def process_button(self, button: int, state: bool) -> bytes: code = 0x00 match button: - case "left": + case ecodes.BTN_LEFT: code = 0x01 - case "right": + case ecodes.BTN_RIGHT: code = 0x02 - case "middle": + case ecodes.BTN_MIDDLE: code = 0x04 - case "up": + case ecodes.BTN_BACK: code = 0x08 - case "down": + case ecodes.BTN_FORWARD: code = 0x10 if code: if state: diff --git a/kvmd/plugins/hid/otg/__init__.py b/kvmd/plugins/hid/otg/__init__.py index c9051f82..bf615fdf 100644 --- a/kvmd/plugins/hid/otg/__init__.py +++ b/kvmd/plugins/hid/otg/__init__.py @@ -206,10 +206,10 @@ class Plugin(BaseHid): # pylint: disable=too-many-instance-attributes self._set_jiggler_active(jiggler) self.__notifier.notify() - def _send_key_event(self, key: str, state: bool) -> None: + def _send_key_event(self, key: int, state: bool) -> None: self.__keyboard_proc.send_key_event(key, state) - def _send_mouse_button_event(self, button: str, state: bool) -> None: + def _send_mouse_button_event(self, button: int, state: bool) -> None: self.__mouse_current.send_button_event(button, state) def _send_mouse_move_event(self, to_x: int, to_y: int) -> None: diff --git a/kvmd/plugins/hid/otg/events.py b/kvmd/plugins/hid/otg/events.py index 44f5e373..6ee23c67 100644 --- a/kvmd/plugins/hid/otg/events.py +++ b/kvmd/plugins/hid/otg/events.py @@ -23,6 +23,8 @@ import struct import dataclasses +from evdev import ecodes + from ....keyboard.mappings import UsbKey from ....keyboard.mappings import KEYMAP @@ -46,7 +48,7 @@ class ResetEvent(BaseEvent): # ===== @dataclasses.dataclass(frozen=True) class KeyEvent(BaseEvent): - key: UsbKey + key: UsbKey state: bool def __post_init__(self) -> None: @@ -56,13 +58,13 @@ class KeyEvent(BaseEvent): @dataclasses.dataclass(frozen=True) class ModifierEvent(BaseEvent): modifier: UsbKey - state: bool + state: bool def __post_init__(self) -> None: assert self.modifier.is_modifier -def make_keyboard_event(key: str, state: bool) -> (KeyEvent | ModifierEvent): +def make_keyboard_event(key: int, state: bool) -> (KeyEvent | ModifierEvent): usb_key = KEYMAP[key].usb if usb_key.is_modifier: return ModifierEvent(usb_key, state) @@ -102,17 +104,17 @@ def make_keyboard_report( # ===== @dataclasses.dataclass(frozen=True) class MouseButtonEvent(BaseEvent): - button: str - state: bool - code: int = 0 + button: int + state: bool + code: int = 0 def __post_init__(self) -> None: object.__setattr__(self, "code", { - "left": 0x1, - "right": 0x2, - "middle": 0x4, - "up": 0x8, # Back - "down": 0x10, # Forward + ecodes.BTN_LEFT: 0x1, + ecodes.BTN_RIGHT: 0x2, + ecodes.BTN_MIDDLE: 0x4, + ecodes.BTN_BACK: 0x8, # Back/Up + ecodes.BTN_FORWARD: 0x10, # Forward/Down }[self.button]) diff --git a/kvmd/plugins/hid/otg/keyboard.py b/kvmd/plugins/hid/otg/keyboard.py index e82d95a3..23dc05d7 100644 --- a/kvmd/plugins/hid/otg/keyboard.py +++ b/kvmd/plugins/hid/otg/keyboard.py @@ -67,7 +67,7 @@ class KeyboardProcess(BaseDeviceProcess): self._clear_queue() self._queue_event(ResetEvent()) - def send_key_event(self, key: str, state: bool) -> None: + def send_key_event(self, key: int, state: bool) -> None: self._queue_event(make_keyboard_event(key, state)) # ===== diff --git a/kvmd/plugins/hid/otg/mouse.py b/kvmd/plugins/hid/otg/mouse.py index ae8d53b0..24dc9bf1 100644 --- a/kvmd/plugins/hid/otg/mouse.py +++ b/kvmd/plugins/hid/otg/mouse.py @@ -85,7 +85,7 @@ class MouseProcess(BaseDeviceProcess): self._clear_queue() self._queue_event(ResetEvent()) - def send_button_event(self, button: str, state: bool) -> None: + def send_button_event(self, button: int, state: bool) -> None: self._queue_event(MouseButtonEvent(button, state)) def send_move_event(self, to_x: int, to_y: int) -> None: diff --git a/kvmd/validators/hid.py b/kvmd/validators/hid.py index 0f74a6eb..c09ad37f 100644 --- a/kvmd/validators/hid.py +++ b/kvmd/validators/hid.py @@ -22,7 +22,8 @@ from typing import Any -from ..keyboard.mappings import KEYMAP +from ..keyboard.mappings import WEB_TO_EVDEV +from ..mouse import MOUSE_TO_EVDEV from ..mouse import MouseRange from ..mouse import MouseDelta @@ -42,7 +43,7 @@ def valid_hid_mouse_output(arg: Any) -> str: def valid_hid_key(arg: Any) -> str: - return check_string_in_list(arg, "Keyboard key", KEYMAP, lower=False) + return check_string_in_list(arg, "Keyboard key", WEB_TO_EVDEV, lower=False) def valid_hid_mouse_move(arg: Any) -> int: @@ -51,7 +52,7 @@ def valid_hid_mouse_move(arg: Any) -> int: def valid_hid_mouse_button(arg: Any) -> str: - return check_string_in_list(arg, "Mouse button", ["left", "right", "middle", "up", "down"]) + return check_string_in_list(arg, "Mouse button", MOUSE_TO_EVDEV) def valid_hid_mouse_delta(arg: Any) -> int: diff --git a/testenv/Dockerfile b/testenv/Dockerfile index bb7c7bd7..293bc9bb 100644 --- a/testenv/Dockerfile +++ b/testenv/Dockerfile @@ -51,6 +51,7 @@ RUN pacman --noconfirm --ask=4 -Syy \ python-qrcode \ python-pyserial \ python-pyudev \ + python-evdev \ python-setproctitle \ python-psutil \ python-netifaces \ diff --git a/testenv/linters/vulture-wl.py b/testenv/linters/vulture-wl.py index 2b308682..979ae4c8 100644 --- a/testenv/linters/vulture-wl.py +++ b/testenv/linters/vulture-wl.py @@ -29,6 +29,7 @@ _AtxApiPart.switch_power _UsbKey.arduino_modifier_code _KeyMapping.web_name +_KeyMapping.evdev_name _KeyMapping.mcu_code _KeyMapping.usb_key _KeyMapping.ps2_key diff --git a/testenv/tests/keyboard/test_keymap.py b/testenv/tests/keyboard/test_keymap.py deleted file mode 100644 index 8c5b3312..00000000 --- a/testenv/tests/keyboard/test_keymap.py +++ /dev/null @@ -1,35 +0,0 @@ -# ========================================================================== # -# # -# KVMD - The main PiKVM daemon. # -# # -# Copyright (C) 2018-2024 Maxim Devaev # -# # -# This program is free software: you can redistribute it and/or modify # -# it under the terms of the GNU General Public License as published by # -# the Free Software Foundation, either version 3 of the License, or # -# (at your option) any later version. # -# # -# This program is distributed in the hope that it will be useful, # -# but WITHOUT ANY WARRANTY; without even the implied warranty of # -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # -# GNU General Public License for more details. # -# # -# You should have received a copy of the GNU General Public License # -# along with this program. If not, see . # -# # -# ========================================================================== # - - -import pytest - -from kvmd.keyboard.mappings import KEYMAP - - -# ===== -def test_ok__keymap() -> None: - assert KEYMAP["KeyA"].mcu.code == 1 - - -def test_fail__keymap() -> None: - with pytest.raises(KeyError): - print(KEYMAP["keya"]) diff --git a/testenv/tests/validators/test_hid.py b/testenv/tests/validators/test_hid.py index a1031a13..f3fc32e1 100644 --- a/testenv/tests/validators/test_hid.py +++ b/testenv/tests/validators/test_hid.py @@ -24,7 +24,7 @@ from typing import Any import pytest -from kvmd.keyboard.mappings import KEYMAP +from kvmd.keyboard.mappings import WEB_TO_EVDEV from kvmd.validators import ValidatorError from kvmd.validators.hid import valid_hid_key @@ -35,7 +35,7 @@ from kvmd.validators.hid import valid_hid_mouse_delta # ===== def test_ok__valid_hid_key() -> None: - for key in KEYMAP: + for key in WEB_TO_EVDEV: print(valid_hid_key(key)) print(valid_hid_key(key + " "))