mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
otg mouse
This commit is contained in:
parent
e1b7e4fbcd
commit
ce6cb3057a
@ -20,6 +20,7 @@
|
|||||||
# ========================================================================== #
|
# ========================================================================== #
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ def _check_config(config: Section) -> None:
|
|||||||
raise RuntimeError("Nothing to do")
|
raise RuntimeError("Nothing to do")
|
||||||
|
|
||||||
|
|
||||||
def _cmd_start(config: Section) -> None:
|
def _cmd_start(config: Section) -> None: # pylint: disable=too-many-statements
|
||||||
# https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
|
# https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt
|
||||||
# https://www.isticktoit.net/?p=1383
|
# https://www.isticktoit.net/?p=1383
|
||||||
|
|
||||||
@ -115,6 +116,26 @@ def _cmd_start(config: Section) -> None:
|
|||||||
)
|
)
|
||||||
symlink(func_path, join(config_path, "hid.usb0"))
|
symlink(func_path, join(config_path, "hid.usb0"))
|
||||||
|
|
||||||
|
# https://github.com/NicoHood/HID/blob/0835e6a/src/SingleReport/SingleAbsoluteMouse.cpp
|
||||||
|
# Репорт взят отсюда ^^^, но изменен диапазон значений координат перемещений.
|
||||||
|
# Автор предлагает использовать -32768...32767, но семерка почему-то не хочет работать
|
||||||
|
# с отрицательными значениями координат, как не хочет хавать 65536 и 32768.
|
||||||
|
# Так что мы ей скармливаем диапазон 0...32767, и передаем рукожопам из микрософта привет,
|
||||||
|
# потому что линуксы прекрасно работают с любыми двухбайтовыми диапазонами.
|
||||||
|
func_path = join(gadget_path, "functions/hid.usb1") # Mouse
|
||||||
|
mkdir(func_path)
|
||||||
|
_write(join(func_path, "protocol"), "0")
|
||||||
|
_write(join(func_path, "subclass"), "0")
|
||||||
|
_write(join(func_path, "report_length"), "6")
|
||||||
|
with open(join(func_path, "report_desc"), "wb") as report_file:
|
||||||
|
report_file.write(
|
||||||
|
b"\x05\x01\x09\x02\xA1\x01\x05\x09\x19\x01\x29\x08"
|
||||||
|
b"\x15\x00\x25\x01\x95\x08\x75\x01\x81\x02\x05\x01\x09\x30"
|
||||||
|
b"\x09\x31\x16\x00\x00\x26\xFF\x7F\x75\x10\x95\x02\x81\x02"
|
||||||
|
b"\x09\x38\x15\x81\x25\x7f\x75\x08\x95\x01\x81\x06\xc0"
|
||||||
|
)
|
||||||
|
symlink(func_path, join(config_path, "hid.usb1"))
|
||||||
|
|
||||||
if config.kvmd.msd.type == "otg":
|
if config.kvmd.msd.type == "otg":
|
||||||
func_path = join(gadget_path, "functions/mass_storage.usb0")
|
func_path = join(gadget_path, "functions/mass_storage.usb0")
|
||||||
mkdir(func_path)
|
mkdir(func_path)
|
||||||
@ -141,14 +162,14 @@ def _cmd_stop(config: Section) -> None:
|
|||||||
|
|
||||||
config_path = join(gadget_path, "configs/c.1")
|
config_path = join(gadget_path, "configs/c.1")
|
||||||
for func in listdir(config_path):
|
for func in listdir(config_path):
|
||||||
if func.endswith(".usb0"):
|
if re.search(r"\.usb\d+$", func):
|
||||||
unlink(join(config_path, func))
|
unlink(join(config_path, func))
|
||||||
rmdir(join(config_path, "strings/0x409"))
|
rmdir(join(config_path, "strings/0x409"))
|
||||||
rmdir(config_path)
|
rmdir(config_path)
|
||||||
|
|
||||||
funcs_path = join(gadget_path, "functions")
|
funcs_path = join(gadget_path, "functions")
|
||||||
for func in listdir(funcs_path):
|
for func in listdir(funcs_path):
|
||||||
if func.endswith(".usb0"):
|
if re.search(r"\.usb\d+$", func):
|
||||||
rmdir(join(funcs_path, func))
|
rmdir(join(funcs_path, func))
|
||||||
|
|
||||||
rmdir(join(gadget_path, "strings/0x409"))
|
rmdir(join(gadget_path, "strings/0x409"))
|
||||||
|
|||||||
@ -37,6 +37,7 @@ from ....validators.os import valid_abs_path
|
|||||||
from .. import BaseHid
|
from .. import BaseHid
|
||||||
|
|
||||||
from .keyboard import KeyboardProcess
|
from .keyboard import KeyboardProcess
|
||||||
|
from .mouse import MouseProcess
|
||||||
|
|
||||||
|
|
||||||
# =====
|
# =====
|
||||||
@ -44,11 +45,13 @@ class Plugin(BaseHid):
|
|||||||
def __init__( # pylint: disable=super-init-not-called
|
def __init__( # pylint: disable=super-init-not-called
|
||||||
self,
|
self,
|
||||||
keyboard: Dict[str, Any],
|
keyboard: Dict[str, Any],
|
||||||
|
mouse: Dict[str, Any],
|
||||||
noop: bool,
|
noop: bool,
|
||||||
state_poll: float,
|
state_poll: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.__keyboard_proc = KeyboardProcess(noop=noop, **keyboard)
|
self.__keyboard_proc = KeyboardProcess(noop=noop, **keyboard)
|
||||||
|
self.__mouse_proc = MouseProcess(noop=noop, **mouse)
|
||||||
|
|
||||||
self.__state_poll = state_poll
|
self.__state_poll = state_poll
|
||||||
|
|
||||||
@ -64,19 +67,33 @@ class Plugin(BaseHid):
|
|||||||
"write_retries_delay": Option(0.1, type=valid_float_f01),
|
"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),
|
"noop": Option(False, type=valid_bool),
|
||||||
"state_poll": Option(0.1, type=valid_float_f01),
|
"state_poll": Option(0.1, type=valid_float_f01),
|
||||||
}
|
}
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
self.__keyboard_proc.start()
|
self.__keyboard_proc.start()
|
||||||
|
self.__mouse_proc.start()
|
||||||
|
|
||||||
def get_state(self) -> Dict:
|
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]:
|
async def poll_state(self) -> AsyncGenerator[Dict, None]:
|
||||||
prev_state: Dict = {}
|
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()
|
state = self.get_state()
|
||||||
if state != prev_state:
|
if state != prev_state:
|
||||||
yield self.get_state()
|
yield self.get_state()
|
||||||
@ -85,23 +102,28 @@ class Plugin(BaseHid):
|
|||||||
|
|
||||||
async def reset(self) -> None:
|
async def reset(self) -> None:
|
||||||
self.__keyboard_proc.send_reset_event()
|
self.__keyboard_proc.send_reset_event()
|
||||||
|
self.__mouse_proc.send_reset_event()
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
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:
|
async def send_key_event(self, key: str, state: bool) -> None:
|
||||||
self.__keyboard_proc.send_key_event(key, state)
|
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:
|
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:
|
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:
|
async def clear_events(self) -> None:
|
||||||
self.__keyboard_proc.send_clear_event()
|
self.__keyboard_proc.send_clear_event()
|
||||||
|
self.__mouse_proc.send_clear_event()
|
||||||
|
|||||||
@ -126,11 +126,12 @@ class DeviceProcess(multiprocessing.Process): # pylint: disable=too-many-instan
|
|||||||
self.__online_shared.value = 1
|
self.__online_shared.value = 1
|
||||||
return True
|
return True
|
||||||
else:
|
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))
|
self.__name, written, len(report))
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
if isinstance(err, OSError) and err.errno == errno.EAGAIN: # pylint: disable=no-member
|
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:
|
else:
|
||||||
logger.exception("Can't write report to HID-%s", self.__name)
|
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
|
self.__online_shared.value = 1
|
||||||
return True
|
return True
|
||||||
else:
|
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:
|
except Exception as err:
|
||||||
logger.error("Can't select() HID-%s: %s: %s", self.__name, type(err).__name__, err)
|
logger.error("Can't select() HID-%s: %s: %s", self.__name, type(err).__name__, err)
|
||||||
self._close_device()
|
self._close_device()
|
||||||
|
|||||||
169
kvmd/plugins/hid/otg/mouse.py
Normal file
169
kvmd/plugins/hid/otg/mouse.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
# ========================================================================== #
|
||||||
|
# #
|
||||||
|
# KVMD - The main Pi-KVM daemon. #
|
||||||
|
# #
|
||||||
|
# Copyright (C) 2018 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 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
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class _ButtonEvent(BaseEvent):
|
||||||
|
code: int
|
||||||
|
state: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(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
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(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)
|
||||||
Loading…
x
Reference in New Issue
Block a user