summaryrefslogtreecommitdiff
path: root/kvmd/plugins/hid/otg
diff options
context:
space:
mode:
authorDevaev Maxim <[email protected]>2019-09-30 04:59:40 +0300
committerDevaev Maxim <[email protected]>2019-09-30 05:47:45 +0300
commitce6cb3057a477fa11ab13e732c9589aab6efcc69 (patch)
tree5404045c840358cb42125b5f4ab2202bde6b63eb /kvmd/plugins/hid/otg
parente1b7e4fbcd1f34878162bc8bbeb92b7f47f058cc (diff)
otg mouse
Diffstat (limited to 'kvmd/plugins/hid/otg')
-rw-r--r--kvmd/plugins/hid/otg/__init__.py38
-rw-r--r--kvmd/plugins/hid/otg/hid.py7
-rw-r--r--kvmd/plugins/hid/otg/mouse.py169
3 files changed, 203 insertions, 11 deletions
diff --git a/kvmd/plugins/hid/otg/__init__.py b/kvmd/plugins/hid/otg/__init__.py
index d17ad179..7129e5d4 100644
--- a/kvmd/plugins/hid/otg/__init__.py
+++ b/kvmd/plugins/hid/otg/__init__.py
@@ -37,6 +37,7 @@ from ....validators.os import valid_abs_path
from .. import BaseHid
from .keyboard import KeyboardProcess
+from .mouse import MouseProcess
# =====
@@ -44,11 +45,13 @@ class Plugin(BaseHid):
def __init__( # pylint: disable=super-init-not-called
self,
keyboard: Dict[str, Any],
+ mouse: Dict[str, Any],
noop: bool,
state_poll: float,
) -> None:
self.__keyboard_proc = KeyboardProcess(noop=noop, **keyboard)
+ self.__mouse_proc = MouseProcess(noop=noop, **mouse)
self.__state_poll = state_poll
@@ -64,19 +67,33 @@ class Plugin(BaseHid):
"write_retries_delay": Option(0.1, type=valid_float_f01),
},
+ "mouse": {
+ "device": Option("", type=valid_abs_path, unpack_as="device_path"),
+ "select_timeout": Option(1.0, type=valid_float_f01),
+ "write_retries": Option(5, type=valid_int_f1),
+ "write_retries_delay": Option(0.1, type=valid_float_f01),
+ },
+
"noop": Option(False, type=valid_bool),
"state_poll": Option(0.1, type=valid_float_f01),
}
def start(self) -> None:
self.__keyboard_proc.start()
+ self.__mouse_proc.start()
def get_state(self) -> Dict:
- return {"online": self.__keyboard_proc.is_online()}
+ keyboard_online = self.__keyboard_proc.is_online()
+ mouse_online = self.__mouse_proc.is_online()
+ return {
+ "online": (keyboard_online and mouse_online),
+ "keyboard": {"online": keyboard_online},
+ "mouse": {"online": mouse_online},
+ }
async def poll_state(self) -> AsyncGenerator[Dict, None]:
prev_state: Dict = {}
- while self.__keyboard_proc.is_alive():
+ while self.__keyboard_proc.is_alive() and self.__mouse_proc.is_alive():
state = self.get_state()
if state != prev_state:
yield self.get_state()
@@ -85,23 +102,28 @@ class Plugin(BaseHid):
async def reset(self) -> None:
self.__keyboard_proc.send_reset_event()
+ self.__mouse_proc.send_reset_event()
async def cleanup(self) -> None:
- self.__keyboard_proc.cleanup()
+ try:
+ self.__keyboard_proc.cleanup()
+ finally:
+ self.__mouse_proc.cleanup()
# =====
async def send_key_event(self, key: str, state: bool) -> None:
self.__keyboard_proc.send_key_event(key, state)
- async def send_mouse_move_event(self, to_x: int, to_y: int) -> None:
- pass
-
async def send_mouse_button_event(self, button: str, state: bool) -> None:
- pass
+ self.__mouse_proc.send_button_event(button, state)
+
+ async def send_mouse_move_event(self, to_x: int, to_y: int) -> None:
+ self.__mouse_proc.send_move_event(to_x, to_y)
async def send_mouse_wheel_event(self, delta_y: int) -> None:
- pass
+ self.__mouse_proc.send_wheel_event(delta_y)
async def clear_events(self) -> None:
self.__keyboard_proc.send_clear_event()
+ self.__mouse_proc.send_clear_event()
diff --git a/kvmd/plugins/hid/otg/hid.py b/kvmd/plugins/hid/otg/hid.py
index e0cf1120..2d8b9245 100644
--- a/kvmd/plugins/hid/otg/hid.py
+++ b/kvmd/plugins/hid/otg/hid.py
@@ -126,11 +126,12 @@ class DeviceProcess(multiprocessing.Process): # pylint: disable=too-many-instan
self.__online_shared.value = 1
return True
else:
- logger.error("HID-%s write error: written (%s) != report length (%d)",
+ logger.error("HID-%s write() error: written (%s) != report length (%d)",
self.__name, written, len(report))
except Exception as err:
if isinstance(err, OSError) and err.errno == errno.EAGAIN: # pylint: disable=no-member
- logger.error("HID-%s is busy/unplugged: %s: %s", self.__name, type(err).__name__, err) # TODO debug
+ logger.error("HID-%s busy/unplugged (write): %s: %s", # TODO debug
+ self.__name, type(err).__name__, err)
else:
logger.exception("Can't write report to HID-%s", self.__name)
@@ -164,7 +165,7 @@ class DeviceProcess(multiprocessing.Process): # pylint: disable=too-many-instan
self.__online_shared.value = 1
return True
else:
- logger.error("HID-%s is busy/unplugged", self.__name) # TODO debug
+ logger.error("HID-%s is busy/unplugged (select)", self.__name) # TODO debug
except Exception as err:
logger.error("Can't select() HID-%s: %s: %s", self.__name, type(err).__name__, err)
self._close_device()
diff --git a/kvmd/plugins/hid/otg/mouse.py b/kvmd/plugins/hid/otg/mouse.py
new file mode 100644
index 00000000..4132a1cc
--- /dev/null
+++ b/kvmd/plugins/hid/otg/mouse.py
@@ -0,0 +1,169 @@
+# ========================================================================== #
+# #
+# KVMD - The main Pi-KVM daemon. #
+# #
+# Copyright (C) 2018 Maxim Devaev <[email protected]> #
+# #
+# 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 struct
+import dataclasses
+
+from typing import Any
+
+from ....logging import get_logger
+
+from .hid import BaseEvent
+from .hid import DeviceProcess
+
+
+# =====
+_BUTTONS = {
+ "left": 0x1,
+ "right": 0x2,
+ "middle": 0x4,
+}
+
+
+# =====
+class _ClearEvent(BaseEvent):
+ pass
+
+
+class _ResetEvent(BaseEvent):
+ pass
+
+
[email protected](frozen=True)
+class _ButtonEvent(BaseEvent):
+ code: int
+ state: bool
+
+
[email protected](frozen=True)
+class _MoveEvent(BaseEvent):
+ to_x: int
+ to_y: int
+
+ def __post_init__(self) -> None:
+ assert -32768 <= self.to_x <= 32767
+ assert -32768 <= self.to_y <= 32767
+
+
[email protected](frozen=True)
+class _WheelEvent(BaseEvent):
+ delta_y: int
+
+ def __post_init__(self) -> None:
+ assert -127 <= self.delta_y <= 127
+
+
+# =====
+class MouseProcess(DeviceProcess):
+ def __init__(self, **kwargs: Any) -> None:
+ super().__init__(name="mouse", **kwargs)
+
+ self.__pressed_buttons: int = 0
+ self.__x = 0
+ self.__y = 0
+
+ def cleanup(self) -> None:
+ self._stop()
+ get_logger().info("Clearing HID-mouse events ...")
+ if self._ensure_device():
+ try:
+ self._write_report(self.__make_report(0, self.__x, self.__y, 0)) # Release all buttons
+ finally:
+ self._close_device()
+
+ def send_clear_event(self) -> None:
+ self._queue_event(_ClearEvent())
+
+ def send_reset_event(self) -> None:
+ self._queue_event(_ResetEvent())
+
+ def send_button_event(self, button: str, state: bool) -> None:
+ assert button in _BUTTONS
+ self._queue_event(_ButtonEvent(code=_BUTTONS[button], state=state))
+
+ def send_move_event(self, to_x: int, to_y: int) -> None:
+ self._queue_event(_MoveEvent(to_x, to_y))
+
+ def send_wheel_event(self, delta_y: int) -> None:
+ self._queue_event(_WheelEvent(delta_y))
+
+ # =====
+
+ def _process_event(self, event: BaseEvent) -> None:
+ if isinstance(event, _ClearEvent):
+ self.__process_clear_event()
+ elif isinstance(event, _ResetEvent):
+ self.__process_clear_event(reopen=True)
+ elif isinstance(event, _ButtonEvent):
+ self.__process_button_event(event)
+ elif isinstance(event, _MoveEvent):
+ self.__process_move_event(event)
+ elif isinstance(event, _WheelEvent):
+ self.__process_wheel_event(event)
+
+ def __process_clear_event(self, reopen: bool=False) -> None:
+ self.__clear_state()
+ if reopen:
+ self._close_device()
+ self.__send_current_state(0)
+
+ def __process_button_event(self, event: _ButtonEvent) -> None:
+ if event.code & self.__pressed_buttons:
+ # Ранее нажатую кнопку отжимаем
+ self.__pressed_buttons &= ~event.code
+ if not self.__send_current_state(0):
+ return
+ if event.state:
+ # Нажимаем если нужно
+ self.__pressed_buttons |= event.code
+ self.__send_current_state(0)
+
+ def __process_move_event(self, event: _MoveEvent) -> None:
+ self.__x = event.to_x
+ self.__y = event.to_y
+ self.__send_current_state(0)
+
+ def __process_wheel_event(self, event: _WheelEvent) -> None:
+ self.__send_current_state(event.delta_y)
+
+ def __send_current_state(self, delta_y: int) -> bool:
+ ok = False
+ if self._ensure_device():
+ ok = self._write_report(self.__make_report(
+ buttons=self.__pressed_buttons,
+ to_x=self.__x,
+ to_y=self.__y,
+ delta_y=delta_y,
+ ))
+ if not ok:
+ self.__clear_state()
+ return ok
+
+ def __clear_state(self) -> None:
+ self.__pressed_buttons = 0
+ self.__x = 0
+ self.__y = 0
+
+ def __make_report(self, buttons: int, to_x: int, to_y: int, delta_y: int) -> bytes:
+ to_x = (to_x + 32768) // 2
+ to_y = (to_y + 32768) // 2
+ return struct.pack("<BHHb", buttons, to_x, to_y, delta_y)