diff options
author | Devaev Maxim <[email protected]> | 2020-10-08 15:26:37 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2020-10-08 15:26:37 +0300 |
commit | a0b920a9d6ca0a9b1ac65276e08292dc30aed2d8 (patch) | |
tree | 70e591dcbc6472ac9e6f3b7da750e7f29df1637d | |
parent | f1910f7c8ec085eece1adc56ff999527881e71d9 (diff) |
vnc: qemu ext keys
-rw-r--r-- | kvmd/apps/vnc/rfb/__init__.py | 22 | ||||
-rw-r--r-- | kvmd/apps/vnc/rfb/encodings.py | 5 | ||||
-rw-r--r-- | kvmd/apps/vnc/server.py | 31 | ||||
-rw-r--r-- | kvmd/keyboard/keysym.py | 25 | ||||
-rw-r--r-- | kvmd/keyboard/mappings.py | 27 | ||||
-rw-r--r-- | kvmd/keyboard/mappings.py.mako | 27 | ||||
-rw-r--r-- | kvmd/keyboard/printer.py | 5 |
7 files changed, 114 insertions, 28 deletions
diff --git a/kvmd/apps/vnc/rfb/__init__.py b/kvmd/apps/vnc/rfb/__init__.py index f552321d..8ca93c8a 100644 --- a/kvmd/apps/vnc/rfb/__init__.py +++ b/kvmd/apps/vnc/rfb/__init__.py @@ -134,6 +134,9 @@ class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attribute async def _on_key_event(self, code: int, state: bool) -> None: raise NotImplementedError + 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: raise NotImplementedError @@ -360,6 +363,7 @@ class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attribute 4: self.__handle_key_event, 5: self.__handle_pointer_event, 6: self.__handle_client_cut_text, + 255: self.__handle_qemu_event, } while True: msg_type = await self._read_number("B") @@ -380,9 +384,12 @@ class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attribute if encodings_count > 1024: raise RfbError(f"Too many encodings: {encodings_count}") self._encodings = RfbClientEncodings(frozenset(await self._read_struct("l" * encodings_count))) - get_logger(0).info("[main] %s: Features: resize=%d; rename=%d; leds=%d", - self._remote, self._encodings.has_resize, self._encodings.has_rename, self._encodings.has_leds_state) + get_logger(0).info("[main] %s: Features: resize=%d, rename=%d, leds=%d, extkeys=%d", + self._remote, self._encodings.has_resize, self._encodings.has_rename, + self._encodings.has_leds_state, self._encodings.has_ext_keys) self.__check_tight_jpeg() + if self._encodings.has_ext_keys: # Preferred method + await self._write_fb_update(0, 0, RfbEncodings.EXT_KEYS, drain=True) await self._on_set_encodings() async def __handle_fb_update_request(self) -> None: @@ -417,6 +424,17 @@ class RfbClient(RfbClientStream): # pylint: disable=too-many-instance-attribute text = await self._read_text(length) await self._on_cut_event(text) + async def __handle_qemu_event(self) -> None: + (sub_type, state, code) = await self._read_struct("B H xxxx L") + if sub_type != 0: + raise RfbError(f"Invalid QEMU sub-message type: {sub_type}") + if code == 0xB7: + # For backwards compatibility servers SHOULD accept 0xB7 as a synonym for 0x54 (PrintScreen) + code = 0x54 + if code & 0x80: + code = (0xE0 << 8) | (code & ~0x80) + await self._on_ext_key_event(code, bool(state)) + def __check_tight_jpeg(self) -> None: # JpegCompression may only be used when the client has advertized # a quality level using the JPEG Quality Level Pseudo-encoding diff --git a/kvmd/apps/vnc/rfb/encodings.py b/kvmd/apps/vnc/rfb/encodings.py index cb1d4761..ad83f726 100644 --- a/kvmd/apps/vnc/rfb/encodings.py +++ b/kvmd/apps/vnc/rfb/encodings.py @@ -30,7 +30,8 @@ from typing import Any class RfbEncodings: RESIZE = -223 # DesktopSize Pseudo-encoding RENAME = -307 # DesktopName Pseudo-encoding - LEDS_STATE = -261 # LED State Pseudo-encoding + LEDS_STATE = -261 # QEMU LED State Pseudo-encoding + EXT_KEYS = -258 # QEMU Extended Key Events Pseudo-encoding TIGHT = 7 TIGHT_JPEG_QUALITIES = dict(zip( # JPEG Quality Level Pseudo-encoding @@ -46,6 +47,7 @@ class RfbClientEncodings: has_resize: bool = dataclasses.field(default=False) has_rename: bool = dataclasses.field(default=False) has_leds_state: bool = dataclasses.field(default=False) + has_ext_keys: bool = dataclasses.field(default=False) has_tight: bool = dataclasses.field(default=False) tight_jpeg_quality: int = dataclasses.field(default=0) @@ -54,6 +56,7 @@ class RfbClientEncodings: self.__set("has_resize", (RfbEncodings.RESIZE in self.encodings)) self.__set("has_rename", (RfbEncodings.RENAME in self.encodings)) self.__set("has_leds_state", (RfbEncodings.LEDS_STATE in self.encodings)) + self.__set("has_ext_keys", (RfbEncodings.EXT_KEYS in self.encodings)) self.__set("has_tight", (RfbEncodings.TIGHT in self.encodings)) self.__set("tight_jpeg_quality", self.__get_tight_jpeg_quality()) diff --git a/kvmd/apps/vnc/server.py b/kvmd/apps/vnc/server.py index 41fd0487..d871ec3e 100644 --- a/kvmd/apps/vnc/server.py +++ b/kvmd/apps/vnc/server.py @@ -27,14 +27,18 @@ import dataclasses import contextlib from typing import Dict +from typing import Union from typing import Optional import aiohttp from ...logging import get_logger -from ...keyboard.keysym import switch_symmap_modifiers +from ...keyboard.keysym import SymmapModifiers from ...keyboard.keysym import build_symmap +from ...keyboard.mappings import WebModifiers +from ...keyboard.mappings import X11Modifiers +from ...keyboard.mappings import AT1_TO_WEB from ...clients.kvmd import KvmdClientWs from ...clients.kvmd import KvmdClientSession @@ -240,7 +244,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes # ===== async def _on_key_event(self, code: int, state: bool) -> None: - (is_modifier, self.__modifiers) = switch_symmap_modifiers(self.__modifiers, code, state) + is_modifier = self.__switch_modifiers(code, state) if self.__kvmd_ws: web_keys = self.__symmap.get(code) if web_keys: @@ -253,6 +257,29 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes if web_key is not None: await self.__kvmd_ws.send_key_event(web_key, state) + async def _on_ext_key_event(self, code: int, state: bool) -> None: + web_key = AT1_TO_WEB.get(code) + if web_key is not None: + self.__switch_modifiers(web_key, state) # Предполагаем, что модификаторы всегда известны + if self.__kvmd_ws: + await self.__kvmd_ws.send_key_event(web_key, state) + + def __switch_modifiers(self, key: Union[int, str], state: bool) -> bool: + mod = 0 + if key in X11Modifiers.SHIFTS or key in WebModifiers.SHIFTS: + mod = SymmapModifiers.SHIFT + elif key == X11Modifiers.ALTGR or key == WebModifiers.ALT_RIGHT: + mod = SymmapModifiers.ALTGR + elif key in X11Modifiers.CTRLS or key in WebModifiers.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: Dict[str, int], move: Dict[str, int]) -> None: if self.__kvmd_ws: for (button, state) in buttons.items(): diff --git a/kvmd/keyboard/keysym.py b/kvmd/keyboard/keysym.py index 0f3568ed..a535a5e2 100644 --- a/kvmd/keyboard/keysym.py +++ b/kvmd/keyboard/keysym.py @@ -23,7 +23,6 @@ import pkgutil import functools -from typing import Tuple from typing import List from typing import Dict @@ -32,6 +31,7 @@ import Xlib.keysymdef from ..logging import get_logger from .mappings import At1Key +from .mappings import WebModifiers from .mappings import X11_TO_AT1 from .mappings import AT1_TO_WEB @@ -43,23 +43,6 @@ class SymmapModifiers: CTRL: int = 0x4 -def switch_symmap_modifiers(modifiers: int, code: int, state: bool) -> Tuple[bool, int]: - mod = 0 - if code == 65505 or code == 65506: # XK_Shift_L, XK_Shift_R - mod = SymmapModifiers.SHIFT - elif code == 65027: # AltGR aka XK_ISO_Level3_Shift - mod = SymmapModifiers.ALTGR - elif code == 65507 or code == 65508: # XK_Control_L, XK_Control_R - mod = SymmapModifiers.CTRL - if mod == 0: - return (False, modifiers) - if state: - modifiers |= mod - else: - modifiers &= ~mod - return (True, modifiers) - - def build_symmap(path: str) -> Dict[int, Dict[int, str]]: # https://github.com/qemu/qemu/blob/95a9457fd44ad97c518858a4e1586a5498f9773c/ui/keymaps.c logger = get_logger() @@ -74,9 +57,9 @@ def build_symmap(path: str) -> Dict[int, Dict[int, str]]: web_name = AT1_TO_WEB.get(key.code) if web_name is not None: if ( - (web_name in ["ShiftLeft", "ShiftRight"] and key.shift) # pylint: disable=too-many-boolean-expressions - or (web_name in ["AltLeft", "AltRight"] and key.altgr) - or (web_name in ["ControlLeft", "ControlRight"] and key.ctrl) + (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) ): logger.error("Invalid modifier key at mapping %s: %s / %s", src, web_name, key) continue diff --git a/kvmd/keyboard/mappings.py b/kvmd/keyboard/mappings.py index 310c50a0..0185a0bc 100644 --- a/kvmd/keyboard/mappings.py +++ b/kvmd/keyboard/mappings.py @@ -154,6 +154,33 @@ KEYMAP: Dict[str, Key] = { # ===== +class WebModifiers: + SHIFT_LEFT = "ShiftLeft" + SHIFT_RIGHT = "ShiftRight" + SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) + + ALT_LEFT = "AltLeft" + ALT_RIGHT = "AltRight" + ALTS = set([ALT_LEFT, ALT_RIGHT]) + + CTRL_LEFT = "ControlLeft" + CTRL_RIGHT = "ControlRight" + CTRLS = set([CTRL_RIGHT, CTRL_RIGHT]) + + +class X11Modifiers: + SHIFT_LEFT = 65505 + SHIFT_RIGHT = 65506 + SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) + + ALTGR = 65027 # XK_ISO_Level3_Shift + + CTRL_LEFT = 65507 + CTRL_RIGHT = 65508 + CTRLS = set([CTRL_LEFT, CTRL_RIGHT]) + + +# ===== @dataclasses.dataclass(frozen=True) class At1Key: code: int diff --git a/kvmd/keyboard/mappings.py.mako b/kvmd/keyboard/mappings.py.mako index 1c13b8a9..399ed75e 100644 --- a/kvmd/keyboard/mappings.py.mako +++ b/kvmd/keyboard/mappings.py.mako @@ -51,6 +51,33 @@ KEYMAP: Dict[str, Key] = { # ===== +class WebModifiers: + SHIFT_LEFT = "ShiftLeft" + SHIFT_RIGHT = "ShiftRight" + SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) + + ALT_LEFT = "AltLeft" + ALT_RIGHT = "AltRight" + ALTS = set([ALT_LEFT, ALT_RIGHT]) + + CTRL_LEFT = "ControlLeft" + CTRL_RIGHT = "ControlRight" + CTRLS = set([CTRL_RIGHT, CTRL_RIGHT]) + + +class X11Modifiers: + SHIFT_LEFT = 65505 + SHIFT_RIGHT = 65506 + SHIFTS = set([SHIFT_LEFT, SHIFT_RIGHT]) + + ALTGR = 65027 # XK_ISO_Level3_Shift + + CTRL_LEFT = 65507 + CTRL_RIGHT = 65508 + CTRLS = set([CTRL_LEFT, CTRL_RIGHT]) + + +# ===== @dataclasses.dataclass(frozen=True) class At1Key: code: int diff --git a/kvmd/keyboard/printer.py b/kvmd/keyboard/printer.py index acef102b..5553a01d 100644 --- a/kvmd/keyboard/printer.py +++ b/kvmd/keyboard/printer.py @@ -25,16 +25,17 @@ from typing import Dict from typing import Generator from .keysym import SymmapModifiers +from .mappings import WebModifiers # ===== def text_to_web_keys( text: str, symmap: Dict[int, Dict[int, str]], - shift_key: str="ShiftLeft", + shift_key: str=WebModifiers.SHIFT_LEFT, ) -> Generator[Tuple[str, bool], None, None]: - assert shift_key in ["ShiftLeft", "ShiftRight"] + assert shift_key in WebModifiers.SHIFTS shifted = False for ch in text: |