major keymaps improvement

This commit is contained in:
Devaev Maxim
2020-05-23 15:57:02 +03:00
parent a795fe5ed6
commit e9d86c058d
20 changed files with 139 additions and 105 deletions

View File

@@ -226,6 +226,7 @@ def _get_config_scheme() -> Dict:
"hid": {
"type": Option("", type=valid_stripped_string_not_empty),
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_path),
# Dynamic content
},
@@ -324,7 +325,7 @@ def _get_config_scheme() -> Dict:
"vnc": {
"desired_fps": Option(30, type=valid_stream_fps),
"keymap": Option("", type=valid_abs_path),
"keymap": Option("/usr/share/kvmd/keymaps/en-us", type=valid_abs_path),
"server": {
"host": Option("::", type=valid_ip_or_host),

View File

@@ -76,13 +76,15 @@ def main(argv: Optional[List[str]]=None) -> None:
log_reader=LogReader(),
wol=WakeOnLan(**config.wol._unpack()),
hid=get_hid_class(config.hid.type)(**config.hid._unpack(ignore=["type"])),
hid=get_hid_class(config.hid.type)(**config.hid._unpack(ignore=["type", "keymap"])),
atx=get_atx_class(config.atx.type)(**config.atx._unpack(ignore=["type"])),
msd=get_msd_class(config.msd.type)(**msd_kwargs),
streamer=Streamer(**config.streamer._unpack()),
heartbeat=config.server.heartbeat,
sync_chunk_size=config.server.sync_chunk_size,
keymap_path=config.hid.keymap,
).run(**config.server._unpack(ignore=["heartbeat", "sync_chunk_size"]))
get_logger(0).info("Bye-bye")

View File

@@ -20,9 +20,13 @@
# ========================================================================== #
import os
import stat
import asyncio
import functools
from typing import Dict
from typing import Set
from aiohttp.web import Request
from aiohttp.web import Response
@@ -30,14 +34,21 @@ from aiohttp.web import WebSocketResponse
from ....plugins.hid import BaseHid
from ....validators import raise_error
from ....validators.basic import valid_bool
from ....validators.basic import valid_number
from ....validators.os import valid_printable_filename
from ....validators.kvm import valid_hid_key
from ....validators.kvm import valid_hid_mouse_move
from ....validators.kvm import valid_hid_mouse_button
from ....validators.kvm import valid_hid_mouse_wheel
from ....keyboard.keysym import SymmapWebKey
from ....keyboard.keysym import build_symmap
from ....keyboard.printer import text_to_web_keys
from ..http import exposed_http
@@ -47,9 +58,14 @@ from ..http import make_json_response
# =====
class HidApi:
def __init__(self, hid: BaseHid) -> None:
def __init__(self, hid: BaseHid, keymap_path: str) -> None:
self.__hid = hid
self.__keymaps_dir_path = os.path.dirname(keymap_path)
self.__default_keymap_name = os.path.basename(keymap_path)
self.__ensure_symmap(self.__default_keymap_name)
self.__key_lock = asyncio.Lock()
# =====
@@ -63,17 +79,50 @@ class HidApi:
await self.__hid.reset()
return make_json_response()
# =====
@exposed_http("GET", "/hid/keymaps")
async def __keymaps_handler(self, _: Request) -> Response:
keymaps: Set[str] = set()
for keymap_name in os.listdir(self.__keymaps_dir_path):
path = os.path.join(self.__keymaps_dir_path, keymap_name)
if os.access(path, os.R_OK) and stat.S_ISREG(os.stat(path).st_mode):
keymaps.add(keymap_name)
return make_json_response({
"keymaps": {
"default": self.__default_keymap_name,
"available": sorted(keymaps),
},
})
@exposed_http("POST", "/hid/print")
async def __print_handler(self, request: Request) -> Response:
text = await request.text()
limit = int(valid_number(request.query.get("limit", "1024"), min=0, type=int))
if limit > 0:
text = text[:limit]
symmap = self.__ensure_symmap(request.query.get("keymap", self.__default_keymap_name))
async with self.__key_lock:
for (key, state) in text_to_web_keys(text):
for (key, state) in text_to_web_keys(text, symmap):
self.__hid.send_key_event(key, state)
return make_json_response()
def __ensure_symmap(self, keymap_name: str) -> Dict[int, SymmapWebKey]:
keymap_name = valid_printable_filename(keymap_name, "keymap")
path = os.path.join(self.__keymaps_dir_path, keymap_name)
try:
st = os.stat(path)
if not (os.access(path, os.R_OK) and stat.S_ISREG(st.st_mode)):
raise_error(keymap_name, "keymap")
except Exception:
raise_error(keymap_name, "keymap")
return self.__inner_ensure_symmap(path, st.st_mtime)
@functools.lru_cache(maxsize=10)
def __inner_ensure_symmap(self, path: str, mtime: int) -> Dict[int, SymmapWebKey]:
_ = mtime # For LRU
return build_symmap(path)
# =====
@exposed_ws("key")

View File

@@ -119,6 +119,8 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
heartbeat: float,
sync_chunk_size: int,
keymap_path: str,
) -> None:
self.__auth_manager = auth_manager
@@ -136,7 +138,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
self,
LogApi(log_reader),
WolApi(wol),
HidApi(hid),
HidApi(hid, keymap_path),
AtxApi(atx),
MsdApi(msd, sync_chunk_size),
]

View File

@@ -23,8 +23,6 @@
from typing import List
from typing import Optional
from ...keyboard.keysym import build_symmap
from ...clients.kvmd import KvmdClient
from ...clients.streamer import StreamerClient
@@ -56,7 +54,7 @@ def main(argv: Optional[List[str]]=None) -> None:
tls_timeout=config.server.tls.timeout,
desired_fps=config.desired_fps,
symmap=build_symmap(config.keymap),
keymap_path=config.keymap,
kvmd=KvmdClient(
user_agent=user_agent,

View File

@@ -20,6 +20,7 @@
# ========================================================================== #
import os
import asyncio
import asyncio.queues
import socket
@@ -34,6 +35,9 @@ import aiohttp
from ...logging import get_logger
from ...keyboard.keysym import SymmapWebKey
from ...keyboard.keysym import build_symmap
from ...clients.kvmd import KvmdClient
from ...clients.streamer import StreamerError
@@ -69,7 +73,8 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
tls_timeout: float,
desired_fps: int,
symmap: Dict[int, str],
keymap_name: str,
symmap: Dict[int, SymmapWebKey],
kvmd: KvmdClient,
streamer: StreamerClient,
@@ -92,6 +97,7 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
)
self.__desired_fps = desired_fps
self.__keymap_name = keymap_name
self.__symmap = symmap
self.__kvmd = kvmd
@@ -249,10 +255,10 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
# =====
async def _on_key_event(self, code: int, state: bool) -> None:
if (web_name := self.__symmap.get(code)) is not None:
if (web_key := self.__symmap.get(code)) is not None:
await self.__ws_writer_queue.put({
"event_type": "key",
"event": {"key": web_name, "state": state},
"event": {"key": web_key.name, "state": state},
})
async def _on_pointer_event(self, buttons: Dict[str, bool], wheel: Dict[str, int], move: Dict[str, int]) -> None:
@@ -283,7 +289,14 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
logger = get_logger(0)
logger.info("[main] Client %s: Printing %d characters ...", self._remote, len(text))
try:
await self.__kvmd.hid.print(user, passwd, text, 0)
(default, available) = await self.__kvmd.hid.get_keymaps(user, passwd)
await self.__kvmd.hid.print(
user=user,
passwd=passwd,
text=text,
limit=0,
keymap_name=(self.__keymap_name if self.__keymap_name in available else default),
)
except Exception:
logger.exception("[main] Client %s: Can't print characters", self._remote)
@@ -311,7 +324,7 @@ class VncServer: # pylint: disable=too-many-instance-attributes
tls_timeout: float,
desired_fps: int,
symmap: Dict[int, str],
keymap_path: str,
kvmd: KvmdClient,
streamer: StreamerClient,
@@ -322,6 +335,9 @@ class VncServer: # pylint: disable=too-many-instance-attributes
self.__port = port
self.__max_clients = max_clients
keymap_name = os.path.basename(keymap_path)
symmap = build_symmap(keymap_path)
self.__vnc_auth_manager = vnc_auth_manager
shared_params = _SharedParams()
@@ -343,6 +359,7 @@ class VncServer: # pylint: disable=too-many-instance-attributes
tls_ciphers=tls_ciphers,
tls_timeout=tls_timeout,
desired_fps=desired_fps,
keymap_name=keymap_name,
symmap=symmap,
kvmd=kvmd,
streamer=streamer,