HIDResource¶
HIDResource
¶
HID keyboard and mouse control for PiKVM.
Source code in src/aiopikvm/resources/hid.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | |
get_state()
async
¶
get_inactivity()
async
¶
Get the time since the last keyboard or mouse event.
The counter is what drives the jiggler. It tracks the input kvmd itself delivered, from any of its clients — somebody typing on a keyboard plugged straight into the target host does not reset it.
Returns:
| Type | Description |
|---|---|
int
|
Seconds since the last HID event kvmd sent. |
Source code in src/aiopikvm/resources/hid.py
set_params(*, keyboard_output=None, mouse_output=None, jiggler=None)
async
¶
Set HID output parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keyboard_output
|
KeyboardOutput | None
|
Keyboard output type, one of
|
None
|
mouse_output
|
MouseOutput | None
|
Mouse output type, one of
|
None
|
jiggler
|
bool | None
|
Whether the mouse jiggler moves the pointer while the host is idle. |
None
|
Raises:
| Type | Description |
|---|---|
APIError
|
If kvmd does not know one of these output names (HTTP 400). An output it knows but the running backend does not offer is not an error — it answers 200 and what becomes of the name is up to the backend — so read the state back to see what took. |
Source code in src/aiopikvm/resources/hid.py
set_connected(connected)
async
¶
Unplug the emulated HID from the target host, or plug it back in.
Only the MCU-based backends do this: kvmd 4.206 implements it in the
ones that drive a separate microcontroller, hid.type set to
serial or spi, and nowhere else. Under otg, ch9329 or
bt the call reaches a base implementation that discards its
argument, so kvmd answers 200 and nothing happens. The capture device
behind this project's fixtures, a v3, runs otg.
Nothing in the response says which of the two took place, so read
HIDState.connected — but read it as the one-way signal it is. A
bool there is a backend that does implement this call. None
is not the opposite: an MCU backend reports None as well until
its microcontroller has answered with a status word that carries the
flag, so a board that is merely offline, or whose firmware answers
the shorter pong, looks exactly like one that cannot unplug
anything. HIDState.online rules out the offline board; the
firmware that never sends the flag is not distinguishable at all.
Give the change a moment before reading it back, too: it travels to the microcontroller through a queue and this call returns as soon as it is queued. On the way in it empties that queue, so keystrokes sent a moment earlier and not yet delivered are dropped with it.
reset() is a different
matter: every backend overrides that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
connected
|
bool
|
Whether the host should see the HID as plugged in. |
required |
Source code in src/aiopikvm/resources/hid.py
reset()
async
¶
Reset the HID subsystem.
Every backend overrides this, unlike
set_connected(),
but what it means differs by more than the name suggests. Under
otg kvmd drops the input still queued and releases every key and
button the host sees as held — the way out of a modifier left stuck by
a script that died mid-shortcut. bt does that and then drops its
Bluetooth clients, unpairing them unless unpair_on_close is turned
off, so the host has to pair again. An MCU backend resets the
microcontroller itself, through its reset pin where one is configured,
and keeps the queued input to deliver afterwards. Under ch9329
nothing happens that anything can observe: the reset request its loop
would send is commented out in kvmd 4.206, and all it does instead is
set an internal busy flag that get_state() never reports.
Source code in src/aiopikvm/resources/hid.py
get_keymaps()
async
¶
Get the keyboard layouts installed on the device.
Returns:
| Type | Description |
|---|---|
HIDKeymaps
|
The available layout names and the device-wide default. |
Source code in src/aiopikvm/resources/hid.py
type_text(text, *, limit=0, keymap=None, delay=None, slow=False, timeout=None)
async
¶
Type text via HID keyboard.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
Text string to type. |
required |
limit
|
int
|
Server-side truncation: kvmd types the first |
0
|
keymap
|
str | None
|
Layout used to translate the text into key events, from
|
None
|
delay
|
float | None
|
Seconds to sleep between key events, 0 to 5. Defaults to
|
None
|
slow
|
bool
|
Enable server-side per-character delays for reliable input. |
False
|
timeout
|
float | None
|
Per-call timeout in seconds. kvmd types the whole string
before answering, so anything that stretches that out needs a
wider timeout than the client default: |
None
|
Source code in src/aiopikvm/resources/hid.py
send_key(key, *, state=None, finish=None)
async
¶
Send a single key event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Key name, one of
|
required |
state
|
bool | None
|
Key state ( |
None
|
finish
|
bool | None
|
Ask kvmd to release the key in the same event that
pressed it, so a script that dies mid-keystroke leaves
nothing held. It goes out only on a press: kvmd reads it
beside state and acts on it only when that is one, so a
release is sent as a plain release. Not every key is
released — kvmd exempts the eight modifiers,
|
None
|
Raises:
| Type | Description |
|---|---|
APIError
|
If kvmd has no key by that name (HTTP 400). |
Source code in src/aiopikvm/resources/hid.py
send_shortcut(*keys)
async
¶
Send a keyboard shortcut.
The server presses the keys in order and releases them in reverse order, with a fixed 50 ms delay between events.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*keys
|
str
|
Key names forming the shortcut, each one of
|
()
|
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If no keys are given, or if one of them is
empty or holds a comma or any whitespace. kvmd takes the
shortcut as one string, strips it, splits it on commas, spaces
and tabs and throws away what falls out empty, so such a key
would not survive the trip: it vanishes and the rest of the
shortcut is pressed as if it had never been asked for. No name
in |
APIError
|
If kvmd has no key by one of those names (HTTP 400). It validates the whole list before pressing anything, so a shortcut with one bad name sends nothing at all. |
Source code in src/aiopikvm/resources/hid.py
send_mouse_button(button, *, state=None)
async
¶
Send a mouse button event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
button
|
MouseButton
|
Button name, one of
|
required |
state
|
bool | None
|
Button state ( |
None
|
Raises:
| Type | Description |
|---|---|
APIError
|
If kvmd has no button by that name (HTTP 400). |
Source code in src/aiopikvm/resources/hid.py
send_mouse_move(to_x, to_y)
async
¶
Move the mouse to absolute coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to_x
|
int
|
Target X coordinate. |
required |
to_y
|
int
|
Target Y coordinate. |
required |
Source code in src/aiopikvm/resources/hid.py
send_mouse_relative(delta_x, delta_y)
async
¶
Move the mouse by relative offset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
delta_x
|
int
|
Horizontal offset. |
required |
delta_y
|
int
|
Vertical offset. |
required |
Source code in src/aiopikvm/resources/hid.py
send_mouse_wheel(delta_x, delta_y)
async
¶
Send a mouse wheel event.
Deltas are steps in kvmd's own range, -127 to 127, clamped by kvmd
rather than rejected, and carried in the HID wheel field. They are not
a browser's pixel deltas: a browser reports a scroll-down gesture as a
positive deltaY, and kvmd's own web UI negates it and sizes it by
its scroll-rate setting (1 to 25, 5 by default), so the gesture
reaches the device as delta_y = -5.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
delta_x
|
int
|
Horizontal step, -127 to 127. It needs a backend with a
horizontal wheel behind it, and in kvmd 4.206 only |
required |
delta_y
|
int
|
Vertical step, -127 to 127. Negative scrolls down on a
host with the usual wheel mapping. |
required |
Source code in src/aiopikvm/resources/hid.py
KEY_NAMES = frozenset({'AltLeft', 'AltRight', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'AudioVolumeDown', 'AudioVolumeMute', 'AudioVolumeUp', 'Backquote', 'Backslash', 'Backspace', 'BracketLeft', 'BracketRight', 'CapsLock', 'Comma', 'ContextMenu', 'ControlLeft', 'ControlRight', 'Convert', 'Delete', 'Digit0', 'Digit1', 'Digit2', 'Digit3', 'Digit4', 'Digit5', 'Digit6', 'Digit7', 'Digit8', 'Digit9', 'End', 'Enter', 'Equal', 'Escape', 'F1', 'F10', 'F11', 'F12', 'F13', 'F14', 'F15', 'F16', 'F17', 'F18', 'F19', 'F2', 'F20', 'F21', 'F22', 'F23', 'F24', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'Home', 'Insert', 'IntlBackslash', 'IntlRo', 'IntlYen', 'KanaMode', 'KeyA', 'KeyB', 'KeyC', 'KeyD', 'KeyE', 'KeyF', 'KeyG', 'KeyH', 'KeyI', 'KeyJ', 'KeyK', 'KeyL', 'KeyM', 'KeyN', 'KeyO', 'KeyP', 'KeyQ', 'KeyR', 'KeyS', 'KeyT', 'KeyU', 'KeyV', 'KeyW', 'KeyX', 'KeyY', 'KeyZ', 'MetaLeft', 'MetaRight', 'Minus', 'NonConvert', 'NumLock', 'Numpad0', 'Numpad1', 'Numpad2', 'Numpad3', 'Numpad4', 'Numpad5', 'Numpad6', 'Numpad7', 'Numpad8', 'Numpad9', 'NumpadAdd', 'NumpadDecimal', 'NumpadDivide', 'NumpadEnter', 'NumpadMultiply', 'NumpadSubtract', 'PageDown', 'PageUp', 'Pause', 'Period', 'Power', 'PrintScreen', 'Quote', 'ScrollLock', 'Semicolon', 'ShiftLeft', 'ShiftRight', 'Slash', 'Space', 'Tab'})
module-attribute
¶
Every key name kvmd accepts, matched case-sensitively.
These are the keys of kvmd's WEB_TO_EVDEV table, which is where its
validator looks: the names a browser puts in KeyboardEvent.code, which
is why they read like "KeyA" and "Digit1" rather than "a" and
"1". Anything else is refused — "keya" and "a" included.
Only one of the two transports says so. An HTTP call raises
APIError with HTTP 400, and its message names the key
kvmd would not take — except from
HIDResource.send_key(), where
a name past 16 characters is refused on length alone and the message names
nothing at all. A key sent over the WebSocket is dropped inside kvmd's handler
with no answer of any kind, and nothing there tells a typo from a keystroke
that landed. Checking a name that came from somewhere untrusted is what stands
in for the answer the socket does not give:
if key not in KEY_NAMES:
raise ValueError(f"kvmd has no key named {key!r}")
await ws.send_key(key, state=True)
The set is kvmd 4.206's, recorded from the device behind this project's fixtures; no endpoint exposes the table, so this cannot be read from a device at runtime. Another version may know more names — nothing in the client enforces the set, and a name outside it is sent as given.
This is a runtime set rather than a type, unlike the smaller vocabularies below. A key name is usually computed — read out of a browser event, a config file, a table of shortcuts — and a static list of 126 members would be in the way far more often than it caught a typo.
KeyboardOutput = Literal['usb', 'ps2', 'disabled']
¶
What keyboard_output may be in
HIDResource.set_params().
These three are what kvmd's own validator accepts; anything else is
HTTP 400. It lowercases the value first, so a device would also take
"USB" — only the canonical spelling is typed here.
Being accepted is not being applied. kvmd validates against this list
whatever HID backend is running, and then hands the value to a backend
that may have no use for it. In kvmd 4.206 only the MCU backends act on
it at all; otg, ch9329 and bt discard the argument and still
answer 200. HIDState.keyboard.outputs.available is what the running
backend offers, and it is empty when there is no choice to make.
MouseOutput = Literal['usb', 'usb_win98', 'usb_rel', 'ps2', 'disabled']
¶
What mouse_output may be in
HIDResource.set_params().
"usb" is the absolute mouse, "usb_rel" the relative one, and
"usb_win98" an absolute mouse with a workaround for Windows 98's driver.
HIDState.mouse.outputs.active names the one in use, and
HIDState.mouse.absolute says whether it reports positions or movement —
which is what decides between
HIDResource.send_mouse_move()
and
HIDResource.send_mouse_relative().
The same two-step as
KeyboardOutput: kvmd validates the
name against this list on every backend, then hands it to a backend that may
not have that mouse. What happens then is the backend's own business and not
always visible — otg ignores a name outside
HIDState.mouse.outputs.available, under an HTTP 200, while ch9329
offers two names and acts on any of the five, taking everything but "usb"
as its relative mouse. Read the state back rather than assume the name was
applied as asked.
MouseButton = Literal['left', 'right', 'middle', 'up', 'down']
¶
The mouse buttons kvmd knows, over REST and over the WebSocket alike.
"up" and "down" are the side buttons a browser reports as back and
forward — not wheel directions, which are
HIDResource.send_mouse_wheel().
kvmd lowercases the name before it looks it up, so only the canonical spelling
is typed.
This is the only one of these vocabularies with two ways in, and they report a
wrong name differently:
HIDResource.send_mouse_button()
raises APIError with HTTP 400, while
PiKVMWebSocket.send_mouse_button() gets no answer of any kind — the frame
is dropped inside kvmd's handler, as a bad key name is.