From 0f452fe37c3a2c744cf215e1da7febd9fee4deeb Mon Sep 17 00:00:00 2001 From: Gregory Lirent Date: Wed, 24 Jun 2026 17:14:15 +0300 Subject: [PATCH] feat(input): drop absolute-pointer (ABS) support ABS was glued onto device A alongside the keyboard and never worked right; it is not needed in practice. Remove it entirely: device A is now keyboard-only, and device B is the relative mouse (motion + buttons incl. middle + wheel). Drops the ptr_mode model (one layout remains), VMCTL_EV_ABS/PTR_*, and the absolute axes. The public input-kind enum keeps its numeric values (MOVE_REL=1, BTN=2, KEY=3, SCROLL=4) so the wire stays compatible -- only MOVE_ABS (0) is removed and its slot reserved; an unknown/0 kind is a no-op. Bump 0.3.11. --- CMakeLists.txt | 2 +- include/vmctl.h | 17 +++---- include/vmsig_event.h | 11 +++-- src/adapter/input/input.c | 17 +++---- src/adapter/vmhost/include/vmhost.h | 2 +- src/cli.c | 4 +- src/si/input/include/driver.h | 7 ++- src/si/input/include/uinput_layout.h | 36 +++++++-------- src/si/input/linux/uinput_driver.c | 66 ++++++++-------------------- src/si/input/open.c | 17 +------ src/si/input/qmp_driver.c | 6 --- src/test/test_uinputlayout.c | 58 ++++++++++-------------- 12 files changed, 85 insertions(+), 158 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49af849..fd84edb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) # Single source of truth for the version: CI passes -DVMSIG_VERSION=${TAG#v}, so the project # version (-> libvgpu-perception SONAME/.so version) and the .deb version come from one tag. -set(VMSIG_VERSION "0.3.10" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag") +set(VMSIG_VERSION "0.3.11" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag") project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C) set(CMAKE_C_STANDARD 17) diff --git a/include/vmctl.h b/include/vmctl.h index 3a24134..66c1075 100644 --- a/include/vmctl.h +++ b/include/vmctl.h @@ -15,10 +15,6 @@ typedef enum { /* via QEMU virtio-input-host-pci (Linux). uinput != virtio. */ } vmctl_driver; -#define VMCTL_PTR_ABS 1 /* uinput: absolute tablet */ -#define VMCTL_PTR_REL 2 /* uinput: relative mouse */ -#define VMCTL_PTR_BOTH 3 /* uinput: two devices A=abs B=rel */ - typedef struct { unsigned bustype; /* HID bus type, e.g. 0x0003 (USB) */ unsigned vendor; /* vendor id */ @@ -31,7 +27,6 @@ typedef struct { vmctl_driver driver; const char* qmp_path; /* QMP unix socket; required for QMP, optional (passthrough) for UINPUT */ const char* input_bus; /* virtio-input-host-pci bus "pci.0" for passthrough; "" = none */ - int ptr_mode; /* UINPUT VMCTL_PTR_*; 0 for QMP */ const vmctl_uinput_id* uinput_id; /* UINPUT only; NULL = built-in defaults */ } vmctl_config; @@ -39,13 +34,13 @@ vmctl_t* vmctl_open (const vmctl_config* cfg); /* NULL on error */ void vmctl_close(vmctl_t* v); /* safe on NULL */ /* Copy the host evdev node paths of the created uinput devices (UINPUT driver only). - * a[] receives device A, b[] receives device B (empty if not VMCTL_PTR_BOTH); each buffer - * must be >=64 bytes. Returns the count of non-empty paths filled (0/1/2), or -1 if the - * handle's driver is not UINPUT. Paths are valid while the handle is open. */ + * a[] receives device A (keyboard), b[] receives device B (relative mouse); both are always + * created, so count==2 in the normal case. Each buffer must be >=64 bytes. Returns the count + * of non-empty paths filled (0/1/2), or -1 if the handle's driver is not UINPUT. Paths are + * valid while the handle is open. */ int vmctl_uinput_evdev(vmctl_t* v, char a[64], char b[64]); /* ===== Input constants ===== */ -#define VMCTL_ABS_MAX 32767 /* abs coordinates 0..VMCTL_ABS_MAX */ #define VMCTL_AXIS_X 0 #define VMCTL_AXIS_Y 1 #define VMCTL_SCROLL_V 0 /* vertical */ @@ -67,13 +62,12 @@ int vmctl_uinput_evdev(vmctl_t* v, char a[64], char b[64]); typedef struct { int kind; /* internal event-kind code; set by builders */ int code; /* axis / button / evdev-code (per kind) */ - int value; /* abs-value / rel-delta / down(0|1) */ + int value; /* rel-delta / down(0|1) */ double scroll; /* scroll magnitude (scroll only) */ } vmctl_event; typedef struct { vmctl_event ev[VMCTL_BATCH_MAX]; int count; } vmctl_batch; void vmctl_batch_init (vmctl_batch* b); -void vmctl_batch_abs (vmctl_batch* b, int axis, int value); void vmctl_batch_rel (vmctl_batch* b, int axis, int delta); void vmctl_batch_btn (vmctl_batch* b, int btn, int down); void vmctl_batch_key (vmctl_batch* b, int evdev_code, int down); @@ -81,7 +75,6 @@ void vmctl_batch_scroll(vmctl_batch* b, int axis, double value); int vmctl_batch_send (vmctl_t* v, vmctl_batch* b); /* one round-trip; 0=ok, -1=err */ /* ===== Single events (wrappers over a 1-event batch) ===== */ -int vmctl_abs (vmctl_t* v, int axis, int value); /* 0..VMCTL_ABS_MAX */ int vmctl_rel (vmctl_t* v, int axis, int delta); 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_* */ diff --git a/include/vmsig_event.h b/include/vmsig_event.h index 69f7133..928e4ff 100644 --- a/include/vmsig_event.h +++ b/include/vmsig_event.h @@ -159,9 +159,12 @@ enum { * encodes vmsig_input into vmsig_event.inln. * * Pointer motion carries BOTH coordinates in ONE event (a pointer position is a single entity, - * not two independent axis updates). btn/key/scroll stay single-valued. */ + * not two independent axis updates). btn/key/scroll stay single-valued. + * + * Numbering is FROZEN: an external control encodes these on the wire and is not rebuilt from + * this header. Removing a member must NOT shift the others. */ typedef enum { - VMSIG_INPUT_MOVE_ABS = 0, /* absolute pointer: x,y are coordinates (0..VMCTL_ABS_MAX) */ + /* 0 reserved (was MOVE_ABS, removed) */ VMSIG_INPUT_MOVE_REL = 1, /* relative pointer: x,y are deltas (dx,dy) */ VMSIG_INPUT_BTN = 2, /* button: code=button, value=pressed(1)/released(0) */ VMSIG_INPUT_KEY = 3, /* key: code=evdev code, value=pressed/released */ @@ -175,8 +178,8 @@ typedef struct { uint16_t kind; /* vmsig_input_kind */ uint16_t code; /* button / evdev code / scroll axis (NOT used by MOVE_*) */ int32_t value; /* pressed(1)|released(0) for BTN/KEY (not used by MOVE or SCROLL) */ - int32_t x; /* MOVE_ABS: abs X (0..VMCTL_ABS_MAX); MOVE_REL: dx */ - int32_t y; /* MOVE_ABS: abs Y; MOVE_REL: dy */ + int32_t x; /* MOVE_REL: dx */ + int32_t y; /* MOVE_REL: dy */ double scroll; /* SCROLL magnitude only */ uint32_t flags; /* VMSIG_INPUT_F_* (see above) */ uint32_t _pad; /* reserved; zero on emit */ diff --git a/src/adapter/input/input.c b/src/adapter/input/input.c index 37f042f..d47ea72 100644 --- a/src/adapter/input/input.c +++ b/src/adapter/input/input.c @@ -22,8 +22,8 @@ typedef struct { int kind; /* vmsig_input_kind (for cmd==0) */ int code; /* btn/evdev-code/scroll-axis */ int value; /* pressed(1)/released(0) for btn/key */ - int x; /* MOVE_ABS: abs X; MOVE_REL: dx */ - int y; /* MOVE_ABS: abs Y; MOVE_REL: dy */ + int x; /* MOVE_REL: dx */ + int y; /* MOVE_REL: dy */ double scroll; int noack; /* CMD_INPUT fire-and-forget: emit no ACT_ACK */ int life_op; /* VMSIG_LIFE_* (powerdown/reset/wakeup/pause/resume) */ @@ -57,10 +57,6 @@ static int input_job(void* user, const void* reqp, void* resp) { /* Pointer motion is ONE packet: both axes in a single batch -> one round-trip. */ vmctl_batch b; vmctl_batch_init(&b); switch (rq->kind) { - case VMSIG_INPUT_MOVE_ABS: - vmctl_batch_abs(&b, VMCTL_AXIS_X, rq->x); - vmctl_batch_abs(&b, VMCTL_AXIS_Y, rq->y); - break; case VMSIG_INPUT_MOVE_REL: vmctl_batch_rel(&b, VMCTL_AXIS_X, rq->x); vmctl_batch_rel(&b, VMCTL_AXIS_Y, rq->y); @@ -68,7 +64,7 @@ static int input_job(void* user, const void* reqp, void* resp) { case VMSIG_INPUT_BTN: vmctl_batch_btn(&b, rq->code, rq->value); break; case VMSIG_INPUT_KEY: vmctl_batch_key(&b, rq->code, rq->value); break; case VMSIG_INPUT_SCROLL: vmctl_batch_scroll(&b, rq->code, rq->scroll); break; - default: break; + default: break; /* unknown/0 kind (e.g. retired MOVE_ABS): no-op */ } r = vmctl_batch_send(a->vmctl, &b); } else { @@ -112,15 +108,14 @@ static int in_attach(vmsig_adapter* a, const vmsig_emit* emit, vmsig_fd_reg* reg if (!a->stub) { /* armed: open the actuator. Injection is ALWAYS uinput; the resulting evdev nodes are * forwarded into the guest by the vmhost seam's input-linux object (published below). - * PTR_BOTH gives both pointer forms a device (A=kbd+abs tablet, B=rel mouse+buttons+ - * wheel) — the contract now promises both MOVE_ABS and MOVE_REL, so neither may be - * disabled. qmp_path serves the SERVICE power/lifecycle path, not input injection. */ + * uinput always creates two devices: A=keyboard, B=relative mouse+buttons+wheel — the + * contract carries MOVE_REL (there is no absolute pointer). qmp_path serves the SERVICE + * power/lifecycle path, not input injection. */ vmctl_config vcfg; memset(&vcfg, 0, sizeof vcfg); vcfg.driver = VMCTL_DRIVER_UINPUT; vcfg.qmp_path = a->qmp_path; vcfg.input_bus = ""; - vcfg.ptr_mode = VMCTL_PTR_BOTH; vcfg.uinput_id = NULL; /* built-in HID identity defaults */ a->vmctl = vmctl_open(&vcfg); if (!a->vmctl) { vmsig_worker_free(a->worker); a->worker = NULL; return -1; } diff --git a/src/adapter/vmhost/include/vmhost.h b/src/adapter/vmhost/include/vmhost.h index 9069008..e273e9e 100644 --- a/src/adapter/vmhost/include/vmhost.h +++ b/src/adapter/vmhost/include/vmhost.h @@ -10,7 +10,7 @@ typedef struct { const char* qmp_path; /* Host->guest input bridge: evdev node paths of the uinput devices (published by the input * seam). When non-NULL/non-empty, on reaching READY the seam adds an input-linux QMP object - * forwarding them into the guest (A=kbd+abs with grab_all, B=mouse). NULL/"" => no bridge + * forwarding them into the guest (A=keyboard with grab_all, B=relative mouse). NULL/"" => no bridge * (stub/tests are fail-closed). Pointers are borrowed from the stable per-endpoint home and * outlive the adapter. */ const char* bridge_evdev_a; diff --git a/src/cli.c b/src/cli.c index a7ebb5e..03ff556 100644 --- a/src/cli.c +++ b/src/cli.c @@ -106,9 +106,9 @@ static int on_event(void* user, const vmsig_event* ev) { in.prio = VMSIG_PRIO_HIGH; in.endpoint = 0; in.corr = 0xC0FFEEu; in.payload.flags = VMSIG_PL_INLINE; vmsig_input act; memset(&act, 0, sizeof act); /* neutral public input contract */ - act.kind = VMSIG_INPUT_MOVE_ABS; act.x = 100; act.y = 100; /* demo: abs pointer (100,100) */ + act.kind = VMSIG_INPUT_MOVE_REL; act.x = 5; act.y = 5; /* demo: relative move (dx=5,dy=5) */ memcpy(in.inln, &act, sizeof act); - printf(" DOWN CMD_INPUT MOVE_ABS x=100 y=100 corr=0x%X\n", (unsigned)in.corr); + printf(" DOWN CMD_INPUT MOVE_REL dx=5 dy=5 corr=0x%X\n", (unsigned)in.corr); vmsig_inproc_send(d->ctl, &in); vmsig_event vm; diff --git a/src/si/input/include/driver.h b/src/si/input/include/driver.h index 8140f18..1e7d6d5 100644 --- a/src/si/input/include/driver.h +++ b/src/si/input/include/driver.h @@ -8,7 +8,7 @@ * driver switches on (never on magic numbers). */ typedef enum { - VMCTL_EV_ABS, VMCTL_EV_REL, VMCTL_EV_BTN, VMCTL_EV_KEY, VMCTL_EV_SCROLL + VMCTL_EV_REL, VMCTL_EV_BTN, VMCTL_EV_KEY, VMCTL_EV_SCROLL } vmctl_ev_kind; typedef struct { @@ -20,9 +20,8 @@ struct vmctl { vmctl_driver_ops ops; vmctl_driver driver; qmp_conn* qmp; /* control channel; NULL if none */ - 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 */ + int ui_fd_a; /* uinput driver: device A (keyboard); -1 for QMP */ + int ui_fd_b; /* uinput driver: device B (relative mouse); -1 */ char ui_evdev_a[64]; /* uinput driver: /dev/input/eventN of A ("" if none) */ char ui_evdev_b[64]; /* uinput driver: /dev/input/eventN of B ("" if none) */ diff --git a/src/si/input/include/uinput_layout.h b/src/si/input/include/uinput_layout.h index 1840d66..bd49a48 100644 --- a/src/si/input/include/uinput_layout.h +++ b/src/si/input/include/uinput_layout.h @@ -3,39 +3,37 @@ #include "vmctl.h" /* uinput_layout.h — DECLARATIVE capability split for the uinput driver, kept pure (no ioctl) - * so it is unit-testable without /dev/uinput. The roles are derived from ptr_mode, NOT inferred - * as a side effect of rel_motion; the hot path's button/wheel carrier follows the same rule. + * so it is unit-testable without /dev/uinput. The roles are passed into the driver as DATA, not + * inferred as a side effect of one another; the hot path's button/wheel carrier follows the same + * rule. * - * Layout: device A always carries the keyboard. Mouse buttons + scroll wheel ride the device - * carrying the relative pointer (B in BOTH, A in REL-only); with no relative pointer (ABS-only) - * they fall back to A. So in BOTH: A=keyboard+abs, B=rel+buttons+wheel. */ + * Layout is CONSTANT (no absolute pointer): device A = keyboard only; device B = relative pointer + * + mouse buttons + scroll wheel. Buttons + wheel ride device B (the relative-pointer carrier). */ typedef struct { - int present; /* 1 if this device is created for the given ptr_mode */ - int rel_motion; /* advertise relative X/Y (else absolute X/Y) */ + int present; /* 1 if this device is created */ + int rel_motion; /* advertise relative X/Y (no device advertises abs) */ int want_keyboard; /* advertise the keyboard keymap */ int want_buttons; /* advertise the 8 mouse buttons */ int want_wheel; /* advertise REL_WHEEL / REL_HWHEEL */ } uinput_role; -/* Fill role_a/role_b from ptr_mode (VMCTL_PTR_*). Sets *btn_on_b to 1 when the button/wheel - * carrier on the hot path is device B (only in PTR_BOTH). role_b.present is 0 unless BOTH. */ -static inline void vmctl_uinput_layout(int ptr_mode, uinput_role* role_a, uinput_role* role_b, - int* btn_on_b) { - int both = (ptr_mode == VMCTL_PTR_BOTH); +/* Fill role_a/role_b with the constant layout. *btn_on_b is always 1: the button/wheel carrier + * on the hot path is device B (the relative-pointer device). Both devices are always present. */ +static inline void vmctl_uinput_layout(uinput_role* role_a, uinput_role* role_b, int* btn_on_b) { role_a->present = 1; - role_a->rel_motion = (ptr_mode == VMCTL_PTR_REL); + role_a->rel_motion = 0; /* keyboard-only: no pointer on A */ role_a->want_keyboard = 1; - role_a->want_buttons = !both; /* B carries buttons when there are two devices */ - role_a->want_wheel = !both; + role_a->want_buttons = 0; + role_a->want_wheel = 0; - role_b->present = both; + role_b->present = 1; role_b->rel_motion = 1; role_b->want_keyboard = 0; - role_b->want_buttons = both; - role_b->want_wheel = both; + role_b->want_buttons = 1; + role_b->want_wheel = 1; - if (btn_on_b) *btn_on_b = both; + if (btn_on_b) *btn_on_b = 1; } #endif /* VMCTL_UINPUT_LAYOUT_H */ diff --git a/src/si/input/linux/uinput_driver.c b/src/si/input/linux/uinput_driver.c index c27d295..77758b6 100644 --- a/src/si/input/linux/uinput_driver.c +++ b/src/si/input/linux/uinput_driver.c @@ -14,12 +14,10 @@ * connection (the evdev paths are exported via vmctl_uinput_evdev). The driver * switches on vmctl_ev_kind (never on magic numbers). * - * Capability layout (VMCTL_PTR_BOTH): keyboard + absolute pointer on device A; - * relative pointer + mouse buttons + scroll wheel on device B. Buttons/wheel ride - * the device carrying the relative pointer (B in BOTH, A in REL-only); with no - * relative pointer (ABS-only) they fall back to A. This split is DECLARATIVE: the - * roles (want_buttons/want_wheel/rel_motion) are passed into uinput_create, not - * inferred from rel_motion as a side effect. */ + * Capability layout (constant, no absolute pointer): device A = keyboard only; + * device B = relative pointer + mouse buttons + scroll wheel. Buttons/wheel ride + * device B (the relative-pointer carrier). This split is DECLARATIVE: the roles + * (want_buttons/want_wheel/rel_motion) are passed into uinput_create as data. */ #include "driver.h" #include "keymap.h" @@ -60,7 +58,7 @@ static void emit(int fd, uint16_t type, uint16_t code, int32_t val) { static void syn(int fd) { emit(fd, EV_SYN, SYN_REPORT, 0); } -/* The declarative per-device role (uinput_role) and the ptr_mode -> A/B split live in +/* The declarative per-device role (uinput_role) and the constant A/B split live in * uinput_layout.h so the layout is unit-testable without /dev/uinput. */ static int uinput_create(const uinput_role* role, const vmctl_uinput_id* id, const char* name, char evdev[64]) { @@ -91,21 +89,6 @@ static int uinput_create(const uinput_role* role, const vmctl_uinput_id* id, ioctl(fd, UI_SET_RELBIT, REL_Y); } - if (!role->rel_motion) { - ioctl(fd, UI_SET_EVBIT, EV_ABS); - ioctl(fd, UI_SET_ABSBIT, ABS_X); - ioctl(fd, UI_SET_ABSBIT, ABS_Y); - - struct uinput_abs_setup ax; - memset(&ax, 0, sizeof ax); - ax.code = ABS_X; - ax.absinfo.minimum = 0; - ax.absinfo.maximum = VMCTL_ABS_MAX; - ioctl(fd, UI_ABS_SETUP, &ax); - ax.code = ABS_Y; - ioctl(fd, UI_ABS_SETUP, &ax); - } - struct uinput_setup us; memset(&us, 0, sizeof us); us.id.bustype = (uint16_t)id->bustype; @@ -183,14 +166,10 @@ static void qmp_unplug(qmp_conn* qmp, const char* id) { static int uinput_driver_send(vmctl_t* v, const vmctl_batch* b) { int fd_a = v->ui_fd_a; int fd_b = v->ui_fd_b; - int both = (fd_b >= 0); - /* Relative motion, mouse buttons and the scroll wheel all ride ONE carrier device — the - * relative-pointer device. Selected once from the same declarative layout used at create - * time (btn_on_b == carrier is B), so the hot path and the advertised capabilities agree. */ - uinput_role ra, rb; int btn_on_b = 0; - vmctl_uinput_layout(v->ptr_mode, &ra, &rb, &btn_on_b); - int fd_rel = btn_on_b ? fd_b : fd_a; - int fd_btn = fd_rel; + /* Relative motion, mouse buttons and the scroll wheel all ride device B (the relative-pointer + * carrier), matching the constant layout used at create time; the keyboard rides device A. */ + int fd_rel = fd_b; + int fd_btn = fd_b; for (int i = 0; i < b->count; i++) { int code = b->ev[i].code; @@ -198,13 +177,7 @@ static int uinput_driver_send(vmctl_t* v, const vmctl_batch* b) { double scl = b->ev[i].scroll; switch ((vmctl_ev_kind)b->ev[i].kind) { - case VMCTL_EV_ABS: - if (v->ptr_mode == VMCTL_PTR_REL) return -1; - emit(fd_a, EV_ABS, code == VMCTL_AXIS_X ? ABS_X : ABS_Y, value); - syn(fd_a); - break; case VMCTL_EV_REL: { - if (!both && v->ptr_mode == VMCTL_PTR_ABS) return -1; emit(fd_rel, EV_REL, code == VMCTL_AXIS_X ? REL_X : REL_Y, value); syn(fd_rel); break; @@ -257,13 +230,13 @@ vmctl_t* vmctl_open_uinput_driver(const vmctl_config* cfg) { const char* base = (cfg->uinput_id && cfg->uinput_id->name && cfg->uinput_id->name[0]) ? cfg->uinput_id->name : NULL; - /* A/B suffix is added by the library only when two devices are created - * (VMCTL_PTR_BOTH) and only over a caller-supplied base name. */ + /* Two devices are always created (A=keyboard, B=relative mouse); the A/B suffix is added by + * the library over a caller-supplied base name. */ char name_a[UINPUT_MAX_NAME_SIZE]; char name_b[UINPUT_MAX_NAME_SIZE]; const char* dev_a = base ? base : HWID_NAME_A; const char* dev_b = HWID_NAME_B; - if (cfg->ptr_mode == VMCTL_PTR_BOTH && base) { + if (base) { int base_max = (int)(sizeof name_a - 1 /*NUL*/ - 2 /*"-A"*/); snprintf(name_a, sizeof name_a, "%.*s-A", base_max, base); snprintf(name_b, sizeof name_b, "%.*s-B", base_max, base); @@ -273,7 +246,7 @@ vmctl_t* vmctl_open_uinput_driver(const vmctl_config* cfg) { char evdev_a[64], evdev_b[64]; uinput_role role_a, role_b; - vmctl_uinput_layout(cfg->ptr_mode, &role_a, &role_b, NULL); /* declarative A/B split */ + vmctl_uinput_layout(&role_a, &role_b, NULL); /* constant A/B split */ v->ui_fd_a = uinput_create(&role_a, id, dev_a, evdev_a); if (v->ui_fd_a < 0) { free(v); return NULL; } @@ -305,19 +278,16 @@ vmctl_t* vmctl_open_uinput_driver(const vmctl_config* cfg) { free(v); return NULL; } - if (cfg->ptr_mode == VMCTL_PTR_BOTH) { - if (qmp_plug(v->qmp, cfg->input_bus, evdev_b, PLUG_ID_B) < 0) { - qmp_unplug(v->qmp, PLUG_ID_A); - uinput_driver_close(v); - free(v); - return NULL; - } + if (qmp_plug(v->qmp, cfg->input_bus, evdev_b, PLUG_ID_B) < 0) { + qmp_unplug(v->qmp, PLUG_ID_A); + uinput_driver_close(v); + free(v); + return NULL; } } } v->ops.send = uinput_driver_send; v->ops.close = uinput_driver_close; - v->ptr_mode = cfg->ptr_mode; return v; } diff --git a/src/si/input/open.c b/src/si/input/open.c index 1ba8af3..67b1e00 100644 --- a/src/si/input/open.c +++ b/src/si/input/open.c @@ -29,12 +29,6 @@ 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++]; @@ -65,7 +59,7 @@ int vmctl_batch_send(vmctl_t* v, vmctl_batch* 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. */ + * never reads this map). 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; @@ -86,7 +80,7 @@ int vmctl_batch_send(vmctl_t* v, vmctl_batch* b) { else v->btns_held &= ~mask; break; } - default: break; /* abs/rel/scroll: no-op for receipt */ + default: break; /* rel/scroll: no-op for receipt */ } } return rc; @@ -94,13 +88,6 @@ int vmctl_batch_send(vmctl_t* v, vmctl_batch* b) { /* ===== 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); diff --git a/src/si/input/qmp_driver.c b/src/si/input/qmp_driver.c index 7f9d8bc..967a928 100644 --- a/src/si/input/qmp_driver.c +++ b/src/si/input/qmp_driver.c @@ -29,11 +29,6 @@ static int qmp_driver_send(vmctl_t* v, const vmctl_batch* b) { double scl = b->ev[i].scroll; switch ((vmctl_ev_kind)b->ev[i].kind) { - case VMCTL_EV_ABS: - pos += snprintf(json + pos, (int)sizeof json - pos, - "{\"type\":\"abs\",\"data\":{\"axis\":\"%s\",\"value\":%d}}", - code == VMCTL_AXIS_X ? "x" : "y", value); - break; case VMCTL_EV_REL: pos += snprintf(json + pos, (int)sizeof json - pos, "{\"type\":\"rel\",\"data\":{\"axis\":\"%s\",\"value\":%d}}", @@ -87,7 +82,6 @@ vmctl_t* vmctl_open_qmp_driver(const vmctl_config* cfg) { v->qmp = qmp; v->ui_fd_a = -1; v->ui_fd_b = -1; - v->ptr_mode = 0; v->ops.send = qmp_driver_send; v->ops.close = qmp_driver_close; return v; diff --git a/src/test/test_uinputlayout.c b/src/test/test_uinputlayout.c index 6e006e5..3868cff 100644 --- a/src/test/test_uinputlayout.c +++ b/src/test/test_uinputlayout.c @@ -1,10 +1,10 @@ /* test_uinputlayout.c — DECLARATIVE uinput capability split (pure, no /dev/uinput). * - * Verifies the ptr_mode -> A/B role mapping that drives both device creation and the hot-path - * button/wheel carrier selection: in PTR_BOTH A is keyboard+abs and B is rel+buttons+wheel, and - * the button/wheel carrier is B; single-pointer modes keep buttons+wheel on the sole device. - * The actuation ioctls remain armed-only (they need a real /dev/uinput); this covers the logic - * that decides the layout, which is the part that single-mode regressions would break. */ + * Verifies the CONSTANT A/B role mapping that drives both device creation and the hot-path + * button/wheel carrier selection: device A = keyboard only, device B = relative pointer + buttons + * + wheel, and the button/wheel carrier is B. There is no absolute pointer anywhere — the abs role + * has been removed and is unrepresentable (no abs field exists in uinput_role). The actuation + * ioctls remain armed-only (they need a real /dev/uinput); this covers the layout logic. */ #include "vmctl.h" #include "uinput_layout.h" #include @@ -17,37 +17,25 @@ static int g_fail = 0; int main(void) { uinput_role a, b; int btn_on_b; - /* PTR_BOTH: A = keyboard + absolute pointer, no buttons/wheel; B = relative pointer + - * buttons + wheel; carrier is B. This is the requested layout (mouse buttons incl. middle - * and the wheel moved off A onto B). */ - vmctl_uinput_layout(VMCTL_PTR_BOTH, &a, &b, &btn_on_b); - CHECK(a.present && b.present, "BOTH: two devices"); - CHECK(a.want_keyboard, "BOTH: A has keyboard"); - CHECK(!a.rel_motion, "BOTH: A is absolute"); - CHECK(!a.want_buttons, "BOTH: A has NO mouse buttons"); - CHECK(!a.want_wheel, "BOTH: A has NO wheel"); - CHECK(!b.want_keyboard, "BOTH: B has no keyboard"); - CHECK(b.rel_motion, "BOTH: B is relative"); - CHECK(b.want_buttons, "BOTH: B has mouse buttons"); - CHECK(b.want_wheel, "BOTH: B has wheel"); - CHECK(btn_on_b == 1, "BOTH: button/wheel carrier is B"); + /* Constant layout: A = keyboard only (no pointer, no buttons/wheel); B = relative pointer + + * buttons + wheel; the button/wheel carrier is B. */ + vmctl_uinput_layout(&a, &b, &btn_on_b); + CHECK(a.present && b.present, "two devices"); + CHECK(a.want_keyboard, "A has keyboard"); + CHECK(!a.rel_motion, "A has no pointer (keyboard-only)"); + CHECK(!a.want_buttons, "A has NO mouse buttons"); + CHECK(!a.want_wheel, "A has NO wheel"); + CHECK(!b.want_keyboard, "B has no keyboard"); + CHECK(b.rel_motion, "B is relative"); + CHECK(b.want_buttons, "B has mouse buttons"); + CHECK(b.want_wheel, "B has wheel"); + CHECK(btn_on_b == 1, "button/wheel carrier is B"); - /* PTR_REL: single relative device A carries motion + buttons + wheel (no B). */ - vmctl_uinput_layout(VMCTL_PTR_REL, &a, &b, &btn_on_b); - CHECK(a.present && !b.present, "REL: single device A"); - CHECK(a.rel_motion, "REL: A is relative"); - CHECK(a.want_buttons, "REL: A has buttons"); - CHECK(a.want_wheel, "REL: A has wheel"); - CHECK(a.want_keyboard, "REL: A has keyboard"); - CHECK(btn_on_b == 0, "REL: carrier is A"); - - /* PTR_ABS: single absolute device A carries abs + buttons + wheel (the only device). */ - vmctl_uinput_layout(VMCTL_PTR_ABS, &a, &b, &btn_on_b); - CHECK(a.present && !b.present, "ABS: single device A"); - CHECK(!a.rel_motion, "ABS: A is absolute"); - CHECK(a.want_buttons, "ABS: A has buttons (sole device)"); - CHECK(a.want_wheel, "ABS: A has wheel (sole device)"); - CHECK(btn_on_b == 0, "ABS: carrier is A"); + /* No absolute pointer: the abs role is removed and unrepresentable (uinput_role carries no abs + * field). The invariant is that each device is either relative or has no pointer at all — A is + * keyboard-only (no pointer), B is relative. Neither advertises an absolute axis. */ + CHECK(!a.rel_motion && !a.want_buttons && !a.want_wheel, "A is keyboard-only (no pointer)"); + CHECK(b.rel_motion, "B is the relative pointer (not absolute)"); /* evdev export contract: a NULL handle reports "not a uinput handle" (-1). The populated * path (real /dev/input/eventN) is armed-only — it needs a created uinput device. */