[client] keybind: use physical keycodes for layout-independent hotkeys

Fixes #1298
This commit is contained in:
Geoffrey McRae
2026-07-30 10:21:26 +10:00
parent deb447c3ce
commit e754f5841e
14 changed files with 45 additions and 100 deletions

View File

@@ -225,17 +225,6 @@ done:
close(fd); 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, static void keyboardEnterHandler(void * data, struct wl_keyboard * keyboard,
uint32_t serial, struct wl_surface * surface, struct wl_array * keys) uint32_t serial, struct wl_surface * surface, struct wl_array * keys)
{ {

View File

@@ -320,7 +320,6 @@ struct LG_DisplayServerOps LGDS_Wayland =
.uncapturePointer = waylandUncapturePointer, .uncapturePointer = waylandUncapturePointer,
.grabKeyboard = waylandGrabKeyboard, .grabKeyboard = waylandGrabKeyboard,
.ungrabKeyboard = waylandUngrabKeyboard, .ungrabKeyboard = waylandUngrabKeyboard,
.getCharCode = waylandGetCharCode,
.warpPointer = waylandWarpPointer, .warpPointer = waylandWarpPointer,
.realignPointer = waylandRealignPointer, .realignPointer = waylandRealignPointer,
.isValidPointerPos = waylandIsValidPointerPos, .isValidPointerPos = waylandIsValidPointerPos,

View File

@@ -378,8 +378,6 @@ void waylandUncapturePointer(void);
void waylandRealignPointer(void); void waylandRealignPointer(void);
void waylandWarpPointer(int x, int y, bool exiting); void waylandWarpPointer(int x, int y, bool exiting);
void waylandGuestPointerUpdated(double x, double y, double localX, double localY); void waylandGuestPointerUpdated(double x, double y, double localX, double localY);
int waylandGetCharCode(int key);
// output module // output module
bool waylandOutputInit(void); bool waylandOutputInit(void);
void waylandOutputFree(void); void waylandOutputFree(void);

View File

@@ -40,8 +40,6 @@
#include <X11/extensions/Xpresent.h> #include <X11/extensions/Xpresent.h>
#include <X11/Xcursor/Xcursor.h> #include <X11/Xcursor/Xcursor.h>
#include <xkbcommon/xkbcommon.h>
#include <GL/glx.h> #include <GL/glx.h>
#include <GL/glxext.h> #include <GL/glxext.h>
@@ -570,9 +568,8 @@ static bool x11Init(const LG_DSInitParams params)
goto fail_window; goto fail_window;
} }
XDisplayKeycodes(x11.display, &x11.minKeycode, &x11.maxKeycode); int maxKeycode;
x11.keysyms = XGetKeyboardMapping(x11.display, x11.minKeycode, XDisplayKeycodes(x11.display, &x11.minKeycode, &maxKeycode);
x11.maxKeycode - x11.minKeycode, &x11.symsPerKeycode);
XIFreeDeviceInfo(devinfo); XIFreeDeviceInfo(devinfo);
@@ -786,9 +783,6 @@ static void x11Free(void)
if (x11.cursors[i]) if (x11.cursors[i])
XFreeCursor(x11.display, x11.cursors[i]); XFreeCursor(x11.display, x11.cursors[i]);
if (x11.keysyms)
XFree(x11.keysyms);
x11.wm->deinit(); x11.wm->deinit();
XCloseDisplay(x11.display); XCloseDisplay(x11.display);
} }
@@ -1092,18 +1086,6 @@ static void setFocus(bool focused, double x, double y)
app_handleFocusEvent(focused); 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 void x11XInputEvent(XGenericEventCookie *cookie)
{ {
static int button_state = 0; static int button_state = 0;
@@ -2019,7 +2001,6 @@ struct LG_DisplayServerOps LGDS_X11 =
.ungrabPointer = x11UngrabPointer, .ungrabPointer = x11UngrabPointer,
.capturePointer = x11CapturePointer, .capturePointer = x11CapturePointer,
.uncapturePointer = x11UncapturePointer, .uncapturePointer = x11UncapturePointer,
.getCharCode = x11GetCharCode,
.grabKeyboard = x11GrabKeyboard, .grabKeyboard = x11GrabKeyboard,
.ungrabKeyboard = x11UngrabKeyboard, .ungrabKeyboard = x11UngrabKeyboard,
.warpPointer = x11WarpPointer, .warpPointer = x11WarpPointer,

View File

@@ -60,9 +60,7 @@ struct X11DSState
XVisualInfo * visual; XVisualInfo * visual;
X11WM * wm; X11WM * wm;
int minKeycode, maxKeycode; int minKeycode;
int symsPerKeycode;
KeySym * keysyms;
//Extended Window Manager Hints //Extended Window Manager Hints
//ref: https://specifications.freedesktop.org/wm-spec/latest/ //ref: https://specifications.freedesktop.org/wm-spec/latest/

View File

@@ -176,16 +176,15 @@ typedef void (*KeybindFn)(int sc, void * opaque);
void app_showRecord(bool show); void app_showRecord(bool show);
/** /**
* Register a handler for the <super>+<key> combination * Register a handler for the configured escape key + key combination
* @param sc The scancode to register * @param sc The Linux keycode to register
* @param charcode The charcode to register (used instead of sc if non zero)
* @param callback The function to be called when the combination is pressed * @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 * @param opaque A pointer to be passed to the callback, may be NULL
* @retval A handle for the binding or NULL on failure. * @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 * 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, KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque,
void * opaque, const char * description); const char * description);
/** /**
* Release an existing key binding * Release an existing key binding

View File

@@ -206,9 +206,6 @@ struct LG_DisplayServerOps
void (*capturePointer)(void); void (*capturePointer)(void);
void (*uncapturePointer)(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 */ /* exiting = true if the warp is to leave the window */
void (*warpPointer)(int x, int y, bool exiting); void (*warpPointer)(int x, int y, bool exiting);
@@ -286,7 +283,6 @@ struct LG_DisplayServerOps
DEBUG_ASSERT((x)->ungrabPointer ); \ DEBUG_ASSERT((x)->ungrabPointer ); \
DEBUG_ASSERT((x)->capturePointer ); \ DEBUG_ASSERT((x)->capturePointer ); \
DEBUG_ASSERT((x)->uncapturePointer ); \ DEBUG_ASSERT((x)->uncapturePointer ); \
DEBUG_ASSERT((x)->getCharCode ); \
DEBUG_ASSERT((x)->warpPointer ); \ DEBUG_ASSERT((x)->warpPointer ); \
DEBUG_ASSERT((x)->realignPointer ); \ DEBUG_ASSERT((x)->realignPointer ); \
DEBUG_ASSERT((x)->isValidPointerPos ); \ DEBUG_ASSERT((x)->isValidPointerPos ); \

View File

@@ -204,7 +204,7 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display,
return false; return false;
} }
app_registerKeybind(0, 'N', toggleNV, desktop, app_registerKeybind(KEY_N, toggleNV, desktop,
"Toggle night vision mode"); "Toggle night vision mode");
desktop->nvMax = option_get_int("egl", "nvGainMax"); desktop->nvMax = option_get_int("egl", "nvGainMax");

View File

@@ -39,7 +39,6 @@
#include <stdarg.h> #include <stdarg.h>
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
#include <ctype.h>
#define SHADER_MOUSE_VALID (UINT32_C(1) << 31) #define SHADER_MOUSE_VALID (UINT32_C(1) << 31)
@@ -465,11 +464,9 @@ void app_handleKeyPressInternal(int sc)
{ {
g_state.escapeAction = sc; g_state.escapeAction = sc;
KeybindHandle handle; KeybindHandle handle;
int charcode = g_state.ds->getCharCode(sc);
ll_forEachNL(g_state.bindings, item, handle) ll_forEachNL(g_state.bindings, item, handle)
{ {
if ((handle->sc && handle->sc == sc ) || if (handle->sc == sc)
(handle->charcode && handle->charcode == charcode))
{ {
handle->callback(sc, handle->opaque); handle->callback(sc, handle->opaque);
break; break;
@@ -849,18 +846,12 @@ void app_showRecord(bool show)
overlayStatus_set(LG_USER_STATUS_RECORDING, show); overlayStatus_set(LG_USER_STATUS_RECORDING, show);
} }
KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback, KeybindHandle app_registerKeybind(int sc, KeybindFn callback, void * opaque,
void * opaque, const char * description) 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"); DEBUG_ERROR("invalid keybind keycode: %d", sc);
return NULL;
}
if (charcode && islower(charcode))
{
DEBUG_ERROR("invalid keybind, charcode must be uppercase");
return NULL; return NULL;
} }
@@ -869,8 +860,7 @@ KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback,
// don't allow duplicate binds // don't allow duplicate binds
ll_forEachNL(g_state.bindings, item, handle) ll_forEachNL(g_state.bindings, item, handle)
{ {
if ((sc && handle->sc == sc ) || if (handle->sc == sc)
(charcode && handle->charcode == charcode))
{ {
DEBUG_INFO("Key already bound"); DEBUG_INFO("Key already bound");
return NULL; return NULL;
@@ -885,7 +875,6 @@ KeybindHandle app_registerKeybind(int sc, int charcode, KeybindFn callback,
} }
handle->sc = sc; handle->sc = sc;
handle->charcode = charcode;
handle->callback = callback; handle->callback = callback;
handle->description = description; handle->description = description;
handle->opaque = opaque; handle->opaque = opaque;

View File

@@ -148,17 +148,17 @@ static void bind_setGuestRes(int sc, void * opaque)
void keybind_commonRegister(void) void keybind_commonRegister(void)
{ {
app_registerKeybind(0, 'F', bind_fullscreen , NULL, app_registerKeybind(KEY_F, bind_fullscreen , NULL,
"Full screen toggle"); "Full screen toggle");
app_registerKeybind(0, 'V', bind_video , NULL, app_registerKeybind(KEY_V, bind_video , NULL,
"Video stream toggle"); "Video stream toggle");
app_registerKeybind(0, 'R', bind_rotate , NULL, app_registerKeybind(KEY_R, bind_rotate , NULL,
"Rotate the output clockwise by 90° increments"); "Rotate the output clockwise by 90° increments");
app_registerKeybind(0, 'Q', bind_quit , NULL, app_registerKeybind(KEY_Q, bind_quit , NULL,
"Quit"); "Quit");
app_registerKeybind(0, 'O', bind_toggleOverlay, NULL, app_registerKeybind(KEY_O, bind_toggleOverlay, NULL,
"Toggle overlay"); "Toggle overlay");
app_registerKeybind(0, '=', bind_setGuestRes, NULL, app_registerKeybind(KEY_EQUAL, bind_setGuestRes, NULL,
"Set guest resolution to match window size"); "Set guest resolution to match window size");
} }
@@ -189,32 +189,32 @@ void keybind_spiceRegister(void)
static bool firstTime = true; static bool firstTime = true;
if (firstTime) if (firstTime)
{ {
app_registerKeybind(0, 'I', bind_input, NULL, app_registerKeybind(KEY_I, bind_input, NULL,
"Spice keyboard & mouse toggle"); "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"); "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"); "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"); "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"); "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"); "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"); "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"); "Send RWin to the guest");
#if ENABLE_AUDIO #if ENABLE_AUDIO
if (audio_supportsRecord()) if (audio_supportsRecord())
{ {
app_registerKeybind(0, 'E', audio_recordToggleKeybind, NULL, app_registerKeybind(KEY_E, audio_recordToggleKeybind, NULL,
"Toggle audio recording"); "Toggle audio recording");
app_registerKeybind(0, 'C', bind_toggleMicDefault, NULL, app_registerKeybind(KEY_C, bind_toggleMicDefault, NULL,
"Cycle audio recording default"); "Cycle audio recording default");
} }
#endif #endif
@@ -232,29 +232,29 @@ void keybind_spiceRegister(void)
/* register OS based keybinds */ /* register OS based keybinds */
if (app_guestIsLinux()) 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"); "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"); "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"); "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"); "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"); "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"); "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"); "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"); "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"); "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"); "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"); "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"); "Send Ctrl+Alt+F12 to the guest");
} }
} }

View File

@@ -254,7 +254,6 @@ struct CBRequest
struct KeybindHandle struct KeybindHandle
{ {
int sc; int sc;
int charcode;
KeybindFn callback; KeybindFn callback;
const char * description; const char * description;
void * opaque; void * opaque;

View File

@@ -53,7 +53,7 @@ static void fps_earlyInit(void)
static bool fps_init(void ** udata, const void * params) 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"); showFPS = option_get_bool("win", "showFPS");
return true; return true;
} }

View File

@@ -82,7 +82,7 @@ static void graphs_earlyInit(void)
static bool graphs_init(void ** udata, const void * params) static bool graphs_init(void ** udata, const void * params)
{ {
app_overlayConfigRegister("Performance Metrics", configCallback, NULL); app_overlayConfigRegister("Performance Metrics", configCallback, NULL);
app_registerKeybind(0, 'T', showTimingKeybind, NULL, app_registerKeybind(KEY_T, showTimingKeybind, NULL,
"Show frame timing information"); "Show frame timing information");
return true; return true;
} }

View File

@@ -70,10 +70,7 @@ static int help_render(void * udata, bool interactive, struct Rect * windowRects
continue; continue;
igTableNextColumn(); igTableNextColumn();
if (handle->sc) igText("%s+%s", escapeName, linux_to_display[handle->sc]);
igText("%s+%s", escapeName, linux_to_display[handle->sc]);
else
igText("%s+%c", escapeName, handle->charcode);
igTableNextColumn(); igTableNextColumn();
igTextUnformatted(handle->description, NULL); igTextUnformatted(handle->description, NULL);
} }