feat(input): map the keypad (numpad) keys

VMCTL_KEYS carried only NumLock, not the keypad block — so any keypad evdev
code sent in CMD_INPUT{KEY} was dropped on both paths: uinput device A never
declared the keybit, and the QMP qcode lookup returned NULL.

Add the full keypad in sorted evdev positions (the table is bsearch'd):
kp_0..kp_9, kp_add/subtract/multiply/divide, kp_enter, kp_decimal, kp_equals,
kp_comma. Both the uinput keybits and the QMP qcodes derive from this one
table, so both paths pick the keys up.

Add test_keymap pinning the strictly-ascending-by-evdev invariant (bsearch
precondition + no duplicates) and the keypad lookup.

Bump 0.3.13.
This commit is contained in:
2026-06-26 02:10:58 +03:00
parent bcf5d4f824
commit 8c48084e33
3 changed files with 81 additions and 1 deletions
+8 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# Single source of truth for the version: CI passes -DVMSIG_VERSION=${TAG#v}, so the project # 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. # version (-> libvgpu-perception SONAME/.so version) and the .deb version come from one tag.
set(VMSIG_VERSION "0.3.12" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag") set(VMSIG_VERSION "0.3.13" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag")
project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C) project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C)
set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD 17)
@@ -276,6 +276,13 @@ target_include_directories(vmsig_uinputlayouttest PRIVATE
target_compile_options(vmsig_uinputlayouttest PRIVATE -Wall -Wextra) target_compile_options(vmsig_uinputlayouttest PRIVATE -Wall -Wextra)
add_test(NAME uinputlayout COMMAND vmsig_uinputlayouttest) add_test(NAME uinputlayout COMMAND vmsig_uinputlayouttest)
add_executable(vmsig_keymaptest src/test/test_keymap.c)
target_link_libraries(vmsig_keymaptest PRIVATE vmsig)
target_include_directories(vmsig_keymaptest PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/si/input/include)
target_compile_options(vmsig_keymaptest PRIVATE -Wall -Wextra)
add_test(NAME keymap COMMAND vmsig_keymaptest)
add_executable(vmsig_memwritetest src/test/test_memwrite.c) add_executable(vmsig_memwritetest src/test/test_memwrite.c)
target_link_libraries(vmsig_memwritetest PRIVATE vmsig Threads::Threads) target_link_libraries(vmsig_memwritetest PRIVATE vmsig Threads::Threads)
target_include_directories(vmsig_memwritetest PRIVATE target_include_directories(vmsig_memwritetest PRIVATE
+18
View File
@@ -62,6 +62,7 @@ const vmctl_keymap VMCTL_KEYS[] = {
{ KEY_DOT, "dot" }, { KEY_DOT, "dot" },
{ KEY_SLASH, "slash" }, { KEY_SLASH, "slash" },
{ KEY_RIGHTSHIFT, "shift_r" }, { KEY_RIGHTSHIFT, "shift_r" },
{ KEY_KPASTERISK, "kp_multiply" },
{ KEY_LEFTALT, "alt" }, { KEY_LEFTALT, "alt" },
{ KEY_SPACE, "spc" }, { KEY_SPACE, "spc" },
{ KEY_CAPSLOCK, "caps_lock" }, { KEY_CAPSLOCK, "caps_lock" },
@@ -77,10 +78,25 @@ const vmctl_keymap VMCTL_KEYS[] = {
{ KEY_F10, "f10" }, { KEY_F10, "f10" },
{ KEY_NUMLOCK, "num_lock" }, { KEY_NUMLOCK, "num_lock" },
{ KEY_SCROLLLOCK, "scroll_lock" }, { KEY_SCROLLLOCK, "scroll_lock" },
{ KEY_KP7, "kp_7" },
{ KEY_KP8, "kp_8" },
{ KEY_KP9, "kp_9" },
{ KEY_KPMINUS, "kp_subtract" },
{ KEY_KP4, "kp_4" },
{ KEY_KP5, "kp_5" },
{ KEY_KP6, "kp_6" },
{ KEY_KPPLUS, "kp_add" },
{ KEY_KP1, "kp_1" },
{ KEY_KP2, "kp_2" },
{ KEY_KP3, "kp_3" },
{ KEY_KP0, "kp_0" },
{ KEY_KPDOT, "kp_decimal" },
{ KEY_102ND, "less" }, { KEY_102ND, "less" },
{ KEY_F11, "f11" }, { KEY_F11, "f11" },
{ KEY_F12, "f12" }, { KEY_F12, "f12" },
{ KEY_KPENTER, "kp_enter" },
{ KEY_RIGHTCTRL, "ctrl_r" }, { KEY_RIGHTCTRL, "ctrl_r" },
{ KEY_KPSLASH, "kp_divide" },
{ KEY_SYSRQ, "print" }, { KEY_SYSRQ, "print" },
{ KEY_RIGHTALT, "alt_r" }, { KEY_RIGHTALT, "alt_r" },
{ KEY_HOME, "home" }, { KEY_HOME, "home" },
@@ -94,7 +110,9 @@ const vmctl_keymap VMCTL_KEYS[] = {
{ KEY_INSERT, "insert" }, { KEY_INSERT, "insert" },
{ KEY_DELETE, "delete" }, { KEY_DELETE, "delete" },
{ KEY_POWER, "power" }, { KEY_POWER, "power" },
{ KEY_KPEQUAL, "kp_equals" },
{ KEY_PAUSE, "pause" }, { KEY_PAUSE, "pause" },
{ KEY_KPCOMMA, "kp_comma" },
{ KEY_LEFTMETA, "meta_l" }, { KEY_LEFTMETA, "meta_l" },
{ KEY_RIGHTMETA, "meta_r" }, { KEY_RIGHTMETA, "meta_r" },
{ KEY_SLEEP, "sleep" }, { KEY_SLEEP, "sleep" },
+55
View File
@@ -0,0 +1,55 @@
/* test_keymap.c — VMCTL_KEYS invariants. The table is the single source of truth for BOTH the
* uinput keybits (UI_SET_KEYBIT per entry) and the QMP qcode lookup, and it is searched with
* bsearch — so it MUST stay strictly ascending by evdev, and a missing entry silently drops the
* key on both paths. This test pins the ordering invariant and the keypad block that was added. */
#include "keymap.h"
#include <linux/input-event-codes.h>
#include <string.h>
#include <stdio.h>
static int g_fail = 0;
#define CHECK(cond, msg) do { if (!(cond)) { printf(" FAIL: %s\n", (msg)); g_fail = 1; } } while (0)
/* vmctl_evdev_to_qcode(evdev) must resolve to `want`. */
static void expect_qcode(int evdev, const char* want) {
const char* got = vmctl_evdev_to_qcode(evdev);
if (!got || strcmp(got, want) != 0) {
printf(" FAIL: evdev %d -> %s (want %s)\n", evdev, got ? got : "(null)", want);
g_fail = 1;
}
}
int main(void) {
/* 1. strictly ascending by evdev => no duplicates AND a valid bsearch precondition. */
for (int i = 1; i < VMCTL_KEYS_LEN; i++)
CHECK(VMCTL_KEYS[i].evdev > VMCTL_KEYS[i - 1].evdev,
"VMCTL_KEYS must be strictly ascending by evdev (bsearch precondition + no dup)");
/* 2. every entry round-trips through the bsearch lookup in its current order. */
for (int i = 0; i < VMCTL_KEYS_LEN; i++)
CHECK(vmctl_evdev_to_qcode(VMCTL_KEYS[i].evdev) == VMCTL_KEYS[i].qcode,
"every evdev resolves to its own qcode");
/* 3. keypad block -> correct QEMU QKeyCodes (the gap that was fixed). */
expect_qcode(KEY_KP0, "kp_0"); expect_qcode(KEY_KP1, "kp_1"); expect_qcode(KEY_KP2, "kp_2");
expect_qcode(KEY_KP3, "kp_3"); expect_qcode(KEY_KP4, "kp_4"); expect_qcode(KEY_KP5, "kp_5");
expect_qcode(KEY_KP6, "kp_6"); expect_qcode(KEY_KP7, "kp_7"); expect_qcode(KEY_KP8, "kp_8");
expect_qcode(KEY_KP9, "kp_9");
expect_qcode(KEY_KPPLUS, "kp_add");
expect_qcode(KEY_KPMINUS, "kp_subtract");
expect_qcode(KEY_KPASTERISK, "kp_multiply");
expect_qcode(KEY_KPSLASH, "kp_divide");
expect_qcode(KEY_KPENTER, "kp_enter");
expect_qcode(KEY_KPDOT, "kp_decimal");
expect_qcode(KEY_KPEQUAL, "kp_equals");
expect_qcode(KEY_KPCOMMA, "kp_comma");
/* 4. NumLock toggle is present; a main-block key still resolves; an unmapped code is NULL. */
expect_qcode(KEY_NUMLOCK, "num_lock");
expect_qcode(KEY_A, "a");
CHECK(vmctl_evdev_to_qcode(KEY_FN) == NULL, "an unmapped evdev returns NULL");
printf("keymap tests: %s\n", g_fail ? "FAIL" : "PASS");
return g_fail ? 1 : 0;
}