diff --git a/include/vmctl.h b/include/vmctl.h index 53bcb36..04e8f6c 100644 --- a/include/vmctl.h +++ b/include/vmctl.h @@ -53,6 +53,9 @@ void vmctl_close(vmctl_t* v); /* safe on NULL */ #define VMCTL_BTN_BACK 6 #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) ===== */ #define VMCTL_BATCH_MAX 64 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_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) ===== */ int vmctl_powerdown(vmctl_t* v); /* system_powerdown (ACPI soft-off) */ int vmctl_reset (vmctl_t* v); /* system_reset */ diff --git a/src/vmctl/include/driver.h b/src/vmctl/include/driver.h index 3f78977..8dfd5ee 100644 --- a/src/vmctl/include/driver.h +++ b/src/vmctl/include/driver.h @@ -23,6 +23,13 @@ struct vmctl { int ui_fd_a; /* uinput driver: device A; -1 for QMP */ int ui_fd_b; /* uinput driver: device B (BOTH); -1 */ 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) */ diff --git a/src/vmctl/open.c b/src/vmctl/open.c index 06c7c65..3a9e109 100644 --- a/src/vmctl/open.c +++ b/src/vmctl/open.c @@ -6,6 +6,7 @@ #include "driver.h" #include +#include vmctl_t* vmctl_open(const vmctl_config* cfg) { 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) { 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 ===== */ @@ -99,3 +128,29 @@ int vmctl_scroll(vmctl_t* v, int axis, double value) { 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; +}