From e754f5841ebff9a04cc392b1a5b0b6f7da363e4d Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 30 Jul 2026 10:21:26 +1000 Subject: [PATCH] [client] keybind: use physical keycodes for layout-independent hotkeys Fixes #1298 --- client/displayservers/Wayland/input.c | 11 ----- client/displayservers/Wayland/wayland.c | 1 - client/displayservers/Wayland/wayland.h | 2 - client/displayservers/X11/x11.c | 23 +--------- client/displayservers/X11/x11.h | 4 +- client/include/app.h | 9 ++-- client/include/interface/displayserver.h | 4 -- client/renderers/EGL/desktop.c | 2 +- client/src/app.c | 23 +++------- client/src/keybind.c | 56 ++++++++++++------------ client/src/main.h | 1 - client/src/overlay/fps.c | 2 +- client/src/overlay/graphs.c | 2 +- client/src/overlay/help.c | 5 +-- 14 files changed, 45 insertions(+), 100 deletions(-) diff --git a/client/displayservers/Wayland/input.c b/client/displayservers/Wayland/input.c index 5a84f10f..fdd66801 100644 --- a/client/displayservers/Wayland/input.c +++ b/client/displayservers/Wayland/input.c @@ -225,17 +225,6 @@ done: close(fd); } -int waylandGetCharCode(int key) -{ - key += 8; // xkb scancode is evdev scancode + 8 - xkb_keysym_t sym = xkb_state_key_get_one_sym(wlWm.xkbState, key); - if (sym == XKB_KEY_NoSymbol) - return 0; - - sym = xkb_keysym_to_upper(sym); - return xkb_keysym_to_utf32(sym); -} - static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard, uint32_t serial, struct wl_surface * surface, struct wl_array * keys) { diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index ebc8ede9..8fa79eee 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -320,7 +320,6 @@ struct LG_DisplayServerOps LGDS_Wayland = .uncapturePointer = waylandUncapturePointer, .grabKeyboard = waylandGrabKeyboard, .ungrabKeyboard = waylandUngrabKeyboard, - .getCharCode = waylandGetCharCode, .warpPointer = waylandWarpPointer, .realignPointer = waylandRealignPointer, .isValidPointerPos = waylandIsValidPointerPos, diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h index 1c9aa84c..29ef390e 100644 --- a/client/displayservers/Wayland/wayland.h +++ b/client/displayservers/Wayland/wayland.h @@ -378,8 +378,6 @@ void waylandUncapturePointer(void); void waylandRealignPointer(void); void waylandWarpPointer(int x, int y, bool exiting); void waylandGuestPointerUpdated(double x, double y, double localX, double localY); -int waylandGetCharCode(int key); - // output module bool waylandOutputInit(void); void waylandOutputFree(void); diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index a9386377..1c563934 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -40,8 +40,6 @@ #include #include -#include - #include #include @@ -570,9 +568,8 @@ static bool x11Init(const LG_DSInitParams params) goto fail_window; } - XDisplayKeycodes(x11.display, &x11.minKeycode, &x11.maxKeycode); - x11.keysyms = XGetKeyboardMapping(x11.display, x11.minKeycode, - x11.maxKeycode - x11.minKeycode, &x11.symsPerKeycode); + int maxKeycode; + XDisplayKeycodes(x11.display, &x11.minKeycode, &maxKeycode); XIFreeDeviceInfo(devinfo); @@ -786,9 +783,6 @@ static void x11Free(void) if (x11.cursors[i]) XFreeCursor(x11.display, x11.cursors[i]); - if (x11.keysyms) - XFree(x11.keysyms); - x11.wm->deinit(); XCloseDisplay(x11.display); } @@ -1092,18 +1086,6 @@ static void setFocus(bool focused, double x, double y) app_handleFocusEvent(focused); } -static int x11GetCharCode(int detail) -{ - detail += x11.minKeycode; - if (detail < x11.minKeycode || detail > x11.maxKeycode) - return 0; - - KeySym sym = x11.keysyms[(detail - x11.minKeycode) * - x11.symsPerKeycode]; - sym = xkb_keysym_to_upper(sym); - return xkb_keysym_to_utf32(sym); -} - static void x11XInputEvent(XGenericEventCookie *cookie) { static int button_state = 0; @@ -2019,7 +2001,6 @@ struct LG_DisplayServerOps LGDS_X11 = .ungrabPointer = x11UngrabPointer, .capturePointer = x11CapturePointer, .uncapturePointer = x11UncapturePointer, - .getCharCode = x11GetCharCode, .grabKeyboard = x11GrabKeyboard, .ungrabKeyboard = x11UngrabKeyboard, .warpPointer = x11WarpPointer, diff --git a/client/displayservers/X11/x11.h b/client/displayservers/X11/x11.h index 46f52bd2..4741695a 100644 --- a/client/displayservers/X11/x11.h +++ b/client/displayservers/X11/x11.h @@ -60,9 +60,7 @@ struct X11DSState XVisualInfo * visual; X11WM * wm; - int minKeycode, maxKeycode; - int symsPerKeycode; - KeySym * keysyms; + int minKeycode; //Extended Window Manager Hints //ref: https://specifications.freedesktop.org/wm-spec/latest/ diff --git a/client/include/app.h b/client/include/app.h index 6d53990c..5c73d9b0 100644 --- a/client/include/app.h +++ b/client/include/app.h @@ -176,16 +176,15 @@ typedef void (*KeybindFn)(int sc, void * opaque); void app_showRecord(bool show); /** - * Register a handler for the + combination - * @param sc The scancode to register - * @param charcode The charcode to register (used instead of sc if non zero) + * Register a handler for the configured escape key + key combination + * @param sc The Linux keycode to register * @param callback The function to be called when the combination is pressed * @param opaque A pointer to be passed to the callback, may be NULL * @retval A handle for the binding or NULL on failure. * The caller is required to release the handle via `app_releaseKeybind` when it is no longer required */ -KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, - void * opaque, const char * description); +KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, + const char * description); /** * Release an existing key binding diff --git a/client/include/interface/displayserver.h b/client/include/interface/displayserver.h index df13c693..197a6553 100644 --- a/client/include/interface/displayserver.h +++ b/client/include/interface/displayserver.h @@ -206,9 +206,6 @@ struct LG_DisplayServerOps void (*capturePointer)(void); void (*uncapturePointer)(void); - /* get the character code for the provided scancode */ - int (*getCharCode)(int sc); - /* exiting = true if the warp is to leave the window */ void (*warpPointer)(int x, int y, bool exiting); @@ -286,7 +283,6 @@ struct LG_DisplayServerOps DEBUG_ASSERT((x)->ungrabPointer ); \ DEBUG_ASSERT((x)->capturePointer ); \ DEBUG_ASSERT((x)->uncapturePointer ); \ - DEBUG_ASSERT((x)->getCharCode ); \ DEBUG_ASSERT((x)->warpPointer ); \ DEBUG_ASSERT((x)->realignPointer ); \ DEBUG_ASSERT((x)->isValidPointerPos ); \ diff --git a/client/renderers/EGL/desktop.c b/client/renderers/EGL/desktop.c index 1538f15a..9f1d7415 100644 --- a/client/renderers/EGL/desktop.c +++ b/client/renderers/EGL/desktop.c @@ -204,7 +204,7 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display, return false; } - app_registerKeybind(0, 'N', toggleNV, desktop, + app_registerKeybind(KEY_N, toggleNV, desktop, "Toggle night vision mode"); desktop->nvMax = option_get_int("egl", "nvGainMax"); diff --git a/client/src/app.c b/client/src/app.c index 18700f5e..3c8881ba 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -39,7 +39,6 @@ #include #include #include -#include #define SHADER_MOUSE_VALID (UINT32_C(1) << 31) @@ -465,11 +464,9 @@ void app_handleKeyPressInternal(int sc) { g_state.escapeAction = sc; KeybindHandle handle; - int charcode = g_state.ds->getCharCode(sc); ll_forEachNL(g_state.bindings, item, handle) { - if ((handle->sc && handle->sc == sc ) || - (handle->charcode && handle->charcode == charcode)) + if (handle->sc == sc) { handle->callback(sc, handle->opaque); break; @@ -849,18 +846,12 @@ void app_showRecord(bool show) overlayStatus_set(LG_USER_STATUS_RECORDING, show); } -KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, - void * opaque, const char * description) +KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque, + const char * description) { - if (charcode != 0 && sc != 0) + if (sc <= KEY_RESERVED || sc >= KEY_MAX || !linux_to_display[sc]) { - DEBUG_ERROR("invalid keybind, one of scancode or charcode must be 0"); - return NULL; - } - - if (charcode && islower(charcode)) - { - DEBUG_ERROR("invalid keybind, charcode must be uppercase"); + DEBUG_ERROR("invalid keybind keycode: %d", sc); return NULL; } @@ -869,8 +860,7 @@ KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, // don't allow duplicate binds ll_forEachNL(g_state.bindings, item, handle) { - if ((sc && handle->sc == sc ) || - (charcode && handle->charcode == charcode)) + if (handle->sc == sc) { DEBUG_INFO("Key already bound"); return NULL; @@ -885,7 +875,6 @@ KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, } handle->sc = sc; - handle->charcode = charcode; handle->callback = callback; handle->description = description; handle->opaque = opaque; diff --git a/client/src/keybind.c b/client/src/keybind.c index 79e56ceb..e949a76c 100644 --- a/client/src/keybind.c +++ b/client/src/keybind.c @@ -148,17 +148,17 @@ static void bind_setGuestRes(int sc, void * opaque) void keybind_commonRegister(void) { - app_registerKeybind(0, 'F', bind_fullscreen , NULL, + app_registerKeybind(KEY_F, bind_fullscreen , NULL, "Full screen toggle"); - app_registerKeybind(0, 'V', bind_video , NULL, + app_registerKeybind(KEY_V, bind_video , NULL, "Video stream toggle"); - app_registerKeybind(0, 'R', bind_rotate , NULL, + app_registerKeybind(KEY_R, bind_rotate , NULL, "Rotate the output clockwise by 90° increments"); - app_registerKeybind(0, 'Q', bind_quit , NULL, + app_registerKeybind(KEY_Q, bind_quit , NULL, "Quit"); - app_registerKeybind(0, 'O', bind_toggleOverlay, NULL, + app_registerKeybind(KEY_O, bind_toggleOverlay, NULL, "Toggle overlay"); - app_registerKeybind(0, '=', bind_setGuestRes, NULL, + app_registerKeybind(KEY_EQUAL, bind_setGuestRes, NULL, "Set guest resolution to match window size"); } @@ -189,32 +189,32 @@ void keybind_spiceRegister(void) static bool firstTime = true; if (firstTime) { - app_registerKeybind(0, 'I', bind_input, NULL, + app_registerKeybind(KEY_I, bind_input, NULL, "Spice keyboard & mouse toggle"); - app_registerKeybind(KEY_INSERT, 0, bind_mouseSens, (void *) true , + app_registerKeybind(KEY_INSERT, bind_mouseSens, (void *) true, "Increase mouse sensitivity in capture mode"); - app_registerKeybind(KEY_DELETE, 0, bind_mouseSens, (void *) false, + app_registerKeybind(KEY_DELETE, bind_mouseSens, (void *) false, "Decrease mouse sensitivity in capture mode"); - app_registerKeybind(KEY_UP , 0 , bind_toggleKey, (void *) PS2_VOLUME_UP , + app_registerKeybind(KEY_UP, bind_toggleKey, (void *) PS2_VOLUME_UP, "Send volume up to the guest"); - app_registerKeybind(KEY_DOWN, 0 , bind_toggleKey, (void *) PS2_VOLUME_DOWN, + app_registerKeybind(KEY_DOWN, bind_toggleKey, (void *) PS2_VOLUME_DOWN, "Send volume down to the guest"); - app_registerKeybind(0 , 'M', bind_toggleKey, (void *) PS2_MUTE , + app_registerKeybind(KEY_M, bind_toggleKey, (void *) PS2_MUTE, "Send mute to the guest"); - app_registerKeybind(KEY_LEFTMETA , 0, bind_passthrough, NULL, + app_registerKeybind(KEY_LEFTMETA, bind_passthrough, NULL, "Send LWin to the guest"); - app_registerKeybind(KEY_RIGHTMETA, 0, bind_passthrough, NULL, + app_registerKeybind(KEY_RIGHTMETA, bind_passthrough, NULL, "Send RWin to the guest"); #if ENABLE_AUDIO if (audio_supportsRecord()) { - app_registerKeybind(0, 'E', audio_recordToggleKeybind, NULL, + app_registerKeybind(KEY_E, audio_recordToggleKeybind, NULL, "Toggle audio recording"); - app_registerKeybind(0, 'C', bind_toggleMicDefault, NULL, + app_registerKeybind(KEY_C, bind_toggleMicDefault, NULL, "Cycle audio recording default"); } #endif @@ -232,29 +232,29 @@ void keybind_spiceRegister(void) /* register OS based keybinds */ if (app_guestIsLinux()) { - handles[handleCount++] = app_registerKeybind(KEY_F1 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F1, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F1 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F2 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F2, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F2 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F3 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F3, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F3 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F4 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F4, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F4 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F5 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F5, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F5 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F6 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F6, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F6 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F7 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F7, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F7 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F8 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F8, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F8 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F9 , 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F9, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F9 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F10, 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F10, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F10 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F11, 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F11, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F11 to the guest"); - handles[handleCount++] = app_registerKeybind(KEY_F12, 0, bind_ctrlAltFn, NULL, + handles[handleCount++] = app_registerKeybind(KEY_F12, bind_ctrlAltFn, NULL, "Send Ctrl+Alt+F12 to the guest"); } } diff --git a/client/src/main.h b/client/src/main.h index dcfc4d55..b430d4b1 100644 --- a/client/src/main.h +++ b/client/src/main.h @@ -254,7 +254,6 @@ struct CBRequest struct KeybindHandle { int sc; - int charcode; KeybindFn callback; const char * description; void * opaque; diff --git a/client/src/overlay/fps.c b/client/src/overlay/fps.c index d1c4f240..851426a3 100644 --- a/client/src/overlay/fps.c +++ b/client/src/overlay/fps.c @@ -53,7 +53,7 @@ static void fps_earlyInit(void) static bool fps_init(void ** udata, const void * params) { - app_registerKeybind(0, 'D', showFPSKeybind, NULL, "FPS display toggle"); + app_registerKeybind(KEY_D, showFPSKeybind, NULL, "FPS display toggle"); showFPS = option_get_bool("win", "showFPS"); return true; } diff --git a/client/src/overlay/graphs.c b/client/src/overlay/graphs.c index 9ff07653..5c52b790 100644 --- a/client/src/overlay/graphs.c +++ b/client/src/overlay/graphs.c @@ -82,7 +82,7 @@ static void graphs_earlyInit(void) static bool graphs_init(void ** udata, const void * params) { app_overlayConfigRegister("Performance Metrics", configCallback, NULL); - app_registerKeybind(0, 'T', showTimingKeybind, NULL, + app_registerKeybind(KEY_T, showTimingKeybind, NULL, "Show frame timing information"); return true; } diff --git a/client/src/overlay/help.c b/client/src/overlay/help.c index a6b1d347..199dfd69 100644 --- a/client/src/overlay/help.c +++ b/client/src/overlay/help.c @@ -70,10 +70,7 @@ static int help_render(void * udata, bool interactive, struct Rect * windowRects continue; igTableNextColumn(); - if (handle->sc) - igText("%s+%s", escapeName, linux_to_display[handle->sc]); - else - igText("%s+%c", escapeName, handle->charcode); + igText("%s+%s", escapeName, linux_to_display[handle->sc]); igTableNextColumn(); igTextUnformatted(handle->description, NULL); }