Files
vatrog-vm-signaling/src/si/input/open.c
T
lirent d6c45ddb04 feat(input): daemon sets up the host->guest input-linux bridge via QMP
The uinput devices the input adapter creates were never forwarded into the
guest: the input-linux bridge was external (manual monitor object_add), so it
was lost on every reconfigure and whenever the kernel-assigned device numbers
changed. Make the vmhost seam -- which already owns the VM's single QMP
connection -- add the input-linux objects itself on reaching READY (A=keyboard
+abs with grab_all, B=mouse) and object_del them on teardown. The input adapter
publishes the uinput evdev paths into a per-endpoint home; discovery attaches
input before vmhost so the paths are ready. No second QMP socket or connection.

Also move the mouse buttons (incl. middle) and the wheel onto device B, so B is
a complete relative mouse and A is keyboard+abs only.

Bump 0.3.8.
2026-06-24 16:31:23 +03:00

167 lines
5.3 KiB
C

/* open.c — handle lifecycle and the input batch API. vmctl_open dispatches to a
* driver factory by cfg->driver; vmctl_close releases via ops.close. The batch
* builders set vmctl_event.kind (the single event-kind code that drivers read),
* and the single-event wrappers are thin batches of one. */
#include "driver.h"
#include <stdlib.h>
#include <string.h>
vmctl_t* vmctl_open(const vmctl_config* cfg) {
if (!cfg) return NULL;
switch (cfg->driver) {
case VMCTL_DRIVER_QMP: return vmctl_open_qmp_driver(cfg);
case VMCTL_DRIVER_UINPUT: return vmctl_open_uinput_driver(cfg);
default: return NULL;
}
}
void vmctl_close(vmctl_t* v) {
if (!v) return;
v->ops.close(v);
free(v);
}
/* ===== Batch builders ===== */
void vmctl_batch_init(vmctl_batch* b) {
b->count = 0;
}
void vmctl_batch_abs(vmctl_batch* b, int axis, int value) {
if (b->count >= VMCTL_BATCH_MAX) return;
vmctl_event* e = &b->ev[b->count++];
e->kind = VMCTL_EV_ABS; e->code = axis; e->value = value; e->scroll = 0.0;
}
void vmctl_batch_rel(vmctl_batch* b, int axis, int delta) {
if (b->count >= VMCTL_BATCH_MAX) return;
vmctl_event* e = &b->ev[b->count++];
e->kind = VMCTL_EV_REL; e->code = axis; e->value = delta; e->scroll = 0.0;
}
void vmctl_batch_btn(vmctl_batch* b, int btn, int down) {
if (b->count >= VMCTL_BATCH_MAX) return;
vmctl_event* e = &b->ev[b->count++];
e->kind = VMCTL_EV_BTN; e->code = btn; e->value = down; e->scroll = 0.0;
}
void vmctl_batch_key(vmctl_batch* b, int evdev_code, int down) {
if (b->count >= VMCTL_BATCH_MAX) return;
vmctl_event* e = &b->ev[b->count++];
e->kind = VMCTL_EV_KEY; e->code = evdev_code; e->value = down; e->scroll = 0.0;
}
void vmctl_batch_scroll(vmctl_batch* b, int axis, double value) {
if (b->count >= VMCTL_BATCH_MAX) return;
vmctl_event* e = &b->ev[b->count++];
e->kind = VMCTL_EV_SCROLL; e->code = axis; e->value = 0; e->scroll = value;
}
int vmctl_batch_send(vmctl_t* v, vmctl_batch* b) {
if (b->count == 0) return 0;
int rc = v->ops.send(v, b);
if (rc != 0) return rc; /* not sent = not recorded; never touch the receipt */
/* Record the actuated key/btn down-bits (write-only; the send path above
* never reads this map). abs/rel/scroll have no held state. */
for (int i = 0; i < b->count; i++) {
const vmctl_event* e = &b->ev[i];
int down = e->value ? 1 : 0;
switch (e->kind) {
case VMCTL_EV_KEY: {
int code = e->code;
if (code < 0 || code > VMCTL_KEY_CODE_MAX) break; /* out of range: ignore */
unsigned char mask = (unsigned char)(1u << (code & 7));
if (down) v->keys_held[code >> 3] |= mask;
else v->keys_held[code >> 3] &= (unsigned char)~mask;
break;
}
case VMCTL_EV_BTN: {
int btn = e->code;
if (btn < 0 || btn >= 8) break; /* out of range: ignore */
unsigned mask = 1u << btn;
if (down) v->btns_held |= mask;
else v->btns_held &= ~mask;
break;
}
default: break; /* abs/rel/scroll: no-op for receipt */
}
}
return rc;
}
/* ===== Single-event wrappers ===== */
int vmctl_abs(vmctl_t* v, int axis, int value) {
vmctl_batch b;
vmctl_batch_init(&b);
vmctl_batch_abs(&b, axis, value);
return vmctl_batch_send(v, &b);
}
int vmctl_rel(vmctl_t* v, int axis, int delta) {
vmctl_batch b;
vmctl_batch_init(&b);
vmctl_batch_rel(&b, axis, delta);
return vmctl_batch_send(v, &b);
}
int vmctl_btn(vmctl_t* v, int btn, int down) {
vmctl_batch b;
vmctl_batch_init(&b);
vmctl_batch_btn(&b, btn, down);
return vmctl_batch_send(v, &b);
}
int vmctl_key(vmctl_t* v, int evdev_code, int down) {
vmctl_batch b;
vmctl_batch_init(&b);
vmctl_batch_key(&b, evdev_code, down);
return vmctl_batch_send(v, &b);
}
int vmctl_scroll(vmctl_t* v, int axis, double value) {
vmctl_batch b;
vmctl_batch_init(&b);
vmctl_batch_scroll(&b, axis, value);
return vmctl_batch_send(v, &b);
}
/* ===== Held-state receipt (read-only) =====
* Reads of the actuator's own last output; never mutate driver state. The
* in-range predicate matches the write path in vmctl_batch_send. */
int vmctl_key_held(vmctl_t* v, int evdev_code) {
if (!v || evdev_code < 0 || evdev_code > VMCTL_KEY_CODE_MAX) return 0;
return (v->keys_held[evdev_code >> 3] >> (evdev_code & 7)) & 1;
}
int vmctl_btn_held(vmctl_t* v, int btn) {
if (!v || btn < 0 || btn >= 8) return 0;
return (int)((v->btns_held >> btn) & 1u);
}
int vmctl_keys_snapshot(vmctl_t* v, unsigned char* bits, size_t nbytes) {
if (!v || !bits) return -1;
size_t n = nbytes < VMCTL_KEYS_SNAPSHOT_BYTES ? nbytes : VMCTL_KEYS_SNAPSHOT_BYTES;
memcpy(bits, v->keys_held, n);
return (int)n;
}
unsigned vmctl_btns_snapshot(vmctl_t* v) {
if (!v) return 0;
return v->btns_held;
}
/* ===== uinput evdev export (UINPUT-only) ===== */
int vmctl_uinput_evdev(vmctl_t* v, char a[64], char b[64]) {
if (!v || v->driver != VMCTL_DRIVER_UINPUT) return -1;
int n = 0;
if (a) { memcpy(a, v->ui_evdev_a, sizeof v->ui_evdev_a); if (a[0]) n++; }
if (b) { memcpy(b, v->ui_evdev_b, sizeof v->ui_evdev_b); if (b[0]) n++; }
return n;
}