From 7252738f9e34a65930b08b075b4483b593cfb846 Mon Sep 17 00:00:00 2001 From: Toria Date: Mon, 11 May 2026 02:58:54 +0100 Subject: [PATCH 1/4] Uh, this kinda just sets the keyboard and it just...works... Signed-off-by: Toria --- src/wlserver.cpp | 73 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 1631ca780d..6d9938e7df 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -101,7 +101,11 @@ struct wlserver_content_override { std::mutex g_wlserver_xdg_shell_windows_lock; static struct wl_list pending_surfaces = {0}; - +struct gamescope_input_ctx { + struct wl_listener destroy; + struct wlr_input_device *device; +}; +static struct wlr_keyboard *parent_keyboard = NULL; static std::atomic g_bShutdownWLServer{ false }; static void wlserver_x11_surface_info_set_wlr( struct wlserver_x11_surface_info *surf, struct wlr_surface *wlr_surf, bool override ); @@ -475,6 +479,17 @@ static void wlserver_handle_touch_motion(struct wl_listener *listener, void *dat wlserver_touchmotion( event->x, event->y, event->touch_id, event->time_msec, false, touch->connector ); } +static void wlserver_destroy_input(struct wl_listener *listener, void *data) +{ + struct gamescope_input_ctx *ctx = wl_container_of(listener, ctx, destroy); + + if (parent_keyboard && &parent_keyboard->base == ctx->device) { + wl_log.infof("Parent keyboard destroyed"); + parent_keyboard = NULL; + } + delete(ctx); +} + static void wlserver_new_input(struct wl_listener *listener, void *data) { struct wlr_input_device *device = (struct wlr_input_device *) data; @@ -484,7 +499,19 @@ static void wlserver_new_input(struct wl_listener *listener, void *data) case WLR_INPUT_DEVICE_KEYBOARD: { struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(device); + struct gamescope_input_ctx *ctx = new gamescope_input_ctx { + .destroy = { .notify = wlserver_destroy_input }, + .device = device + }; + wl_signal_add(&device->events.destroy, &ctx->destroy); + + if (!parent_keyboard) { + parent_keyboard = keyboard; + wl_log.infof("Captured parent keyboard from %s", device->name); + } + wlr_keyboard_set_keymap(keyboard, wlserver.keyboard_group->keyboard.keymap); + if (!wlr_keyboard_group_add_keyboard(wlserver.keyboard_group, keyboard)) { wl_log.errorf("failed to add physical keyboard %s", device->name); break; @@ -1967,19 +1994,10 @@ bool wlserver_init( void ) { wlserver.wlr.virtual_keyboard_device = kbd; // Create a keyboard group to keep all externally connected keyboards - // in sync (one single layout and a shared state) - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - struct xkb_rule_names rules = { 0 }; - rules.rules = getenv("XKB_DEFAULT_RULES"); - rules.model = getenv("XKB_DEFAULT_MODEL"); - rules.layout = getenv("XKB_DEFAULT_LAYOUT"); - rules.variant = getenv("XKB_DEFAULT_VARIANT"); - rules.options = getenv("XKB_DEFAULT_OPTIONS"); - struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + // in sync (one single layout and a shared state) - defer keymap to later wlserver.keyboard_group = wlr_keyboard_group_create(); struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; wlr_keyboard_set_repeat_info(keyboard, 25, 600); - wlr_keyboard_set_keymap(keyboard, keymap); wlserver.keyboard_group_modifiers.notify = wlserver_handle_modifiers; wl_signal_add(&keyboard->events.modifiers, &wlserver.keyboard_group_modifiers); wlserver.keyboard_group_key.notify = wlserver_handle_key; @@ -2088,6 +2106,39 @@ bool wlserver_init( void ) { return false; } + // Backend initialized, input_devices populated. Now we can extract keymap + // from parent, then override with any set env vars + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_rule_names rules = { 0 }; + struct xkb_keymap *keymap = NULL; + + // Fetch xkb env vars from session + const char *env_rules = getenv("XKB_DEFAULT_RULES"); + const char *env_model = getenv("XKB_DEFAULT_MODEL"); + const char *env_layout = getenv("XKB_DEFAULT_LAYOUT"); + const char *env_variant = getenv("XKB_DEFAULT_VARIANT"); + const char *env_options = getenv("XKB_DEFAULT_OPTIONS"); + + // priority: env > parent > default + if (env_rules || env_model || env_layout || env_variant || env_options) { + rules.rules = env_rules ?: "evdev"; + rules.model = env_model ?: "pc105"; + rules.layout = env_layout ?: "us"; + rules.variant = env_variant; // NULL + rules.options = env_options; // NULL + + keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + } else if (parent_keyboard && parent_keyboard->keymap) { + keymap = xkb_keymap_ref(parent_keyboard->keymap); + } else { + rules.rules = "evdev"; + rules.model = "pc105"; + rules.layout = "us"; + keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + } + + wlr_keyboard_set_keymap(kbd, keymap); + wl_signal_emit( &wlserver.wlr.multi_backend->events.new_input, kbd ); #if HAVE_LIBEIS From a2968d6dd781d2ce8db4ea2b26b4b3d9f62d1e76 Mon Sep 17 00:00:00 2001 From: Toria Date: Mon, 11 May 2026 21:45:13 +0100 Subject: [PATCH 2/4] Move xkb_keymap over to WaylandBackend.cpp! Signed-off-by: Toria --- src/Backends/WaylandBackend.cpp | 26 +++++++++++++-- src/backend.h | 4 +++ src/main.cpp | 5 ++- src/steamcompmgr.hpp | 2 ++ src/wlserver.cpp | 57 +++++++++++---------------------- src/wlserver.hpp | 2 +- 6 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index abdf7820b0..cb45b38f68 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -11,6 +11,7 @@ #include "waitable.h" #include "Utils/TempFiles.h" +#include #include #include #include @@ -521,6 +522,8 @@ namespace gamescope void SetRelativePointer( bool bRelative ); + ::xkb_keymap *Wayland_GetParentKeymap() const; + private: void HandleKey( uint32_t uKey, bool bPressed ); @@ -547,8 +550,8 @@ namespace gamescope uint32_t m_uFakeTimestamp = 0; - xkb_context *m_pXkbContext = nullptr; - xkb_keymap *m_pXkbKeymap = nullptr; + xkb_context *m_pXkbContext = nullptr; + ::xkb_keymap *m_pXkbKeymap = nullptr; uint32_t m_uKeyModifiers = 0; uint32_t m_uModMask[ GAMESCOPE_WAYLAND_MOD_COUNT ]; @@ -626,6 +629,7 @@ namespace gamescope .modifiers = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_Modifiers ), .repeat_info = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_Keyboard_RepeatInfo ), }; + const zwp_relative_pointer_v1_listener CWaylandInputThread::s_RelativePointerListener = { .relative_motion = WAYLAND_USERDATA_TO_THIS( CWaylandInputThread, Wayland_RelativePointer_RelativeMotion ), @@ -676,6 +680,8 @@ namespace gamescope virtual bool UsesVirtualConnectors() override; virtual std::shared_ptr CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override; + + ::xkb_keymap *GetParentKeymap() const override; protected: virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override; @@ -2349,6 +2355,11 @@ namespace gamescope return pConnector; } + ::xkb_keymap *CWaylandBackend::GetParentKeymap() const + { + return m_InputThread.Wayland_GetParentKeymap(); + } + /////////////////// // INestedHints /////////////////// @@ -2891,6 +2902,10 @@ namespace gamescope } } + ::xkb_keymap *CWaylandInputThread::Wayland_GetParentKeymap() const{ + return m_pXkbKeymap; + } + void CWaylandInputThread::HandleKey( uint32_t uKey, bool bPressed ) { if ( m_uKeyModifiers & m_uModMask[ GAMESCOPE_WAYLAND_MOD_META ] ) @@ -3163,7 +3178,12 @@ namespace gamescope } defer( munmap( pMap, uSize ) ); - xkb_keymap *pKeymap = xkb_keymap_new_from_string( m_pXkbContext, pMap, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS ); + ::xkb_keymap *pKeymap = xkb_keymap_new_from_string( + m_pXkbContext, + pMap, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS + ); if ( !pKeymap ) { xdg_log.errorf( "Failed to create xkb_keymap" ); diff --git a/src/backend.h b/src/backend.h index 607c25d9a0..e6daab95df 100644 --- a/src/backend.h +++ b/src/backend.h @@ -18,6 +18,8 @@ #include #include +#include + struct wlr_buffer; struct wlr_dmabuf_attributes; @@ -396,6 +398,8 @@ namespace gamescope virtual void OnEndFrame() = 0; + virtual ::xkb_keymap *GetParentKeymap() const { return nullptr; } + static IBackend *Get(); template static bool Set(); diff --git a/src/main.cpp b/src/main.cpp index 1397028e7f..8a70873ce5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -997,7 +997,10 @@ int main(int argc, char **argv) if ( g_nNestedWidth == 0 ) g_nNestedWidth = g_nNestedHeight * 16 / 9; - if ( !wlserver_init() ) + ::xkb_keymap *parent_keymap = nullptr; + parent_keymap = GetBackend()->GetParentKeymap(); + + if (!wlserver_init(parent_keymap)) { fprintf( stderr, "Failed to initialize wlserver\n" ); return 1; diff --git a/src/steamcompmgr.hpp b/src/steamcompmgr.hpp index 6734c15ace..72b1fe3765 100644 --- a/src/steamcompmgr.hpp +++ b/src/steamcompmgr.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include "wlr_begin.hpp" diff --git a/src/wlserver.cpp b/src/wlserver.cpp index 6d9938e7df..cc8a6156c1 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -101,11 +101,6 @@ struct wlserver_content_override { std::mutex g_wlserver_xdg_shell_windows_lock; static struct wl_list pending_surfaces = {0}; -struct gamescope_input_ctx { - struct wl_listener destroy; - struct wlr_input_device *device; -}; -static struct wlr_keyboard *parent_keyboard = NULL; static std::atomic g_bShutdownWLServer{ false }; static void wlserver_x11_surface_info_set_wlr( struct wlserver_x11_surface_info *surf, struct wlr_surface *wlr_surf, bool override ); @@ -479,17 +474,6 @@ static void wlserver_handle_touch_motion(struct wl_listener *listener, void *dat wlserver_touchmotion( event->x, event->y, event->touch_id, event->time_msec, false, touch->connector ); } -static void wlserver_destroy_input(struct wl_listener *listener, void *data) -{ - struct gamescope_input_ctx *ctx = wl_container_of(listener, ctx, destroy); - - if (parent_keyboard && &parent_keyboard->base == ctx->device) { - wl_log.infof("Parent keyboard destroyed"); - parent_keyboard = NULL; - } - delete(ctx); -} - static void wlserver_new_input(struct wl_listener *listener, void *data) { struct wlr_input_device *device = (struct wlr_input_device *) data; @@ -499,18 +483,6 @@ static void wlserver_new_input(struct wl_listener *listener, void *data) case WLR_INPUT_DEVICE_KEYBOARD: { struct wlr_keyboard *keyboard = wlr_keyboard_from_input_device(device); - struct gamescope_input_ctx *ctx = new gamescope_input_ctx { - .destroy = { .notify = wlserver_destroy_input }, - .device = device - }; - wl_signal_add(&device->events.destroy, &ctx->destroy); - - if (!parent_keyboard) { - parent_keyboard = keyboard; - wl_log.infof("Captured parent keyboard from %s", device->name); - } - - wlr_keyboard_set_keymap(keyboard, wlserver.keyboard_group->keyboard.keymap); if (!wlr_keyboard_group_add_keyboard(wlserver.keyboard_group, keyboard)) { wl_log.errorf("failed to add physical keyboard %s", device->name); @@ -1963,7 +1935,7 @@ static gamescope::CAsyncWaiter g_LibEisWaiter( "gamescope-eis" ); static std::unique_ptr g_InputServer; #endif -bool wlserver_init( void ) { +bool wlserver_init( ::xkb_keymap *p_keymap ) { assert( wlserver.display != nullptr ); wl_list_init(&pending_surfaces); @@ -2108,9 +2080,9 @@ bool wlserver_init( void ) { // Backend initialized, input_devices populated. Now we can extract keymap // from parent, then override with any set env vars - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - struct xkb_rule_names rules = { 0 }; - struct xkb_keymap *keymap = NULL; + ::xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + ::xkb_rule_names rules = { 0 }; + ::xkb_keymap *keymap = nullptr; // Fetch xkb env vars from session const char *env_rules = getenv("XKB_DEFAULT_RULES"); @@ -2119,22 +2091,31 @@ bool wlserver_init( void ) { const char *env_variant = getenv("XKB_DEFAULT_VARIANT"); const char *env_options = getenv("XKB_DEFAULT_OPTIONS"); - // priority: env > parent > default + // priority: env overrides parent keymap overrides default if (env_rules || env_model || env_layout || env_variant || env_options) { + // Compile from env vars rules.rules = env_rules ?: "evdev"; rules.model = env_model ?: "pc105"; rules.layout = env_layout ?: "us"; - rules.variant = env_variant; // NULL + rules.variant = (env_layout && env_variant) ? env_variant : NULL; // NULL rules.options = env_options; // NULL - keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); - } else if (parent_keyboard && parent_keyboard->keymap) { - keymap = xkb_keymap_ref(parent_keyboard->keymap); - } else { + wl_log.infof("Using XKB keymap from environment variables"); + } + else { + if (p_keymap) { + keymap = xkb_keymap_ref(p_keymap); + wl_log.infof("Using parent session keymap"); + } + } + + // Fallback if nothing worked + if (!keymap) { rules.rules = "evdev"; rules.model = "pc105"; rules.layout = "us"; keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); + wl_log.infof("Using default XKB keymap"); } wlr_keyboard_set_keymap(kbd, keymap); diff --git a/src/wlserver.hpp b/src/wlserver.hpp index eb1270806d..554bae8614 100644 --- a/src/wlserver.hpp +++ b/src/wlserver.hpp @@ -228,7 +228,7 @@ bool wlsession_init( void ); int wlsession_open_kms( const char *device_name ); void wlsession_close_kms(); -bool wlserver_init( void ); +bool wlserver_init(::xkb_keymap *p_keymap = nullptr); void wlserver_run(void); From 380f11ee6fd244733e36856316df16aa8135418f Mon Sep 17 00:00:00 2001 From: Toria Date: Tue, 12 May 2026 16:49:33 +0100 Subject: [PATCH 3/4] Whoops, missed the fix from before, check again! Actually, optimize that whole if-chain block so it looks cleaner and is efficient with the new checks. Remove a redundant check too. If only env_variant is set of all env vars, we discard the value and continue in p_keymap attempt. Just small optimizations outside of that! Signed-off-by: Toria --- src/Backends/WaylandBackend.cpp | 2 +- src/main.cpp | 5 +- src/wlserver.cpp | 93 +++++++++++++++++++++++---------- 3 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index cb45b38f68..b6828de21f 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -550,7 +550,7 @@ namespace gamescope uint32_t m_uFakeTimestamp = 0; - xkb_context *m_pXkbContext = nullptr; + xkb_context *m_pXkbContext = nullptr; ::xkb_keymap *m_pXkbKeymap = nullptr; uint32_t m_uKeyModifiers = 0; diff --git a/src/main.cpp b/src/main.cpp index 8a70873ce5..330d74819a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -997,12 +997,11 @@ int main(int argc, char **argv) if ( g_nNestedWidth == 0 ) g_nNestedWidth = g_nNestedHeight * 16 / 9; - ::xkb_keymap *parent_keymap = nullptr; - parent_keymap = GetBackend()->GetParentKeymap(); + ::xkb_keymap *parent_keymap = GetBackend()->GetParentKeymap(); if (!wlserver_init(parent_keymap)) { - fprintf( stderr, "Failed to initialize wlserver\n" ); + fprintf(stderr, "Failed to initialize wlserver\n"); return 1; } diff --git a/src/wlserver.cpp b/src/wlserver.cpp index cc8a6156c1..34e6623025 100644 --- a/src/wlserver.cpp +++ b/src/wlserver.cpp @@ -1967,11 +1967,14 @@ bool wlserver_init( ::xkb_keymap *p_keymap ) { // Create a keyboard group to keep all externally connected keyboards // in sync (one single layout and a shared state) - defer keymap to later - wlserver.keyboard_group = wlr_keyboard_group_create(); + wlserver.keyboard_group = wlr_keyboard_group_create(); struct wlr_keyboard *keyboard = &wlserver.keyboard_group->keyboard; wlr_keyboard_set_repeat_info(keyboard, 25, 600); wlserver.keyboard_group_modifiers.notify = wlserver_handle_modifiers; - wl_signal_add(&keyboard->events.modifiers, &wlserver.keyboard_group_modifiers); + wl_signal_add( + &keyboard->events.modifiers, + &wlserver.keyboard_group_modifiers + ); wlserver.keyboard_group_key.notify = wlserver_handle_key; wl_signal_add(&keyboard->events.key, &wlserver.keyboard_group_key); @@ -2082,40 +2085,72 @@ bool wlserver_init( ::xkb_keymap *p_keymap ) { // from parent, then override with any set env vars ::xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); ::xkb_rule_names rules = { 0 }; - ::xkb_keymap *keymap = nullptr; + ::xkb_keymap *keymap = nullptr; // Fetch xkb env vars from session - const char *env_rules = getenv("XKB_DEFAULT_RULES"); - const char *env_model = getenv("XKB_DEFAULT_MODEL"); - const char *env_layout = getenv("XKB_DEFAULT_LAYOUT"); + const char *env_rules = getenv("XKB_DEFAULT_RULES"); + const char *env_model = getenv("XKB_DEFAULT_MODEL"); + const char *env_layout = getenv("XKB_DEFAULT_LAYOUT"); const char *env_variant = getenv("XKB_DEFAULT_VARIANT"); const char *env_options = getenv("XKB_DEFAULT_OPTIONS"); // priority: env overrides parent keymap overrides default - if (env_rules || env_model || env_layout || env_variant || env_options) { - // Compile from env vars - rules.rules = env_rules ?: "evdev"; - rules.model = env_model ?: "pc105"; - rules.layout = env_layout ?: "us"; - rules.variant = (env_layout && env_variant) ? env_variant : NULL; // NULL - rules.options = env_options; // NULL - keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); - wl_log.infof("Using XKB keymap from environment variables"); - } - else { - if (p_keymap) { - keymap = xkb_keymap_ref(p_keymap); - wl_log.infof("Using parent session keymap"); + if ( + env_rules + || env_model + || env_layout + || env_options + ) + { + // Build keymap from env + rules.rules = env_rules ? : "evdev"; + rules.model = env_model ? : "pc105"; + rules.layout = env_layout ? : "us"; + rules.variant = (env_layout && env_variant) ? env_variant : NULL; + rules.options = env_options; + keymap = xkb_keymap_new_from_names( + context, + &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS + ); + } + + // Note: xkb_keymap_new_from_names can fail and leave keymap UB. This is + // why we check. + if (keymap) + { + wl_log.infof("Using xkb_keymap from environment variables"); + } + else if (p_keymap) + { + // Ref parent session's keymap + keymap = xkb_keymap_ref(p_keymap); + wl_log.infof("Using parent session xkb_keymap"); + } + + // Build default keymap as fallback + if (!keymap) + { + rules.rules = "evdev"; + rules.model = "pc105"; + rules.layout = "us"; + rules.variant = NULL; + rules.options = NULL; + keymap = xkb_keymap_new_from_names( + context, + &rules, + XKB_KEYMAP_COMPILE_NO_FLAGS + ); + + if (keymap) + { + wl_log.infof("Using default xkb_keymap."); + } + else + { + wl_log.errorf("Failed to create any xkb_keymap."); + return false; } - } - - // Fallback if nothing worked - if (!keymap) { - rules.rules = "evdev"; - rules.model = "pc105"; - rules.layout = "us"; - keymap = xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); - wl_log.infof("Using default XKB keymap"); } wlr_keyboard_set_keymap(kbd, keymap); From a132ab5f3a55fefb1a881da5f40e5dc70963ad12 Mon Sep 17 00:00:00 2001 From: Toria Date: Tue, 12 May 2026 16:56:39 +0100 Subject: [PATCH 4/4] Remove a stale comment too! Signed-off-by: Toria --- src/Backends/WaylandBackend.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Backends/WaylandBackend.cpp b/src/Backends/WaylandBackend.cpp index b6828de21f..e757572e1e 100644 --- a/src/Backends/WaylandBackend.cpp +++ b/src/Backends/WaylandBackend.cpp @@ -3163,9 +3163,6 @@ namespace gamescope void CWaylandInputThread::Wayland_Keyboard_Keymap( wl_keyboard *pKeyboard, uint32_t uFormat, int32_t nFd, uint32_t uSize ) { - // We are not doing much with the keymap, we pass keycodes thru. - // Ideally we'd use this to influence our keymap to clients, eg. x server. - defer( close( nFd ) ); if ( uFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 ) return;