From 670be5434892f706d02f68cc0045567f4e084ce1 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Mon, 9 Jul 2018 07:45:00 +0000 Subject: moar keyboard --- kvmd/kvmd/ps2.py | 75 +++++++++++++++++++++++++++++++++++++++++------------ kvmd/kvmd/server.py | 13 +++++----- kvmd/web/index.html | 6 ++--- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/kvmd/kvmd/ps2.py b/kvmd/kvmd/ps2.py index fe20e2c6..0820c13b 100644 --- a/kvmd/kvmd/ps2.py +++ b/kvmd/kvmd/ps2.py @@ -1,14 +1,29 @@ +import asyncio import multiprocessing import multiprocessing.queues import queue import time +from typing import List +from typing import Set +from typing import NamedTuple + from .logging import get_logger from . import gpio # ===== +class _KeyEvent(NamedTuple): + key: str + state: bool + + +def _key_event_to_ps2_codes(event: _KeyEvent) -> List[int]: + get_logger().info(str(event)) + return [] # TODO + + class Ps2Keyboard(multiprocessing.Process): def __init__(self, clock: int, data: int, pulse: float) -> None: super().__init__(daemon=True) @@ -17,39 +32,65 @@ class Ps2Keyboard(multiprocessing.Process): self.__data = gpio.set_output(data, initial=True) self.__pulse = pulse + self.__pressed_keys: Set[str] = set() + self.__lock = asyncio.Lock() self.__queue: multiprocessing.queues.Queue = multiprocessing.Queue() - self.__event = multiprocessing.Event() + + self.__stop_event = multiprocessing.Event() def start(self) -> None: get_logger().info("Starting keyboard daemon ...") super().start() - def stop(self) -> None: - get_logger().info("Stopping keyboard daemon ...") - self.__event.set() - self.join() + async def send_event(self, key: str, state: bool) -> None: + if not self.__stop_event.is_set(): + async with self.__lock: + if state and key not in self.__pressed_keys: + self.__pressed_keys.add(key) + self.__queue.put(_KeyEvent(key, state)) + elif not state and key in self.__pressed_keys: + self.__pressed_keys.remove(key) + self.__queue.put(_KeyEvent(key, state)) + + async def clear_events(self) -> None: + if not self.__stop_event.is_set(): + async with self.__lock: + self.__unsafe_clear_events() + + async def cleanup(self) -> None: + async with self.__lock: + if self.is_alive(): + self.__unsafe_clear_events() + get_logger().info("Stopping keyboard daemon ...") + self.__stop_event.set() + self.join() + else: + get_logger().warning("Emergency cleaning up keyboard events ...") + self.__emergency_clear_events() - def send_event(self, code: str, state: bool) -> None: - if state: - get_logger().info("Key pressed: %s", code) - else: - get_logger().info("Key released: %s", code) - # TODO: self.__queue.put(code) + def __unsafe_clear_events(self) -> None: + for key in self.__pressed_keys: + self.__queue.put(_KeyEvent(key, False)) + self.__pressed_keys.clear() - def cleanup(self) -> None: - if self.is_alive(): - self.stop() + def __emergency_clear_events(self) -> None: + for key in self.__pressed_keys: + for code in _key_event_to_ps2_codes(_KeyEvent(key, False)): + self.__send_byte(code) def run(self) -> None: with gpio.bcm(): try: - while not self.__event.is_set(): + while True: try: - code = self.__queue.get(timeout=0.1) + event = self.__queue.get(timeout=0.1) except queue.Empty: pass else: - self.__send_byte(code) + for code in _key_event_to_ps2_codes(event): + self.__send_byte(code) + if self.__stop_event.is_set() and self.__queue.qsize() == 0: + break except Exception: get_logger().exception("Unhandled exception") raise diff --git a/kvmd/kvmd/server.py b/kvmd/kvmd/server.py index 71a3a02c..2ec5ae41 100644 --- a/kvmd/kvmd/server.py +++ b/kvmd/kvmd/server.py @@ -135,11 +135,11 @@ class Server: # pylint: disable=too-many-instance-attributes except Exception: logger.exception("Can't parse JSON event from websocket") else: - if event.get("event_type") == "key_event": - key_code = str(event.get("key_code", ""))[:64].strip() - key_state = event.get("key_state") - if key_code and key_state in [True, False]: - self.__keyboard.send_event(key_code, key_state) + if event.get("event_type") == "key": + key = str(event.get("key", ""))[:64].strip() + state = event.get("state") + if key and state in [True, False]: + await self.__keyboard.send_event(key, state) continue else: logger.error("Invalid websocket event: %r", event) @@ -236,7 +236,7 @@ class Server: # pylint: disable=too-many-instance-attributes await self.__remove_socket(ws) async def __on_cleanup(self, _: aiohttp.web.Application) -> None: - self.__keyboard.cleanup() + await self.__keyboard.cleanup() await self.__streamer.cleanup() await self.__msd.cleanup() @@ -307,6 +307,7 @@ class Server: # pylint: disable=too-many-instance-attributes async def __remove_socket(self, ws: aiohttp.web.WebSocketResponse) -> None: async with self.__sockets_lock: + await self.__keyboard.clear_events() try: self.__sockets.remove(ws) get_logger().info("Removed client socket: remote=%s; id=%d; active=%d", diff --git a/kvmd/web/index.html b/kvmd/web/index.html index cb61185a..3bfe8cc5 100644 --- a/kvmd/web/index.html +++ b/kvmd/web/index.html @@ -42,9 +42,9 @@ function onKeyEvent(event, state) { // TODO: run this code under the lock console.log("Key", (state ? "pressed:" : "released:"), event) ws.send(JSON.stringify({ - event_type: "key_event", - key_code: event.code, - key_state: state, + event_type: "key", + key: event.code, + state: state, })); } -- cgit v1.2.3