pikvm/pikvm#1334: Bad link mode for keyboard events

This commit is contained in:
Maxim Devaev 2024-12-30 18:55:59 +02:00
parent d52bb34bb9
commit fed3bf1efd
9 changed files with 53 additions and 17 deletions

View File

@ -149,16 +149,17 @@ class HidApi:
async def __ws_bin_key_handler(self, _: WsSession, data: bytes) -> None: async def __ws_bin_key_handler(self, _: WsSession, data: bytes) -> None:
try: try:
key = valid_hid_key(data[1:].decode("ascii")) key = valid_hid_key(data[1:].decode("ascii"))
state = valid_bool(data[0]) state = bool(data[0] & 0b01)
finish = bool(data[0] & 0b10)
except Exception: except Exception:
return return
self.__hid.send_key_event(key, state) self.__hid.send_key_event(key, state, finish)
@exposed_ws(2) @exposed_ws(2)
async def __ws_bin_mouse_button_handler(self, _: WsSession, data: bytes) -> None: async def __ws_bin_mouse_button_handler(self, _: WsSession, data: bytes) -> None:
try: try:
button = valid_hid_mouse_button(data[1:].decode("ascii")) button = valid_hid_mouse_button(data[1:].decode("ascii"))
state = valid_bool(data[0]) state = bool(data[0] & 0b01)
except Exception: except Exception:
return return
self.__hid.send_mouse_button_event(button, state) self.__hid.send_mouse_button_event(button, state)
@ -183,7 +184,7 @@ class HidApi:
def __process_ws_bin_delta_request(self, data: bytes, handler: Callable[[Iterable[tuple[int, int]], bool], None]) -> None: def __process_ws_bin_delta_request(self, data: bytes, handler: Callable[[Iterable[tuple[int, int]], bool], None]) -> None:
try: try:
squash = valid_bool(data[0]) squash = bool(data[0] & 0b01)
data = data[1:] data = data[1:]
deltas: list[tuple[int, int]] = [] deltas: list[tuple[int, int]] = []
for index in range(0, len(data), 2): for index in range(0, len(data), 2):
@ -200,9 +201,10 @@ class HidApi:
try: try:
key = valid_hid_key(event["key"]) key = valid_hid_key(event["key"])
state = valid_bool(event["state"]) state = valid_bool(event["state"])
finish = valid_bool(event.get("finish", False))
except Exception: except Exception:
return return
self.__hid.send_key_event(key, state) self.__hid.send_key_event(key, state, finish)
@exposed_ws("mouse_button") @exposed_ws("mouse_button")
async def __ws_mouse_button_handler(self, _: WsSession, event: dict) -> None: async def __ws_mouse_button_handler(self, _: WsSession, event: dict) -> None:
@ -249,9 +251,10 @@ class HidApi:
key = valid_hid_key(req.query.get("key")) key = valid_hid_key(req.query.get("key"))
if "state" in req.query: if "state" in req.query:
state = valid_bool(req.query["state"]) state = valid_bool(req.query["state"])
self.__hid.send_key_event(key, state) finish = valid_bool(req.query.get("finish", False))
self.__hid.send_key_event(key, state, finish)
else: else:
await self.__hid.send_key_events([(key, True), (key, False)], slow=True) self.__hid.send_key_event(key, True, True)
return make_json_response() return make_json_response()
@exposed_http("POST", "/hid/events/send_mouse_button") @exposed_http("POST", "/hid/events/send_mouse_button")

View File

@ -183,10 +183,12 @@ class KvmdClientWs:
self.__communicated = False self.__communicated = False
async def send_key_event(self, key: str, state: bool) -> None: async def send_key_event(self, key: str, state: bool) -> None:
await self.__writer_queue.put(bytes([1, state]) + key.encode("ascii")) mask = (0b01 if state else 0)
await self.__writer_queue.put(bytes([1, mask]) + key.encode("ascii"))
async def send_mouse_button_event(self, button: str, state: bool) -> None: async def send_mouse_button_event(self, button: str, state: bool) -> None:
await self.__writer_queue.put(bytes([2, state]) + button.encode("ascii")) mask = (0b01 if state else 0)
await self.__writer_queue.put(bytes([2, mask]) + button.encode("ascii"))
async def send_mouse_move_event(self, to_x: int, to_y: int) -> None: async def send_mouse_move_event(self, to_x: int, to_y: int) -> None:
await self.__writer_queue.put(struct.pack(">bhh", 3, to_x, to_y)) await self.__writer_queue.put(struct.pack(">bhh", 3, to_x, to_y))

View File

@ -170,6 +170,8 @@ class WebModifiers:
CTRL_RIGHT = "ControlRight" CTRL_RIGHT = "ControlRight"
CTRLS = set([CTRL_RIGHT, CTRL_RIGHT]) CTRLS = set([CTRL_RIGHT, CTRL_RIGHT])
ALL = (SHIFTS | ALTS | CTRLS)
class X11Modifiers: class X11Modifiers:
SHIFT_LEFT = 65505 SHIFT_LEFT = 65505

View File

@ -37,6 +37,7 @@ from ...validators.basic import valid_string_list
from ...validators.hid import valid_hid_key from ...validators.hid import valid_hid_key
from ...validators.hid import valid_hid_mouse_move from ...validators.hid import valid_hid_mouse_move
from ...keyboard.mappings import WebModifiers
from ...mouse import MouseRange from ...mouse import MouseRange
from .. import BasePlugin from .. import BasePlugin
@ -148,10 +149,14 @@ class BaseHid(BasePlugin): # pylint: disable=too-many-instance-attributes
if no_ignore_keys or key not in self.__ignore_keys: if no_ignore_keys or key not in self.__ignore_keys:
if slow: if slow:
await asyncio.sleep(0.02) await asyncio.sleep(0.02)
self.send_key_event(key, state) self.send_key_event(key, state, False)
def send_key_event(self, key: str, state: bool) -> None: def send_key_event(self, key: str, state: bool, finish: bool) -> None:
self._send_key_event(key, state) self._send_key_event(key, state)
if state and finish and (key not in WebModifiers.ALL and key != "PrintScreen"):
# Считаем что PrintScreen это модификатор для Alt+SysRq+...
# По-хорошему надо учитывать факт нажатия на Alt, но можно и забить.
self._send_key_event(key, False)
self.__bump_activity() self.__bump_activity()
def _send_key_event(self, key: str, state: bool) -> None: def _send_key_event(self, key: str, state: bool) -> None:

View File

@ -285,7 +285,7 @@
</tr> </tr>
</table> </table>
<details> <details>
<summary>Keyboard &amp; Mouse (HID) settings</summary> <summary>Keyboard &amp; mouse (HID) settings</summary>
<div class="spoiler"> <div class="spoiler">
<table class="kv"> <table class="kv">
<tr> <tr>
@ -401,6 +401,15 @@
</div> </div>
</details> </details>
<table class="kv"> <table class="kv">
<tr>
<td>Bad link mode (release keys immediately):</td>
<td align="right">
<div class="switch-box">
<input type="checkbox" id="hid-keyboard-bad-link-switch">
<label for="hid-keyboard-bad-link-switch"><span class="switch-inner"></span><span class="switch"></span></label>
</div>
</td>
</tr>
<tr class="feature-disabled" id="hid-connect"> <tr class="feature-disabled" id="hid-connect">
<td>Connect HID to Server:</td> <td>Connect HID to Server:</td>
<td align="right"> <td align="right">
@ -420,7 +429,7 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Mute HID input events:</td> <td>Mute all input HID events:</td>
<td align="right"> <td align="right">
<div class="switch-box"> <div class="switch-box">
<input type="checkbox" id="hid-mute-switch"> <input type="checkbox" id="hid-mute-switch">

View File

@ -85,7 +85,7 @@ li(id="system-dropdown" class="right")
td Mouse #[a(target="_blank" href="https://docs.pikvm.org/mouse") mode]: td Mouse #[a(target="_blank" href="https://docs.pikvm.org/mouse") mode]:
td #[div(id="hid-outputs-mouse-box" class="radio-box")] td #[div(id="hid-outputs-mouse-box" class="radio-box")]
details details
summary Keyboard &amp; Mouse (HID) settings summary Keyboard &amp; mouse (HID) settings
div(class="spoiler") div(class="spoiler")
table(class="kv") table(class="kv")
tr tr
@ -127,12 +127,14 @@ li(id="system-dropdown" class="right")
tr tr
+menu_switch_notable("page-full-tab-stream-switch", "Expand for the entire tab by default", true, false) +menu_switch_notable("page-full-tab-stream-switch", "Expand for the entire tab by default", true, false)
table(class="kv") table(class="kv")
tr
+menu_switch_notable("hid-keyboard-bad-link-switch", "Bad link mode (release keys immediately)", true, false)
tr(id="hid-connect" class="feature-disabled") tr(id="hid-connect" class="feature-disabled")
+menu_switch_notable("hid-connect-switch", "Connect HID to Server", true, true) +menu_switch_notable("hid-connect-switch", "Connect HID to Server", true, true)
tr(id="hid-jiggler" class="feature-disabled") tr(id="hid-jiggler" class="feature-disabled")
+menu_switch_notable("hid-jiggler-switch", "<a href=\"https://docs.pikvm.org/mouse_jiggler\" target=\"_blank\">Mouse jiggler</a>", false, false) +menu_switch_notable("hid-jiggler-switch", "<a href=\"https://docs.pikvm.org/mouse_jiggler\" target=\"_blank\">Mouse jiggler</a>", false, false)
tr tr
+menu_switch_notable("hid-mute-switch", "Mute HID input events", true, false) +menu_switch_notable("hid-mute-switch", "Mute all input HID events", true, false)
tr(id="v3-usb-breaker" class="feature-disabled") tr(id="v3-usb-breaker" class="feature-disabled")
+menu_switch_notable_gpio("__v3_usb_breaker__", "Connect main USB to Server", +menu_switch_notable_gpio("__v3_usb_breaker__", "Connect main USB to Server",
"Turning off this switch will disconnect the main USB from the server. Are you sure you want to continue?") "Turning off this switch will disconnect the main USB from the server. Are you sure you want to continue?")

View File

@ -52,6 +52,7 @@ export function Keyboard(__recordWsEvent) {
window.addEventListener("focusin", __updateOnlineLeds); window.addEventListener("focusin", __updateOnlineLeds);
window.addEventListener("focusout", __updateOnlineLeds); window.addEventListener("focusout", __updateOnlineLeds);
tools.storage.bindSimpleSwitch($("hid-keyboard-bad-link-switch"), "hid.keyboard.bad_link", false);
tools.storage.bindSimpleSwitch($("hid-keyboard-swap-cc-switch"), "hid.keyboard.swap_cc", false); tools.storage.bindSimpleSwitch($("hid-keyboard-swap-cc-switch"), "hid.keyboard.swap_cc", false);
}; };
@ -140,11 +141,16 @@ export function Keyboard(__recordWsEvent) {
} }
let event = { let event = {
"event_type": "key", "event_type": "key",
"event": {"key": code, "state": state}, "event": {
"key": code,
"state": state,
"finish": $("hid-keyboard-bad-link-switch").checked,
},
}; };
if (__ws && !$("hid-mute-switch").checked) { if (__ws && !$("hid-mute-switch").checked) {
__ws.sendHidEvent(event); __ws.sendHidEvent(event);
} }
delete event.event.finish;
__recordWsEvent(event); __recordWsEvent(event);
}; };

View File

@ -336,7 +336,11 @@ export function Recorder() {
}); });
return; return;
} else if (["key", "mouse_button", "mouse_move", "mouse_wheel", "mouse_relative"].includes(event.event_type)) { } else if (event.event_type === "key") {
event.event.finish = $("hid-keyboard-bad-link-switch").checked;
__ws.sendHidEvent(event);
} else if (["mouse_button", "mouse_move", "mouse_wheel", "mouse_relative"].includes(event.event_type)) {
__ws.sendHidEvent(event); __ws.sendHidEvent(event);
} else if (event.event_type === "mouse_move_random") { } else if (event.event_type === "mouse_move_random") {

View File

@ -316,6 +316,9 @@ export function Session() {
if (event_type == "key") { if (event_type == "key") {
let data = __ascii_encoder.encode("\x01\x00" + event.key); let data = __ascii_encoder.encode("\x01\x00" + event.key);
data[1] = (event.state ? 1 : 0); data[1] = (event.state ? 1 : 0);
if (event.finish === true) { // Optional
data[1] |= 0x02;
}
ws.send(data); ws.send(data);
} else if (event_type == "mouse_button") { } else if (event_type == "mouse_button") {