vmctl: read-only held-state receipt for keys and pointer buttons

Add vmctl_key_held, vmctl_btn_held, vmctl_keys_snapshot and
vmctl_btns_snapshot, exposing the down-state each handle last actuated.
Two bitmaps in the handle (keys by evdev KEY_*, buttons by VMCTL_BTN_*)
are updated after a successful send in vmctl_batch_send; the send path
never reads them. VMCTL_KEY_CODE_MAX and VMCTL_KEYS_SNAPSHOT_BYTES give
callers the keys-snapshot buffer size.
This commit is contained in:
2026-06-18 22:51:36 +03:00
parent c5fac9c077
commit 8c878b6d77
3 changed files with 79 additions and 1 deletions
+16
View File
@@ -53,6 +53,9 @@ void vmctl_close(vmctl_t* v); /* safe on NULL */
#define VMCTL_BTN_BACK 6 #define VMCTL_BTN_BACK 6
#define VMCTL_BTN_TASK 7 #define VMCTL_BTN_TASK 7
#define VMCTL_KEY_CODE_MAX 0x2ff /* highest supported evdev key code (inclusive) */
#define VMCTL_KEYS_SNAPSHOT_BYTES ((VMCTL_KEY_CODE_MAX + 1) / 8) /* bytes for vmctl_keys_snapshot */
/* ===== Event batch (value-type, stack; build ONLY via builders — ev[] is not API) ===== */ /* ===== Event batch (value-type, stack; build ONLY via builders — ev[] is not API) ===== */
#define VMCTL_BATCH_MAX 64 #define VMCTL_BATCH_MAX 64
typedef struct { typedef struct {
@@ -78,6 +81,19 @@ int vmctl_btn (vmctl_t* v, int btn, int down); /* VMCTL_BTN_* */
int vmctl_key (vmctl_t* v, int evdev_code, int down); /* Linux KEY_* */ int vmctl_key (vmctl_t* v, int evdev_code, int down); /* Linux KEY_* */
int vmctl_scroll(vmctl_t* v, int axis, double value); /* VMCTL_SCROLL_* */ int vmctl_scroll(vmctl_t* v, int axis, double value); /* VMCTL_SCROLL_* */
/* ===== Held-state receipt (read-only) =====
* "held" = key/button state as THIS handle last actuated it, not guest truth.
* It is the actuator's record of its own last output (sensing the guest belongs
* to the sensors layer, not here). Updated only after a successful send; the
* send path NEVER reads this map (no dedup, no auto-release, no autorepeat). */
int vmctl_key_held (vmctl_t* v, int evdev_code); /* Linux KEY_*; 1=down 0=up */
int vmctl_btn_held (vmctl_t* v, int btn); /* VMCTL_BTN_*; 1=down 0=up */
int vmctl_keys_snapshot(vmctl_t* v, unsigned char* bits, size_t nbytes);
/* copy key down-bits (EVIOCGKEY-style);
returns bytes written, -1 on bad args */
unsigned vmctl_btns_snapshot(vmctl_t* v); /* VMCTL_BTN_* down-bits as a mask (bits 0..7) */
/* ===== Power/lifecycle actuation (requires a QMP connection; -1 if there is none) ===== */ /* ===== Power/lifecycle actuation (requires a QMP connection; -1 if there is none) ===== */
int vmctl_powerdown(vmctl_t* v); /* system_powerdown (ACPI soft-off) */ int vmctl_powerdown(vmctl_t* v); /* system_powerdown (ACPI soft-off) */
int vmctl_reset (vmctl_t* v); /* system_reset */ int vmctl_reset (vmctl_t* v); /* system_reset */
+7
View File
@@ -23,6 +23,13 @@ struct vmctl {
int ui_fd_a; /* uinput driver: device A; -1 for QMP */ int ui_fd_a; /* uinput driver: device A; -1 for QMP */
int ui_fd_b; /* uinput driver: device B (BOTH); -1 */ int ui_fd_b; /* uinput driver: device B (BOTH); -1 */
int ptr_mode; /* uinput driver: VMCTL_PTR_*; 0 for QMP */ int ptr_mode; /* uinput driver: VMCTL_PTR_*; 0 for QMP */
/* Held-state receipt: key/btn down-bits as THIS handle last actuated them
* (not guest truth). Written only after a successful send in
* vmctl_batch_send; the send path never reads them. Zero-initialised by
* calloc at open = all up. Single-threaded (one handle owner): no locks. */
unsigned char keys_held[VMCTL_KEYS_SNAPSHOT_BYTES]; /* evdev-indexed key down-bits */
unsigned btns_held; /* VMCTL_BTN_* 0..7 down-bits */
}; };
/* driver factories (called from open.c per cfg->driver) */ /* driver factories (called from open.c per cfg->driver) */
+56 -1
View File
@@ -6,6 +6,7 @@
#include "driver.h" #include "driver.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
vmctl_t* vmctl_open(const vmctl_config* cfg) { vmctl_t* vmctl_open(const vmctl_config* cfg) {
if (!cfg) return NULL; if (!cfg) return NULL;
@@ -60,7 +61,35 @@ void vmctl_batch_scroll(vmctl_batch* b, int axis, double value) {
int vmctl_batch_send(vmctl_t* v, vmctl_batch* b) { int vmctl_batch_send(vmctl_t* v, vmctl_batch* b) {
if (b->count == 0) return 0; if (b->count == 0) return 0;
return v->ops.send(v, b); 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 ===== */ /* ===== Single-event wrappers ===== */
@@ -99,3 +128,29 @@ int vmctl_scroll(vmctl_t* v, int axis, double value) {
vmctl_batch_scroll(&b, axis, value); vmctl_batch_scroll(&b, axis, value);
return vmctl_batch_send(v, &b); 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;
}