using evdev instead of string constants

This commit is contained in:
Maxim Devaev 2025-05-01 03:03:25 +03:00
parent 1624b0cbf8
commit ebbd55ee17
29 changed files with 692 additions and 539 deletions

View File

@ -82,6 +82,7 @@ depends=(
python-luma-oled python-luma-oled
python-pyusb python-pyusb
python-pyudev python-pyudev
python-evdev
"libgpiod>=2.1" "libgpiod>=2.1"
freetype2 freetype2
"v4l-utils>=1.22.1-1" "v4l-utils>=1.22.1-1"

View File

@ -69,6 +69,7 @@ class _X11Key:
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class _KeyMapping: class _KeyMapping:
web_name: str web_name: str
evdev_name: str
mcu_code: int mcu_code: int
usb_key: _UsbKey usb_key: _UsbKey
ps2_key: _Ps2Key ps2_key: _Ps2Key
@ -122,6 +123,7 @@ def _read_keymap_csv(path: str) -> list[_KeyMapping]:
if len(row) >= 6: if len(row) >= 6:
keymap.append(_KeyMapping( keymap.append(_KeyMapping(
web_name=row["web_name"], web_name=row["web_name"],
evdev_name=row["evdev_name"],
mcu_code=int(row["mcu_code"]), mcu_code=int(row["mcu_code"]),
usb_key=_parse_usb_key(row["usb_key"]), usb_key=_parse_usb_key(row["usb_key"]),
ps2_key=_parse_ps2_key(row["ps2_key"]), ps2_key=_parse_ps2_key(row["ps2_key"]),
@ -150,6 +152,7 @@ def main() -> None:
# Fields list: # Fields list:
# - Web # - Web
# - Linux/evdev
# - MCU code # - MCU code
# - USB code (^ for the modifier mask) # - USB code (^ for the modifier mask)
# - PS/2 key # - PS/2 key

View File

@ -1,112 +1,112 @@
web_name,mcu_code,usb_key,ps2_key,at1_code,x11_names web_name,evdev_name,mcu_code,usb_key,ps2_key,at1_code,x11_names
KeyA,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a" KeyA,KEY_A,1,0x04,reg:0x1c,0x1e,"^XK_A,XK_a"
KeyB,2,0x05,reg:0x32,0x30,"^XK_B,XK_b" KeyB,KEY_B,2,0x05,reg:0x32,0x30,"^XK_B,XK_b"
KeyC,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c" KeyC,KEY_C,3,0x06,reg:0x21,0x2e,"^XK_C,XK_c"
KeyD,4,0x07,reg:0x23,0x20,"^XK_D,XK_d" KeyD,KEY_D,4,0x07,reg:0x23,0x20,"^XK_D,XK_d"
KeyE,5,0x08,reg:0x24,0x12,"^XK_E,XK_e" KeyE,KEY_E,5,0x08,reg:0x24,0x12,"^XK_E,XK_e"
KeyF,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f" KeyF,KEY_F,6,0x09,reg:0x2b,0x21,"^XK_F,XK_f"
KeyG,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g" KeyG,KEY_G,7,0x0a,reg:0x34,0x22,"^XK_G,XK_g"
KeyH,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h" KeyH,KEY_H,8,0x0b,reg:0x33,0x23,"^XK_H,XK_h"
KeyI,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i" KeyI,KEY_I,9,0x0c,reg:0x43,0x17,"^XK_I,XK_i"
KeyJ,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j" KeyJ,KEY_J,10,0x0d,reg:0x3b,0x24,"^XK_J,XK_j"
KeyK,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k" KeyK,KEY_K,11,0x0e,reg:0x42,0x25,"^XK_K,XK_k"
KeyL,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l" KeyL,KEY_L,12,0x0f,reg:0x4b,0x26,"^XK_L,XK_l"
KeyM,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m" KeyM,KEY_M,13,0x10,reg:0x3a,0x32,"^XK_M,XK_m"
KeyN,14,0x11,reg:0x31,0x31,"^XK_N,XK_n" KeyN,KEY_N,14,0x11,reg:0x31,0x31,"^XK_N,XK_n"
KeyO,15,0x12,reg:0x44,0x18,"^XK_O,XK_o" KeyO,KEY_O,15,0x12,reg:0x44,0x18,"^XK_O,XK_o"
KeyP,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p" KeyP,KEY_P,16,0x13,reg:0x4d,0x19,"^XK_P,XK_p"
KeyQ,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q" KeyQ,KEY_Q,17,0x14,reg:0x15,0x10,"^XK_Q,XK_q"
KeyR,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r" KeyR,KEY_R,18,0x15,reg:0x2d,0x13,"^XK_R,XK_r"
KeyS,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s" KeyS,KEY_S,19,0x16,reg:0x1b,0x1f,"^XK_S,XK_s"
KeyT,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t" KeyT,KEY_T,20,0x17,reg:0x2c,0x14,"^XK_T,XK_t"
KeyU,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u" KeyU,KEY_U,21,0x18,reg:0x3c,0x16,"^XK_U,XK_u"
KeyV,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v" KeyV,KEY_V,22,0x19,reg:0x2a,0x2f,"^XK_V,XK_v"
KeyW,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w" KeyW,KEY_W,23,0x1a,reg:0x1d,0x11,"^XK_W,XK_w"
KeyX,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x" KeyX,KEY_X,24,0x1b,reg:0x22,0x2d,"^XK_X,XK_x"
KeyY,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y" KeyY,KEY_Y,25,0x1c,reg:0x35,0x15,"^XK_Y,XK_y"
KeyZ,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z" KeyZ,KEY_Z,26,0x1d,reg:0x1a,0x2c,"^XK_Z,XK_z"
Digit1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam" Digit1,KEY_1,27,0x1e,reg:0x16,0x02,"XK_1,^XK_exclam"
Digit2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at" Digit2,KEY_2,28,0x1f,reg:0x1e,0x03,"XK_2,^XK_at"
Digit3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign" Digit3,KEY_3,29,0x20,reg:0x26,0x04,"XK_3,^XK_numbersign"
Digit4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar" Digit4,KEY_4,30,0x21,reg:0x25,0x05,"XK_4,^XK_dollar"
Digit5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent" Digit5,KEY_5,31,0x22,reg:0x2e,0x06,"XK_5,^XK_percent"
Digit6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum" Digit6,KEY_6,32,0x23,reg:0x36,0x07,"XK_6,^XK_asciicircum"
Digit7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand" Digit7,KEY_7,33,0x24,reg:0x3d,0x08,"XK_7,^XK_ampersand"
Digit8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk" Digit8,KEY_8,34,0x25,reg:0x3e,0x09,"XK_8,^XK_asterisk"
Digit9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft" Digit9,KEY_9,35,0x26,reg:0x46,0x0a,"XK_9,^XK_parenleft"
Digit0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright" Digit0,KEY_0,36,0x27,reg:0x45,0x0b,"XK_0,^XK_parenright"
Enter,37,0x28,reg:0x5a,0x1c,XK_Return Enter,KEY_ENTER,37,0x28,reg:0x5a,0x1c,XK_Return
Escape,38,0x29,reg:0x76,0x01,XK_Escape Escape,KEY_ESC,38,0x29,reg:0x76,0x01,XK_Escape
Backspace,39,0x2a,reg:0x66,0x0e,XK_BackSpace Backspace,KEY_BACKSPACE,39,0x2a,reg:0x66,0x0e,XK_BackSpace
Tab,40,0x2b,reg:0x0d,0x0f,XK_Tab Tab,KEY_TAB,40,0x2b,reg:0x0d,0x0f,XK_Tab
Space,41,0x2c,reg:0x29,0x39,XK_space Space,KEY_SPACE,41,0x2c,reg:0x29,0x39,XK_space
Minus,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore" Minus,KEY_MINUS,42,0x2d,reg:0x4e,0x0c,"XK_minus,^XK_underscore"
Equal,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus" Equal,KEY_EQUAL,43,0x2e,reg:0x55,0x0d,"XK_equal,^XK_plus"
BracketLeft,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft" BracketLeft,KEY_LEFTBRACE,44,0x2f,reg:0x54,0x1a,"XK_bracketleft,^XK_braceleft"
BracketRight,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright" BracketRight,KEY_RIGHTBRACE,45,0x30,reg:0x5b,0x1b,"XK_bracketright,^XK_braceright"
Backslash,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar" Backslash,KEY_BACKSLASH,46,0x31,reg:0x5d,0x2b,"XK_backslash,^XK_bar"
Semicolon,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon" Semicolon,KEY_SEMICOLON,47,0x33,reg:0x4c,0x27,"XK_semicolon,^XK_colon"
Quote,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl" Quote,KEY_APOSTROPHE,48,0x34,reg:0x52,0x28,"XK_apostrophe,^XK_quotedbl"
Backquote,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde" Backquote,KEY_GRAVE,49,0x35,reg:0x0e,0x29,"XK_grave,^XK_asciitilde"
Comma,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less" Comma,KEY_COMMA,50,0x36,reg:0x41,0x33,"XK_comma,^XK_less"
Period,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater" Period,KEY_DOT,51,0x37,reg:0x49,0x34,"XK_period,^XK_greater"
Slash,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question" Slash,KEY_SLASH,52,0x38,reg:0x4a,0x35,"XK_slash,^XK_question"
CapsLock,53,0x39,reg:0x58,0x3a,XK_Caps_Lock CapsLock,KEY_CAPSLOCK,53,0x39,reg:0x58,0x3a,XK_Caps_Lock
F1,54,0x3a,reg:0x05,0x3b,XK_F1 F1,KEY_F1,54,0x3a,reg:0x05,0x3b,XK_F1
F2,55,0x3b,reg:0x06,0x3c,XK_F2 F2,KEY_F2,55,0x3b,reg:0x06,0x3c,XK_F2
F3,56,0x3c,reg:0x04,0x3d,XK_F3 F3,KEY_F3,56,0x3c,reg:0x04,0x3d,XK_F3
F4,57,0x3d,reg:0x0c,0x3e,XK_F4 F4,KEY_F4,57,0x3d,reg:0x0c,0x3e,XK_F4
F5,58,0x3e,reg:0x03,0x3f,XK_F5 F5,KEY_F5,58,0x3e,reg:0x03,0x3f,XK_F5
F6,59,0x3f,reg:0x0b,0x40,XK_F6 F6,KEY_F6,59,0x3f,reg:0x0b,0x40,XK_F6
F7,60,0x40,reg:0x83,0x41,XK_F7 F7,KEY_F7,60,0x40,reg:0x83,0x41,XK_F7
F8,61,0x41,reg:0x0a,0x42,XK_F8 F8,KEY_F8,61,0x41,reg:0x0a,0x42,XK_F8
F9,62,0x42,reg:0x01,0x43,XK_F9 F9,KEY_F9,62,0x42,reg:0x01,0x43,XK_F9
F10,63,0x43,reg:0x09,0x44,XK_F10 F10,KEY_F10,63,0x43,reg:0x09,0x44,XK_F10
F11,64,0x44,reg:0x78,0x57,XK_F11 F11,KEY_F11,64,0x44,reg:0x78,0x57,XK_F11
F12,65,0x45,reg:0x07,0x58,XK_F12 F12,KEY_F12,65,0x45,reg:0x07,0x58,XK_F12
PrintScreen,66,0x46,print:0xff,0x54,XK_Sys_Req PrintScreen,KEY_SYSRQ,66,0x46,print:0xff,0x54,XK_Sys_Req
Insert,67,0x49,spec:0x70,0xe052,XK_Insert Insert,KEY_INSERT,67,0x49,spec:0x70,0xe052,XK_Insert
Home,68,0x4a,spec:0x6c,0xe047,XK_Home Home,KEY_HOME,68,0x4a,spec:0x6c,0xe047,XK_Home
PageUp,69,0x4b,spec:0x7d,0xe049,XK_Page_Up PageUp,KEY_PAGEUP,69,0x4b,spec:0x7d,0xe049,XK_Page_Up
Delete,70,0x4c,spec:0x71,0xe053,XK_Delete Delete,KEY_DELETE,70,0x4c,spec:0x71,0xe053,XK_Delete
End,71,0x4d,spec:0x69,0xe04f,XK_End End,KEY_END,71,0x4d,spec:0x69,0xe04f,XK_End
PageDown,72,0x4e,spec:0x7a,0xe051,XK_Page_Down PageDown,KEY_PAGEDOWN,72,0x4e,spec:0x7a,0xe051,XK_Page_Down
ArrowRight,73,0x4f,spec:0x74,0xe04d,XK_Right ArrowRight,KEY_RIGHT,73,0x4f,spec:0x74,0xe04d,XK_Right
ArrowLeft,74,0x50,spec:0x6b,0xe04b,XK_Left ArrowLeft,KEY_LEFT,74,0x50,spec:0x6b,0xe04b,XK_Left
ArrowDown,75,0x51,spec:0x72,0xe050,XK_Down ArrowDown,KEY_DOWN,75,0x51,spec:0x72,0xe050,XK_Down
ArrowUp,76,0x52,spec:0x75,0xe048,XK_Up ArrowUp,KEY_UP,76,0x52,spec:0x75,0xe048,XK_Up
ControlLeft,77,^0x01,reg:0x14,0x1d,XK_Control_L ControlLeft,KEY_LEFTCTRL,77,^0x01,reg:0x14,0x1d,XK_Control_L
ShiftLeft,78,^0x02,reg:0x12,0x2a,XK_Shift_L ShiftLeft,KEY_LEFTSHIFT,78,^0x02,reg:0x12,0x2a,XK_Shift_L
AltLeft,79,^0x04,reg:0x11,0x38,XK_Alt_L AltLeft,KEY_LEFTALT,79,^0x04,reg:0x11,0x38,XK_Alt_L
MetaLeft,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L" MetaLeft,KEY_LEFTMETA,80,^0x08,spec:0x1f,0xe05b,"XK_Meta_L,XK_Super_L"
ControlRight,81,^0x10,spec:0x14,0xe01d,XK_Control_R ControlRight,KEY_RIGHTCTRL,81,^0x10,spec:0x14,0xe01d,XK_Control_R
ShiftRight,82,^0x20,reg:0x59,0x36,XK_Shift_R ShiftRight,KEY_RIGHTSHIFT,82,^0x20,reg:0x59,0x36,XK_Shift_R
AltRight,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift" AltRight,KEY_RIGHTALT,83,^0x40,spec:0x11,0xe038,"XK_Alt_R,XK_ISO_Level3_Shift"
MetaRight,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R" MetaRight,KEY_RIGHTMETA,84,^0x80,spec:0x27,0xe05c,"XK_Meta_R,XK_Super_R"
Pause,85,0x48,pause:0xff,0xe046,XK_Pause Pause,KEY_PAUSE,85,0x48,pause:0xff,0xe046,XK_Pause
ScrollLock,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock ScrollLock,KEY_SCROLLLOCK,86,0x47,reg:0x7e,0x46,XK_Scroll_Lock
NumLock,87,0x53,reg:0x77,0x45,XK_Num_Lock NumLock,KEY_NUMLOCK,87,0x53,reg:0x77,0x45,XK_Num_Lock
ContextMenu,88,0x65,spec:0x2f,0xe05d,XK_Menu ContextMenu,KEY_CONTEXT_MENU,88,0x65,spec:0x2f,0xe05d,XK_Menu
NumpadDivide,89,0x54,spec:0x4a,0xe035,XK_KP_Divide NumpadDivide,KEY_KPSLASH,89,0x54,spec:0x4a,0xe035,XK_KP_Divide
NumpadMultiply,90,0x55,reg:0x7c,0x37,XK_multiply NumpadMultiply,KEY_KPASTERISK,90,0x55,reg:0x7c,0x37,XK_multiply
NumpadSubtract,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract NumpadSubtract,KEY_KPMINUS,91,0x56,reg:0x7b,0x4a,XK_KP_Subtract
NumpadAdd,92,0x57,reg:0x79,0x4e,XK_KP_Add NumpadAdd,KEY_KPPLUS,92,0x57,reg:0x79,0x4e,XK_KP_Add
NumpadEnter,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter NumpadEnter,KEY_KPENTER,93,0x58,spec:0x5a,0xe01c,XK_KP_Enter
Numpad1,94,0x59,reg:0x69,0x4f,XK_KP_1 Numpad1,KEY_KP1,94,0x59,reg:0x69,0x4f,XK_KP_1
Numpad2,95,0x5a,reg:0x72,0x50,XK_KP_2 Numpad2,KEY_KP2,95,0x5a,reg:0x72,0x50,XK_KP_2
Numpad3,96,0x5b,reg:0x7a,0x51,XK_KP_3 Numpad3,KEY_KP3,96,0x5b,reg:0x7a,0x51,XK_KP_3
Numpad4,97,0x5c,reg:0x6b,0x4b,XK_KP_4 Numpad4,KEY_KP4,97,0x5c,reg:0x6b,0x4b,XK_KP_4
Numpad5,98,0x5d,reg:0x73,0x4c,XK_KP_5 Numpad5,KEY_KP5,98,0x5d,reg:0x73,0x4c,XK_KP_5
Numpad6,99,0x5e,reg:0x74,0x4d,XK_KP_6 Numpad6,KEY_KP6,99,0x5e,reg:0x74,0x4d,XK_KP_6
Numpad7,100,0x5f,reg:0x6c,0x47,XK_KP_7 Numpad7,KEY_KP7,100,0x5f,reg:0x6c,0x47,XK_KP_7
Numpad8,101,0x60,reg:0x75,0x48,XK_KP_8 Numpad8,KEY_KP8,101,0x60,reg:0x75,0x48,XK_KP_8
Numpad9,102,0x61,reg:0x7d,0x49,XK_KP_9 Numpad9,KEY_KP9,102,0x61,reg:0x7d,0x49,XK_KP_9
Numpad0,103,0x62,reg:0x70,0x52,XK_KP_0 Numpad0,KEY_KP0,103,0x62,reg:0x70,0x52,XK_KP_0
NumpadDecimal,104,0x63,reg:0x71,0x53,XK_KP_Decimal NumpadDecimal,KEY_KPDOT,104,0x63,reg:0x71,0x53,XK_KP_Decimal
Power,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep Power,KEY_POWER,105,0x66,spec:0x5e,0xe05e,XK_XF86_Sleep
IntlBackslash,106,0x64,reg:0x61,0x56,"" IntlBackslash,KEY_102ND,106,0x64,reg:0x61,0x56,
IntlYen,107,0x89,reg:0x6a,0x7d,"" IntlYen,KEY_YEN,107,0x89,reg:0x6a,0x7d,
IntlRo,108,0x87,reg:0x51,0x73,"" IntlRo,KEY_RO,108,0x87,reg:0x51,0x73,
KanaMode,109,0x88,reg:0x13,0x70,"" KanaMode,KEY_KATAKANA,109,0x88,reg:0x13,0x70,
Convert,110,0x8a,reg:0x64,0x79,"" Convert,KEY_HENKAN,110,0x8a,reg:0x64,0x79,
NonConvert,111,0x8b,reg:0x67,0x7b,"" NonConvert,KEY_MUHENKAN,111,0x8b,reg:0x67,0x7b,

1 web_name evdev_name mcu_code usb_key ps2_key at1_code x11_names
2 KeyA KEY_A 1 0x04 reg:0x1c 0x1e ^XK_A,XK_a
3 KeyB KEY_B 2 0x05 reg:0x32 0x30 ^XK_B,XK_b
4 KeyC KEY_C 3 0x06 reg:0x21 0x2e ^XK_C,XK_c
5 KeyD KEY_D 4 0x07 reg:0x23 0x20 ^XK_D,XK_d
6 KeyE KEY_E 5 0x08 reg:0x24 0x12 ^XK_E,XK_e
7 KeyF KEY_F 6 0x09 reg:0x2b 0x21 ^XK_F,XK_f
8 KeyG KEY_G 7 0x0a reg:0x34 0x22 ^XK_G,XK_g
9 KeyH KEY_H 8 0x0b reg:0x33 0x23 ^XK_H,XK_h
10 KeyI KEY_I 9 0x0c reg:0x43 0x17 ^XK_I,XK_i
11 KeyJ KEY_J 10 0x0d reg:0x3b 0x24 ^XK_J,XK_j
12 KeyK KEY_K 11 0x0e reg:0x42 0x25 ^XK_K,XK_k
13 KeyL KEY_L 12 0x0f reg:0x4b 0x26 ^XK_L,XK_l
14 KeyM KEY_M 13 0x10 reg:0x3a 0x32 ^XK_M,XK_m
15 KeyN KEY_N 14 0x11 reg:0x31 0x31 ^XK_N,XK_n
16 KeyO KEY_O 15 0x12 reg:0x44 0x18 ^XK_O,XK_o
17 KeyP KEY_P 16 0x13 reg:0x4d 0x19 ^XK_P,XK_p
18 KeyQ KEY_Q 17 0x14 reg:0x15 0x10 ^XK_Q,XK_q
19 KeyR KEY_R 18 0x15 reg:0x2d 0x13 ^XK_R,XK_r
20 KeyS KEY_S 19 0x16 reg:0x1b 0x1f ^XK_S,XK_s
21 KeyT KEY_T 20 0x17 reg:0x2c 0x14 ^XK_T,XK_t
22 KeyU KEY_U 21 0x18 reg:0x3c 0x16 ^XK_U,XK_u
23 KeyV KEY_V 22 0x19 reg:0x2a 0x2f ^XK_V,XK_v
24 KeyW KEY_W 23 0x1a reg:0x1d 0x11 ^XK_W,XK_w
25 KeyX KEY_X 24 0x1b reg:0x22 0x2d ^XK_X,XK_x
26 KeyY KEY_Y 25 0x1c reg:0x35 0x15 ^XK_Y,XK_y
27 KeyZ KEY_Z 26 0x1d reg:0x1a 0x2c ^XK_Z,XK_z
28 Digit1 KEY_1 27 0x1e reg:0x16 0x02 XK_1,^XK_exclam
29 Digit2 KEY_2 28 0x1f reg:0x1e 0x03 XK_2,^XK_at
30 Digit3 KEY_3 29 0x20 reg:0x26 0x04 XK_3,^XK_numbersign
31 Digit4 KEY_4 30 0x21 reg:0x25 0x05 XK_4,^XK_dollar
32 Digit5 KEY_5 31 0x22 reg:0x2e 0x06 XK_5,^XK_percent
33 Digit6 KEY_6 32 0x23 reg:0x36 0x07 XK_6,^XK_asciicircum
34 Digit7 KEY_7 33 0x24 reg:0x3d 0x08 XK_7,^XK_ampersand
35 Digit8 KEY_8 34 0x25 reg:0x3e 0x09 XK_8,^XK_asterisk
36 Digit9 KEY_9 35 0x26 reg:0x46 0x0a XK_9,^XK_parenleft
37 Digit0 KEY_0 36 0x27 reg:0x45 0x0b XK_0,^XK_parenright
38 Enter KEY_ENTER 37 0x28 reg:0x5a 0x1c XK_Return
39 Escape KEY_ESC 38 0x29 reg:0x76 0x01 XK_Escape
40 Backspace KEY_BACKSPACE 39 0x2a reg:0x66 0x0e XK_BackSpace
41 Tab KEY_TAB 40 0x2b reg:0x0d 0x0f XK_Tab
42 Space KEY_SPACE 41 0x2c reg:0x29 0x39 XK_space
43 Minus KEY_MINUS 42 0x2d reg:0x4e 0x0c XK_minus,^XK_underscore
44 Equal KEY_EQUAL 43 0x2e reg:0x55 0x0d XK_equal,^XK_plus
45 BracketLeft KEY_LEFTBRACE 44 0x2f reg:0x54 0x1a XK_bracketleft,^XK_braceleft
46 BracketRight KEY_RIGHTBRACE 45 0x30 reg:0x5b 0x1b XK_bracketright,^XK_braceright
47 Backslash KEY_BACKSLASH 46 0x31 reg:0x5d 0x2b XK_backslash,^XK_bar
48 Semicolon KEY_SEMICOLON 47 0x33 reg:0x4c 0x27 XK_semicolon,^XK_colon
49 Quote KEY_APOSTROPHE 48 0x34 reg:0x52 0x28 XK_apostrophe,^XK_quotedbl
50 Backquote KEY_GRAVE 49 0x35 reg:0x0e 0x29 XK_grave,^XK_asciitilde
51 Comma KEY_COMMA 50 0x36 reg:0x41 0x33 XK_comma,^XK_less
52 Period KEY_DOT 51 0x37 reg:0x49 0x34 XK_period,^XK_greater
53 Slash KEY_SLASH 52 0x38 reg:0x4a 0x35 XK_slash,^XK_question
54 CapsLock KEY_CAPSLOCK 53 0x39 reg:0x58 0x3a XK_Caps_Lock
55 F1 KEY_F1 54 0x3a reg:0x05 0x3b XK_F1
56 F2 KEY_F2 55 0x3b reg:0x06 0x3c XK_F2
57 F3 KEY_F3 56 0x3c reg:0x04 0x3d XK_F3
58 F4 KEY_F4 57 0x3d reg:0x0c 0x3e XK_F4
59 F5 KEY_F5 58 0x3e reg:0x03 0x3f XK_F5
60 F6 KEY_F6 59 0x3f reg:0x0b 0x40 XK_F6
61 F7 KEY_F7 60 0x40 reg:0x83 0x41 XK_F7
62 F8 KEY_F8 61 0x41 reg:0x0a 0x42 XK_F8
63 F9 KEY_F9 62 0x42 reg:0x01 0x43 XK_F9
64 F10 KEY_F10 63 0x43 reg:0x09 0x44 XK_F10
65 F11 KEY_F11 64 0x44 reg:0x78 0x57 XK_F11
66 F12 KEY_F12 65 0x45 reg:0x07 0x58 XK_F12
67 PrintScreen KEY_SYSRQ 66 0x46 print:0xff 0x54 XK_Sys_Req
68 Insert KEY_INSERT 67 0x49 spec:0x70 0xe052 XK_Insert
69 Home KEY_HOME 68 0x4a spec:0x6c 0xe047 XK_Home
70 PageUp KEY_PAGEUP 69 0x4b spec:0x7d 0xe049 XK_Page_Up
71 Delete KEY_DELETE 70 0x4c spec:0x71 0xe053 XK_Delete
72 End KEY_END 71 0x4d spec:0x69 0xe04f XK_End
73 PageDown KEY_PAGEDOWN 72 0x4e spec:0x7a 0xe051 XK_Page_Down
74 ArrowRight KEY_RIGHT 73 0x4f spec:0x74 0xe04d XK_Right
75 ArrowLeft KEY_LEFT 74 0x50 spec:0x6b 0xe04b XK_Left
76 ArrowDown KEY_DOWN 75 0x51 spec:0x72 0xe050 XK_Down
77 ArrowUp KEY_UP 76 0x52 spec:0x75 0xe048 XK_Up
78 ControlLeft KEY_LEFTCTRL 77 ^0x01 reg:0x14 0x1d XK_Control_L
79 ShiftLeft KEY_LEFTSHIFT 78 ^0x02 reg:0x12 0x2a XK_Shift_L
80 AltLeft KEY_LEFTALT 79 ^0x04 reg:0x11 0x38 XK_Alt_L
81 MetaLeft KEY_LEFTMETA 80 ^0x08 spec:0x1f 0xe05b XK_Meta_L,XK_Super_L
82 ControlRight KEY_RIGHTCTRL 81 ^0x10 spec:0x14 0xe01d XK_Control_R
83 ShiftRight KEY_RIGHTSHIFT 82 ^0x20 reg:0x59 0x36 XK_Shift_R
84 AltRight KEY_RIGHTALT 83 ^0x40 spec:0x11 0xe038 XK_Alt_R,XK_ISO_Level3_Shift
85 MetaRight KEY_RIGHTMETA 84 ^0x80 spec:0x27 0xe05c XK_Meta_R,XK_Super_R
86 Pause KEY_PAUSE 85 0x48 pause:0xff 0xe046 XK_Pause
87 ScrollLock KEY_SCROLLLOCK 86 0x47 reg:0x7e 0x46 XK_Scroll_Lock
88 NumLock KEY_NUMLOCK 87 0x53 reg:0x77 0x45 XK_Num_Lock
89 ContextMenu KEY_CONTEXT_MENU 88 0x65 spec:0x2f 0xe05d XK_Menu
90 NumpadDivide KEY_KPSLASH 89 0x54 spec:0x4a 0xe035 XK_KP_Divide
91 NumpadMultiply KEY_KPASTERISK 90 0x55 reg:0x7c 0x37 XK_multiply
92 NumpadSubtract KEY_KPMINUS 91 0x56 reg:0x7b 0x4a XK_KP_Subtract
93 NumpadAdd KEY_KPPLUS 92 0x57 reg:0x79 0x4e XK_KP_Add
94 NumpadEnter KEY_KPENTER 93 0x58 spec:0x5a 0xe01c XK_KP_Enter
95 Numpad1 KEY_KP1 94 0x59 reg:0x69 0x4f XK_KP_1
96 Numpad2 KEY_KP2 95 0x5a reg:0x72 0x50 XK_KP_2
97 Numpad3 KEY_KP3 96 0x5b reg:0x7a 0x51 XK_KP_3
98 Numpad4 KEY_KP4 97 0x5c reg:0x6b 0x4b XK_KP_4
99 Numpad5 KEY_KP5 98 0x5d reg:0x73 0x4c XK_KP_5
100 Numpad6 KEY_KP6 99 0x5e reg:0x74 0x4d XK_KP_6
101 Numpad7 KEY_KP7 100 0x5f reg:0x6c 0x47 XK_KP_7
102 Numpad8 KEY_KP8 101 0x60 reg:0x75 0x48 XK_KP_8
103 Numpad9 KEY_KP9 102 0x61 reg:0x7d 0x49 XK_KP_9
104 Numpad0 KEY_KP0 103 0x62 reg:0x70 0x52 XK_KP_0
105 NumpadDecimal KEY_KPDOT 104 0x63 reg:0x71 0x53 XK_KP_Decimal
106 Power KEY_POWER 105 0x66 spec:0x5e 0xe05e XK_XF86_Sleep
107 IntlBackslash KEY_102ND 106 0x64 reg:0x61 0x56
108 IntlYen KEY_YEN 107 0x89 reg:0x6a 0x7d
109 IntlRo KEY_RO 108 0x87 reg:0x51 0x73
110 KanaMode KEY_KATAKANA 109 0x88 reg:0x13 0x70
111 Convert KEY_HENKAN 110 0x8a reg:0x64 0x79
112 NonConvert KEY_MUHENKAN 111 0x8b reg:0x67 0x7b

View File

@ -31,8 +31,11 @@ from typing import Callable
from aiohttp.web import Request from aiohttp.web import Request
from aiohttp.web import Response from aiohttp.web import Response
from ....keyboard.mappings import WEB_TO_EVDEV
from ....keyboard.keysym import build_symmap 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_http
from ....htserver import exposed_ws from ....htserver import exposed_ws
@ -124,10 +127,10 @@ class HidApi:
text = text[:limit] text = text[:limit]
symmap = self.__ensure_symmap(req.query.get("keymap", self.__default_keymap_name)) symmap = self.__ensure_symmap(req.query.get("keymap", self.__default_keymap_name))
slow = valid_bool(req.query.get("slow", False)) 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() 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") keymap_name = valid_printable_filename(keymap_name, "keymap")
path = os.path.join(self.__keymaps_dir_path, keymap_name) path = os.path.join(self.__keymaps_dir_path, keymap_name)
try: try:
@ -139,7 +142,7 @@ class HidApi:
return self.__inner_ensure_symmap(path, st.st_mtime) return self.__inner_ensure_symmap(path, st.st_mtime)
@functools.lru_cache(maxsize=10) @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 _ = mod_ts # For LRU
return build_symmap(path) return build_symmap(path)
@ -148,9 +151,12 @@ class HidApi:
@exposed_ws(1) @exposed_ws(1)
async def __ws_bin_key_handler(self, _: WsSession, data: bytes) -> None: async def __ws_bin_key_handler(self, _: WsSession, data: bytes) -> None:
try: try:
key = valid_hid_key(data[1:].decode("ascii"))
state = bool(data[0] & 0b01) state = bool(data[0] & 0b01)
finish = bool(data[0] & 0b10) 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: except Exception:
return return
self.__hid.send_key_event(key, state, finish) self.__hid.send_key_event(key, state, finish)
@ -158,7 +164,11 @@ class HidApi:
@exposed_ws(2) @exposed_ws(2)
async def __ws_bin_mouse_button_handler(self, _: WsSession, data: bytes) -> None: async def __ws_bin_mouse_button_handler(self, _: WsSession, data: bytes) -> None:
try: 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) state = bool(data[0] & 0b01)
except Exception: except Exception:
return return
@ -199,7 +209,7 @@ class HidApi:
@exposed_ws("key") @exposed_ws("key")
async def __ws_key_handler(self, _: WsSession, event: dict) -> None: async def __ws_key_handler(self, _: WsSession, event: dict) -> None:
try: try:
key = valid_hid_key(event["key"]) key = WEB_TO_EVDEV[valid_hid_key(event["key"])]
state = valid_bool(event["state"]) state = valid_bool(event["state"])
finish = valid_bool(event.get("finish", False)) finish = valid_bool(event.get("finish", False))
except Exception: except Exception:
@ -209,7 +219,7 @@ class HidApi:
@exposed_ws("mouse_button") @exposed_ws("mouse_button")
async def __ws_mouse_button_handler(self, _: WsSession, event: dict) -> None: async def __ws_mouse_button_handler(self, _: WsSession, event: dict) -> None:
try: try:
button = valid_hid_mouse_button(event["button"]) button = MOUSE_TO_EVDEV[valid_hid_mouse_button(event["button"])]
state = valid_bool(event["state"]) state = valid_bool(event["state"])
except Exception: except Exception:
return return
@ -248,7 +258,7 @@ class HidApi:
@exposed_http("POST", "/hid/events/send_key") @exposed_http("POST", "/hid/events/send_key")
async def __events_send_key_handler(self, req: Request) -> Response: 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: if "state" in req.query:
state = valid_bool(req.query["state"]) state = valid_bool(req.query["state"])
finish = valid_bool(req.query.get("finish", False)) finish = valid_bool(req.query.get("finish", False))
@ -259,7 +269,7 @@ class HidApi:
@exposed_http("POST", "/hid/events/send_mouse_button") @exposed_http("POST", "/hid/events/send_mouse_button")
async def __events_send_mouse_button_handler(self, req: Request) -> Response: 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: if "state" in req.query:
state = valid_bool(req.query["state"]) state = valid_bool(req.query["state"])
self.__hid.send_mouse_button_event(button, state) self.__hid.send_mouse_button_event(button, state)

View File

@ -31,6 +31,8 @@ from ... import aiotools
from ...plugins.hid import BaseHid from ...plugins.hid import BaseHid
from ...keyboard.mappings import WEB_TO_EVDEV
from .streamer import Streamer from .streamer import Streamer
@ -63,7 +65,7 @@ class Snapshoter: # pylint: disable=too-many-instance-attributes
else: else:
self.__idle_interval = self.__live_interval = 0.0 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.__wakeup_move = wakeup_move
self.__online_delay = online_delay self.__online_delay = online_delay
@ -121,8 +123,8 @@ class Snapshoter: # pylint: disable=too-many-instance-attributes
async def __wakeup(self) -> None: async def __wakeup(self) -> None:
logger = get_logger(0) logger = get_logger(0)
if self.__wakeup_key: if self.__wakeup_key > 0:
logger.info("Waking up using key %r ...", self.__wakeup_key) logger.info("Waking up using keyboard ...")
await self.__hid.send_key_events( await self.__hid.send_key_events(
keys=[(self.__wakeup_key, True), (self.__wakeup_key, False)], keys=[(self.__wakeup_key, True), (self.__wakeup_key, False)],
no_ignore_keys=True, no_ignore_keys=True,

View File

@ -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: async def _on_ext_key_event(self, code: int, state: bool) -> None:
raise NotImplementedError 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 raise NotImplementedError
async def _on_cut_event(self, text: str) -> None: 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 sr = self.__scroll_rate
await self._on_pointer_event( await self._on_pointer_event(
buttons={ buttons={
"left": bool(buttons & 0x1), "left": bool(buttons & 0x1),
"right": bool(buttons & 0x4), "right": bool(buttons & 0x4),
"middle": bool(buttons & 0x2), "middle": bool(buttons & 0x2),
"up": bool(ext_buttons & 0x2), "up": bool(ext_buttons & 0x2),
"down": bool(ext_buttons & 0x1), "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),
}, },
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: async def __handle_client_cut_text(self) -> None:

View File

@ -32,9 +32,11 @@ from ...logging import get_logger
from ...keyboard.keysym import SymmapModifiers from ...keyboard.keysym import SymmapModifiers
from ...keyboard.keysym import build_symmap 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 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 KvmdClientWs
from ...clients.kvmd import KvmdClientSession from ...clients.kvmd import KvmdClientSession
@ -80,7 +82,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
desired_fps: int, desired_fps: int,
mouse_output: str, mouse_output: str,
keymap_name: str, keymap_name: str,
symmap: dict[int, dict[int, str]], symmap: dict[int, dict[int, int]],
scroll_rate: int, scroll_rate: int,
allow_cut_after: float, allow_cut_after: float,
@ -132,8 +134,8 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
# Эти состояния шарить не обязательно - бекенд исключает дублирующиеся события. # Эти состояния шарить не обязательно - бекенд исключает дублирующиеся события.
# Все это нужно только чтобы не посылать лишние жсоны в сокет KVMD # Все это нужно только чтобы не посылать лишние жсоны в сокет KVMD
self.__mouse_buttons: dict[str, (bool | None)] = dict.fromkeys(["left", "right", "middle", "up", "down"], None) self.__mouse_buttons: dict[str, (bool | None)] = dict.fromkeys(MOUSE_TO_EVDEV, None)
self.__mouse_move = {"x": -1, "y": -1} self.__mouse_move = (-1, -1) # (X, Y)
self.__modifiers = 0 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: 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) variants = self.__symmap.get(code)
fake_shift = False fake_shift = False
if variants: if variants:
if is_modifier: if is_modifier:
web_key = variants.get(0) key = variants.get(0)
else: else:
web_key = variants.get(self.__modifiers) key = variants.get(self.__modifiers)
if web_key is None: if key is None:
web_key = variants.get(0) 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: # JUMP doesn't send shift events:
# - https://github.com/pikvm/pikvm/issues/820 # - https://github.com/pikvm/pikvm/issues/820
web_key = variants[SymmapModifiers.SHIFT] key = variants[SymmapModifiers.SHIFT]
fake_shift = True fake_shift = True
if web_key and self.__kvmd_ws: if key and self.__kvmd_ws:
if fake_shift: if fake_shift:
await self.__kvmd_ws.send_key_event(WebModifiers.SHIFT_LEFT, True) await self.__kvmd_ws.send_key_event(EvdevModifiers.SHIFT_LEFT, True)
await self.__kvmd_ws.send_key_event(web_key, state) await self.__kvmd_ws.send_key_event(key, state)
if fake_shift: 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: async def _on_ext_key_event(self, code: int, state: bool) -> None:
web_key = AT1_TO_WEB.get(code) key = AT1_TO_EVDEV.get(code, 0)
if web_key: if key:
self.__switch_modifiers(web_key, state) # Предполагаем, что модификаторы всегда известны self.__switch_modifiers_evdev(key, state) # Предполагаем, что модификаторы всегда известны
if self.__kvmd_ws: 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 mod = 0
if key in X11Modifiers.SHIFTS or key in WebModifiers.SHIFTS: if key in X11Modifiers.SHIFTS:
mod = SymmapModifiers.SHIFT mod = SymmapModifiers.SHIFT
elif key == X11Modifiers.ALTGR or key == WebModifiers.ALT_RIGHT: elif key == X11Modifiers.ALTGR:
mod = SymmapModifiers.ALTGR mod = SymmapModifiers.ALTGR
elif key in X11Modifiers.CTRLS or key in WebModifiers.CTRLS: elif key in X11Modifiers.CTRLS:
mod = SymmapModifiers.CTRL mod = SymmapModifiers.CTRL
if mod == 0: if mod == 0:
return False return False
@ -385,18 +387,34 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
self.__modifiers &= ~mod self.__modifiers &= ~mod
return True 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 self.__kvmd_ws:
if wheel["x"] or wheel["y"]: if wheel[0] or wheel[1]:
await self.__kvmd_ws.send_mouse_wheel_event(wheel["x"], wheel["y"]) await self.__kvmd_ws.send_mouse_wheel_event(*wheel)
if self.__mouse_move != move: 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 self.__mouse_move = move
for (button, state) in buttons.items(): for (button, state) in buttons.items():
if self.__mouse_buttons[button] != state: 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 self.__mouse_buttons[button] = state
async def _on_cut_event(self, text: str) -> None: async def _on_cut_event(self, text: str) -> None:

View File

@ -182,22 +182,22 @@ class KvmdClientWs:
finally: finally:
self.__communicated = False self.__communicated = False
async def send_key_event(self, key: str, state: bool) -> None: async def send_key_event(self, key: int, state: bool) -> None:
mask = (0b01 if state else 0) mask = (0b10000000 | int(bool(state)))
await self.__writer_queue.put(bytes([1, mask]) + key.encode("ascii")) await self.__writer_queue.put(struct.pack(">BBH", 1, mask, key))
async def send_mouse_button_event(self, button: str, state: bool) -> None: async def send_mouse_button_event(self, button: int, state: bool) -> None:
mask = (0b01 if state else 0) mask = (0b10000000 | int(bool(state)))
await self.__writer_queue.put(bytes([2, mask]) + button.encode("ascii")) 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: 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: 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: 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): class KvmdClientSession(BaseHttpClientSession):

View File

@ -30,9 +30,9 @@ import Xlib.keysymdef
from ..logging import get_logger from ..logging import get_logger
from .mappings import At1Key from .mappings import At1Key
from .mappings import WebModifiers from .mappings import EvdevModifiers
from .mappings import X11_TO_AT1 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 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 # https://github.com/qemu/qemu/blob/95a9457fd44ad97c518858a4e1586a5498f9773c/ui/keymaps.c
logger = get_logger() logger = get_logger()
symmap: dict[int, dict[int, str]] = {} symmap: dict[int, dict[int, int]] = {}
for (src, items) in [ for (src, items) in [
(path, list(_read_keyboard_layout(path).items())), (path, list(_read_keyboard_layout(path).items())),
("<builtin>", list(X11_TO_AT1.items())), ("<builtin>", 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 (code, keys) in items:
for key in keys: for key in keys:
web_name = AT1_TO_WEB.get(key.code) evdev_code = AT1_TO_EVDEV.get(key.code)
if web_name is not None: if evdev_code is not None:
if ( if (
(web_name in WebModifiers.SHIFTS and key.shift) # pylint: disable=too-many-boolean-expressions (evdev_code in EvdevModifiers.SHIFTS and key.shift) # pylint: disable=too-many-boolean-expressions
or (web_name in WebModifiers.ALTS and key.altgr) or (evdev_code in EvdevModifiers.ALTS and key.altgr)
or (web_name in WebModifiers.CTRLS and key.ctrl) 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 continue
modifiers = ( modifiers = (
@ -75,7 +75,7 @@ def build_symmap(path: str) -> dict[int, dict[int, str]]: # x11 keysym -> [(mod
) )
if code not in symmap: if code not in symmap:
symmap[code] = {} symmap[code] = {}
symmap[code].setdefault(modifiers, web_name) symmap[code].setdefault(modifiers, evdev_code)
return symmap return symmap

View File

@ -22,6 +22,8 @@
import dataclasses import dataclasses
from evdev import ecodes
# ===== # =====
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
@ -31,7 +33,7 @@ class McuKey:
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class UsbKey: class UsbKey:
code: int code: int
is_modifier: bool is_modifier: bool
@ -41,137 +43,252 @@ class Key:
usb: UsbKey usb: UsbKey
KEYMAP: dict[str, Key] = { KEYMAP: dict[int, Key] = {
"KeyA": Key(mcu=McuKey(code=1), usb=UsbKey(code=4, is_modifier=False)), ecodes.KEY_A: 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)), ecodes.KEY_B: 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)), ecodes.KEY_C: 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)), ecodes.KEY_D: 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)), ecodes.KEY_E: 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)), ecodes.KEY_F: 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)), ecodes.KEY_G: 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)), ecodes.KEY_H: 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)), ecodes.KEY_I: 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)), ecodes.KEY_J: 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)), ecodes.KEY_K: 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)), ecodes.KEY_L: 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)), ecodes.KEY_M: 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)), ecodes.KEY_N: 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)), ecodes.KEY_O: 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)), ecodes.KEY_P: 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)), ecodes.KEY_Q: 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)), ecodes.KEY_R: 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)), ecodes.KEY_S: 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)), ecodes.KEY_T: 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)), ecodes.KEY_U: 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)), ecodes.KEY_V: 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)), ecodes.KEY_W: 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)), ecodes.KEY_X: 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)), ecodes.KEY_Y: 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)), ecodes.KEY_Z: 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)), ecodes.KEY_1: 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)), ecodes.KEY_2: 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)), ecodes.KEY_3: 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)), ecodes.KEY_4: 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)), ecodes.KEY_5: 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)), ecodes.KEY_6: 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)), ecodes.KEY_7: 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)), ecodes.KEY_8: 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)), ecodes.KEY_9: 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)), ecodes.KEY_0: 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)), ecodes.KEY_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)), ecodes.KEY_ESC: 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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_LEFTBRACE: 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)), ecodes.KEY_RIGHTBRACE: 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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_APOSTROPHE: 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)), ecodes.KEY_GRAVE: 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)), ecodes.KEY_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)), ecodes.KEY_DOT: 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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_SYSRQ: 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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_RIGHT: 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)), ecodes.KEY_LEFT: 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)), ecodes.KEY_DOWN: 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)), ecodes.KEY_UP: 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)), ecodes.KEY_LEFTCTRL: 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)), ecodes.KEY_LEFTSHIFT: 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)), ecodes.KEY_LEFTALT: 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)), ecodes.KEY_LEFTMETA: 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)), ecodes.KEY_RIGHTCTRL: 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)), ecodes.KEY_RIGHTSHIFT: 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)), ecodes.KEY_RIGHTALT: 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)), ecodes.KEY_RIGHTMETA: 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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_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)), ecodes.KEY_CONTEXT_MENU: 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)), ecodes.KEY_KPSLASH: 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)), ecodes.KEY_KPASTERISK: 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)), ecodes.KEY_KPMINUS: 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)), ecodes.KEY_KPPLUS: 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)), ecodes.KEY_KPENTER: 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)), ecodes.KEY_KP1: 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)), ecodes.KEY_KP2: 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)), ecodes.KEY_KP3: 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)), ecodes.KEY_KP4: 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)), ecodes.KEY_KP5: 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)), ecodes.KEY_KP6: 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)), ecodes.KEY_KP7: 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)), ecodes.KEY_KP8: 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)), ecodes.KEY_KP9: 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)), ecodes.KEY_KP0: 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)), ecodes.KEY_KPDOT: 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)), ecodes.KEY_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)), ecodes.KEY_102ND: 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)), ecodes.KEY_YEN: 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)), ecodes.KEY_RO: 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)), ecodes.KEY_KATAKANA: 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)), ecodes.KEY_HENKAN: 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)), 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: class EvdevModifiers:
SHIFT_LEFT = "ShiftLeft" SHIFT_LEFT = ecodes.KEY_LEFTSHIFT
SHIFT_RIGHT = "ShiftRight" SHIFT_RIGHT = ecodes.KEY_RIGHTSHIFT
SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT])
ALT_LEFT = "AltLeft" ALT_LEFT = ecodes.KEY_LEFTALT
ALT_RIGHT = "AltRight" ALT_RIGHT = ecodes.KEY_RIGHTALT
ALTS = set([ALT_LEFT, ALT_RIGHT]) ALTS = set([ALT_LEFT, ALT_RIGHT])
CTRL_LEFT = "ControlLeft" CTRL_LEFT = ecodes.KEY_LEFTCTRL
CTRL_RIGHT = "ControlRight" CTRL_RIGHT = ecodes.KEY_RIGHTCTRL
CTRLS = set([CTRL_LEFT, CTRL_RIGHT]) CTRLS = set([CTRL_LEFT, CTRL_RIGHT])
META_LEFT = "MetaLeft" META_LEFT = ecodes.KEY_LEFTMETA
META_RIGHT = "MetaRight" META_RIGHT = ecodes.KEY_RIGHTMETA
METAS = set([META_LEFT, META_RIGHT]) METAS = set([META_LEFT, META_RIGHT])
ALL = (SHIFTS | ALTS | CTRLS | METAS) ALL = (SHIFTS | ALTS | CTRLS | METAS)
@ -192,10 +309,10 @@ class X11Modifiers:
# ===== # =====
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class At1Key: class At1Key:
code: int code: int
shift: bool shift: bool
altgr: bool = False altgr: bool = False
ctrl: bool = False ctrl: bool = False
X11_TO_AT1 = { X11_TO_AT1 = {
@ -357,116 +474,116 @@ X11_TO_AT1 = {
} }
AT1_TO_WEB = { AT1_TO_EVDEV = {
1: "Escape", 1: ecodes.KEY_ESC,
2: "Digit1", 2: ecodes.KEY_1,
3: "Digit2", 3: ecodes.KEY_2,
4: "Digit3", 4: ecodes.KEY_3,
5: "Digit4", 5: ecodes.KEY_4,
6: "Digit5", 6: ecodes.KEY_5,
7: "Digit6", 7: ecodes.KEY_6,
8: "Digit7", 8: ecodes.KEY_7,
9: "Digit8", 9: ecodes.KEY_8,
10: "Digit9", 10: ecodes.KEY_9,
11: "Digit0", 11: ecodes.KEY_0,
12: "Minus", 12: ecodes.KEY_MINUS,
13: "Equal", 13: ecodes.KEY_EQUAL,
14: "Backspace", 14: ecodes.KEY_BACKSPACE,
15: "Tab", 15: ecodes.KEY_TAB,
16: "KeyQ", 16: ecodes.KEY_Q,
17: "KeyW", 17: ecodes.KEY_W,
18: "KeyE", 18: ecodes.KEY_E,
19: "KeyR", 19: ecodes.KEY_R,
20: "KeyT", 20: ecodes.KEY_T,
21: "KeyY", 21: ecodes.KEY_Y,
22: "KeyU", 22: ecodes.KEY_U,
23: "KeyI", 23: ecodes.KEY_I,
24: "KeyO", 24: ecodes.KEY_O,
25: "KeyP", 25: ecodes.KEY_P,
26: "BracketLeft", 26: ecodes.KEY_LEFTBRACE,
27: "BracketRight", 27: ecodes.KEY_RIGHTBRACE,
28: "Enter", 28: ecodes.KEY_ENTER,
29: "ControlLeft", 29: ecodes.KEY_LEFTCTRL,
30: "KeyA", 30: ecodes.KEY_A,
31: "KeyS", 31: ecodes.KEY_S,
32: "KeyD", 32: ecodes.KEY_D,
33: "KeyF", 33: ecodes.KEY_F,
34: "KeyG", 34: ecodes.KEY_G,
35: "KeyH", 35: ecodes.KEY_H,
36: "KeyJ", 36: ecodes.KEY_J,
37: "KeyK", 37: ecodes.KEY_K,
38: "KeyL", 38: ecodes.KEY_L,
39: "Semicolon", 39: ecodes.KEY_SEMICOLON,
40: "Quote", 40: ecodes.KEY_APOSTROPHE,
41: "Backquote", 41: ecodes.KEY_GRAVE,
42: "ShiftLeft", 42: ecodes.KEY_LEFTSHIFT,
43: "Backslash", 43: ecodes.KEY_BACKSLASH,
44: "KeyZ", 44: ecodes.KEY_Z,
45: "KeyX", 45: ecodes.KEY_X,
46: "KeyC", 46: ecodes.KEY_C,
47: "KeyV", 47: ecodes.KEY_V,
48: "KeyB", 48: ecodes.KEY_B,
49: "KeyN", 49: ecodes.KEY_N,
50: "KeyM", 50: ecodes.KEY_M,
51: "Comma", 51: ecodes.KEY_COMMA,
52: "Period", 52: ecodes.KEY_DOT,
53: "Slash", 53: ecodes.KEY_SLASH,
54: "ShiftRight", 54: ecodes.KEY_RIGHTSHIFT,
55: "NumpadMultiply", 55: ecodes.KEY_KPASTERISK,
56: "AltLeft", 56: ecodes.KEY_LEFTALT,
57: "Space", 57: ecodes.KEY_SPACE,
58: "CapsLock", 58: ecodes.KEY_CAPSLOCK,
59: "F1", 59: ecodes.KEY_F1,
60: "F2", 60: ecodes.KEY_F2,
61: "F3", 61: ecodes.KEY_F3,
62: "F4", 62: ecodes.KEY_F4,
63: "F5", 63: ecodes.KEY_F5,
64: "F6", 64: ecodes.KEY_F6,
65: "F7", 65: ecodes.KEY_F7,
66: "F8", 66: ecodes.KEY_F8,
67: "F9", 67: ecodes.KEY_F9,
68: "F10", 68: ecodes.KEY_F10,
69: "NumLock", 69: ecodes.KEY_NUMLOCK,
70: "ScrollLock", 70: ecodes.KEY_SCROLLLOCK,
71: "Numpad7", 71: ecodes.KEY_KP7,
72: "Numpad8", 72: ecodes.KEY_KP8,
73: "Numpad9", 73: ecodes.KEY_KP9,
74: "NumpadSubtract", 74: ecodes.KEY_KPMINUS,
75: "Numpad4", 75: ecodes.KEY_KP4,
76: "Numpad5", 76: ecodes.KEY_KP5,
77: "Numpad6", 77: ecodes.KEY_KP6,
78: "NumpadAdd", 78: ecodes.KEY_KPPLUS,
79: "Numpad1", 79: ecodes.KEY_KP1,
80: "Numpad2", 80: ecodes.KEY_KP2,
81: "Numpad3", 81: ecodes.KEY_KP3,
82: "Numpad0", 82: ecodes.KEY_KP0,
83: "NumpadDecimal", 83: ecodes.KEY_KPDOT,
84: "PrintScreen", 84: ecodes.KEY_SYSRQ,
86: "IntlBackslash", 86: ecodes.KEY_102ND,
87: "F11", 87: ecodes.KEY_F11,
88: "F12", 88: ecodes.KEY_F12,
112: "KanaMode", 112: ecodes.KEY_KATAKANA,
115: "IntlRo", 115: ecodes.KEY_RO,
121: "Convert", 121: ecodes.KEY_HENKAN,
123: "NonConvert", 123: ecodes.KEY_MUHENKAN,
125: "IntlYen", 125: ecodes.KEY_YEN,
57372: "NumpadEnter", 57372: ecodes.KEY_KPENTER,
57373: "ControlRight", 57373: ecodes.KEY_RIGHTCTRL,
57397: "NumpadDivide", 57397: ecodes.KEY_KPSLASH,
57400: "AltRight", 57400: ecodes.KEY_RIGHTALT,
57414: "Pause", 57414: ecodes.KEY_PAUSE,
57415: "Home", 57415: ecodes.KEY_HOME,
57416: "ArrowUp", 57416: ecodes.KEY_UP,
57417: "PageUp", 57417: ecodes.KEY_PAGEUP,
57419: "ArrowLeft", 57419: ecodes.KEY_LEFT,
57421: "ArrowRight", 57421: ecodes.KEY_RIGHT,
57423: "End", 57423: ecodes.KEY_END,
57424: "ArrowDown", 57424: ecodes.KEY_DOWN,
57425: "PageDown", 57425: ecodes.KEY_PAGEDOWN,
57426: "Insert", 57426: ecodes.KEY_INSERT,
57427: "Delete", 57427: ecodes.KEY_DELETE,
57435: "MetaLeft", 57435: ecodes.KEY_LEFTMETA,
57436: "MetaRight", 57436: ecodes.KEY_RIGHTMETA,
57437: "ContextMenu", 57437: ecodes.KEY_CONTEXT_MENU,
57438: "Power", 57438: ecodes.KEY_POWER,
} }

View File

@ -22,6 +22,8 @@
import dataclasses import dataclasses
from evdev import ecodes
# ===== # =====
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
@ -31,7 +33,7 @@ class McuKey:
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class UsbKey: class UsbKey:
code: int code: int
is_modifier: bool is_modifier: bool
@ -41,29 +43,36 @@ class Key:
usb: UsbKey usb: UsbKey
<%! import operator %> <%! import operator %>
KEYMAP: dict[str, Key] = { KEYMAP: dict[int, Key] = {
% for km in sorted(keymap, key=operator.attrgetter("mcu_code")): % 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 % endfor
} }
# ===== # =====
class WebModifiers: class EvdevModifiers:
SHIFT_LEFT = "ShiftLeft" SHIFT_LEFT = ecodes.KEY_LEFTSHIFT
SHIFT_RIGHT = "ShiftRight" SHIFT_RIGHT = ecodes.KEY_RIGHTSHIFT
SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT])
ALT_LEFT = "AltLeft" ALT_LEFT = ecodes.KEY_LEFTALT
ALT_RIGHT = "AltRight" ALT_RIGHT = ecodes.KEY_RIGHTALT
ALTS = set([ALT_LEFT, ALT_RIGHT]) ALTS = set([ALT_LEFT, ALT_RIGHT])
CTRL_LEFT = "ControlLeft" CTRL_LEFT = ecodes.KEY_LEFTCTRL
CTRL_RIGHT = "ControlRight" CTRL_RIGHT = ecodes.KEY_RIGHTCTRL
CTRLS = set([CTRL_LEFT, CTRL_RIGHT]) CTRLS = set([CTRL_LEFT, CTRL_RIGHT])
META_LEFT = "MetaLeft" META_LEFT = ecodes.KEY_LEFTMETA
META_RIGHT = "MetaRight" META_RIGHT = ecodes.KEY_RIGHTMETA
METAS = set([META_LEFT, META_RIGHT]) METAS = set([META_LEFT, META_RIGHT])
ALL = (SHIFTS | ALTS | CTRLS | METAS) ALL = (SHIFTS | ALTS | CTRLS | METAS)
@ -84,10 +93,10 @@ class X11Modifiers:
# ===== # =====
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class At1Key: class At1Key:
code: int code: int
shift: bool shift: bool
altgr: bool = False altgr: bool = False
ctrl: bool = False ctrl: bool = False
X11_TO_AT1 = { 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")): % for km in sorted(keymap, key=operator.attrgetter("at1_code")):
${km.at1_code}: "${km.web_name}", ${km.at1_code}: ecodes.${km.evdev_name},
% endfor % endfor
} }

View File

@ -25,8 +25,9 @@ import ctypes.util
from typing import Generator from typing import Generator
from evdev import ecodes
from .keysym import SymmapModifiers 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, text: str,
symmap: dict[int, dict[int, str]], symmap: dict[int, dict[int, int]],
) -> Generator[tuple[str, bool], None, None]: ) -> Generator[tuple[int, bool], None, None]:
shift = False shift = False
altgr = 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://stackoverflow.com/questions/12343987/convert-ascii-character-to-x11-keycode
# https://www.ascii-code.com # https://www.ascii-code.com
if ch == "\n": if ch == "\n":
keys = {0: "Enter"} keys = {0: ecodes.KEY_ENTER}
elif ch == "\t": elif ch == "\t":
keys = {0: "Tab"} keys = {0: ecodes.KEY_TAB}
elif ch == " ": elif ch == " ":
keys = {0: "Space"} keys = {0: ecodes.KEY_SPACE}
else: else:
if ch in ["", "", ""]: if ch in ["", "", ""]:
ch = "'" ch = "'"
@ -95,17 +96,17 @@ def text_to_web_keys( # pylint: disable=too-many-branches
continue continue
if modifiers & SymmapModifiers.SHIFT and not shift: if modifiers & SymmapModifiers.SHIFT and not shift:
yield (WebModifiers.SHIFT_LEFT, True) yield (ecodes.KEY_LEFTSHIFT, True)
shift = True shift = True
elif not (modifiers & SymmapModifiers.SHIFT) and shift: elif not (modifiers & SymmapModifiers.SHIFT) and shift:
yield (WebModifiers.SHIFT_LEFT, False) yield (ecodes.KEY_LEFTSHIFT, False)
shift = False shift = False
if modifiers & SymmapModifiers.ALTGR and not altgr: if modifiers & SymmapModifiers.ALTGR and not altgr:
yield (WebModifiers.ALT_RIGHT, True) yield (ecodes.KEY_RIGHTALT, True)
altgr = True altgr = True
elif not (modifiers & SymmapModifiers.ALTGR) and altgr: elif not (modifiers & SymmapModifiers.ALTGR) and altgr:
yield (WebModifiers.ALT_RIGHT, False) yield (ecodes.KEY_RIGHTALT, False)
altgr = False altgr = False
yield (key, True) yield (key, True)
@ -113,6 +114,6 @@ def text_to_web_keys( # pylint: disable=too-many-branches
break break
if shift: if shift:
yield (WebModifiers.SHIFT_LEFT, False) yield (ecodes.KEY_LEFTSHIFT, False)
if altgr: if altgr:
yield (WebModifiers.ALT_RIGHT, False) yield (ecodes.KEY_RIGHTALT, False)

View File

@ -20,6 +20,8 @@
# ========================================================================== # # ========================================================================== #
from evdev import ecodes
from . import tools from . import tools
@ -46,3 +48,13 @@ class MouseDelta:
@classmethod @classmethod
def normalize(cls, value: int) -> int: def normalize(cls, value: int) -> int:
return min(max(cls.MIN, value), cls.MAX) 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,
}

View File

@ -29,6 +29,8 @@ from typing import Callable
from typing import AsyncGenerator from typing import AsyncGenerator
from typing import Any from typing import Any
from evdev import ecodes
from ...yamlconf import Option from ...yamlconf import Option
from ...validators.basic import valid_bool 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_key
from ...validators.hid import valid_hid_mouse_move 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 ...mouse import MouseRange
from .. import BasePlugin from .. import BasePlugin
@ -60,7 +63,7 @@ class BaseHid(BasePlugin): # pylint: disable=too-many-instance-attributes
jiggler_interval: int, jiggler_interval: int,
) -> None: ) -> 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_x_range = (mouse_x_min, mouse_x_max)
self.__mouse_y_range = (mouse_y_min, mouse_y_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( async def send_key_events(
self, self,
keys: Iterable[tuple[str, bool]], keys: Iterable[tuple[int, bool]],
no_ignore_keys: bool=False, no_ignore_keys: bool=False,
slow: bool=False, slow: bool=False,
) -> None: ) -> None:
@ -153,24 +156,24 @@ class BaseHid(BasePlugin): # pylint: disable=too-many-instance-attributes
await asyncio.sleep(0.02) await asyncio.sleep(0.02)
self.send_key_event(key, state, False) 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) 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+... # Считаем что PrintScreen это модификатор для Alt+SysRq+...
# По-хорошему надо учитывать факт нажатия на Alt, но можно и забить. # По-хорошему надо учитывать факт нажатия на Alt, но можно и забить.
self._send_key_event(key, False) self._send_key_event(key, False)
self.__bump_activity() 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 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._send_mouse_button_event(button, state)
self.__bump_activity() 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 raise NotImplementedError
# ===== # =====

View File

@ -285,10 +285,10 @@ class BaseMcuHid(BaseHid, multiprocessing.Process): # pylint: disable=too-many-
def set_connected(self, connected: bool) -> None: def set_connected(self, connected: bool) -> None:
self.__queue_event(SetConnectedEvent(connected), clear=True) 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)) 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)) self.__queue_event(MouseButtonEvent(button, state))
def _send_mouse_move_event(self, to_x: int, to_y: int) -> None: def _send_mouse_move_event(self, to_x: int, to_y: int) -> None:

View File

@ -23,6 +23,8 @@
import dataclasses import dataclasses
import struct import struct
from evdev import ecodes
from ....keyboard.mappings import KEYMAP from ....keyboard.mappings import KEYMAP
from ....mouse import MouseRange from ....mouse import MouseRange
@ -106,33 +108,36 @@ class ClearEvent(BaseEvent):
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class KeyEvent(BaseEvent): class KeyEvent(BaseEvent):
name: str code: int
state: bool state: bool
def __post_init__(self) -> None: def __post_init__(self) -> None:
assert self.name in KEYMAP assert self.code in KEYMAP
def make_request(self) -> bytes: 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))) return _make_request(struct.pack(">BBBxx", 0x11, code, int(self.state)))
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class MouseButtonEvent(BaseEvent): class MouseButtonEvent(BaseEvent):
name: str code: int
state: bool state: bool
def __post_init__(self) -> None: 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: def make_request(self) -> bytes:
(code, state_pressed, is_main) = { (code, state_pressed, is_main) = {
"left": (0b10000000, 0b00001000, True), ecodes.BTN_LEFT: (0b10000000, 0b00001000, True),
"right": (0b01000000, 0b00000100, True), ecodes.BTN_RIGHT: (0b01000000, 0b00000100, True),
"middle": (0b00100000, 0b00000010, True), ecodes.BTN_MIDDLE: (0b00100000, 0b00000010, True),
"up": (0b10000000, 0b00001000, False), # Back ecodes.BTN_BACK: (0b10000000, 0b00001000, False), # Up
"down": (0b01000000, 0b00000100, False), # Forward ecodes.BTN_FORWARD: (0b01000000, 0b00000100, False), # Down
}[self.name] }[self.code]
if self.state: if self.state:
code |= state_pressed code |= state_pressed
if is_main: if is_main:

View File

@ -203,10 +203,10 @@ class Plugin(BaseHid): # pylint: disable=too-many-instance-attributes
self._set_jiggler_active(jiggler) self._set_jiggler_active(jiggler)
self.__notifier.notify() 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)) 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)) self.__server.queue_event(MouseButtonEvent(button, state))
def _send_mouse_relative_event(self, delta_x: int, delta_y: int) -> None: def _send_mouse_relative_event(self, delta_x: int, delta_y: int) -> None:

View File

@ -168,10 +168,10 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst
self._set_jiggler_active(jiggler) self._set_jiggler_active(jiggler)
self.__notifier.notify() 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)) 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)) self.__queue_cmd(self.__mouse.process_button(button, state))
def _send_mouse_move_event(self, to_x: int, to_y: int) -> None: def _send_mouse_move_event(self, to_x: int, to_y: int) -> None:

View File

@ -46,7 +46,7 @@ class Keyboard:
async def get_leds(self) -> dict[str, bool]: async def get_leds(self) -> dict[str, bool]:
return (await self.__leds.get()) 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 code = KEYMAP[key].usb.code
is_modifier = KEYMAP[key].usb.is_modifier is_modifier = KEYMAP[key].usb.is_modifier
if state: if state:

View File

@ -22,6 +22,8 @@
import math import math
from evdev import ecodes
from ....mouse import MouseRange from ....mouse import MouseRange
from ....mouse import MouseDelta from ....mouse import MouseDelta
@ -43,18 +45,18 @@ class Mouse: # pylint: disable=too-many-instance-attributes
def is_absolute(self) -> bool: def is_absolute(self) -> bool:
return self.__absolute return self.__absolute
def process_button(self, button: str, state: bool) -> bytes: def process_button(self, button: int, state: bool) -> bytes:
code = 0x00 code = 0x00
match button: match button:
case "left": case ecodes.BTN_LEFT:
code = 0x01 code = 0x01
case "right": case ecodes.BTN_RIGHT:
code = 0x02 code = 0x02
case "middle": case ecodes.BTN_MIDDLE:
code = 0x04 code = 0x04
case "up": case ecodes.BTN_BACK:
code = 0x08 code = 0x08
case "down": case ecodes.BTN_FORWARD:
code = 0x10 code = 0x10
if code: if code:
if state: if state:

View File

@ -206,10 +206,10 @@ class Plugin(BaseHid): # pylint: disable=too-many-instance-attributes
self._set_jiggler_active(jiggler) self._set_jiggler_active(jiggler)
self.__notifier.notify() 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) 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) self.__mouse_current.send_button_event(button, state)
def _send_mouse_move_event(self, to_x: int, to_y: int) -> None: def _send_mouse_move_event(self, to_x: int, to_y: int) -> None:

View File

@ -23,6 +23,8 @@
import struct import struct
import dataclasses import dataclasses
from evdev import ecodes
from ....keyboard.mappings import UsbKey from ....keyboard.mappings import UsbKey
from ....keyboard.mappings import KEYMAP from ....keyboard.mappings import KEYMAP
@ -46,7 +48,7 @@ class ResetEvent(BaseEvent):
# ===== # =====
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class KeyEvent(BaseEvent): class KeyEvent(BaseEvent):
key: UsbKey key: UsbKey
state: bool state: bool
def __post_init__(self) -> None: def __post_init__(self) -> None:
@ -56,13 +58,13 @@ class KeyEvent(BaseEvent):
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class ModifierEvent(BaseEvent): class ModifierEvent(BaseEvent):
modifier: UsbKey modifier: UsbKey
state: bool state: bool
def __post_init__(self) -> None: def __post_init__(self) -> None:
assert self.modifier.is_modifier 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 usb_key = KEYMAP[key].usb
if usb_key.is_modifier: if usb_key.is_modifier:
return ModifierEvent(usb_key, state) return ModifierEvent(usb_key, state)
@ -102,17 +104,17 @@ def make_keyboard_report(
# ===== # =====
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class MouseButtonEvent(BaseEvent): class MouseButtonEvent(BaseEvent):
button: str button: int
state: bool state: bool
code: int = 0 code: int = 0
def __post_init__(self) -> None: def __post_init__(self) -> None:
object.__setattr__(self, "code", { object.__setattr__(self, "code", {
"left": 0x1, ecodes.BTN_LEFT: 0x1,
"right": 0x2, ecodes.BTN_RIGHT: 0x2,
"middle": 0x4, ecodes.BTN_MIDDLE: 0x4,
"up": 0x8, # Back ecodes.BTN_BACK: 0x8, # Back/Up
"down": 0x10, # Forward ecodes.BTN_FORWARD: 0x10, # Forward/Down
}[self.button]) }[self.button])

View File

@ -67,7 +67,7 @@ class KeyboardProcess(BaseDeviceProcess):
self._clear_queue() self._clear_queue()
self._queue_event(ResetEvent()) 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)) self._queue_event(make_keyboard_event(key, state))
# ===== # =====

View File

@ -85,7 +85,7 @@ class MouseProcess(BaseDeviceProcess):
self._clear_queue() self._clear_queue()
self._queue_event(ResetEvent()) 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)) self._queue_event(MouseButtonEvent(button, state))
def send_move_event(self, to_x: int, to_y: int) -> None: def send_move_event(self, to_x: int, to_y: int) -> None:

View File

@ -22,7 +22,8 @@
from typing import Any 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 MouseRange
from ..mouse import MouseDelta from ..mouse import MouseDelta
@ -42,7 +43,7 @@ def valid_hid_mouse_output(arg: Any) -> str:
def valid_hid_key(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: 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: 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: def valid_hid_mouse_delta(arg: Any) -> int:

View File

@ -51,6 +51,7 @@ RUN pacman --noconfirm --ask=4 -Syy \
python-qrcode \ python-qrcode \
python-pyserial \ python-pyserial \
python-pyudev \ python-pyudev \
python-evdev \
python-setproctitle \ python-setproctitle \
python-psutil \ python-psutil \
python-netifaces \ python-netifaces \

View File

@ -29,6 +29,7 @@ _AtxApiPart.switch_power
_UsbKey.arduino_modifier_code _UsbKey.arduino_modifier_code
_KeyMapping.web_name _KeyMapping.web_name
_KeyMapping.evdev_name
_KeyMapping.mcu_code _KeyMapping.mcu_code
_KeyMapping.usb_key _KeyMapping.usb_key
_KeyMapping.ps2_key _KeyMapping.ps2_key

View File

@ -1,35 +0,0 @@
# ========================================================================== #
# #
# KVMD - The main PiKVM daemon. #
# #
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
# #
# 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 <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
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"])

View File

@ -24,7 +24,7 @@ from typing import Any
import pytest import pytest
from kvmd.keyboard.mappings import KEYMAP from kvmd.keyboard.mappings import WEB_TO_EVDEV
from kvmd.validators import ValidatorError from kvmd.validators import ValidatorError
from kvmd.validators.hid import valid_hid_key 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: 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))
print(valid_hid_key(key + " ")) print(valid_hid_key(key + " "))