From 3e4072c366d2726f9bc1e20def4a7f43e7f401b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Tue, 19 May 2026 15:42:39 +0200 Subject: [PATCH] Add remote desktop API for controlling the keymap Resolves: RHEL-106779 --- mutter.spec | 4 + remote-desktop-keymap.patch | 7645 +++++++++++++++++++++++++++++++++++ 2 files changed, 7649 insertions(+) create mode 100644 remote-desktop-keymap.patch diff --git a/mutter.spec b/mutter.spec index d934746..eb6627f 100644 --- a/mutter.spec +++ b/mutter.spec @@ -60,6 +60,10 @@ Patch: 0007-tests-Add-test-for-external-constraints.patch # https://redhat.atlassian.net/browse/RHEL-156728 Patch: unmaximize-when-headless.patch +# Add remote desktop API for controlling the keymap. +# https://redhat.atlassian.net/browse/RHEL-106779 +Patch: remote-desktop-keymap.patch + BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0 BuildRequires: pkgconfig(sm) BuildRequires: pkgconfig(libadwaita-1) diff --git a/remote-desktop-keymap.patch b/remote-desktop-keymap.patch new file mode 100644 index 0000000..151f175 --- /dev/null +++ b/remote-desktop-keymap.patch @@ -0,0 +1,7645 @@ +From 8e6cb6aec87192acf821dac8a31fc4261a82ce0f Mon Sep 17 00:00:00 2001 +From: Serhii Tereshchenko +Date: Sat, 27 Sep 2025 15:09:29 +0300 +Subject: [PATCH 01/32] test: Add test-case for Keyboard Layout Change using + Shift+Caps + +Refs #4346 + +Part-of: +(cherry picked from commit 9ad80a4aebe4cfba437b44facd2d072ec3507791) +--- + src/tests/keyboard-map-tests.c | 81 ++++++++++++++++++++++++++++++++++ + 1 file changed, 81 insertions(+) + +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index 9b220f81d1..6a547f8cbc 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -186,6 +186,78 @@ meta_test_native_keyboard_map_set_async (void) + g_signal_handler_disconnect (keymap, keymap_state_changed_handler_id); + } + ++static void ++meta_test_native_keyboard_map_change_layout (void) ++{ ++ MetaBackend *backend = meta_context_get_backend (test_context); ++ ClutterSeat *seat = meta_backend_get_default_seat (backend); ++ ++ g_autoptr (ClutterVirtualInputDevice) virtual_keyboard = NULL; ++ struct xkb_keymap *xkb_keymap = meta_backend_get_keymap (backend); ++ struct xkb_keymap *new_xkb_keymap; ++ gboolean done = FALSE; ++ ++ virtual_keyboard = clutter_seat_create_virtual_device (seat, ++ CLUTTER_KEYBOARD_DEVICE); ++ ++ xkb_keymap_ref (xkb_keymap); ++ ++ meta_backend_set_keymap_async (backend, ++ "us,ua", ++ NULL, ++ "grp:caps_select", ++ NULL, NULL, ++ set_keymap_cb, &done); ++ ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ new_xkb_keymap = meta_backend_get_keymap (backend); ++ g_assert_true (new_xkb_keymap != xkb_keymap); ++ g_assert_cmpuint (xkb_keymap_num_layouts (new_xkb_keymap), ==, 2); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (new_xkb_keymap, 0), ++ ==, ++ "English (US)"); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (new_xkb_keymap, 1), ++ ==, ++ "Ukrainian"); ++ ++ xkb_keymap_unref (xkb_keymap); ++ ++ /* Test layout switching with Caps Lock */ ++ /* First verify we start with layout 0 (English US) */ ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); ++ ++ /* Press Shift key */ ++ clutter_virtual_input_device_notify_key (virtual_keyboard, ++ g_get_monotonic_time (), ++ KEY_LEFTSHIFT, ++ CLUTTER_KEY_STATE_PRESSED); ++ ++ /* Press Caps Lock while Shift is held (Shift+Caps Lock) */ ++ clutter_virtual_input_device_notify_key (virtual_keyboard, ++ g_get_monotonic_time (), ++ KEY_CAPSLOCK, ++ CLUTTER_KEY_STATE_PRESSED); ++ ++ /* Release Caps Lock */ ++ clutter_virtual_input_device_notify_key (virtual_keyboard, ++ g_get_monotonic_time (), ++ KEY_CAPSLOCK, ++ CLUTTER_KEY_STATE_RELEASED); ++ ++ /* Release Shift key */ ++ clutter_virtual_input_device_notify_key (virtual_keyboard, ++ g_get_monotonic_time (), ++ KEY_LEFTSHIFT, ++ CLUTTER_KEY_STATE_RELEASED); ++ meta_flush_input (test_context); ++ meta_wait_for_update (test_context); ++ ++ /* Verify that layout switched to Ukrainian (layout 1) */ ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 1); ++} ++ + static void + set_keymap_layout_group_cb (GObject *source_object, + GAsyncResult *result, +@@ -218,6 +290,13 @@ meta_test_native_keyboard_map_set_layout_index (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + ++ done = FALSE; ++ meta_backend_set_keymap_layout_group_async (backend, 0, NULL, ++ set_keymap_layout_group_cb, ++ &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ + keymap = xkb_keymap_ref (meta_backend_get_keymap (backend)); + g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); + g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), +@@ -353,6 +432,8 @@ init_tests (void) + { + g_test_add_func ("/backends/native/keyboard-map/set-async", + meta_test_native_keyboard_map_set_async); ++ g_test_add_func ("/backends/native/keyboard-map/change-layout", ++ meta_test_native_keyboard_map_change_layout); + g_test_add_func ("/backends/native/keyboard-map/set-layout-index", + meta_test_native_keyboard_map_set_layout_index); + g_test_add_func ("/backends/native/keyboard-map/modifiers", +-- +2.54.0 + + +From d5f19cd112a590206b18dca94fb9aa22381beca8 Mon Sep 17 00:00:00 2001 +From: Sebastian Keller +Date: Thu, 22 Jan 2026 14:39:52 +0100 +Subject: [PATCH 02/32] keymap/native: Use effective instead of locked layout + group + +The locked layout group does not take depressed/latched keys into +consideration, resulting in the layout group not being switched when +using grp:caps_switch and holding caps lock while typing. + +Fixes: aeac0f33c2 ("keymap/native: Plumb all modifier state") +Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/4528 +Part-of: +(cherry picked from commit f015c1059ff29adc95383aa725afbe50580df020) +--- + clutter/clutter/clutter-keymap-private.h | 2 +- + clutter/clutter/clutter-keymap.c | 10 +++++----- + src/backends/native/meta-keymap-native.c | 8 ++++---- + 3 files changed, 10 insertions(+), 10 deletions(-) + +diff --git a/clutter/clutter/clutter-keymap-private.h b/clutter/clutter/clutter-keymap-private.h +index fbba11980a..047425c05e 100644 +--- a/clutter/clutter/clutter-keymap-private.h ++++ b/clutter/clutter/clutter-keymap-private.h +@@ -24,7 +24,7 @@ CLUTTER_EXPORT + void clutter_keymap_update_state (ClutterKeymap *keymap, + gboolean caps_lock_state, + gboolean num_lock_state, +- xkb_layout_index_t locked_layout_group, ++ xkb_layout_index_t effective_layout_group, + xkb_mod_mask_t depressed_mods, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods); +diff --git a/clutter/clutter/clutter-keymap.c b/clutter/clutter/clutter-keymap.c +index 00bef2933e..8721990f74 100644 +--- a/clutter/clutter/clutter-keymap.c ++++ b/clutter/clutter/clutter-keymap.c +@@ -43,7 +43,7 @@ typedef struct _ClutterKeymapPrivate + xkb_mod_mask_t latched_mods; + xkb_mod_mask_t locked_mods; + +- xkb_layout_index_t locked_layout_group; ++ xkb_layout_index_t effective_layout_group; + } ClutterKeymapPrivate; + + G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ClutterKeymap, clutter_keymap, +@@ -163,7 +163,7 @@ void + clutter_keymap_update_state (ClutterKeymap *keymap, + gboolean caps_lock_state, + gboolean num_lock_state, +- xkb_layout_index_t locked_layout_group, ++ xkb_layout_index_t effective_layout_group, + xkb_mod_mask_t depressed_mods, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods) +@@ -172,13 +172,13 @@ clutter_keymap_update_state (ClutterKeymap *keymap, + + if (priv->caps_lock_state == caps_lock_state && + priv->num_lock_state == num_lock_state && +- priv->locked_layout_group == locked_layout_group && ++ priv->effective_layout_group == effective_layout_group && + priv->depressed_mods == depressed_mods && + priv->latched_mods == latched_mods && + priv->locked_mods == locked_mods) + return; + +- priv->locked_layout_group = locked_layout_group; ++ priv->effective_layout_group = effective_layout_group; + priv->depressed_mods = depressed_mods; + priv->latched_mods = latched_mods; + priv->locked_mods = locked_mods; +@@ -222,5 +222,5 @@ clutter_keymap_get_layout_index (ClutterKeymap *keymap) + { + ClutterKeymapPrivate *priv = clutter_keymap_get_instance_private (keymap); + +- return priv->locked_layout_group; ++ return priv->effective_layout_group; + } +diff --git a/src/backends/native/meta-keymap-native.c b/src/backends/native/meta-keymap-native.c +index faa4439f57..8d1d804c12 100644 +--- a/src/backends/native/meta-keymap-native.c ++++ b/src/backends/native/meta-keymap-native.c +@@ -112,7 +112,7 @@ typedef struct + xkb_mod_mask_t latched_mods; + xkb_mod_mask_t locked_mods; + +- xkb_layout_index_t locked_layout_group; ++ xkb_layout_index_t effective_layout_group; + } UpdateLockedModifierStateData; + + static gboolean +@@ -135,7 +135,7 @@ update_state_in_main (gpointer user_data) + clutter_keymap_update_state (CLUTTER_KEYMAP (keymap_native), + caps_lock_state, + num_lock_state, +- data->locked_layout_group, ++ data->effective_layout_group, + data->depressed_mods, + data->latched_mods, + data->locked_mods); +@@ -160,8 +160,8 @@ meta_keymap_native_update_in_impl (MetaKeymapNative *keymap_native, + data->locked_mods = + xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_LOCKED); + +- data->locked_layout_group = +- xkb_state_serialize_layout (xkb_state, XKB_STATE_LAYOUT_LOCKED); ++ data->effective_layout_group = ++ xkb_state_serialize_layout (xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); + + meta_seat_impl_queue_main_thread_idle (seat_impl, + update_state_in_main, +-- +2.54.0 + + +From 3b317ede1d9ecbc1c0db490577d1014edf07d193 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 23 Sep 2025 12:02:15 +0200 +Subject: [PATCH 03/32] build: Fix a file sort order issue + +Part-of: +(cherry picked from commit ece0c71ca17260f79907de86299343bc442d12a8) +--- + src/meta/meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/meta/meson.build b/src/meta/meson.build +index f34ace7439..601b020044 100644 +--- a/src/meta/meson.build ++++ b/src/meta/meson.build +@@ -24,8 +24,8 @@ mutter_public_headers = [ + 'meta-external-constraint.h', + 'meta-idle-monitor.h', + 'meta-inhibit-shortcuts-dialog.h', +- 'meta-launch-context.h', + 'meta-later.h', ++ 'meta-launch-context.h', + 'meta-logical-monitor.h', + 'meta-monitor.h', + 'meta-monitor-manager.h', +-- +2.54.0 + + +From d2c3ab0a4a89c33ea9115f50df6746b6c1ec1b16 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 15 Dec 2025 21:39:18 +0100 +Subject: [PATCH 04/32] tests/keyboard-map-tests: Don't leak xkb_keymap + +Part-of: +(cherry picked from commit c4f7155c900a414a9c45b4d2441c383c82b906b7) +--- + src/tests/keyboard-map-tests.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index 6a547f8cbc..bca99da74d 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -297,7 +297,7 @@ meta_test_native_keyboard_map_set_layout_index (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- keymap = xkb_keymap_ref (meta_backend_get_keymap (backend)); ++ keymap = meta_backend_get_keymap (backend); + g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); + g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), + ==, +-- +2.54.0 + + +From c13eb4351bf2d102f673cb8258233806606de3c5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 23 Sep 2025 12:02:54 +0200 +Subject: [PATCH 05/32] meta/backend: Fix include order + +Part-of: +(cherry picked from commit 13f59a7765f8e954d94450d61127c2f029542907) +--- + src/meta/meta-backend.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/meta/meta-backend.h b/src/meta/meta-backend.h +index 9939f1ae1b..5511acbeec 100644 +--- a/src/meta/meta-backend.h ++++ b/src/meta/meta-backend.h +@@ -27,8 +27,8 @@ + #include "clutter/clutter.h" + #include "meta/meta-dnd.h" + #include "meta/meta-idle-monitor.h" +-#include "meta/meta-monitor-manager.h" + #include "meta/meta-logical-monitor.h" ++#include "meta/meta-monitor-manager.h" + #include "meta/meta-orientation-manager.h" + #include "meta/meta-remote-access-controller.h" + +-- +2.54.0 + + +From 7276f386325234493429eccb49e3e6d3b56ac6ef Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 23 Sep 2025 12:00:04 +0200 +Subject: [PATCH 06/32] backend: Set keymap using new MetaKeymapDescription + type + +This allows moving some logic to a type that centralizes handling of +describing a keyboard map. It also allows attaching more metadata to the +keymap which will be useful in the future. + +The MetaKeymapDescription type is a immutable description of a XKB keyboard +map. Switching related state, i.e. layout index, is done externally to +it. + +Part-of: +(cherry picked from commit 0ddaddd628d34a6a371340ff9c5c3994f356a749) +--- + src/backends/meta-backend-private.h | 9 +- + src/backends/meta-backend.c | 18 ++-- + .../meta-keymap-description-private.h | 27 +++++ + src/backends/meta-keymap-description.c | 101 ++++++++++++++++++ + src/backends/native/meta-backend-native.c | 11 +- + src/backends/native/meta-seat-native.c | 55 +++++----- + src/backends/native/meta-seat-native.h | 13 +-- + src/compositor/plugins/default.c | 11 +- + src/meson.build | 2 + + src/meta/meson.build | 1 + + src/meta/meta-backend.h | 14 ++- + src/meta/meta-keymap-description.h | 44 ++++++++ + src/tests/keyboard-map-tests.c | 41 ++++--- + src/tests/remote-desktop-tests.c | 7 +- + 14 files changed, 267 insertions(+), 87 deletions(-) + create mode 100644 src/backends/meta-keymap-description-private.h + create mode 100644 src/backends/meta-keymap-description.c + create mode 100644 src/meta/meta-keymap-description.h + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index 096bee25ba..b7d92e6a4c 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -124,12 +124,9 @@ struct _MetaBackendClass + ClutterEventSequence *sequence, + MetaSequenceState state); + +- void (* set_keymap_async) (MetaBackend *backend, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GTask *task); ++ void (* set_keymap_async) (MetaBackend *backend, ++ MetaKeymapDescription *description, ++ GTask *task); + + struct xkb_keymap * (* get_keymap) (MetaBackend *backend); + +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index ff01f58b9e..84e4d4cf25 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -1775,14 +1775,11 @@ meta_backend_set_keymap_finish (MetaBackend *backend, + } + + void +-meta_backend_set_keymap_async (MetaBackend *backend, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) ++meta_backend_set_keymap_async (MetaBackend *backend, ++ MetaKeymapDescription *description, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { + GTask *task; + +@@ -1790,10 +1787,7 @@ meta_backend_set_keymap_async (MetaBackend *backend, + g_task_set_source_tag (task, meta_backend_set_keymap_async); + + META_BACKEND_GET_CLASS (backend)->set_keymap_async (backend, +- layouts, +- variants, +- options, +- model, ++ description, + task); + } + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +new file mode 100644 +index 0000000000..2e9a6f8e45 +--- /dev/null ++++ b/src/backends/meta-keymap-description-private.h +@@ -0,0 +1,27 @@ ++/* ++ * Copyright (C) 2025 Red Hat ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ */ ++ ++#pragma once ++ ++#include "meta/meta-keymap-description.h" ++ ++void meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, ++ char **model, ++ char **layout, ++ char **variant, ++ char **options); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +new file mode 100644 +index 0000000000..081fcf64d0 +--- /dev/null ++++ b/src/backends/meta-keymap-description.c +@@ -0,0 +1,101 @@ ++/* ++ * Copyright (C) 2025 Red Hat ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ * ++ */ ++ ++#include "config.h" ++ ++#include "backends/meta-keymap-description-private.h" ++ ++#include ++ ++#include "backends/meta-keymap-utils.h" ++ ++struct _MetaKeymapDescription ++{ ++ gatomicrefcount ref_count; ++ ++ char *rules; ++ char *model; ++ char *layout; ++ char *variant; ++ char *options; ++}; ++ ++#define DEFAULT_XKB_RULES_FILE "evdev" ++#define DEFAULT_XKB_MODEL "pc105+inet" ++ ++G_DEFINE_BOXED_TYPE (MetaKeymapDescription, meta_keymap_description, ++ meta_keymap_description_ref, ++ meta_keymap_description_unref) ++ ++static char * ++strdup_or_empty (const char *string) ++{ ++ return string ? g_strdup (string) : g_strdup (""); ++} ++ ++MetaKeymapDescription * ++meta_keymap_description_new_from_rules (const char *model, ++ const char *layout, ++ const char *variant, ++ const char *options) ++{ ++ MetaKeymapDescription *keymap_description; ++ ++ keymap_description = g_new0 (MetaKeymapDescription, 1); ++ g_atomic_ref_count_init (&keymap_description->ref_count); ++ keymap_description->model = model ? g_strdup (model) ++ : g_strdup (DEFAULT_XKB_MODEL); ++ keymap_description->layout = strdup_or_empty (layout); ++ keymap_description->variant = strdup_or_empty (variant); ++ keymap_description->options = strdup_or_empty (options); ++ ++ return keymap_description; ++} ++ ++MetaKeymapDescription * ++meta_keymap_description_ref (MetaKeymapDescription *keymap_description) ++{ ++ g_atomic_ref_count_inc (&keymap_description->ref_count); ++ return keymap_description; ++} ++ ++void ++meta_keymap_description_unref (MetaKeymapDescription *keymap_description) ++{ ++ if (g_atomic_ref_count_dec (&keymap_description->ref_count)) ++ { ++ g_free (keymap_description->model); ++ g_free (keymap_description->layout); ++ g_free (keymap_description->variant); ++ g_free (keymap_description->options); ++ g_free (keymap_description); ++ } ++} ++ ++void ++meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, ++ char **model, ++ char **layout, ++ char **variant, ++ char **options) ++{ ++ *model = g_strdup (keymap_description->model); ++ *layout = g_strdup (keymap_description->layout); ++ *variant = g_strdup (keymap_description->variant); ++ *options = g_strdup (keymap_description->options); ++} +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index baca057b34..3e39efb098 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -334,19 +334,16 @@ set_keyboard_map_cb (GObject *source_object, + } + + static void +-meta_backend_native_set_keymap_async (MetaBackend *backend, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GTask *task) ++meta_backend_native_set_keymap_async (MetaBackend *backend, ++ MetaKeymapDescription *description, ++ GTask *task) + { + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); + meta_seat_native_set_keyboard_map_async (META_SEAT_NATIVE (seat), +- layouts, variants, options, model, ++ description, + g_task_get_cancellable (task), + set_keyboard_map_cb, + task); +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 1d88e1a131..63392531cd 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -29,6 +29,7 @@ + #include "backends/native/meta-seat-native.h" + + #include "backends/meta-cursor-tracker-private.h" ++#include "backends/meta-keymap-description-private.h" + #include "backends/meta-keymap-utils.h" + #include "backends/native/meta-barrier-native.h" + #include "backends/native/meta-input-thread.h" +@@ -36,6 +37,7 @@ + #include "backends/native/meta-virtual-input-device-native.h" + #include "clutter/clutter-mutter.h" + #include "core/bell.h" ++#include "meta/meta-keymap-description.h" + + #include "meta-private-enum-types.h" + +@@ -55,13 +57,10 @@ static GParamSpec *props[N_PROPS] = { NULL }; + + G_DEFINE_TYPE (MetaSeatNative, meta_seat_native, CLUTTER_TYPE_SEAT) + +-static gboolean meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GCancellable *cancellable, +- GError **error); ++static gboolean meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, ++ MetaKeymapDescription *description, ++ GCancellable *cancellable, ++ GError **error); + + static gboolean + meta_seat_native_handle_event_post (ClutterSeat *seat, +@@ -155,6 +154,7 @@ static void + meta_seat_native_constructed (GObject *object) + { + MetaSeatNative *seat = META_SEAT_NATIVE (object); ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; + g_autoptr (GError) error = NULL; + + seat->impl = meta_seat_impl_new (seat, seat->seat_id, seat->flags); +@@ -171,8 +171,12 @@ meta_seat_native_constructed (GObject *object) + seat->core_pointer = meta_seat_impl_get_pointer (seat->impl); + seat->core_keyboard = meta_seat_impl_get_keyboard (seat->impl); + ++ keymap_description = meta_keymap_description_new_from_rules (NULL, ++ "us", ++ NULL, ++ NULL); + if (!meta_seat_native_set_keyboard_map_sync (seat, +- "us", "", "", DEFAULT_XKB_MODEL, ++ keymap_description, + NULL, &error)) + g_warning ("Failed to set keyboard map: %s", error->message); + +@@ -583,21 +587,27 @@ set_impl_keyboard_map_cb (GObject *source_object, + * is pressed when calling this function. + */ + void +-meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) ++meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, ++ MetaKeymapDescription *description, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { + g_autoptr (GTask) task = NULL; + struct xkb_keymap *keymap, *impl_keymap; ++ g_autofree char *layouts = NULL; ++ g_autofree char *variants = NULL; ++ g_autofree char *options = NULL; ++ g_autofree char *model = NULL; + + task = g_task_new (G_OBJECT (seat), cancellable, callback, user_data); + g_task_set_source_tag (task, meta_seat_native_set_keyboard_map_async); + ++ meta_keymap_description_get_rules (description, ++ &model, ++ &layouts, ++ &variants, ++ &options); + keymap = create_keymap (layouts, variants, options, model); + impl_keymap = create_keymap (layouts, variants, options, model); + +@@ -640,13 +650,10 @@ set_keyboard_map_cb (GObject *source_object, + } + + static gboolean +-meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GCancellable *cancellable, +- GError **error) ++meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, ++ MetaKeymapDescription *description, ++ GCancellable *cancellable, ++ GError **error) + { + g_autoptr (GMainContext) main_context = NULL; + g_autoptr (GMainLoop) main_loop = NULL; +@@ -660,7 +667,7 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + g_task_set_task_data (task, main_loop, NULL); + + meta_seat_native_set_keyboard_map_async (seat_native, +- layouts, variants, options, model, ++ description, + cancellable, + set_keyboard_map_cb, task); + g_main_loop_run (main_loop); +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index 9f300bad79..e0bd689a07 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -100,14 +100,11 @@ void meta_seat_native_set_device_callbacks (MetaOpenDeviceCallback open_callba + void meta_seat_native_release_devices (MetaSeatNative *seat); + void meta_seat_native_reclaim_devices (MetaSeatNative *seat); + +-void meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); ++void meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, ++ MetaKeymapDescription *description, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); + + gboolean meta_seat_native_set_keyboard_map_finish (MetaSeatNative *seat_native, + GAsyncResult *result, +diff --git a/src/compositor/plugins/default.c b/src/compositor/plugins/default.c +index f5e0684588..094cec07ea 100644 +--- a/src/compositor/plugins/default.c ++++ b/src/compositor/plugins/default.c +@@ -364,6 +364,7 @@ init_keymap (MetaDefaultPlugin *self, + g_autofree char *x11_options = NULL; + g_autofree char *x11_variant = NULL; + g_autofree char *x11_model = NULL; ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | +@@ -414,11 +415,13 @@ init_keymap (MetaDefaultPlugin *self, + if (!g_variant_lookup (props, "X11Model", "s", &x11_model)) + x11_model = g_strdup (""); + ++ keymap_description = meta_keymap_description_new_from_rules (x11_model, ++ x11_layout, ++ x11_variant, ++ x11_options); ++ + meta_backend_set_keymap_async (backend, +- x11_layout, +- x11_variant, +- x11_options, +- x11_model, ++ keymap_description, + NULL, NULL, NULL); + } + +diff --git a/src/meson.build b/src/meson.build +index 159e207153..e7824f502e 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -243,6 +243,8 @@ mutter_sources = [ + 'backends/meta-input-settings-private.h', + 'backends/meta-input-settings-dummy.c', + 'backends/meta-input-settings-dummy.h', ++ 'backends/meta-keymap-description.c', ++ 'backends/meta-keymap-description-private.h', + 'backends/meta-keymap-utils.c', + 'backends/meta-keymap-utils.h', + 'backends/meta-logical-monitor.c', +diff --git a/src/meta/meson.build b/src/meta/meson.build +index 601b020044..8374441dd2 100644 +--- a/src/meta/meson.build ++++ b/src/meta/meson.build +@@ -24,6 +24,7 @@ mutter_public_headers = [ + 'meta-external-constraint.h', + 'meta-idle-monitor.h', + 'meta-inhibit-shortcuts-dialog.h', ++ 'meta-keymap-description.h', + 'meta-later.h', + 'meta-launch-context.h', + 'meta-logical-monitor.h', +diff --git a/src/meta/meta-backend.h b/src/meta/meta-backend.h +index 5511acbeec..74ad2bfebf 100644 +--- a/src/meta/meta-backend.h ++++ b/src/meta/meta-backend.h +@@ -27,6 +27,7 @@ + #include "clutter/clutter.h" + #include "meta/meta-dnd.h" + #include "meta/meta-idle-monitor.h" ++#include "meta/meta-keymap-description.h" + #include "meta/meta-logical-monitor.h" + #include "meta/meta-monitor-manager.h" + #include "meta/meta-orientation-manager.h" +@@ -48,14 +49,11 @@ gboolean meta_backend_set_keymap_finish (MetaBackend *backend, + GError **error); + + META_EXPORT +-void meta_backend_set_keymap_async (MetaBackend *backend, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); ++void meta_backend_set_keymap_async (MetaBackend *backend, ++ MetaKeymapDescription *description, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); + + META_EXPORT + gboolean meta_backend_set_keymap_layout_group_finish (MetaBackend *backend, +diff --git a/src/meta/meta-keymap-description.h b/src/meta/meta-keymap-description.h +new file mode 100644 +index 0000000000..32e9ef6d2d +--- /dev/null ++++ b/src/meta/meta-keymap-description.h +@@ -0,0 +1,44 @@ ++/* ++ * Copyright 2025 Red Hat ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#pragma once ++ ++#include ++ ++#include "meta/util.h" ++ ++typedef struct _MetaKeymapDescription MetaKeymapDescription; ++ ++#define META_TYPE_KEYMAP_DESCRIPTION (meta_keymap_description_get_type ()) ++ ++META_EXPORT ++GType meta_keymap_description_get_type (void) G_GNUC_CONST; ++ ++META_EXPORT ++MetaKeymapDescription * meta_keymap_description_new_from_rules (const char *model, ++ const char *layout, ++ const char *variant, ++ const char *options); ++ ++META_EXPORT ++MetaKeymapDescription * meta_keymap_description_ref (MetaKeymapDescription *keymap_description); ++ ++META_EXPORT ++void meta_keymap_description_unref (MetaKeymapDescription *keymap_description); ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (MetaKeymapDescription, ++ meta_keymap_description_unref); +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index bca99da74d..8d93dababf 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -110,6 +110,7 @@ meta_test_native_keyboard_map_set_async (void) + 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_ALT); + ModMaskTuple expected_mods = { alt_mask, 0, 0 }; + ModMaskTuple *expected_mods_ptr = &expected_mods; ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; + struct xkb_keymap *new_xkb_keymap; + gboolean done = FALSE; + gpointer expected_next_handler; +@@ -155,11 +156,13 @@ meta_test_native_keyboard_map_set_async (void) + &expected_next_handler); + + expected_next_handler = (gpointer) on_keymap_changed; +- meta_backend_set_keymap_async (backend, +- "us", +- "dvorak-alt-intl", +- NULL, NULL, NULL, +- set_keymap_cb, &done); ++ keymap_description = ++ meta_keymap_description_new_from_rules (NULL, ++ "us", ++ "dvorak-alt-intl", ++ NULL); ++ meta_backend_set_keymap_async (backend, keymap_description, ++ NULL, set_keymap_cb, &done); + + g_assert_true (xkb_keymap == meta_backend_get_keymap (backend)); + +@@ -191,9 +194,9 @@ meta_test_native_keyboard_map_change_layout (void) + { + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterSeat *seat = meta_backend_get_default_seat (backend); +- + g_autoptr (ClutterVirtualInputDevice) virtual_keyboard = NULL; + struct xkb_keymap *xkb_keymap = meta_backend_get_keymap (backend); ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; + struct xkb_keymap *new_xkb_keymap; + gboolean done = FALSE; + +@@ -202,12 +205,13 @@ meta_test_native_keyboard_map_change_layout (void) + + xkb_keymap_ref (xkb_keymap); + +- meta_backend_set_keymap_async (backend, +- "us,ua", +- NULL, +- "grp:caps_select", +- NULL, NULL, +- set_keymap_cb, &done); ++ keymap_description = ++ meta_keymap_description_new_from_rules (NULL, ++ "us,ua", ++ NULL, ++ "grp:caps_select"); ++ meta_backend_set_keymap_async (backend, keymap_description, ++ NULL, set_keymap_cb, &done); + + while (!done) + g_main_context_iteration (NULL, TRUE); +@@ -279,14 +283,17 @@ static void + meta_test_native_keyboard_map_set_layout_index (void) + { + MetaBackend *backend = meta_context_get_backend (test_context); ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; + gboolean done = FALSE; + struct xkb_keymap *keymap; + +- meta_backend_set_keymap_async (backend, +- "us,se", +- "dvorak-alt-intl,svdvorak", +- NULL, NULL, NULL, +- set_keymap_cb, &done); ++ keymap_description = ++ meta_keymap_description_new_from_rules (NULL, ++ "us,se", ++ "dvorak-alt-intl,svdvorak", ++ NULL); ++ meta_backend_set_keymap_async (backend, keymap_description, ++ NULL, set_keymap_cb, &done); + while (!done) + g_main_context_iteration (NULL, TRUE); + +diff --git a/src/tests/remote-desktop-tests.c b/src/tests/remote-desktop-tests.c +index d006cecdda..04979afa66 100644 +--- a/src/tests/remote-desktop-tests.c ++++ b/src/tests/remote-desktop-tests.c +@@ -70,12 +70,17 @@ remote_desktop_test_client_command (int argc, + const char *layout = argv[1]; + const char *variant = argv[2]; + g_autoptr (GMainContext) main_context = NULL; ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; + gboolean done = FALSE; + + g_debug ("Switching keyboard layout to %s, %s", layout, variant); + main_context = g_main_context_new (); + g_main_context_push_thread_default (main_context); +- meta_backend_set_keymap_async (backend, layout, variant, "", "", ++ keymap_description = meta_keymap_description_new_from_rules (NULL, ++ layout, ++ variant, ++ NULL); ++ meta_backend_set_keymap_async (backend, keymap_description, + NULL, set_keymap_cb, &done); + while (!done) + g_main_context_iteration (main_context, TRUE); +-- +2.54.0 + + +From d2a5ffe3f926391a80fee07e4a5f1dfc5dd8b033 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 17:14:12 +0200 +Subject: [PATCH 07/32] keymap-description: Add helper for creating xkb_keymap + instances + +Will be useful when there are different description sources and not only +rules based methods of creating a keymap. + +Part-of: +(cherry picked from commit 138bffbd16e84143e4d2f3a8db3875a5da5f1bc6) +--- + .../meta-keymap-description-private.h | 3 ++ + src/backends/meta-keymap-description.c | 37 +++++++++++++++++++ + 2 files changed, 40 insertions(+) + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index 2e9a6f8e45..47e2234322 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -25,3 +25,6 @@ void meta_keymap_description_get_rules (MetaKeymapDescription *keymap_descripti + char **layout, + char **variant, + char **options); ++ ++struct xkb_keymap * meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, ++ GError **error); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index 081fcf64d0..39af564a0c 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -99,3 +99,40 @@ meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, + *variant = g_strdup (keymap_description->variant); + *options = g_strdup (keymap_description->options); + } ++ ++struct xkb_keymap * ++meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, ++ GError **error) ++{ ++ struct xkb_rule_names names; ++ struct xkb_context *xkb_context; ++ struct xkb_keymap *xkb_keymap; ++ ++ names.rules = DEFAULT_XKB_RULES_FILE; ++ names.model = keymap_description->model; ++ names.layout = keymap_description->layout; ++ names.variant = keymap_description->variant; ++ names.options = keymap_description->options; ++ ++ xkb_context = meta_create_xkb_context (); ++ xkb_keymap = xkb_keymap_new_from_names (xkb_context, ++ &names, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ xkb_context_unref (xkb_context); ++ ++ if (!xkb_keymap) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Failed to create XKB keymap with " ++ "rules=%s, model=%s, layout=%s, " ++ "variant=%s, options=%s", ++ keymap_description->rules, ++ keymap_description->model, ++ keymap_description->layout, ++ keymap_description->variant, ++ keymap_description->options); ++ return NULL; ++ } ++ ++ return xkb_keymap; ++} +-- +2.54.0 + + +From dae726791082e3e4b37c720f368c30aaca299ad5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 17:12:38 +0200 +Subject: [PATCH 08/32] seat/native: Use new xkb_keymap constructor from + description + +Part-of: +(cherry picked from commit 5fc5f363805e54c7c3fbf890b1539a72438ab9ad) +--- + .../meta-keymap-description-private.h | 6 --- + src/backends/meta-keymap-description.c | 13 ----- + src/backends/native/meta-seat-native.c | 51 ++++--------------- + 3 files changed, 10 insertions(+), 60 deletions(-) + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index 47e2234322..3cacfe6561 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -20,11 +20,5 @@ + + #include "meta/meta-keymap-description.h" + +-void meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, +- char **model, +- char **layout, +- char **variant, +- char **options); +- + struct xkb_keymap * meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, + GError **error); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index 39af564a0c..cd93c543e3 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -87,19 +87,6 @@ meta_keymap_description_unref (MetaKeymapDescription *keymap_description) + } + } + +-void +-meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, +- char **model, +- char **layout, +- char **variant, +- char **options) +-{ +- *model = g_strdup (keymap_description->model); +- *layout = g_strdup (keymap_description->layout); +- *variant = g_strdup (keymap_description->variant); +- *options = g_strdup (keymap_description->options); +-} +- + struct xkb_keymap * + meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, + GError **error) +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 63392531cd..905fa13ce0 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -513,29 +513,6 @@ meta_seat_native_reclaim_devices (MetaSeatNative *seat) + seat->released = FALSE; + } + +-static struct xkb_keymap * +-create_keymap (const char *layouts, +- const char *variants, +- const char *options, +- const char *model) +-{ +- struct xkb_rule_names names; +- struct xkb_keymap *keymap; +- struct xkb_context *context; +- +- names.rules = DEFAULT_XKB_RULES_FILE; +- names.model = model; +- names.layout = layouts; +- names.variant = variants; +- names.options = options; +- +- context = meta_create_xkb_context (); +- keymap = xkb_keymap_new_from_names (context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); +- xkb_context_unref (context); +- +- return keymap; +-} +- + gboolean + meta_seat_native_set_keyboard_map_finish (MetaSeatNative *seat_native, + GAsyncResult *result, +@@ -595,30 +572,22 @@ meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + { + g_autoptr (GTask) task = NULL; + struct xkb_keymap *keymap, *impl_keymap; +- g_autofree char *layouts = NULL; +- g_autofree char *variants = NULL; +- g_autofree char *options = NULL; +- g_autofree char *model = NULL; ++ g_autoptr (GError) error = NULL; + + task = g_task_new (G_OBJECT (seat), cancellable, callback, user_data); + g_task_set_source_tag (task, meta_seat_native_set_keyboard_map_async); + +- meta_keymap_description_get_rules (description, +- &model, +- &layouts, +- &variants, +- &options); +- keymap = create_keymap (layouts, variants, options, model); +- impl_keymap = create_keymap (layouts, variants, options, model); +- ++ keymap = meta_keymap_description_create_xkb_keymap (description, &error); + if (keymap == NULL) + { +- g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, +- "Unable to load configured keymap: " +- "rules=%s, model=%s, ""layout=%s, " +- "variant=%s, options=%s", +- DEFAULT_XKB_RULES_FILE, model, layouts, +- variants, options); ++ g_task_return_error (task, g_steal_pointer (&error)); ++ return; ++ } ++ ++ impl_keymap = meta_keymap_description_create_xkb_keymap (description, &error); ++ if (impl_keymap == NULL) ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); + return; + } + +-- +2.54.0 + + +From 520b5a3d4f46900506a1e0df3cd1dfcf7699a008 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 21:36:32 +0200 +Subject: [PATCH 09/32] keybindings: Create xkb_keymap from keymap description + +This hides xkb_keymap specific details out of sight. + +Part-of: +(cherry picked from commit e0d847357951f107790c421eb772c7ab89be6834) +--- + src/core/keybindings.c | 26 ++++++++++++++------------ + 1 file changed, 14 insertions(+), 12 deletions(-) + +diff --git a/src/core/keybindings.c b/src/core/keybindings.c +index 551a156e50..0f475eae3b 100644 +--- a/src/core/keybindings.c ++++ b/src/core/keybindings.c +@@ -30,7 +30,7 @@ + #include "config.h" + + #include "backends/meta-backend-private.h" +-#include "backends/meta-keymap-utils.h" ++#include "backends/meta-keymap-description-private.h" + #include "backends/meta-logical-monitor-private.h" + #include "backends/meta-monitor-manager-private.h" + #include "compositor/compositor-private.h" +@@ -766,19 +766,21 @@ clear_active_keyboard_layouts (MetaKeyBindingManager *keys) + static MetaKeyBindingKeyboardLayout + create_us_layout (void) + { +- struct xkb_rule_names names; ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; ++ g_autoptr (GError) error = NULL; + struct xkb_keymap *keymap; +- struct xkb_context *context; + +- names.rules = DEFAULT_XKB_RULES_FILE; +- names.model = DEFAULT_XKB_MODEL; +- names.layout = "us"; +- names.variant = ""; +- names.options = ""; +- +- context = meta_create_xkb_context (); +- keymap = xkb_keymap_new_from_names (context, &names, XKB_KEYMAP_COMPILE_NO_FLAGS); +- xkb_context_unref (context); ++ keymap_description = meta_keymap_description_new_from_rules (NULL, ++ "us", ++ NULL, ++ NULL); ++ keymap = meta_keymap_description_create_xkb_keymap (keymap_description, ++ &error); ++ if (!keymap) ++ { ++ g_warning ("Failed to create us keybinding layout: %s", error->message); ++ return (MetaKeyBindingKeyboardLayout) {}; ++ } + + return (MetaKeyBindingKeyboardLayout) { + .keymap = keymap, +-- +2.54.0 + + +From 505c26321ac34934eb45c2555090ece23ca751e4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 21:11:50 +0200 +Subject: [PATCH 10/32] seat/native: Pass keymap description instead of + xkb_keymap + +This means the description is available in places it will later be +useful to have. Right now, there are no functional changes. + +Part-of: +(cherry picked from commit 9df69e82ada1dc620c71d12faca3ffb36f9525e9) +--- + src/backends/native/meta-seat-impl.c | 37 +++++++++++++++++--------- + src/backends/native/meta-seat-impl.h | 10 +++---- + src/backends/native/meta-seat-native.c | 33 ++++++++++------------- + 3 files changed, 44 insertions(+), 36 deletions(-) + +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 0a6aae3cf0..f89dfc90d9 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -35,6 +35,7 @@ + + #include "backends/meta-cursor-tracker-private.h" + #include "backends/meta-fd-source.h" ++#include "backends/meta-keymap-description-private.h" + #include "backends/native/meta-backend-native-private.h" + #include "backends/native/meta-barrier-native.h" + #include "backends/native/meta-device-pool.h" +@@ -3819,16 +3820,28 @@ static gboolean + set_keyboard_map (GTask *task) + { + MetaSeatImpl *seat_impl = g_task_get_source_object (task); +- struct xkb_keymap *xkb_keymap = g_task_get_task_data (task); ++ MetaKeymapDescription *keymap_description = g_task_get_task_data (task); + MetaKeymapNative *keymap; ++ g_autoptr (GError) error = NULL; ++ struct xkb_keymap *xkb_keymap; ++ ++ g_task_set_priority (task, G_PRIORITY_HIGH); + + keymap = seat_impl->keymap; +- meta_keymap_native_set_keyboard_map_in_impl (keymap, xkb_keymap); + +- g_task_set_priority (task, G_PRIORITY_HIGH); +- g_task_return_boolean (task, TRUE); ++ xkb_keymap = meta_keymap_description_create_xkb_keymap (keymap_description, ++ &error); ++ if (!xkb_keymap) ++ { ++ g_prefix_error (&error, "Unable to load configured keymap: "); ++ g_task_return_error (task, g_steal_pointer (&error)); ++ return G_SOURCE_REMOVE; ++ } ++ ++ meta_keymap_native_set_keyboard_map_in_impl (keymap, xkb_keymap); + + meta_seat_impl_update_xkb_state_in_impl (seat_impl); ++ g_task_return_boolean (task, TRUE); + + return G_SOURCE_REMOVE; + } +@@ -3847,22 +3860,22 @@ set_keyboard_map (GTask *task) + * is pressed when calling this function. + */ + void +-meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, +- struct xkb_keymap *xkb_keymap, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) ++meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { + GTask *task; + + g_return_if_fail (META_IS_SEAT_IMPL (seat_impl)); +- g_return_if_fail (xkb_keymap != NULL); ++ g_return_if_fail (keymap_description); + + task = g_task_new (seat_impl, cancellable, callback, user_data); + g_task_set_source_tag (task, meta_seat_impl_set_keyboard_map_async); + g_task_set_task_data (task, +- xkb_keymap_ref (xkb_keymap), +- (GDestroyNotify) xkb_keymap_unref); ++ meta_keymap_description_ref (keymap_description), ++ (GDestroyNotify) meta_keymap_description_unref); + meta_seat_impl_run_input_task (seat_impl, task, (GSourceFunc) set_keyboard_map); + g_object_unref (task); + } +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index 24d0405062..3637627a67 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -189,11 +189,11 @@ gboolean meta_seat_impl_set_keyboard_map_finish (MetaSeatImpl *seat_impl, + GAsyncResult *result, + GError **error); + +-void meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, +- struct xkb_keymap *keymap, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); ++void meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); + + gboolean meta_seat_impl_set_keyboard_layout_index_finish (MetaSeatImpl *seat_impl, + GAsyncResult *result, +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 905fa13ce0..dc2e6683a4 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -536,6 +536,7 @@ set_impl_keyboard_map_cb (GObject *source_object, + g_autoptr (GTask) task = G_TASK (user_data); + g_autoptr (GError) error = NULL; + MetaSeatNative *seat_native; ++ MetaKeymapDescription *keymap_description; + struct xkb_keymap *keymap; + + if (!meta_seat_impl_set_keyboard_map_finish (seat_impl, result, &error)) +@@ -545,7 +546,14 @@ set_impl_keyboard_map_cb (GObject *source_object, + } + + seat_native = META_SEAT_NATIVE (g_task_get_source_object (task)); +- keymap = g_task_get_task_data (task); ++ keymap_description = g_task_get_task_data (task); ++ keymap = meta_keymap_description_create_xkb_keymap (keymap_description, ++ &error); ++ if (!keymap) ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ return; ++ } + + g_clear_pointer (&seat_native->xkb_keymap, xkb_keymap_unref); + seat_native->xkb_keymap = xkb_keymap_ref (keymap); +@@ -571,33 +579,20 @@ meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + gpointer user_data) + { + g_autoptr (GTask) task = NULL; +- struct xkb_keymap *keymap, *impl_keymap; + g_autoptr (GError) error = NULL; + + task = g_task_new (G_OBJECT (seat), cancellable, callback, user_data); + g_task_set_source_tag (task, meta_seat_native_set_keyboard_map_async); + +- keymap = meta_keymap_description_create_xkb_keymap (description, &error); +- if (keymap == NULL) +- { +- g_task_return_error (task, g_steal_pointer (&error)); +- return; +- } +- +- impl_keymap = meta_keymap_description_create_xkb_keymap (description, &error); +- if (impl_keymap == NULL) +- { +- g_task_return_error (task, g_steal_pointer (&error)); +- return; +- } +- +- g_task_set_task_data (task, keymap, (GDestroyNotify) xkb_keymap_unref); ++ g_task_set_task_data (task, ++ meta_keymap_description_ref (description), ++ (GDestroyNotify) meta_keymap_description_unref); + +- meta_seat_impl_set_keyboard_map_async (seat->impl, impl_keymap, ++ meta_seat_impl_set_keyboard_map_async (seat->impl, ++ description, + cancellable, + set_impl_keyboard_map_cb, + g_object_ref (task)); +- xkb_keymap_unref (impl_keymap); + } + + static void +-- +2.54.0 + + +From 248e82f10e7f12b2bb86bc4659d5992c89ea641e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 6 Oct 2025 11:41:07 +0200 +Subject: [PATCH 11/32] tests/remote-desktop-tests: Don't use nested context + when setting keymap + +This will fail when the keymap related state is set via signals that +gets emitted from callbacks from the impl thread, that won't know about +the current thread default context. + +It should also be harmless, we won't read the next line until the +function returns, so there should be no risk of re-entry. + +Part-of: +(cherry picked from commit 2509ba34aad134ecfc9ad33dcfe939c8dd0bc161) +--- + src/tests/remote-desktop-tests.c | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/src/tests/remote-desktop-tests.c b/src/tests/remote-desktop-tests.c +index 04979afa66..a2ce8ce388 100644 +--- a/src/tests/remote-desktop-tests.c ++++ b/src/tests/remote-desktop-tests.c +@@ -69,13 +69,10 @@ remote_desktop_test_client_command (int argc, + MetaBackend *backend = meta_context_get_backend (test_context); + const char *layout = argv[1]; + const char *variant = argv[2]; +- g_autoptr (GMainContext) main_context = NULL; + g_autoptr (MetaKeymapDescription) keymap_description = NULL; + gboolean done = FALSE; + + g_debug ("Switching keyboard layout to %s, %s", layout, variant); +- main_context = g_main_context_new (); +- g_main_context_push_thread_default (main_context); + keymap_description = meta_keymap_description_new_from_rules (NULL, + layout, + variant, +@@ -83,8 +80,7 @@ remote_desktop_test_client_command (int argc, + meta_backend_set_keymap_async (backend, keymap_description, + NULL, set_keymap_cb, &done); + while (!done) +- g_main_context_iteration (main_context, TRUE); +- g_main_context_pop_thread_default (main_context); ++ g_main_context_iteration (NULL, TRUE); + + return TRUE; + } +-- +2.54.0 + + +From 60efa23fa5efe2ca105aef4fa21c653beec30f2c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 21:49:24 +0200 +Subject: [PATCH 12/32] backend/native: Emit 'keymap-changed' via signal on the + ClutterKeymap + +This will allow proper ordering of events (keymap & layout index +changes) if the index also changes at the same time as the keymap. + +Part-of: +(cherry picked from commit ce7f26e16f5e2e7c4e73e2936818f8b528c1b37d) +--- + src/backends/native/meta-backend-native.c | 10 +- + .../native/meta-keymap-native-private.h | 6 +- + src/backends/native/meta-keymap-native.c | 61 ++++++++- + src/backends/native/meta-seat-impl.c | 5 +- + src/backends/native/meta-seat-native.c | 126 +++++++++++++----- + src/backends/native/meta-seat-native.h | 1 + + 6 files changed, 164 insertions(+), 45 deletions(-) + +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index 3e39efb098..95dbfca41e 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -207,6 +207,7 @@ meta_backend_native_init_post (MetaBackend *backend, + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaA11yManager *a11y_manager = meta_backend_get_a11y_manager (backend); ++ ClutterSeat *seat; + + g_clear_pointer (&priv->startup_render_devices, + g_hash_table_unref); +@@ -225,6 +226,11 @@ meta_backend_native_init_post (MetaBackend *backend, + "backend", backend, + NULL); + ++ seat = meta_backend_get_default_seat (backend); ++ g_signal_connect_swapped (seat, "keymap-changed", ++ G_CALLBACK (meta_backend_notify_keymap_changed), ++ backend); ++ + return TRUE; + } + +@@ -319,7 +325,6 @@ set_keyboard_map_cb (GObject *source_object, + MetaSeatNative *seat_native = META_SEAT_NATIVE (source_object); + g_autoptr (GTask) task = G_TASK (user_data); + g_autoptr (GError) error = NULL; +- MetaBackend *backend; + + if (!meta_seat_native_set_keyboard_map_finish (seat_native, result, &error)) + { +@@ -327,9 +332,6 @@ set_keyboard_map_cb (GObject *source_object, + return; + } + +- backend = META_BACKEND (g_task_get_source_object (task)); +- meta_backend_notify_keymap_changed (backend); +- + g_task_return_boolean (task, TRUE); + } + +diff --git a/src/backends/native/meta-keymap-native-private.h b/src/backends/native/meta-keymap-native-private.h +index 318485a102..2084393d5f 100644 +--- a/src/backends/native/meta-keymap-native-private.h ++++ b/src/backends/native/meta-keymap-native-private.h +@@ -25,8 +25,10 @@ + #error "This header cannot be included directly. Use "backends/native/meta-input-thread.h"" + #endif /* META_INPUT_THREAD_H_INSIDE */ + +-void meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, +- struct xkb_keymap *xkb_keymap); ++void meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, ++ MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ struct xkb_keymap *xkb_keymap); + + struct xkb_keymap * meta_keymap_native_get_keyboard_map_in_impl (MetaKeymapNative *keymap); + +diff --git a/src/backends/native/meta-keymap-native.c b/src/backends/native/meta-keymap-native.c +index 8d1d804c12..770f0cb0d0 100644 +--- a/src/backends/native/meta-keymap-native.c ++++ b/src/backends/native/meta-keymap-native.c +@@ -29,6 +29,15 @@ static const char *option_xkb_layout = "us"; + static const char *option_xkb_variant = ""; + static const char *option_xkb_options = ""; + ++enum ++{ ++ KEYMAP_CHANGED, ++ ++ N_SIGNALS ++}; ++ ++static guint signals[N_SIGNALS] = { 0, }; ++ + typedef struct _MetaKeymapNative MetaKeymapNative; + + struct _MetaKeymapNative +@@ -68,6 +77,14 @@ meta_keymap_native_class_init (MetaKeymapNativeClass *klass) + object_class->finalize = meta_keymap_native_finalize; + + keymap_class->get_direction = meta_keymap_native_get_direction; ++ ++ signals[KEYMAP_CHANGED] = ++ g_signal_new ("keymap-changed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_FIRST, ++ 0, NULL, NULL, NULL, ++ G_TYPE_NONE, 1, ++ META_TYPE_KEYMAP_DESCRIPTION); + } + + static void +@@ -88,14 +105,54 @@ meta_keymap_native_init (MetaKeymapNative *keymap) + xkb_context_unref (ctx); + } + ++typedef struct ++{ ++ MetaKeymapNative *keymap_native; ++ ++ MetaKeymapDescription *keymap_description; ++} UpdateKeymapData; ++ ++static void ++update_keymap_data_free (gpointer user_data) ++{ ++ UpdateKeymapData *data = user_data; ++ ++ meta_keymap_description_unref (data->keymap_description); ++ g_free (data); ++} ++ ++static gboolean ++update_keymap_in_main (gpointer user_data) ++{ ++ UpdateKeymapData *data = user_data; ++ MetaKeymapNative *keymap_native = data->keymap_native; ++ ++ g_signal_emit (keymap_native, signals[KEYMAP_CHANGED], 0, ++ data->keymap_description); ++ ++ return G_SOURCE_REMOVE; ++} ++ + void +-meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, +- struct xkb_keymap *xkb_keymap) ++meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, ++ MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ struct xkb_keymap *xkb_keymap) + { ++ UpdateKeymapData *data; ++ + g_return_if_fail (xkb_keymap != NULL); + + g_clear_pointer (&keymap->impl.keymap, xkb_keymap_unref); + keymap->impl.keymap = xkb_keymap_ref (xkb_keymap); ++ ++ data = g_new0 (UpdateKeymapData, 1); ++ data->keymap_native = keymap; ++ data->keymap_description = meta_keymap_description_ref (keymap_description); ++ ++ meta_seat_impl_queue_main_thread_idle (seat_impl, ++ update_keymap_in_main, ++ data, update_keymap_data_free); + } + + struct xkb_keymap * +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index f89dfc90d9..1a54cfe00c 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3838,7 +3838,10 @@ set_keyboard_map (GTask *task) + return G_SOURCE_REMOVE; + } + +- meta_keymap_native_set_keyboard_map_in_impl (keymap, xkb_keymap); ++ meta_keymap_native_set_keyboard_map_in_impl (keymap, ++ seat_impl, ++ keymap_description, ++ xkb_keymap); + + meta_seat_impl_update_xkb_state_in_impl (seat_impl); + g_task_return_boolean (task, TRUE); +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index dc2e6683a4..9be0bd7ce1 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -55,6 +55,15 @@ enum + + static GParamSpec *props[N_PROPS] = { NULL }; + ++enum ++{ ++ KEYMAP_CHANGED, ++ ++ N_SIGNALS ++}; ++ ++static guint signals[N_SIGNALS]; ++ + G_DEFINE_TYPE (MetaSeatNative, meta_seat_native, CLUTTER_TYPE_SEAT) + + static gboolean meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, +@@ -150,12 +159,44 @@ keymap_state_changed_cb (MetaSeatNative *seat_native, + } + } + ++static void ++on_keymap_changed (MetaKeymapNative *keymap_native, ++ MetaKeymapDescription *keymap_description, ++ MetaSeatNative *seat_native) ++{ ++ g_autoptr (GError) error = NULL; ++ struct xkb_keymap *xkb_keymap; ++ ++ if (seat_native->keymap_description == keymap_description) ++ return; ++ ++ xkb_keymap = ++ meta_keymap_description_create_xkb_keymap (keymap_description, ++ &error); ++ if (!xkb_keymap) ++ { ++ g_warning ("Failed to create xkb_keymap for seat: %s", error->message); ++ return; ++ } ++ ++ g_clear_pointer (&seat_native->keymap_description, ++ meta_keymap_description_unref); ++ seat_native->keymap_description = ++ meta_keymap_description_ref (keymap_description); ++ ++ g_clear_pointer (&seat_native->xkb_keymap, xkb_keymap_unref); ++ seat_native->xkb_keymap = xkb_keymap; ++ ++ g_signal_emit (seat_native, signals[KEYMAP_CHANGED], 0); ++} ++ + static void + meta_seat_native_constructed (GObject *object) + { + MetaSeatNative *seat = META_SEAT_NATIVE (object); + g_autoptr (MetaKeymapDescription) keymap_description = NULL; + g_autoptr (GError) error = NULL; ++ ClutterKeymap *keymap; + + seat->impl = meta_seat_impl_new (seat, seat->seat_id, seat->flags); + meta_seat_impl_setup (seat->impl); +@@ -171,6 +212,10 @@ meta_seat_native_constructed (GObject *object) + seat->core_pointer = meta_seat_impl_get_pointer (seat->impl); + seat->core_keyboard = meta_seat_impl_get_keyboard (seat->impl); + ++ keymap = clutter_seat_get_keymap (CLUTTER_SEAT (seat)); ++ g_signal_connect (keymap, "keymap-changed", ++ G_CALLBACK (on_keymap_changed), seat); ++ + keymap_description = meta_keymap_description_new_from_rules (NULL, + "us", + NULL, +@@ -241,6 +286,8 @@ meta_seat_native_dispose (GObject *object) + { + MetaSeatNative *seat = META_SEAT_NATIVE (object); + ++ g_clear_pointer (&seat->keymap_description, ++ meta_keymap_description_unref); + g_clear_pointer (&seat->xkb_keymap, xkb_keymap_unref); + g_clear_object (&seat->core_pointer); + g_clear_object (&seat->core_keyboard); +@@ -447,6 +494,13 @@ meta_seat_native_class_init (MetaSeatNativeClass *klass) + + g_object_class_override_property (object_class, PROP_TOUCH_MODE, + "touch-mode"); ++ ++ signals[KEYMAP_CHANGED] = ++ g_signal_new ("keymap-changed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_FIRST, ++ 0, NULL, NULL, NULL, ++ G_TYPE_NONE, 0); + } + + static void +@@ -535,9 +589,6 @@ set_impl_keyboard_map_cb (GObject *source_object, + MetaSeatImpl *seat_impl = META_SEAT_IMPL (source_object); + g_autoptr (GTask) task = G_TASK (user_data); + g_autoptr (GError) error = NULL; +- MetaSeatNative *seat_native; +- MetaKeymapDescription *keymap_description; +- struct xkb_keymap *keymap; + + if (!meta_seat_impl_set_keyboard_map_finish (seat_impl, result, &error)) + { +@@ -545,19 +596,6 @@ set_impl_keyboard_map_cb (GObject *source_object, + return; + } + +- seat_native = META_SEAT_NATIVE (g_task_get_source_object (task)); +- keymap_description = g_task_get_task_data (task); +- keymap = meta_keymap_description_create_xkb_keymap (keymap_description, +- &error); +- if (!keymap) +- { +- g_task_return_error (task, g_steal_pointer (&error)); +- return; +- } +- +- g_clear_pointer (&seat_native->xkb_keymap, xkb_keymap_unref); +- seat_native->xkb_keymap = xkb_keymap_ref (keymap); +- + g_task_return_boolean (task, TRUE); + } + +@@ -579,15 +617,10 @@ meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + gpointer user_data) + { + g_autoptr (GTask) task = NULL; +- g_autoptr (GError) error = NULL; + + task = g_task_new (G_OBJECT (seat), cancellable, callback, user_data); + g_task_set_source_tag (task, meta_seat_native_set_keyboard_map_async); + +- g_task_set_task_data (task, +- meta_keymap_description_ref (description), +- (GDestroyNotify) meta_keymap_description_unref); +- + meta_seat_impl_set_keyboard_map_async (seat->impl, + description, + cancellable, +@@ -596,20 +629,23 @@ meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + } + + static void +-set_keyboard_map_cb (GObject *source_object, +- GAsyncResult *result, +- gpointer user_data) ++sync_set_impl_keyboard_map_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) + { +- MetaSeatNative *seat_native = META_SEAT_NATIVE (source_object); ++ MetaSeatImpl *seat_impl = META_SEAT_IMPL (source_object); ++ g_autoptr (GError) error = NULL; + GTask *task = G_TASK (user_data); + GMainLoop *main_loop = g_task_get_task_data (task); +- g_autoptr (GError) error = NULL; + +- if (!meta_seat_native_set_keyboard_map_finish (seat_native, result, &error)) +- g_task_return_error (task, error); +- else +- g_task_return_boolean (task, TRUE); ++ if (!meta_seat_impl_set_keyboard_map_finish (seat_impl, result, &error)) ++ { ++ g_task_return_error (task, g_steal_pointer (&error)); ++ g_main_loop_quit (main_loop); ++ return; ++ } + ++ g_task_return_boolean (task, TRUE); + g_main_loop_quit (main_loop); + } + +@@ -622,22 +658,40 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + g_autoptr (GMainContext) main_context = NULL; + g_autoptr (GMainLoop) main_loop = NULL; + g_autoptr (GTask) task = NULL; ++ struct xkb_keymap *xkb_keymap; + + main_context = g_main_context_new (); + main_loop = g_main_loop_new (main_context, FALSE); + g_main_context_push_thread_default (main_context); + +- task = g_task_new (G_OBJECT (seat_native), NULL, NULL, NULL); ++ task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (task, main_loop, NULL); + +- meta_seat_native_set_keyboard_map_async (seat_native, +- description, +- cancellable, +- set_keyboard_map_cb, task); ++ meta_seat_impl_set_keyboard_map_async (seat_native->impl, ++ description, ++ cancellable, ++ sync_set_impl_keyboard_map_cb, ++ task); + g_main_loop_run (main_loop); + g_main_context_pop_thread_default (main_context); + +- return g_task_propagate_boolean (task, error); ++ if (!g_task_propagate_boolean (task, error)) ++ return FALSE; ++ ++ xkb_keymap = ++ meta_keymap_description_create_xkb_keymap (description, ++ error); ++ if (!xkb_keymap) ++ return FALSE; ++ ++ g_clear_pointer (&seat_native->xkb_keymap, xkb_keymap_unref); ++ seat_native->xkb_keymap = xkb_keymap_ref (xkb_keymap); ++ ++ g_clear_pointer (&seat_native->keymap_description, ++ meta_keymap_description_unref); ++ seat_native->keymap_description = meta_keymap_description_ref (description); ++ ++ return TRUE; + } + + /** +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index e0bd689a07..fc0663086e 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -50,6 +50,7 @@ struct _MetaSeatNative + GList *devices; + struct xkb_keymap *xkb_keymap; + xkb_layout_index_t xkb_layout_index; ++ MetaKeymapDescription *keymap_description; + + ClutterInputDevice *core_pointer; + ClutterInputDevice *core_keyboard; +-- +2.54.0 + + +From b024281a74510eaef821cdc6c9bb2f660d1fcae2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 9 Dec 2025 22:10:35 +0100 +Subject: [PATCH 13/32] keymap/native: Move out modifier handling to helpers + +This will make it easier to update modifiers from other places later. + +Part-of: +(cherry picked from commit a2eeb8959e6d3981673fe3d47f909e98877ebcf4) +--- + src/backends/native/meta-keymap-native.c | 85 +++++++++++++++--------- + 1 file changed, 52 insertions(+), 33 deletions(-) + +diff --git a/src/backends/native/meta-keymap-native.c b/src/backends/native/meta-keymap-native.c +index 770f0cb0d0..ec69950e0a 100644 +--- a/src/backends/native/meta-keymap-native.c ++++ b/src/backends/native/meta-keymap-native.c +@@ -49,6 +49,15 @@ struct _MetaKeymapNative + } impl; + }; + ++typedef struct ++{ ++ xkb_mod_mask_t depressed_mods; ++ xkb_mod_mask_t latched_mods; ++ xkb_mod_mask_t locked_mods; ++ ++ xkb_layout_index_t effective_layout_group; ++} ModifierState; ++ + G_DEFINE_TYPE (MetaKeymapNative, meta_keymap_native, + CLUTTER_TYPE_KEYMAP) + +@@ -105,6 +114,46 @@ meta_keymap_native_init (MetaKeymapNative *keymap) + xkb_context_unref (ctx); + } + ++static ModifierState ++calculate_modifier_state (struct xkb_state *xkb_state) ++{ ++ return (ModifierState) { ++ .depressed_mods = ++ xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_DEPRESSED), ++ .latched_mods = ++ xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_LATCHED), ++ .locked_mods = ++ xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_LOCKED), ++ .effective_layout_group = ++ xkb_state_serialize_layout (xkb_state, XKB_STATE_LAYOUT_EFFECTIVE), ++ }; ++} ++ ++static void ++update_state_from_modifier_state (MetaKeymapNative *keymap_native, ++ ModifierState *modifier_state) ++{ ++ gboolean caps_lock_state; ++ gboolean num_lock_state; ++ ++ num_lock_state = ++ !!((modifier_state->latched_mods | modifier_state->locked_mods) & ++ (1 << xkb_keymap_mod_get_index (keymap_native->impl.keymap, ++ XKB_MOD_NAME_NUM))); ++ caps_lock_state = ++ !!((modifier_state->latched_mods | modifier_state->locked_mods) & ++ (1 << xkb_keymap_mod_get_index (keymap_native->impl.keymap, ++ XKB_MOD_NAME_CAPS))); ++ ++ clutter_keymap_update_state (CLUTTER_KEYMAP (keymap_native), ++ caps_lock_state, ++ num_lock_state, ++ modifier_state->effective_layout_group, ++ modifier_state->depressed_mods, ++ modifier_state->latched_mods, ++ modifier_state->locked_mods); ++} ++ + typedef struct + { + MetaKeymapNative *keymap_native; +@@ -165,11 +214,7 @@ typedef struct + { + MetaKeymapNative *keymap_native; + +- xkb_mod_mask_t depressed_mods; +- xkb_mod_mask_t latched_mods; +- xkb_mod_mask_t locked_mods; +- +- xkb_layout_index_t effective_layout_group; ++ ModifierState modifier_state; + } UpdateLockedModifierStateData; + + static gboolean +@@ -177,25 +222,8 @@ update_state_in_main (gpointer user_data) + { + UpdateLockedModifierStateData *data = user_data; + MetaKeymapNative *keymap_native = data->keymap_native; +- gboolean caps_lock_state; +- gboolean num_lock_state; + +- num_lock_state = +- !!((data->latched_mods | data->locked_mods) & +- (1 << xkb_keymap_mod_get_index (keymap_native->impl.keymap, +- XKB_MOD_NAME_NUM))); +- caps_lock_state = +- !!((data->latched_mods | data->locked_mods) & +- (1 << xkb_keymap_mod_get_index (keymap_native->impl.keymap, +- XKB_MOD_NAME_CAPS))); +- +- clutter_keymap_update_state (CLUTTER_KEYMAP (keymap_native), +- caps_lock_state, +- num_lock_state, +- data->effective_layout_group, +- data->depressed_mods, +- data->latched_mods, +- data->locked_mods); ++ update_state_from_modifier_state (keymap_native, &data->modifier_state); + + return G_SOURCE_REMOVE; + } +@@ -209,16 +237,7 @@ meta_keymap_native_update_in_impl (MetaKeymapNative *keymap_native, + + data = g_new0 (UpdateLockedModifierStateData, 1); + data->keymap_native = keymap_native; +- +- data->depressed_mods = +- xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_DEPRESSED); +- data->latched_mods = +- xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_LATCHED); +- data->locked_mods = +- xkb_state_serialize_mods (xkb_state, XKB_STATE_MODS_LOCKED); +- +- data->effective_layout_group = +- xkb_state_serialize_layout (xkb_state, XKB_STATE_LAYOUT_EFFECTIVE); ++ data->modifier_state = calculate_modifier_state (xkb_state); + + meta_seat_impl_queue_main_thread_idle (seat_impl, + update_state_in_main, +-- +2.54.0 + + +From 656d83e985b87b33cc77702502df3b1d65616116 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 21:21:38 +0200 +Subject: [PATCH 14/32] backend: Make set_keymap API also take a layout index + +This avoids always requiring a second call to a separate function to set +the layout index, where there is a short period of time where it's +potentially incorrect. + +Part-of: +(cherry picked from commit 50dd36fe3a2f46e43db618a420709dc6fc55373e) +--- + clutter/clutter/clutter-keymap-private.h | 15 ++-- + clutter/clutter/clutter-keymap.c | 12 ++-- + src/backends/meta-backend-private.h | 1 + + src/backends/meta-backend.c | 2 + + src/backends/native/meta-backend-native.c | 2 + + .../native/meta-keymap-native-private.h | 3 +- + src/backends/native/meta-keymap-native.c | 35 +++++++--- + src/backends/native/meta-seat-impl.c | 70 +++++++++++++++---- + src/backends/native/meta-seat-impl.h | 1 + + src/backends/native/meta-seat-native.c | 10 ++- + src/backends/native/meta-seat-native.h | 1 + + src/compositor/plugins/default.c | 2 +- + src/meta/meta-backend.h | 1 + + src/tests/keyboard-map-tests.c | 6 +- + src/tests/remote-desktop-tests.c | 2 +- + 15 files changed, 119 insertions(+), 44 deletions(-) + +diff --git a/clutter/clutter/clutter-keymap-private.h b/clutter/clutter/clutter-keymap-private.h +index 047425c05e..5b65b880aa 100644 +--- a/clutter/clutter/clutter-keymap-private.h ++++ b/clutter/clutter/clutter-keymap-private.h +@@ -21,10 +21,11 @@ + #include "clutter/clutter-keymap.h" + + CLUTTER_EXPORT +-void clutter_keymap_update_state (ClutterKeymap *keymap, +- gboolean caps_lock_state, +- gboolean num_lock_state, +- xkb_layout_index_t effective_layout_group, +- xkb_mod_mask_t depressed_mods, +- xkb_mod_mask_t latched_mods, +- xkb_mod_mask_t locked_mods); ++gboolean clutter_keymap_update_state (ClutterKeymap *keymap, ++ gboolean caps_lock_state, ++ gboolean num_lock_state, ++ xkb_layout_index_t effective_layout_group, ++ xkb_mod_mask_t depressed_mods, ++ xkb_mod_mask_t latched_mods, ++ xkb_mod_mask_t locked_mods, ++ gboolean emit_signal); +diff --git a/clutter/clutter/clutter-keymap.c b/clutter/clutter/clutter-keymap.c +index 8721990f74..4073da4f17 100644 +--- a/clutter/clutter/clutter-keymap.c ++++ b/clutter/clutter/clutter-keymap.c +@@ -159,14 +159,15 @@ clutter_keymap_get_direction (ClutterKeymap *keymap) + return CLUTTER_KEYMAP_GET_CLASS (keymap)->get_direction (keymap); + } + +-void ++gboolean + clutter_keymap_update_state (ClutterKeymap *keymap, + gboolean caps_lock_state, + gboolean num_lock_state, + xkb_layout_index_t effective_layout_group, + xkb_mod_mask_t depressed_mods, + xkb_mod_mask_t latched_mods, +- xkb_mod_mask_t locked_mods) ++ xkb_mod_mask_t locked_mods, ++ gboolean emit_signal) + { + ClutterKeymapPrivate *priv = clutter_keymap_get_instance_private (keymap); + +@@ -176,7 +177,7 @@ clutter_keymap_update_state (ClutterKeymap *keymap, + priv->depressed_mods == depressed_mods && + priv->latched_mods == latched_mods && + priv->locked_mods == locked_mods) +- return; ++ return FALSE; + + priv->effective_layout_group = effective_layout_group; + priv->depressed_mods = depressed_mods; +@@ -201,7 +202,10 @@ clutter_keymap_update_state (ClutterKeymap *keymap, + priv->num_lock_state ? "set" : "unset", + priv->caps_lock_state ? "set" : "unset"); + +- g_signal_emit (keymap, signals[STATE_CHANGED], 0); ++ if (emit_signal) ++ g_signal_emit (keymap, signals[STATE_CHANGED], 0); ++ ++ return TRUE; + } + + void +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index b7d92e6a4c..2cca4caa13 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -126,6 +126,7 @@ struct _MetaBackendClass + + void (* set_keymap_async) (MetaBackend *backend, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GTask *task); + + struct xkb_keymap * (* get_keymap) (MetaBackend *backend); +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index 84e4d4cf25..926e94365b 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -1777,6 +1777,7 @@ meta_backend_set_keymap_finish (MetaBackend *backend, + void + meta_backend_set_keymap_async (MetaBackend *backend, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +@@ -1788,6 +1789,7 @@ meta_backend_set_keymap_async (MetaBackend *backend, + + META_BACKEND_GET_CLASS (backend)->set_keymap_async (backend, + description, ++ layout_index, + task); + } + +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index 95dbfca41e..ebb95002a1 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -338,6 +338,7 @@ set_keyboard_map_cb (GObject *source_object, + static void + meta_backend_native_set_keymap_async (MetaBackend *backend, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GTask *task) + { + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); +@@ -346,6 +347,7 @@ meta_backend_native_set_keymap_async (MetaBackend *backend, + seat = clutter_backend_get_default_seat (clutter_backend); + meta_seat_native_set_keyboard_map_async (META_SEAT_NATIVE (seat), + description, ++ layout_index, + g_task_get_cancellable (task), + set_keyboard_map_cb, + task); +diff --git a/src/backends/native/meta-keymap-native-private.h b/src/backends/native/meta-keymap-native-private.h +index 2084393d5f..82d249eda8 100644 +--- a/src/backends/native/meta-keymap-native-private.h ++++ b/src/backends/native/meta-keymap-native-private.h +@@ -28,7 +28,8 @@ + void meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + MetaSeatImpl *seat_impl, + MetaKeymapDescription *keymap_description, +- struct xkb_keymap *xkb_keymap); ++ struct xkb_keymap *xkb_keymap, ++ struct xkb_state *xkb_state); + + struct xkb_keymap * meta_keymap_native_get_keyboard_map_in_impl (MetaKeymapNative *keymap); + +diff --git a/src/backends/native/meta-keymap-native.c b/src/backends/native/meta-keymap-native.c +index ec69950e0a..63f5b4cf29 100644 +--- a/src/backends/native/meta-keymap-native.c ++++ b/src/backends/native/meta-keymap-native.c +@@ -129,9 +129,10 @@ calculate_modifier_state (struct xkb_state *xkb_state) + }; + } + +-static void ++static gboolean + update_state_from_modifier_state (MetaKeymapNative *keymap_native, +- ModifierState *modifier_state) ++ ModifierState *modifier_state, ++ gboolean emit_signal) + { + gboolean caps_lock_state; + gboolean num_lock_state; +@@ -145,13 +146,14 @@ update_state_from_modifier_state (MetaKeymapNative *keymap_native, + (1 << xkb_keymap_mod_get_index (keymap_native->impl.keymap, + XKB_MOD_NAME_CAPS))); + +- clutter_keymap_update_state (CLUTTER_KEYMAP (keymap_native), +- caps_lock_state, +- num_lock_state, +- modifier_state->effective_layout_group, +- modifier_state->depressed_mods, +- modifier_state->latched_mods, +- modifier_state->locked_mods); ++ return clutter_keymap_update_state (CLUTTER_KEYMAP (keymap_native), ++ caps_lock_state, ++ num_lock_state, ++ modifier_state->effective_layout_group, ++ modifier_state->depressed_mods, ++ modifier_state->latched_mods, ++ modifier_state->locked_mods, ++ emit_signal); + } + + typedef struct +@@ -159,6 +161,8 @@ typedef struct + MetaKeymapNative *keymap_native; + + MetaKeymapDescription *keymap_description; ++ ++ ModifierState modifier_state; + } UpdateKeymapData; + + static void +@@ -175,9 +179,16 @@ update_keymap_in_main (gpointer user_data) + { + UpdateKeymapData *data = user_data; + MetaKeymapNative *keymap_native = data->keymap_native; ++ gboolean state_changed; ++ ++ state_changed = update_state_from_modifier_state (keymap_native, ++ &data->modifier_state, ++ FALSE); + + g_signal_emit (keymap_native, signals[KEYMAP_CHANGED], 0, + data->keymap_description); ++ if (state_changed) ++ g_signal_emit_by_name (keymap_native, "state-changed"); + + return G_SOURCE_REMOVE; + } +@@ -186,7 +197,8 @@ void + meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + MetaSeatImpl *seat_impl, + MetaKeymapDescription *keymap_description, +- struct xkb_keymap *xkb_keymap) ++ struct xkb_keymap *xkb_keymap, ++ struct xkb_state *xkb_state) + { + UpdateKeymapData *data; + +@@ -197,6 +209,7 @@ meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + + data = g_new0 (UpdateKeymapData, 1); + data->keymap_native = keymap; ++ data->modifier_state = calculate_modifier_state (xkb_state); + data->keymap_description = meta_keymap_description_ref (keymap_description); + + meta_seat_impl_queue_main_thread_idle (seat_impl, +@@ -223,7 +236,7 @@ update_state_in_main (gpointer user_data) + UpdateLockedModifierStateData *data = user_data; + MetaKeymapNative *keymap_native = data->keymap_native; + +- update_state_from_modifier_state (keymap_native, &data->modifier_state); ++ update_state_from_modifier_state (keymap_native, &data->modifier_state, TRUE); + + return G_SOURCE_REMOVE; + } +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 1a54cfe00c..f2ef0d8166 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3670,16 +3670,13 @@ meta_seat_impl_init (MetaSeatImpl *seat_impl) + seat_impl->barrier_manager = meta_barrier_manager_native_new (); + } + +-void +-meta_seat_impl_update_xkb_state_in_impl (MetaSeatImpl *seat_impl) ++static void ++meta_seat_impl_update_xkb_state_in_impl_unlocked (MetaSeatImpl *seat_impl, ++ struct xkb_keymap *xkb_keymap, ++ xkb_layout_index_t layout_index) + { + xkb_mod_mask_t latched_mods = 0; + xkb_mod_mask_t locked_mods = 0; +- struct xkb_keymap *xkb_keymap; +- +- g_rw_lock_writer_lock (&seat_impl->state_lock); +- +- xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (seat_impl->keymap); + + if (seat_impl->xkb) + { +@@ -3696,15 +3693,29 @@ meta_seat_impl_update_xkb_state_in_impl (MetaSeatImpl *seat_impl) + 0, /* depressed */ + latched_mods, + locked_mods, +- 0, 0, seat_impl->layout_idx); ++ 0, 0, layout_index); ++ ++ seat_impl->layout_idx = layout_index; + + update_keyboard_leds (seat_impl); + + meta_seat_impl_sync_leds_in_impl (seat_impl); ++} ++ ++void ++meta_seat_impl_update_xkb_state_in_impl (MetaSeatImpl *seat_impl) ++{ ++ struct xkb_keymap *xkb_keymap; ++ ++ g_rw_lock_writer_lock (&seat_impl->state_lock); ++ ++ xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (seat_impl->keymap); ++ meta_seat_impl_update_xkb_state_in_impl_unlocked (seat_impl, ++ xkb_keymap, ++ seat_impl->layout_idx); + meta_keymap_native_update_in_impl (seat_impl->keymap, + seat_impl, + seat_impl->xkb); +- + g_rw_lock_writer_unlock (&seat_impl->state_lock); + } + +@@ -3816,11 +3827,27 @@ meta_seat_impl_set_keyboard_map_finish (MetaSeatImpl *seat_impl, + return g_task_propagate_boolean (task, error); + } + ++typedef struct ++{ ++ MetaKeymapDescription *keymap_description; ++ xkb_layout_index_t layout_index; ++} SetKeymapData; ++ ++static void ++set_keymap_data_free (gpointer user_data) ++{ ++ SetKeymapData *data = user_data; ++ ++ meta_keymap_description_unref (data->keymap_description); ++ g_free (data); ++} ++ + static gboolean + set_keyboard_map (GTask *task) + { + MetaSeatImpl *seat_impl = g_task_get_source_object (task); +- MetaKeymapDescription *keymap_description = g_task_get_task_data (task); ++ SetKeymapData *data = g_task_get_task_data (task); ++ MetaKeymapDescription *keymap_description = data->keymap_description; + MetaKeymapNative *keymap; + g_autoptr (GError) error = NULL; + struct xkb_keymap *xkb_keymap; +@@ -3838,12 +3865,21 @@ set_keyboard_map (GTask *task) + return G_SOURCE_REMOVE; + } + ++ g_rw_lock_writer_lock (&seat_impl->state_lock); ++ ++ meta_seat_impl_update_xkb_state_in_impl_unlocked (seat_impl, ++ xkb_keymap, ++ data->layout_index); ++ + meta_keymap_native_set_keyboard_map_in_impl (keymap, + seat_impl, + keymap_description, +- xkb_keymap); ++ xkb_keymap, ++ seat_impl->xkb); ++ xkb_keymap_unref (xkb_keymap); ++ ++ g_rw_lock_writer_unlock (&seat_impl->state_lock); + +- meta_seat_impl_update_xkb_state_in_impl (seat_impl); + g_task_return_boolean (task, TRUE); + + return G_SOURCE_REMOVE; +@@ -3865,20 +3901,24 @@ set_keyboard_map (GTask *task) + void + meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, + MetaKeymapDescription *keymap_description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) + { + GTask *task; ++ SetKeymapData *data; + + g_return_if_fail (META_IS_SEAT_IMPL (seat_impl)); + g_return_if_fail (keymap_description); + + task = g_task_new (seat_impl, cancellable, callback, user_data); + g_task_set_source_tag (task, meta_seat_impl_set_keyboard_map_async); +- g_task_set_task_data (task, +- meta_keymap_description_ref (keymap_description), +- (GDestroyNotify) meta_keymap_description_unref); ++ ++ data = g_new0 (SetKeymapData, 1); ++ data->keymap_description = meta_keymap_description_ref (keymap_description); ++ data->layout_index = layout_index; ++ g_task_set_task_data (task, data, set_keymap_data_free); + meta_seat_impl_run_input_task (seat_impl, task, (GSourceFunc) set_keyboard_map); + g_object_unref (task); + } +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index 3637627a67..4950a19a74 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -191,6 +191,7 @@ gboolean meta_seat_impl_set_keyboard_map_finish (MetaSeatImpl *seat_impl, + + void meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, + MetaKeymapDescription *keymap_description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 9be0bd7ce1..921cdb2fc6 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -68,6 +68,7 @@ G_DEFINE_TYPE (MetaSeatNative, meta_seat_native, CLUTTER_TYPE_SEAT) + + static gboolean meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GError **error); + +@@ -186,6 +187,8 @@ on_keymap_changed (MetaKeymapNative *keymap_native, + + g_clear_pointer (&seat_native->xkb_keymap, xkb_keymap_unref); + seat_native->xkb_keymap = xkb_keymap; ++ seat_native->xkb_layout_index = ++ clutter_keymap_get_layout_index (CLUTTER_KEYMAP (keymap_native)); + + g_signal_emit (seat_native, signals[KEYMAP_CHANGED], 0); + } +@@ -221,7 +224,7 @@ meta_seat_native_constructed (GObject *object) + NULL, + NULL); + if (!meta_seat_native_set_keyboard_map_sync (seat, +- keymap_description, ++ keymap_description, 0, + NULL, &error)) + g_warning ("Failed to set keyboard map: %s", error->message); + +@@ -612,6 +615,7 @@ set_impl_keyboard_map_cb (GObject *source_object, + void + meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +@@ -623,6 +627,7 @@ meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + + meta_seat_impl_set_keyboard_map_async (seat->impl, + description, ++ layout_index, + cancellable, + set_impl_keyboard_map_cb, + g_object_ref (task)); +@@ -652,6 +657,7 @@ sync_set_impl_keyboard_map_cb (GObject *source_object, + static gboolean + meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GError **error) + { +@@ -669,6 +675,7 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + + meta_seat_impl_set_keyboard_map_async (seat_native->impl, + description, ++ layout_index, + cancellable, + sync_set_impl_keyboard_map_cb, + task); +@@ -686,6 +693,7 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + + g_clear_pointer (&seat_native->xkb_keymap, xkb_keymap_unref); + seat_native->xkb_keymap = xkb_keymap_ref (xkb_keymap); ++ seat_native->xkb_layout_index = layout_index; + + g_clear_pointer (&seat_native->keymap_description, + meta_keymap_description_unref); +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index fc0663086e..2799d895d2 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -103,6 +103,7 @@ void meta_seat_native_reclaim_devices (MetaSeatNative *seat); + + void meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, + MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +diff --git a/src/compositor/plugins/default.c b/src/compositor/plugins/default.c +index 094cec07ea..3ce1e491e1 100644 +--- a/src/compositor/plugins/default.c ++++ b/src/compositor/plugins/default.c +@@ -421,7 +421,7 @@ init_keymap (MetaDefaultPlugin *self, + x11_options); + + meta_backend_set_keymap_async (backend, +- keymap_description, ++ keymap_description, 0, + NULL, NULL, NULL); + } + +diff --git a/src/meta/meta-backend.h b/src/meta/meta-backend.h +index 74ad2bfebf..cd93fc9a72 100644 +--- a/src/meta/meta-backend.h ++++ b/src/meta/meta-backend.h +@@ -51,6 +51,7 @@ gboolean meta_backend_set_keymap_finish (MetaBackend *backend, + META_EXPORT + void meta_backend_set_keymap_async (MetaBackend *backend, + MetaKeymapDescription *description, ++ uint32_t layout_index, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index 8d93dababf..c307c80e83 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -161,7 +161,7 @@ meta_test_native_keyboard_map_set_async (void) + "us", + "dvorak-alt-intl", + NULL); +- meta_backend_set_keymap_async (backend, keymap_description, ++ meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + + g_assert_true (xkb_keymap == meta_backend_get_keymap (backend)); +@@ -210,7 +210,7 @@ meta_test_native_keyboard_map_change_layout (void) + "us,ua", + NULL, + "grp:caps_select"); +- meta_backend_set_keymap_async (backend, keymap_description, ++ meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + + while (!done) +@@ -292,7 +292,7 @@ meta_test_native_keyboard_map_set_layout_index (void) + "us,se", + "dvorak-alt-intl,svdvorak", + NULL); +- meta_backend_set_keymap_async (backend, keymap_description, ++ meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + while (!done) + g_main_context_iteration (NULL, TRUE); +diff --git a/src/tests/remote-desktop-tests.c b/src/tests/remote-desktop-tests.c +index a2ce8ce388..75266a0838 100644 +--- a/src/tests/remote-desktop-tests.c ++++ b/src/tests/remote-desktop-tests.c +@@ -77,7 +77,7 @@ remote_desktop_test_client_command (int argc, + layout, + variant, + NULL); +- meta_backend_set_keymap_async (backend, keymap_description, ++ meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + while (!done) + g_main_context_iteration (NULL, TRUE); +-- +2.54.0 + + +From c258b53959ba34625f2fb5913f87dc9bba23a7cb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 21:35:05 +0200 +Subject: [PATCH 15/32] Add 'sealed fd' type + +Copied from xdg-desktop-portal, with some unused API skipped. Will be +used to wrap sealed memfd file descriptors, with a convenient API to +retrieve the actual data. + +Part-of: +(cherry picked from commit 7225cec4ced3bd57614800bc8a938c114a226cad) +--- + src/core/meta-sealed-fd.c | 175 ++++++++++++++++++++++++++++++++++++++ + src/core/meta-sealed-fd.h | 43 ++++++++++ + src/meson.build | 2 + + 3 files changed, 220 insertions(+) + create mode 100644 src/core/meta-sealed-fd.c + create mode 100644 src/core/meta-sealed-fd.h + +diff --git a/src/core/meta-sealed-fd.c b/src/core/meta-sealed-fd.c +new file mode 100644 +index 0000000000..57172104a5 +--- /dev/null ++++ b/src/core/meta-sealed-fd.c +@@ -0,0 +1,175 @@ ++/* ++ * Copyright © 2024 GNOME Foundation Inc. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library. If not, see . ++ * ++ * Authors: ++ * Julian Sparber ++ */ ++ ++#include "config.h" ++ ++#include "core/meta-sealed-fd.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define REQUIRED_SEALS (F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SHRINK) ++ ++struct _MetaSealedFd ++{ ++ GObject parent_instance; ++ ++ int fd; ++}; ++ ++G_DEFINE_FINAL_TYPE (MetaSealedFd, meta_sealed_fd, G_TYPE_OBJECT) ++ ++static void ++meta_sealed_fd_finalize (GObject *object) ++{ ++ MetaSealedFd *sealed_fd = META_SEALED_FD (object); ++ g_autoptr (GError) error = NULL; ++ ++ if (!g_clear_fd (&sealed_fd->fd, &error)) ++ g_warning ("Error closing sealed fd: %s", error->message); ++ ++ G_OBJECT_CLASS (meta_sealed_fd_parent_class)->finalize (object); ++} ++ ++static void ++meta_sealed_fd_class_init (MetaSealedFdClass *klass) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = meta_sealed_fd_finalize; ++} ++ ++static void ++meta_sealed_fd_init (MetaSealedFd *sealed_fd) ++{ ++ sealed_fd->fd = -1; ++} ++ ++MetaSealedFd * ++meta_sealed_fd_new_take_memfd (int memfd, ++ GError **error) ++{ ++ g_autoptr (MetaSealedFd) sealed_fd = NULL; ++ g_autofd int fd = g_steal_fd (&memfd); ++ int saved_errno = -1; ++ int seals; ++ ++ g_return_val_if_fail (fd != -1, NULL); ++ ++ seals = fcntl (fd, F_GET_SEALS); ++ if (seals == -1) ++ { ++ saved_errno = errno; ++ ++ g_set_error (error, ++ G_IO_ERROR, ++ g_io_error_from_errno (saved_errno), ++ "fcntl F_GET_SEALS: %s", g_strerror (saved_errno)); ++ return NULL; ++ } ++ ++ /* If the seal seal is set and some required seal is missing report EPERM error directly */ ++ if ((seals & F_SEAL_SEAL) && (seals & REQUIRED_SEALS) != REQUIRED_SEALS) ++ saved_errno = EPERM; ++ else if (fcntl (fd, F_ADD_SEALS, REQUIRED_SEALS) == -1) ++ saved_errno = errno; ++ ++ if (saved_errno != -1) ++ { ++ g_set_error (error, ++ G_IO_ERROR, ++ g_io_error_from_errno (saved_errno), ++ "fcntl F_ADD_SEALS: %s", g_strerror (saved_errno)); ++ return NULL; ++ } ++ ++ sealed_fd = g_object_new (META_TYPE_SEALED_FD, NULL); ++ sealed_fd->fd = g_steal_fd (&fd); ++ ++ return g_steal_pointer (&sealed_fd); ++} ++ ++MetaSealedFd * ++meta_sealed_fd_new_from_handle (GVariant *handle, ++ GUnixFDList *fd_list, ++ GError **error) ++{ ++ g_autofd int fd = -1; ++ int fd_id; ++ ++ if (!g_variant_is_of_type (handle, G_VARIANT_TYPE_HANDLE)) ++ { ++ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "GVariant is not a file descriptor handle"); ++ return NULL; ++ } ++ ++ if (!fd_list) ++ { ++ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "Invalid file descriptor: index not found (empty list)"); ++ return NULL; ++ } ++ ++ fd_id = g_variant_get_handle (handle); ++ if (fd_id >= g_unix_fd_list_get_length (fd_list)) ++ { ++ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, ++ "Invalid file descriptor: index not found"); ++ return NULL; ++ } ++ ++ fd = g_unix_fd_list_get (fd_list, fd_id, error); ++ if (fd == -1) ++ return NULL; ++ ++ return meta_sealed_fd_new_take_memfd (g_steal_fd (&fd), error); ++} ++ ++int ++meta_sealed_fd_get_fd (MetaSealedFd *sealed_fd) ++{ ++ g_return_val_if_fail (META_IS_SEALED_FD (sealed_fd), -1); ++ ++ return sealed_fd->fd; ++} ++ ++int ++meta_sealed_fd_dup_fd (MetaSealedFd *sealed_fd) ++{ ++ g_return_val_if_fail (META_IS_SEALED_FD (sealed_fd), -1); ++ ++ return dup (sealed_fd->fd); ++} ++ ++GBytes * ++meta_sealed_fd_get_bytes (MetaSealedFd *sealed_fd, ++ GError **error) ++{ ++ g_autoptr (GMappedFile) mapped = NULL; ++ ++ mapped = g_mapped_file_new_from_fd (sealed_fd->fd, FALSE, error); ++ return g_mapped_file_get_bytes (mapped); ++} +diff --git a/src/core/meta-sealed-fd.h b/src/core/meta-sealed-fd.h +new file mode 100644 +index 0000000000..2721fdddb9 +--- /dev/null ++++ b/src/core/meta-sealed-fd.h +@@ -0,0 +1,43 @@ ++/* ++ * Copyright © 2024 GNOME Foundation Inc. ++ * ++ * SPDX-License-Identifier: LGPL-2.1-or-later ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library. If not, see . ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++#define META_TYPE_SEALED_FD (meta_sealed_fd_get_type()) ++G_DECLARE_FINAL_TYPE (MetaSealedFd, ++ meta_sealed_fd, ++ META, SEALED_FD, ++ GObject) ++ ++MetaSealedFd * meta_sealed_fd_new_take_memfd (int memfd, ++ GError **error); ++ ++MetaSealedFd * meta_sealed_fd_new_from_handle (GVariant *handle, ++ GUnixFDList *fd_list, ++ GError **error); ++ ++int meta_sealed_fd_get_fd (MetaSealedFd *sealed_fd); ++ ++int meta_sealed_fd_dup_fd (MetaSealedFd *sealed_fd); ++ ++GBytes *meta_sealed_fd_get_bytes (MetaSealedFd *sealed_fd, ++ GError **error); +diff --git a/src/meson.build b/src/meson.build +index e7824f502e..59e8f079c2 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -373,6 +373,8 @@ mutter_sources = [ + 'core/meta-launch-context.c', + 'core/meta-pad-action-mapper.c', + 'core/meta-private-enums.h', ++ 'core/meta-sealed-fd.c', ++ 'core/meta-sealed-fd.h', + 'core/meta-selection.c', + 'core/meta-selection-source.c', + 'core/meta-selection-source-memory.c', +-- +2.54.0 + + +From ab6003a40aa34f95fe8f10ea2e956638163938f8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 21:41:23 +0200 +Subject: [PATCH 16/32] keymap-description: Add way to create keymap + description from fd + +This will be used to set the actual keyboard layout from a sealed memfd +file descriptor via the remote desktop D-Bus API. + +Part-of: +(cherry picked from commit fcd93d433dddbb79dcc01682d2626e5c47dc9eaf) +--- + .../meta-keymap-description-private.h | 13 ++ + src/backends/meta-keymap-description.c | 169 +++++++++++++----- + 2 files changed, 137 insertions(+), 45 deletions(-) + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index 3cacfe6561..6504371a6c 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -20,5 +20,18 @@ + + #include "meta/meta-keymap-description.h" + ++#include "core/meta-sealed-fd.h" ++ ++typedef enum _MetaKeymapDescriptionSource ++{ ++ META_KEYMAP_DESCRIPTION_SOURCE_RULES, ++ META_KEYMAP_DESCRIPTION_SOURCE_FD, ++} MetaKeymapDescriptionSource; ++ ++MetaKeymapDescription * meta_keymap_description_new_from_fd (MetaSealedFd *sealed_fd, ++ enum xkb_keymap_format format); ++ ++MetaKeymapDescriptionSource meta_keymap_description_get_source (MetaKeymapDescription *keymap_description); ++ + struct xkb_keymap * meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, + GError **error); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index cd93c543e3..77d372824e 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -21,18 +21,30 @@ + #include "backends/meta-keymap-description-private.h" + + #include ++#include + + #include "backends/meta-keymap-utils.h" ++#include "core/meta-sealed-fd.h" + + struct _MetaKeymapDescription + { + gatomicrefcount ref_count; + +- char *rules; +- char *model; +- char *layout; +- char *variant; +- char *options; ++ MetaKeymapDescriptionSource source; ++ ++ union { ++ struct { ++ char *rules; ++ char *model; ++ char *layout; ++ char *variant; ++ char *options; ++ } rules; ++ struct { ++ MetaSealedFd *sealed_fd; ++ enum xkb_keymap_format format; ++ } fd; ++ }; + }; + + #define DEFAULT_XKB_RULES_FILE "evdev" +@@ -58,11 +70,27 @@ meta_keymap_description_new_from_rules (const char *model, + + keymap_description = g_new0 (MetaKeymapDescription, 1); + g_atomic_ref_count_init (&keymap_description->ref_count); +- keymap_description->model = model ? g_strdup (model) +- : g_strdup (DEFAULT_XKB_MODEL); +- keymap_description->layout = strdup_or_empty (layout); +- keymap_description->variant = strdup_or_empty (variant); +- keymap_description->options = strdup_or_empty (options); ++ keymap_description->source = META_KEYMAP_DESCRIPTION_SOURCE_RULES; ++ keymap_description->rules.model = model ? g_strdup (model) ++ : g_strdup (DEFAULT_XKB_MODEL); ++ keymap_description->rules.layout = strdup_or_empty (layout); ++ keymap_description->rules.variant = strdup_or_empty (variant); ++ keymap_description->rules.options = strdup_or_empty (options); ++ ++ return keymap_description; ++} ++ ++MetaKeymapDescription * ++meta_keymap_description_new_from_fd (MetaSealedFd *sealed_fd, ++ enum xkb_keymap_format format) ++{ ++ MetaKeymapDescription *keymap_description; ++ ++ keymap_description = g_new0 (MetaKeymapDescription, 1); ++ g_atomic_ref_count_init (&keymap_description->ref_count); ++ keymap_description->source = META_KEYMAP_DESCRIPTION_SOURCE_FD; ++ g_set_object (&keymap_description->fd.sealed_fd, sealed_fd); ++ keymap_description->fd.format = format; + + return keymap_description; + } +@@ -79,47 +107,98 @@ meta_keymap_description_unref (MetaKeymapDescription *keymap_description) + { + if (g_atomic_ref_count_dec (&keymap_description->ref_count)) + { +- g_free (keymap_description->model); +- g_free (keymap_description->layout); +- g_free (keymap_description->variant); +- g_free (keymap_description->options); ++ switch (keymap_description->source) ++ { ++ case META_KEYMAP_DESCRIPTION_SOURCE_RULES: ++ g_free (keymap_description->rules.model); ++ g_free (keymap_description->rules.layout); ++ g_free (keymap_description->rules.variant); ++ g_free (keymap_description->rules.options); ++ break; ++ case META_KEYMAP_DESCRIPTION_SOURCE_FD: ++ g_clear_object (&keymap_description->fd.sealed_fd); ++ break; ++ } ++ + g_free (keymap_description); + } + } + ++MetaKeymapDescriptionSource ++meta_keymap_description_get_source (MetaKeymapDescription *keymap_description) ++{ ++ return keymap_description->source; ++} ++ + struct xkb_keymap * + meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, + GError **error) + { +- struct xkb_rule_names names; +- struct xkb_context *xkb_context; +- struct xkb_keymap *xkb_keymap; +- +- names.rules = DEFAULT_XKB_RULES_FILE; +- names.model = keymap_description->model; +- names.layout = keymap_description->layout; +- names.variant = keymap_description->variant; +- names.options = keymap_description->options; +- +- xkb_context = meta_create_xkb_context (); +- xkb_keymap = xkb_keymap_new_from_names (xkb_context, +- &names, +- XKB_KEYMAP_COMPILE_NO_FLAGS); +- xkb_context_unref (xkb_context); +- +- if (!xkb_keymap) +- { +- g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, +- "Failed to create XKB keymap with " +- "rules=%s, model=%s, layout=%s, " +- "variant=%s, options=%s", +- keymap_description->rules, +- keymap_description->model, +- keymap_description->layout, +- keymap_description->variant, +- keymap_description->options); +- return NULL; +- } +- +- return xkb_keymap; ++ switch (keymap_description->source) ++ { ++ case META_KEYMAP_DESCRIPTION_SOURCE_RULES: ++ { ++ struct xkb_rule_names names; ++ struct xkb_context *xkb_context; ++ struct xkb_keymap *xkb_keymap; ++ ++ names.rules = DEFAULT_XKB_RULES_FILE; ++ names.model = keymap_description->rules.model; ++ names.layout = keymap_description->rules.layout; ++ names.variant = keymap_description->rules.variant; ++ names.options = keymap_description->rules.options; ++ ++ xkb_context = meta_create_xkb_context (); ++ xkb_keymap = xkb_keymap_new_from_names (xkb_context, ++ &names, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ xkb_context_unref (xkb_context); ++ ++ if (!xkb_keymap) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Failed to create XKB keymap with " ++ "rules=%s, model=%s, layout=%s, " ++ "variant=%s, options=%s", ++ keymap_description->rules.rules, ++ keymap_description->rules.model, ++ keymap_description->rules.layout, ++ keymap_description->rules.variant, ++ keymap_description->rules.options); ++ return NULL; ++ } ++ ++ return xkb_keymap; ++ } ++ case META_KEYMAP_DESCRIPTION_SOURCE_FD: ++ { ++ g_autoptr (GBytes) keymap_bytes = NULL; ++ struct xkb_context *xkb_context; ++ struct xkb_keymap *xkb_keymap; ++ ++ keymap_bytes = ++ meta_sealed_fd_get_bytes (keymap_description->fd.sealed_fd, error); ++ if (!keymap_bytes) ++ return NULL; ++ ++ xkb_context = meta_create_xkb_context (); ++ xkb_keymap = ++ xkb_keymap_new_from_string (xkb_context, ++ g_bytes_get_data (keymap_bytes, NULL), ++ keymap_description->fd.format, ++ XKB_KEYMAP_COMPILE_NO_FLAGS); ++ xkb_context_unref (xkb_context); ++ ++ if (!xkb_keymap) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Failed to create XKB keymap from file descriptor"); ++ return NULL; ++ } ++ ++ return xkb_keymap; ++ } ++ } ++ ++ g_assert_not_reached (); + } +-- +2.54.0 + + +From 3e13d05b82513d03a990b8fbb2a146b0d234f7d3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 10:09:25 +0200 +Subject: [PATCH 17/32] backend: Add API to get current keymap description + +This will be used to retrieve information related to the current keymap, +but not part of the keymap itself. Currently there are no such +information, but it will be added in the future. + +Part-of: +(cherry picked from commit f2b1c0e8d1bd867f9b62ac0fff5c5a03dd4381df) +--- + src/backends/meta-backend-private.h | 2 ++ + src/backends/meta-backend.c | 15 +++++++++++++++ + src/backends/native/meta-backend-native.c | 11 +++++++++++ + src/backends/native/meta-seat-native.c | 9 +++++++++ + src/backends/native/meta-seat-native.h | 2 ++ + src/meta/meta-backend.h | 3 +++ + 6 files changed, 42 insertions(+) + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index 2cca4caa13..dccd1116a6 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -131,6 +131,8 @@ struct _MetaBackendClass + + struct xkb_keymap * (* get_keymap) (MetaBackend *backend); + ++ MetaKeymapDescription * (* get_keymap_description) (MetaBackend *backend); ++ + xkb_layout_index_t (* get_keymap_layout_group) (MetaBackend *backend); + + void (* set_keymap_layout_group_async) (MetaBackend *backend, +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index 926e94365b..beaec1f281 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -1799,6 +1799,21 @@ meta_backend_get_keymap (MetaBackend *backend) + return META_BACKEND_GET_CLASS (backend)->get_keymap (backend); + } + ++/** ++ * meta_backend_get_keymap_description: ++ * @backend: a #MetaBackend ++ * keyboard map description ++ * ++ * Gets the description of the current keyboard map. ++ * ++ * Returns: (transfer none): The current keymap description. ++ */ ++MetaKeymapDescription * ++meta_backend_get_keymap_description (MetaBackend *backend) ++{ ++ return META_BACKEND_GET_CLASS (backend)->get_keymap_description (backend); ++} ++ + xkb_layout_index_t + meta_backend_get_keymap_layout_group (MetaBackend *backend) + { +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index ebb95002a1..b2efac59d1 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -364,6 +364,16 @@ meta_backend_native_get_keymap (MetaBackend *backend) + return meta_seat_native_get_keyboard_map (META_SEAT_NATIVE (seat)); + } + ++static MetaKeymapDescription * ++meta_backend_native_get_keymap_description (MetaBackend *backend) ++{ ++ ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); ++ ClutterSeat *seat; ++ ++ seat = clutter_backend_get_default_seat (clutter_backend); ++ return meta_seat_native_get_keyboard_map_description (META_SEAT_NATIVE (seat)); ++} ++ + static xkb_layout_index_t + meta_backend_native_get_keymap_layout_group (MetaBackend *backend) + { +@@ -942,6 +952,7 @@ meta_backend_native_class_init (MetaBackendNativeClass *klass) + + backend_class->set_keymap_async = meta_backend_native_set_keymap_async; + backend_class->get_keymap = meta_backend_native_get_keymap; ++ backend_class->get_keymap_description = meta_backend_native_get_keymap_description; + backend_class->get_keymap_layout_group = meta_backend_native_get_keymap_layout_group; + backend_class->set_keymap_layout_group_async = meta_backend_native_set_keymap_layout_group_async; + backend_class->update_stage = meta_backend_native_update_stage; +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 921cdb2fc6..fc849044f9 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -295,6 +295,7 @@ meta_seat_native_dispose (GObject *object) + g_clear_object (&seat->core_pointer); + g_clear_object (&seat->core_keyboard); + g_clear_pointer (&seat->impl, meta_seat_impl_destroy); ++ g_clear_pointer (&seat->keymap_description, meta_keymap_description_unref); + g_list_free_full (g_steal_pointer (&seat->devices), g_object_unref); + g_clear_pointer (&seat->reserved_virtual_slots, g_hash_table_destroy); + g_clear_pointer (&seat->tablet_cursors, g_hash_table_unref); +@@ -718,6 +719,14 @@ meta_seat_native_get_keyboard_map (MetaSeatNative *seat) + return seat->xkb_keymap; + } + ++MetaKeymapDescription * ++meta_seat_native_get_keyboard_map_description (MetaSeatNative *seat_native) ++{ ++ g_return_val_if_fail (seat_native->keymap_description, NULL); ++ ++ return seat_native->keymap_description; ++} ++ + gboolean + meta_seat_native_set_keyboard_layout_index_finish (MetaSeatNative *seat_native, + GAsyncResult *result, +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index 2799d895d2..061b580bc2 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -125,6 +125,8 @@ void meta_seat_native_set_keyboard_layout_index_async (MetaSeatNative *seat + GAsyncReadyCallback callback, + gpointer user_data); + ++MetaKeymapDescription * meta_seat_native_get_keyboard_map_description (MetaSeatNative *seat_native); ++ + xkb_layout_index_t meta_seat_native_get_keyboard_layout_index (MetaSeatNative *seat); + + void meta_seat_native_set_keyboard_repeat (MetaSeatNative *seat, +diff --git a/src/meta/meta-backend.h b/src/meta/meta-backend.h +index cd93fc9a72..63dde15575 100644 +--- a/src/meta/meta-backend.h ++++ b/src/meta/meta-backend.h +@@ -56,6 +56,9 @@ void meta_backend_set_keymap_async (MetaBackend *backend, + GAsyncReadyCallback callback, + gpointer user_data); + ++META_EXPORT ++MetaKeymapDescription * meta_backend_get_keymap_description (MetaBackend *backend); ++ + META_EXPORT + gboolean meta_backend_set_keymap_layout_group_finish (MetaBackend *backend, + GAsyncResult *result, +-- +2.54.0 + + +From 7f512c1d8a6e3222d79756852a20c0d161d145cc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 10:30:16 +0200 +Subject: [PATCH 18/32] keymap-description: Provide descriptive names together + with xkb_keymap + +These descriptive names are meant to e.g. show in the GNOME Shell input +source indicator if the keymap is set from something other than GNOME +Shell itself. + +Both the short and long names are optional, e.g. not needed by the +default shell, which doesn't show the current layout, however, will fall +back on the name from the xkb_keymap itself if not specified. + +For rules based keymaps, the one creating said keymap can specify both +the display name and short name, and for the descriptions created for +serialized keymaps derive them by searching for the short name in the +XKB registry given the display name. + +Part-of: +(cherry picked from commit f74b640c6ae69338affcc23d9061f8ee73d6fe71) +--- + meson.build | 1 + + .../meta-keymap-description-private.h | 2 + + src/backends/meta-keymap-description.c | 103 +++++++++++++++++- + src/backends/native/meta-seat-impl.c | 2 + + src/backends/native/meta-seat-native.c | 4 + + src/compositor/plugins/default.c | 3 +- + src/core/keybindings.c | 3 + + src/meson.build | 1 + + src/meta/meta-keymap-description.h | 4 +- + src/tests/keyboard-map-tests.c | 8 +- + src/tests/remote-desktop-tests.c | 2 + + 11 files changed, 124 insertions(+), 9 deletions(-) + +diff --git a/meson.build b/meson.build +index f77badb9ae..f508db69ec 100644 +--- a/meson.build ++++ b/meson.build +@@ -131,6 +131,7 @@ libadwaita_dep = dependency('libadwaita-1', required: false) + gmodule_no_export_dep = dependency('gmodule-no-export-2.0', version: glib_req) + gnome_settings_daemon_dep = dependency('gnome-settings-daemon', required: false) + xkbcommon_dep = dependency('xkbcommon', version: xkbcommon_req) ++xkbregistry_dep = dependency('xkbregistry') + atk_dep = dependency('atk', version: atk_req) + colord_dep = dependency('colord', version: colord_req) + lcms2_dep = dependency('lcms2', version: lcms2_req) +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index 6504371a6c..3c0d9cc924 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -34,4 +34,6 @@ MetaKeymapDescription * meta_keymap_description_new_from_fd (MetaSealedFd + MetaKeymapDescriptionSource meta_keymap_description_get_source (MetaKeymapDescription *keymap_description); + + struct xkb_keymap * meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, ++ GStrv *out_display_names, ++ GStrv *out_short_names, + GError **error); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index 77d372824e..63bd539d57 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -39,6 +39,8 @@ struct _MetaKeymapDescription + char *layout; + char *variant; + char *options; ++ GStrv display_names; ++ GStrv short_names; + } rules; + struct { + MetaSealedFd *sealed_fd; +@@ -64,7 +66,9 @@ MetaKeymapDescription * + meta_keymap_description_new_from_rules (const char *model, + const char *layout, + const char *variant, +- const char *options) ++ const char *options, ++ GStrv display_names, ++ GStrv short_names) + { + MetaKeymapDescription *keymap_description; + +@@ -76,6 +80,8 @@ meta_keymap_description_new_from_rules (const char *model, + keymap_description->rules.layout = strdup_or_empty (layout); + keymap_description->rules.variant = strdup_or_empty (variant); + keymap_description->rules.options = strdup_or_empty (options); ++ keymap_description->rules.display_names = g_strdupv (display_names); ++ keymap_description->rules.short_names = g_strdupv (short_names); + + return keymap_description; + } +@@ -114,6 +120,8 @@ meta_keymap_description_unref (MetaKeymapDescription *keymap_description) + g_free (keymap_description->rules.layout); + g_free (keymap_description->rules.variant); + g_free (keymap_description->rules.options); ++ g_strfreev (keymap_description->rules.display_names); ++ g_strfreev (keymap_description->rules.short_names); + break; + case META_KEYMAP_DESCRIPTION_SOURCE_FD: + g_clear_object (&keymap_description->fd.sealed_fd); +@@ -130,17 +138,42 @@ meta_keymap_description_get_source (MetaKeymapDescription *keymap_description) + return keymap_description->source; + } + ++static char * ++maybe_derive_short_name (struct rxkb_context *rxkb_context, ++ const char *layout_name) ++{ ++ struct rxkb_layout *rxkb_layout; ++ if (!layout_name) ++ return NULL; ++ ++ for (rxkb_layout = rxkb_layout_first (rxkb_context); ++ rxkb_layout; ++ rxkb_layout = rxkb_layout_next (rxkb_layout)) ++ { ++ if (g_strcmp0 (layout_name, ++ rxkb_layout_get_description (rxkb_layout)) == 0) ++ return g_strdup (rxkb_layout_get_brief (rxkb_layout)); ++ } ++ ++ return NULL; ++} ++ + struct xkb_keymap * + meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_description, ++ GStrv *out_display_names, ++ GStrv *out_short_names, + GError **error) + { ++ g_auto (GStrv) display_names = NULL; ++ g_auto (GStrv) short_names = NULL; ++ struct xkb_keymap *xkb_keymap = NULL; ++ + switch (keymap_description->source) + { + case META_KEYMAP_DESCRIPTION_SOURCE_RULES: + { + struct xkb_rule_names names; + struct xkb_context *xkb_context; +- struct xkb_keymap *xkb_keymap; + + names.rules = DEFAULT_XKB_RULES_FILE; + names.model = keymap_description->rules.model; +@@ -168,13 +201,17 @@ meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_descri + return NULL; + } + +- return xkb_keymap; ++ if (out_display_names) ++ display_names = g_strdupv (keymap_description->rules.display_names); ++ if (out_short_names) ++ short_names = g_strdupv (keymap_description->rules.short_names); ++ ++ break; + } + case META_KEYMAP_DESCRIPTION_SOURCE_FD: + { + g_autoptr (GBytes) keymap_bytes = NULL; + struct xkb_context *xkb_context; +- struct xkb_keymap *xkb_keymap; + + keymap_bytes = + meta_sealed_fd_get_bytes (keymap_description->fd.sealed_fd, error); +@@ -196,9 +233,63 @@ meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_descri + return NULL; + } + +- return xkb_keymap; ++ break; + } + } + +- g_assert_not_reached (); ++ g_assert (xkb_keymap); ++ ++ if (out_display_names && !display_names) ++ { ++ g_autoptr (GStrvBuilder) display_names_builder = NULL; ++ xkb_layout_index_t n_layouts, i; ++ ++ display_names_builder = g_strv_builder_new (); ++ n_layouts = xkb_keymap_num_layouts (xkb_keymap); ++ for (i = 0; i < n_layouts; i++) ++ { ++ const char *display_name; ++ ++ display_name = xkb_keymap_layout_get_name (xkb_keymap, i); ++ g_strv_builder_add (display_names_builder, ++ display_name ? display_name : ""); ++ } ++ ++ display_names = g_strv_builder_end (display_names_builder); ++ } ++ ++ if (out_short_names && !short_names) ++ { ++ g_autoptr (GStrvBuilder) short_names_builder = NULL; ++ struct rxkb_context *rxkb_context = NULL; ++ ++ short_names_builder = g_strv_builder_new (); ++ ++ rxkb_context = rxkb_context_new (RXKB_CONTEXT_LOAD_EXOTIC_RULES); ++ if (rxkb_context_parse (rxkb_context, "evdev")) ++ { ++ xkb_layout_index_t n_layouts, i; ++ ++ n_layouts = xkb_keymap_num_layouts (xkb_keymap); ++ for (i = 0; i < n_layouts; i++) ++ { ++ g_autofree char *short_name = NULL; ++ ++ short_name = maybe_derive_short_name (rxkb_context, ++ display_names[i]); ++ g_strv_builder_add (short_names_builder, ++ short_name ? short_name : ""); ++ } ++ ++ short_names = g_strv_builder_end (short_names_builder); ++ } ++ rxkb_context_unref (rxkb_context); ++ } ++ ++ if (out_display_names) ++ *out_display_names = g_steal_pointer (&display_names); ++ if (out_short_names) ++ *out_short_names = g_steal_pointer (&short_names); ++ ++ return xkb_keymap; + } +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index f2ef0d8166..a5d490d12b 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3857,6 +3857,8 @@ set_keyboard_map (GTask *task) + keymap = seat_impl->keymap; + + xkb_keymap = meta_keymap_description_create_xkb_keymap (keymap_description, ++ NULL, ++ NULL, + &error); + if (!xkb_keymap) + { +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index fc849044f9..5498bac13f 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -173,6 +173,7 @@ on_keymap_changed (MetaKeymapNative *keymap_native, + + xkb_keymap = + meta_keymap_description_create_xkb_keymap (keymap_description, ++ NULL, NULL, + &error); + if (!xkb_keymap) + { +@@ -222,6 +223,8 @@ meta_seat_native_constructed (GObject *object) + keymap_description = meta_keymap_description_new_from_rules (NULL, + "us", + NULL, ++ NULL, ++ NULL, + NULL); + if (!meta_seat_native_set_keyboard_map_sync (seat, + keymap_description, 0, +@@ -688,6 +691,7 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + + xkb_keymap = + meta_keymap_description_create_xkb_keymap (description, ++ NULL, NULL, + error); + if (!xkb_keymap) + return FALSE; +diff --git a/src/compositor/plugins/default.c b/src/compositor/plugins/default.c +index 3ce1e491e1..cbce7ab959 100644 +--- a/src/compositor/plugins/default.c ++++ b/src/compositor/plugins/default.c +@@ -418,7 +418,8 @@ init_keymap (MetaDefaultPlugin *self, + keymap_description = meta_keymap_description_new_from_rules (x11_model, + x11_layout, + x11_variant, +- x11_options); ++ x11_options, ++ NULL, NULL); + + meta_backend_set_keymap_async (backend, + keymap_description, 0, +diff --git a/src/core/keybindings.c b/src/core/keybindings.c +index 0f475eae3b..6f879eed8e 100644 +--- a/src/core/keybindings.c ++++ b/src/core/keybindings.c +@@ -773,8 +773,11 @@ create_us_layout (void) + keymap_description = meta_keymap_description_new_from_rules (NULL, + "us", + NULL, ++ NULL, ++ NULL, + NULL); + keymap = meta_keymap_description_create_xkb_keymap (keymap_description, ++ NULL, NULL, + &error); + if (!keymap) + { +diff --git a/src/meson.build b/src/meson.build +index 59e8f079c2..72b70e3199 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -26,6 +26,7 @@ mutter_pkg_private_deps = [ + gnome_settings_daemon_dep, + xkbcommon_dep, + gdk_pixbuf_dep, ++ xkbregistry_dep, + libeis_dep, + libdisplay_info_dep, + ] +diff --git a/src/meta/meta-keymap-description.h b/src/meta/meta-keymap-description.h +index 32e9ef6d2d..5140f63b1a 100644 +--- a/src/meta/meta-keymap-description.h ++++ b/src/meta/meta-keymap-description.h +@@ -32,7 +32,9 @@ META_EXPORT + MetaKeymapDescription * meta_keymap_description_new_from_rules (const char *model, + const char *layout, + const char *variant, +- const char *options); ++ const char *options, ++ GStrv display_names, ++ GStrv short_names); + + META_EXPORT + MetaKeymapDescription * meta_keymap_description_ref (MetaKeymapDescription *keymap_description); +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index c307c80e83..e1a65214cc 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -160,6 +160,8 @@ meta_test_native_keyboard_map_set_async (void) + meta_keymap_description_new_from_rules (NULL, + "us", + "dvorak-alt-intl", ++ NULL, ++ NULL, + NULL); + meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); +@@ -209,7 +211,9 @@ meta_test_native_keyboard_map_change_layout (void) + meta_keymap_description_new_from_rules (NULL, + "us,ua", + NULL, +- "grp:caps_select"); ++ "grp:caps_select", ++ NULL, ++ NULL); + meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + +@@ -291,6 +295,8 @@ meta_test_native_keyboard_map_set_layout_index (void) + meta_keymap_description_new_from_rules (NULL, + "us,se", + "dvorak-alt-intl,svdvorak", ++ NULL, ++ NULL, + NULL); + meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); +diff --git a/src/tests/remote-desktop-tests.c b/src/tests/remote-desktop-tests.c +index 75266a0838..9ebc320bd0 100644 +--- a/src/tests/remote-desktop-tests.c ++++ b/src/tests/remote-desktop-tests.c +@@ -76,6 +76,8 @@ remote_desktop_test_client_command (int argc, + keymap_description = meta_keymap_description_new_from_rules (NULL, + layout, + variant, ++ NULL, ++ NULL, + NULL); + meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); +-- +2.54.0 + + +From c01134a4584fad21873333bdbabf06439da9050e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 1 Oct 2025 15:14:40 +0200 +Subject: [PATCH 19/32] clutter/keymap: Add API to update/get layout names + +It's up to the backend to set the names, and the getters are +opportunistic, i.e. will return NULL if none is available. + +The names comes from the keymap description. + +Part-of: +(cherry picked from commit bafbde3f2c47e224beff9547bf9254f7cff79a17) +--- + clutter/clutter/clutter-keymap-private.h | 5 ++ + clutter/clutter/clutter-keymap.c | 67 +++++++++++++++++++ + clutter/clutter/clutter-keymap.h | 18 +++++ + .../native/meta-keymap-native-private.h | 4 +- + src/backends/native/meta-keymap-native.c | 15 ++++- + src/backends/native/meta-seat-impl.c | 10 ++- + 6 files changed, 114 insertions(+), 5 deletions(-) + +diff --git a/clutter/clutter/clutter-keymap-private.h b/clutter/clutter/clutter-keymap-private.h +index 5b65b880aa..2b52666bce 100644 +--- a/clutter/clutter/clutter-keymap-private.h ++++ b/clutter/clutter/clutter-keymap-private.h +@@ -29,3 +29,8 @@ gboolean clutter_keymap_update_state (ClutterKeymap *keymap, + xkb_mod_mask_t latched_mods, + xkb_mod_mask_t locked_mods, + gboolean emit_signal); ++ ++CLUTTER_EXPORT ++void clutter_keymap_update_keymap_names (ClutterKeymap *keymap, ++ GStrv display_names, ++ GStrv short_names); +diff --git a/clutter/clutter/clutter-keymap.c b/clutter/clutter/clutter-keymap.c +index 4073da4f17..ddcf568f78 100644 +--- a/clutter/clutter/clutter-keymap.c ++++ b/clutter/clutter/clutter-keymap.c +@@ -44,6 +44,9 @@ typedef struct _ClutterKeymapPrivate + xkb_mod_mask_t locked_mods; + + xkb_layout_index_t effective_layout_group; ++ ++ GStrv display_names; ++ GStrv short_names; + } ClutterKeymapPrivate; + + G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (ClutterKeymap, clutter_keymap, +@@ -103,6 +106,18 @@ clutter_keymap_set_property (GObject *object, + } + } + ++static void ++clutter_keymap_finalize (GObject *object) ++{ ++ ClutterKeymap *keymap = CLUTTER_KEYMAP (object); ++ ClutterKeymapPrivate *priv = clutter_keymap_get_instance_private (keymap); ++ ++ g_clear_pointer (&priv->display_names, g_strfreev); ++ g_clear_pointer (&priv->short_names, g_strfreev); ++ ++ G_OBJECT_CLASS (clutter_keymap_parent_class)->finalize (object); ++} ++ + static void + clutter_keymap_class_init (ClutterKeymapClass *klass) + { +@@ -110,6 +125,7 @@ clutter_keymap_class_init (ClutterKeymapClass *klass) + + object_class->get_property = clutter_keymap_get_property; + object_class->set_property = clutter_keymap_set_property; ++ object_class->finalize = clutter_keymap_finalize; + + obj_props[PROP_CAPS_LOCK_STATE] = + g_param_spec_boolean ("caps-lock-state", NULL, NULL, +@@ -208,6 +224,19 @@ clutter_keymap_update_state (ClutterKeymap *keymap, + return TRUE; + } + ++void ++clutter_keymap_update_keymap_names (ClutterKeymap *keymap, ++ GStrv display_names, ++ GStrv short_names) ++{ ++ ClutterKeymapPrivate *priv = clutter_keymap_get_instance_private (keymap); ++ ++ g_clear_pointer (&priv->display_names, g_strfreev); ++ g_clear_pointer (&priv->short_names, g_strfreev); ++ priv->display_names = g_steal_pointer (&display_names); ++ priv->short_names = g_steal_pointer (&short_names); ++} ++ + void + clutter_keymap_get_modifier_state (ClutterKeymap *keymap, + xkb_mod_mask_t *depressed_mods, +@@ -228,3 +257,41 @@ clutter_keymap_get_layout_index (ClutterKeymap *keymap) + + return priv->effective_layout_group; + } ++ ++const char * ++clutter_keymap_get_current_display_name (ClutterKeymap *keymap) ++{ ++ ClutterKeymapPrivate *priv = clutter_keymap_get_instance_private (keymap); ++ char *display_name; ++ ++ if (!priv->display_names) ++ return NULL; ++ ++ if (g_strv_length (priv->display_names) <= priv->effective_layout_group) ++ return NULL; ++ ++ display_name = priv->display_names[priv->effective_layout_group]; ++ if (g_strcmp0 (display_name, "") == 0) ++ return NULL; ++ else ++ return display_name; ++} ++ ++const char * ++clutter_keymap_get_current_short_name (ClutterKeymap *keymap) ++{ ++ ClutterKeymapPrivate *priv = clutter_keymap_get_instance_private (keymap); ++ char *short_name; ++ ++ if (!priv->short_names) ++ return NULL; ++ ++ if (g_strv_length (priv->display_names) <= priv->effective_layout_group) ++ return NULL; ++ ++ short_name = priv->short_names[priv->effective_layout_group]; ++ if (g_strcmp0 (short_name, "") == 0) ++ return NULL; ++ else ++ return short_name; ++} +diff --git a/clutter/clutter/clutter-keymap.h b/clutter/clutter/clutter-keymap.h +index a40a90c823..eb28db39b0 100644 +--- a/clutter/clutter/clutter-keymap.h ++++ b/clutter/clutter/clutter-keymap.h +@@ -68,3 +68,21 @@ void clutter_keymap_get_modifier_state (ClutterKeymap *keymap, + */ + CLUTTER_EXPORT + xkb_layout_index_t clutter_keymap_get_layout_index (ClutterKeymap *keymap); ++ ++/** ++ * clutter_keymap_get_current_display_name: ++ * @keymap: A #ClutterKeymap ++ * ++ * Returns: (transfer none) (nullable): The display name of the current layout ++ */ ++CLUTTER_EXPORT ++const char * clutter_keymap_get_current_display_name (ClutterKeymap *keymap); ++ ++/** ++ * clutter_keymap_get_current_short_name: ++ * @keymap: A #ClutterKeymap ++ * ++ * Returns: (transfer none) (nullable): The short name of the current layout ++ */ ++CLUTTER_EXPORT ++const char * clutter_keymap_get_current_short_name (ClutterKeymap *keymap); +diff --git a/src/backends/native/meta-keymap-native-private.h b/src/backends/native/meta-keymap-native-private.h +index 82d249eda8..e119b91c31 100644 +--- a/src/backends/native/meta-keymap-native-private.h ++++ b/src/backends/native/meta-keymap-native-private.h +@@ -29,7 +29,9 @@ void meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + MetaSeatImpl *seat_impl, + MetaKeymapDescription *keymap_description, + struct xkb_keymap *xkb_keymap, +- struct xkb_state *xkb_state); ++ struct xkb_state *xkb_state, ++ GStrv display_names, ++ GStrv short_names); + + struct xkb_keymap * meta_keymap_native_get_keyboard_map_in_impl (MetaKeymapNative *keymap); + +diff --git a/src/backends/native/meta-keymap-native.c b/src/backends/native/meta-keymap-native.c +index 63f5b4cf29..f55b8bd056 100644 +--- a/src/backends/native/meta-keymap-native.c ++++ b/src/backends/native/meta-keymap-native.c +@@ -163,6 +163,9 @@ typedef struct + MetaKeymapDescription *keymap_description; + + ModifierState modifier_state; ++ ++ GStrv display_names; ++ GStrv short_names; + } UpdateKeymapData; + + static void +@@ -171,6 +174,8 @@ update_keymap_data_free (gpointer user_data) + UpdateKeymapData *data = user_data; + + meta_keymap_description_unref (data->keymap_description); ++ g_strfreev (data->display_names); ++ g_strfreev (data->short_names); + g_free (data); + } + +@@ -181,6 +186,10 @@ update_keymap_in_main (gpointer user_data) + MetaKeymapNative *keymap_native = data->keymap_native; + gboolean state_changed; + ++ clutter_keymap_update_keymap_names (CLUTTER_KEYMAP (keymap_native), ++ g_steal_pointer (&data->display_names), ++ g_steal_pointer (&data->short_names)); ++ + state_changed = update_state_from_modifier_state (keymap_native, + &data->modifier_state, + FALSE); +@@ -198,7 +207,9 @@ meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + MetaSeatImpl *seat_impl, + MetaKeymapDescription *keymap_description, + struct xkb_keymap *xkb_keymap, +- struct xkb_state *xkb_state) ++ struct xkb_state *xkb_state, ++ GStrv display_names, ++ GStrv short_names) + { + UpdateKeymapData *data; + +@@ -211,6 +222,8 @@ meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + data->keymap_native = keymap; + data->modifier_state = calculate_modifier_state (xkb_state); + data->keymap_description = meta_keymap_description_ref (keymap_description); ++ data->display_names = g_steal_pointer (&display_names); ++ data->short_names = g_steal_pointer (&short_names); + + meta_seat_impl_queue_main_thread_idle (seat_impl, + update_keymap_in_main, +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index a5d490d12b..8a429dc2ec 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3851,14 +3851,16 @@ set_keyboard_map (GTask *task) + MetaKeymapNative *keymap; + g_autoptr (GError) error = NULL; + struct xkb_keymap *xkb_keymap; ++ g_auto (GStrv) display_names = NULL; ++ g_auto (GStrv) short_names = NULL; + + g_task_set_priority (task, G_PRIORITY_HIGH); + + keymap = seat_impl->keymap; + + xkb_keymap = meta_keymap_description_create_xkb_keymap (keymap_description, +- NULL, +- NULL, ++ &display_names, ++ &short_names, + &error); + if (!xkb_keymap) + { +@@ -3877,7 +3879,9 @@ set_keyboard_map (GTask *task) + seat_impl, + keymap_description, + xkb_keymap, +- seat_impl->xkb); ++ seat_impl->xkb, ++ g_steal_pointer (&display_names), ++ g_steal_pointer (&short_names)); + xkb_keymap_unref (xkb_keymap); + + g_rw_lock_writer_unlock (&seat_impl->state_lock); +-- +2.54.0 + + +From 949bfcc52cae826b15785d462104cc299bbddfd4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 10:55:13 +0200 +Subject: [PATCH 20/32] wayland/keyboard: Get the current layout index after + keymap changes + +The current layout index will potentially have changed together with the +keymap, so don't assume it was left unchanged. + +Part-of: +(cherry picked from commit af4ca9a925e87a303a652da13931d4aeed872afc) +--- + src/wayland/meta-wayland-keyboard.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/wayland/meta-wayland-keyboard.c b/src/wayland/meta-wayland-keyboard.c +index d7e044c458..451c9d834d 100644 +--- a/src/wayland/meta-wayland-keyboard.c ++++ b/src/wayland/meta-wayland-keyboard.c +@@ -159,6 +159,7 @@ static void + meta_wayland_keyboard_take_keymap (MetaWaylandKeyboard *keyboard, + struct xkb_keymap *keymap) + { ++ MetaBackend *backend = backend_from_keyboard (keyboard); + MetaWaylandXkbInfo *xkb_info = &keyboard->xkb_info; + char *keymap_string; + size_t keymap_size; +@@ -198,6 +199,7 @@ meta_wayland_keyboard_take_keymap (MetaWaylandKeyboard *keyboard, + + inform_clients_of_new_keymap (keyboard); + ++ keyboard->xkb_info.group = meta_backend_get_keymap_layout_group (backend); + notify_modifiers (keyboard); + } + +-- +2.54.0 + + +From 752d689006a19a573743dd3ae74cb6f39d61ed6d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 11:19:07 +0200 +Subject: [PATCH 21/32] backend: Add signals to 'reset' the keymap and layout + index + +This allows users of libmutter to fall back on any previously used +keyboard layout once a locked keymap description is unset. + +Part-of: +(cherry picked from commit 6279a85c31710610d551fd71d209dc8cee191f1d) +--- + src/backends/meta-backend-private.h | 9 ++++ + src/backends/meta-backend.c | 70 +++++++++++++++++++++++++++++ + src/compositor/plugins/default.c | 34 ++++++++++++++ + 3 files changed, 113 insertions(+) + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index dccd1116a6..e3b4c308db 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -217,6 +217,15 @@ struct xkb_keymap * meta_backend_get_keymap (MetaBackend *backend); + META_EXPORT_TEST + xkb_layout_index_t meta_backend_get_keymap_layout_group (MetaBackend *backend); + ++gboolean meta_backend_reset_keymap_finish (MetaBackend *backend, ++ GAsyncResult *result, ++ GError **error); ++ ++void meta_backend_reset_keymap_async (MetaBackend *backend, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); ++ + gboolean meta_backend_is_lid_closed (MetaBackend *backend); + + void meta_backend_set_client_pointer_constraint (MetaBackend *backend, +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index beaec1f281..031c0a03cb 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -122,6 +122,8 @@ enum + LID_IS_CLOSED_CHANGED, + GPU_ADDED, + PREPARE_SHUTDOWN, ++ RESET_KEYMAP_DESCRIPTION, ++ RESET_KEYMAP_LAYOUT_INDEX, + + N_SIGNALS + }; +@@ -963,6 +965,20 @@ meta_backend_class_init (MetaBackendClass *klass) + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); ++ signals[RESET_KEYMAP_DESCRIPTION] = ++ g_signal_new ("reset-keymap-description", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ g_signal_accumulator_first_wins, NULL, NULL, ++ META_TYPE_KEYMAP_DESCRIPTION, 0); ++ signals[RESET_KEYMAP_LAYOUT_INDEX] = ++ g_signal_new ("reset-keymap-layout-index", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ g_signal_accumulator_first_wins, NULL, NULL, ++ G_TYPE_UINT, 0); + } + + #ifdef HAVE_LOGIND +@@ -1851,6 +1867,60 @@ meta_backend_set_keymap_layout_group_async (MetaBackend *backend, + task); + } + ++gboolean ++meta_backend_reset_keymap_finish (MetaBackend *backend, ++ GAsyncResult *result, ++ GError **error) ++{ ++ GTask *task = G_TASK (result); ++ ++ g_return_val_if_fail (g_task_is_valid (result, backend), FALSE); ++ g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == ++ meta_backend_reset_keymap_async, FALSE); ++ ++ return g_task_propagate_boolean (task, error); ++} ++ ++void ++meta_backend_reset_keymap_async (MetaBackend *backend, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) ++{ ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; ++ uint32_t layout_index = 0; ++ GTask *task; ++ ++ g_signal_emit (backend, ++ signals[RESET_KEYMAP_DESCRIPTION], 0, ++ &keymap_description); ++ g_signal_emit (backend, ++ signals[RESET_KEYMAP_LAYOUT_INDEX], 0, ++ &layout_index); ++ ++ if (!keymap_description) ++ { ++ g_warning ("No fallback keymap description available, " ++ "falling batk to 'us'"); ++ keymap_description = ++ meta_keymap_description_new_from_rules (NULL, ++ "us", ++ NULL, ++ NULL, ++ NULL, ++ NULL); ++ layout_index = 0; ++ } ++ ++ task = g_task_new (G_OBJECT (backend), cancellable, callback, user_data); ++ g_task_set_source_tag (task, meta_backend_reset_keymap_async); ++ ++ META_BACKEND_GET_CLASS (backend)->set_keymap_async (backend, ++ keymap_description, ++ layout_index, ++ task); ++} ++ + /** + * meta_backend_get_stage: + * @backend: A #MetaBackend +diff --git a/src/compositor/plugins/default.c b/src/compositor/plugins/default.c +index cbce7ab959..cc11c2df8a 100644 +--- a/src/compositor/plugins/default.c ++++ b/src/compositor/plugins/default.c +@@ -117,6 +117,8 @@ struct _MetaDefaultPluginPrivate + ClutterActor *desktop2; + + ClutterActor *background_group; ++ ++ MetaKeymapDescription *keymap_description; + }; + + META_PLUGIN_DECLARE_WITH_CODE (MetaDefaultPlugin, meta_default_plugin, +@@ -149,11 +151,24 @@ typedef struct _DisplayTilePreview + MtkRectangle tile_rect; + } DisplayTilePreview; + ++static void ++meta_default_plugin_finalize (GObject *object) ++{ ++ MetaDefaultPluginPrivate *priv = META_DEFAULT_PLUGIN (object)->priv; ++ ++ g_clear_pointer (&priv->keymap_description, meta_keymap_description_unref); ++ ++ G_OBJECT_CLASS (meta_default_plugin_parent_class)->finalize (object); ++} ++ + static void + meta_default_plugin_class_init (MetaDefaultPluginClass *klass) + { ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); + MetaPluginClass *plugin_class = META_PLUGIN_CLASS (klass); + ++ object_class->finalize = meta_default_plugin_finalize; ++ + plugin_class->start = start; + plugin_class->map = map; + plugin_class->minimize = minimize; +@@ -356,6 +371,7 @@ static void + init_keymap (MetaDefaultPlugin *self, + MetaBackend *backend) + { ++ MetaDefaultPluginPrivate *priv = self->priv; + g_autoptr (GError) error = NULL; + g_autoptr (GDBusProxy) proxy = NULL; + g_autoptr (GVariant) result = NULL; +@@ -424,6 +440,9 @@ init_keymap (MetaDefaultPlugin *self, + meta_backend_set_keymap_async (backend, + keymap_description, 0, + NULL, NULL, NULL); ++ ++ ++ priv->keymap_description = meta_keymap_description_ref (keymap_description); + } + + static void +@@ -433,6 +452,18 @@ prepare_shutdown (MetaBackend *backend, + kill_switch_workspace (META_PLUGIN (plugin)); + } + ++static MetaKeymapDescription * ++on_reset_keymap_description (MetaBackend *backend, ++ MetaDefaultPlugin *self) ++{ ++ MetaDefaultPluginPrivate *priv = self->priv; ++ ++ if (priv->keymap_description) ++ return meta_keymap_description_ref (priv->keymap_description); ++ else ++ return NULL; ++} ++ + static void + start (MetaPlugin *plugin) + { +@@ -453,6 +484,9 @@ start (MetaPlugin *plugin) + + on_monitors_changed (monitor_manager, plugin); + ++ g_signal_connect (backend, "reset-keymap-description", ++ G_CALLBACK (on_reset_keymap_description), self); ++ + g_signal_connect (backend, "prepare-shutdown", + G_CALLBACK (prepare_shutdown), + self); +-- +2.54.0 + + +From 74ea326e9b6ceeb9ea42180918fb6422099fd54f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 15 Dec 2025 13:27:00 +0100 +Subject: [PATCH 22/32] backend: Combine set_keymap() and + set_keymap_layout_group() + +We can already skip changing the actual keymap by comparing whether the +keymap description was replaced by some other one, meaning the the +layout index only API is redundant. + +Doing this also allows us to avoid a race condition where one entity +sets the layout index on a description that was just replaced by another +entity. This is now resolved by the layout index change replacing also +the keymap, but in theory this can be now be tweaked, would we want to, +by adding flags. + +Part-of: +(cherry picked from commit 5cc1cd137d88971d289f30f80cde56d991413a92) +--- + src/backends/meta-backend-private.h | 4 - + src/backends/meta-backend.c | 31 ---- + src/backends/native/meta-backend-native.c | 49 ------ + src/backends/native/meta-seat-impl.c | 185 +++++++++------------- + src/backends/native/meta-seat-impl.h | 10 -- + src/backends/native/meta-seat-native.c | 70 -------- + src/backends/native/meta-seat-native.h | 10 -- + src/tests/keyboard-map-tests.c | 29 ++-- + 8 files changed, 99 insertions(+), 289 deletions(-) + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index e3b4c308db..c82d41032d 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -135,10 +135,6 @@ struct _MetaBackendClass + + xkb_layout_index_t (* get_keymap_layout_group) (MetaBackend *backend); + +- void (* set_keymap_layout_group_async) (MetaBackend *backend, +- xkb_layout_index_t idx, +- GTask *task); +- + void (* update_stage) (MetaBackend *backend); + + void (* select_stage_events) (MetaBackend *backend); +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index 031c0a03cb..03ebf27ee4 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -1836,37 +1836,6 @@ meta_backend_get_keymap_layout_group (MetaBackend *backend) + return META_BACKEND_GET_CLASS (backend)->get_keymap_layout_group (backend); + } + +-gboolean +-meta_backend_set_keymap_layout_group_finish (MetaBackend *backend, +- GAsyncResult *result, +- GError **error) +-{ +- GTask *task = G_TASK (result); +- +- g_return_val_if_fail (g_task_is_valid (result, backend), FALSE); +- g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == +- meta_backend_set_keymap_layout_group_async, FALSE); +- +- return g_task_propagate_boolean (task, error); +-} +- +-void +-meta_backend_set_keymap_layout_group_async (MetaBackend *backend, +- uint32_t idx, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) +-{ +- GTask *task; +- +- task = g_task_new (G_OBJECT (backend), cancellable, callback, user_data); +- g_task_set_source_tag (task, meta_backend_set_keymap_layout_group_async); +- +- META_BACKEND_GET_CLASS (backend)->set_keymap_layout_group_async (backend, +- idx, +- task); +-} +- + gboolean + meta_backend_reset_keymap_finish (MetaBackend *backend, + GAsyncResult *result, +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index b2efac59d1..7d68894cb8 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -384,54 +384,6 @@ meta_backend_native_get_keymap_layout_group (MetaBackend *backend) + return meta_seat_native_get_keyboard_layout_index (META_SEAT_NATIVE (seat)); + } + +-static void +-set_layout_index_cb (GObject *source_object, +- GAsyncResult *result, +- gpointer user_data) +-{ +- MetaSeatNative *seat_native = META_SEAT_NATIVE (source_object); +- g_autoptr (GTask) task = G_TASK (user_data); +- MetaBackend *backend = META_BACKEND (g_task_get_source_object (task)); +- g_autoptr (GError) error = NULL; +- gboolean index_changed; +- +- index_changed = +- meta_seat_native_set_keyboard_layout_index_finish (seat_native, +- result, +- &error); +- if (error) +- { +- g_task_return_error (task, g_steal_pointer (&error)); +- return; +- } +- +- if (index_changed) +- { +- xkb_layout_index_t idx; +- +- idx = meta_seat_native_get_keyboard_layout_index (seat_native); +- meta_backend_notify_keymap_layout_group_changed (backend, idx); +- } +- +- g_task_return_boolean (task, TRUE); +-} +- +-static void +-meta_backend_native_set_keymap_layout_group_async (MetaBackend *backend, +- xkb_layout_index_t idx, +- GTask *task) +-{ +- ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); +- ClutterSeat *seat; +- +- seat = clutter_backend_get_default_seat (clutter_backend); +- meta_seat_native_set_keyboard_layout_index_async (META_SEAT_NATIVE (seat), +- idx, +- g_task_get_cancellable (task), +- set_layout_index_cb, +- task); +-} +- + static gboolean + meta_backend_native_is_headless (MetaBackend *backend) + { +@@ -954,7 +906,6 @@ meta_backend_native_class_init (MetaBackendNativeClass *klass) + backend_class->get_keymap = meta_backend_native_get_keymap; + backend_class->get_keymap_description = meta_backend_native_get_keymap_description; + backend_class->get_keymap_layout_group = meta_backend_native_get_keymap_layout_group; +- backend_class->set_keymap_layout_group_async = meta_backend_native_set_keymap_layout_group_async; + backend_class->update_stage = meta_backend_native_update_stage; + + backend_class->set_pointer_constraint = meta_backend_native_set_pointer_constraint; +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 8a429dc2ec..7c94a6c5cc 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -133,6 +133,8 @@ typedef struct _MetaSeatImplPrivate + uint32_t last_keysym_time; + gboolean saw_first_release; + } a11y; ++ ++ MetaKeymapDescription *keymap_description; + } MetaSeatImplPrivate; + + static void meta_seat_impl_initable_iface_init (GInitableIface *iface); +@@ -3403,6 +3405,7 @@ static void + meta_seat_impl_finalize (GObject *object) + { + MetaSeatImpl *seat_impl = META_SEAT_IMPL (object); ++ MetaSeatImplPrivate *priv = meta_seat_impl_get_instance_private (seat_impl); + + g_assert (!seat_impl->libinput); + g_assert (!seat_impl->tools); +@@ -3410,6 +3413,9 @@ meta_seat_impl_finalize (GObject *object) + + g_free (seat_impl->seat_id); + ++ g_clear_pointer (&priv->keymap_description, ++ meta_keymap_description_unref); ++ + g_rw_lock_clear (&seat_impl->state_lock); + + G_OBJECT_CLASS (meta_seat_impl_parent_class)->finalize (object); +@@ -3842,47 +3848,92 @@ set_keymap_data_free (gpointer user_data) + g_free (data); + } + ++static void ++update_layout_index_unlocked (MetaSeatImpl *seat_impl, ++ xkb_layout_index_t layout_index) ++{ ++ xkb_mod_mask_t depressed_mods; ++ xkb_mod_mask_t latched_mods; ++ xkb_mod_mask_t locked_mods; ++ struct xkb_state *state; ++ ++ state = seat_impl->xkb; ++ ++ depressed_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_DEPRESSED); ++ latched_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_LATCHED); ++ locked_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_LOCKED); ++ ++ xkb_state_update_mask (state, ++ depressed_mods, ++ latched_mods, ++ locked_mods, ++ 0, 0, layout_index); ++ ++ seat_impl->layout_idx = layout_index; ++ ++ meta_seat_impl_sync_leds_in_impl (seat_impl); ++ meta_keymap_native_update_in_impl (seat_impl->keymap, ++ seat_impl, ++ seat_impl->xkb); ++} ++ + static gboolean + set_keyboard_map (GTask *task) + { + MetaSeatImpl *seat_impl = g_task_get_source_object (task); ++ MetaSeatImplPrivate *priv = ++ meta_seat_impl_get_instance_private (seat_impl); + SetKeymapData *data = g_task_get_task_data (task); + MetaKeymapDescription *keymap_description = data->keymap_description; + MetaKeymapNative *keymap; +- g_autoptr (GError) error = NULL; +- struct xkb_keymap *xkb_keymap; +- g_auto (GStrv) display_names = NULL; +- g_auto (GStrv) short_names = NULL; + + g_task_set_priority (task, G_PRIORITY_HIGH); + +- keymap = seat_impl->keymap; +- +- xkb_keymap = meta_keymap_description_create_xkb_keymap (keymap_description, +- &display_names, +- &short_names, +- &error); +- if (!xkb_keymap) +- { +- g_prefix_error (&error, "Unable to load configured keymap: "); +- g_task_return_error (task, g_steal_pointer (&error)); +- return G_SOURCE_REMOVE; +- } +- + g_rw_lock_writer_lock (&seat_impl->state_lock); + +- meta_seat_impl_update_xkb_state_in_impl_unlocked (seat_impl, +- xkb_keymap, +- data->layout_index); ++ if (priv->keymap_description != keymap_description) ++ { ++ g_autoptr (GError) error = NULL; ++ g_auto (GStrv) display_names = NULL; ++ g_auto (GStrv) short_names = NULL; ++ struct xkb_keymap *xkb_keymap = NULL; ++ ++ g_clear_pointer (&priv->keymap_description, ++ meta_keymap_description_unref); ++ priv->keymap_description = ++ meta_keymap_description_ref (keymap_description); ++ ++ xkb_keymap = ++ meta_keymap_description_create_xkb_keymap (keymap_description, ++ &display_names, ++ &short_names, ++ &error); ++ if (!xkb_keymap) ++ { ++ g_prefix_error (&error, "Unable to load configured keymap: "); ++ g_task_return_error (task, g_steal_pointer (&error)); ++ g_rw_lock_writer_unlock (&seat_impl->state_lock); ++ return G_SOURCE_REMOVE; ++ } + +- meta_keymap_native_set_keyboard_map_in_impl (keymap, +- seat_impl, +- keymap_description, +- xkb_keymap, +- seat_impl->xkb, +- g_steal_pointer (&display_names), +- g_steal_pointer (&short_names)); +- xkb_keymap_unref (xkb_keymap); ++ meta_seat_impl_update_xkb_state_in_impl_unlocked (seat_impl, ++ xkb_keymap, ++ data->layout_index); ++ ++ keymap = seat_impl->keymap; ++ meta_keymap_native_set_keyboard_map_in_impl (keymap, ++ seat_impl, ++ keymap_description, ++ xkb_keymap, ++ seat_impl->xkb, ++ g_steal_pointer (&display_names), ++ g_steal_pointer (&short_names)); ++ xkb_keymap_unref (xkb_keymap); ++ } ++ else ++ { ++ update_layout_index_unlocked (seat_impl, data->layout_index); ++ } + + g_rw_lock_writer_unlock (&seat_impl->state_lock); + +@@ -3929,84 +3980,6 @@ meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, + g_object_unref (task); + } + +-gboolean +-meta_seat_impl_set_keyboard_layout_index_finish (MetaSeatImpl *seat_impl, +- GAsyncResult *result, +- GError **error) +-{ +- GTask *task = G_TASK (result); +- +- g_return_val_if_fail (g_task_is_valid (result, seat_impl), FALSE); +- g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == +- meta_seat_impl_set_keyboard_layout_index_async, +- FALSE); +- +- return g_task_propagate_boolean (task, error); +-} +- +-static gboolean +-set_keyboard_layout_index (GTask *task) +-{ +- MetaSeatImpl *seat_impl = g_task_get_source_object (task); +- xkb_layout_index_t idx = GPOINTER_TO_UINT (g_task_get_task_data (task)); +- xkb_mod_mask_t depressed_mods; +- xkb_mod_mask_t latched_mods; +- xkb_mod_mask_t locked_mods; +- struct xkb_state *state; +- +- g_rw_lock_writer_lock (&seat_impl->state_lock); +- +- state = seat_impl->xkb; +- +- depressed_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_DEPRESSED); +- latched_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_LATCHED); +- locked_mods = xkb_state_serialize_mods (state, XKB_STATE_MODS_LOCKED); +- +- xkb_state_update_mask (state, depressed_mods, latched_mods, locked_mods, 0, 0, idx); +- +- seat_impl->layout_idx = idx; +- +- g_task_return_boolean (task, TRUE); +- +- meta_seat_impl_sync_leds_in_impl (seat_impl); +- meta_keymap_native_update_in_impl (seat_impl->keymap, +- seat_impl, +- seat_impl->xkb); +- +- g_rw_lock_writer_unlock (&seat_impl->state_lock); +- +- return G_SOURCE_REMOVE; +-} +- +-/** +- * meta_seat_impl_set_keyboard_layout_index_async: (skip) +- * @seat_impl: the #ClutterSeat created by the evdev backend +- * @idx: the xkb layout index to set +- * @cancellable: a #GCancellable +- * @callback: callback to call when index has changed +- * @user_data: user data to pass to the callback +- * +- * Sets the xkb layout index on the backend's #xkb_state . +- */ +-void +-meta_seat_impl_set_keyboard_layout_index_async (MetaSeatImpl *seat_impl, +- xkb_layout_index_t idx, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) +-{ +- GTask *task; +- +- g_return_if_fail (META_IS_SEAT_IMPL (seat_impl)); +- +- task = g_task_new (seat_impl, cancellable, callback, user_data); +- g_task_set_source_tag (task, meta_seat_impl_set_keyboard_layout_index_async); +- g_task_set_task_data (task, GUINT_TO_POINTER (idx), NULL); +- meta_seat_impl_run_input_task (seat_impl, task, +- (GSourceFunc) set_keyboard_layout_index); +- g_object_unref (task); +-} +- + /** + * meta_seat_impl_set_keyboard_repeat_in_impl: + * @seat_impl: the #ClutterSeat created by the evdev backend +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index 4950a19a74..c4692cac19 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -196,16 +196,6 @@ void meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, + GAsyncReadyCallback callback, + gpointer user_data); + +-gboolean meta_seat_impl_set_keyboard_layout_index_finish (MetaSeatImpl *seat_impl, +- GAsyncResult *result, +- GError **error); +- +-void meta_seat_impl_set_keyboard_layout_index_async (MetaSeatImpl *seat_impl, +- xkb_layout_index_t idx, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); +- + void meta_seat_impl_set_keyboard_repeat_in_impl (MetaSeatImpl *seat_impl, + gboolean repeat, + uint32_t delay, +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 5498bac13f..2722b1d970 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -731,76 +731,6 @@ meta_seat_native_get_keyboard_map_description (MetaSeatNative *seat_native) + return seat_native->keymap_description; + } + +-gboolean +-meta_seat_native_set_keyboard_layout_index_finish (MetaSeatNative *seat_native, +- GAsyncResult *result, +- GError **error) +-{ +- GTask *task = G_TASK (result); +- +- g_return_val_if_fail (g_task_is_valid (result, seat_native), FALSE); +- g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == +- meta_seat_native_set_keyboard_layout_index_async, +- FALSE); +- +- return g_task_propagate_boolean (task, error); +-} +- +-static void +-set_seat_impl_layout_index_cb (GObject *source_object, +- GAsyncResult *result, +- gpointer user_data) +-{ +- MetaSeatImpl *seat_impl = META_SEAT_IMPL (source_object); +- g_autoptr (GTask) task = user_data; +- MetaSeatNative *seat_native = +- META_SEAT_NATIVE (g_task_get_source_object (task)); +- g_autoptr (GError) error = NULL; +- xkb_layout_index_t idx = GPOINTER_TO_UINT (g_task_get_task_data (task)); +- xkb_layout_index_t old_idx; +- +- if (!meta_seat_impl_set_keyboard_layout_index_finish (seat_impl, +- result, +- &error)) +- { +- g_task_return_error (task, error); +- return; +- } +- +- old_idx = seat_native->xkb_layout_index; +- seat_native->xkb_layout_index = idx; +- +- g_task_return_boolean (task, old_idx != idx); +-} +- +-/** +- * meta_seat_native_set_keyboard_layout_index_async: (skip) +- * @seat: the #ClutterSeat created by the evdev backend +- * @idx: the xkb layout index to set +- * +- * Sets the xkb layout index on the backend's #xkb_state . +- */ +-void +-meta_seat_native_set_keyboard_layout_index_async (MetaSeatNative *seat_native, +- xkb_layout_index_t idx, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) +-{ +- g_autoptr (GTask) task = NULL; +- +- g_return_if_fail (META_IS_SEAT_NATIVE (seat_native)); +- +- task = g_task_new (G_OBJECT (seat_native), cancellable, callback, user_data); +- g_task_set_source_tag (task, meta_seat_native_set_keyboard_layout_index_async); +- g_task_set_task_data (task, GUINT_TO_POINTER (idx), NULL); +- +- meta_seat_impl_set_keyboard_layout_index_async (seat_native->impl, idx, +- cancellable, +- set_seat_impl_layout_index_cb, +- g_steal_pointer (&task)); +-} +- + /** + * meta_seat_native_get_keyboard_layout_index: (skip) + */ +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index 061b580bc2..7fbe7dd3e7 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -115,16 +115,6 @@ gboolean meta_seat_native_set_keyboard_map_finish (MetaSeatNative *seat_native, + META_EXPORT_TEST + struct xkb_keymap * meta_seat_native_get_keyboard_map (MetaSeatNative *seat); + +-gboolean meta_seat_native_set_keyboard_layout_index_finish (MetaSeatNative *seat_native, +- GAsyncResult *result, +- GError **error); +- +-void meta_seat_native_set_keyboard_layout_index_async (MetaSeatNative *seat, +- xkb_layout_index_t idx, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); +- + MetaKeymapDescription * meta_seat_native_get_keyboard_map_description (MetaSeatNative *seat_native); + + xkb_layout_index_t meta_seat_native_get_keyboard_layout_index (MetaSeatNative *seat); +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index e1a65214cc..7d233dbe23 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -275,14 +275,18 @@ set_keymap_layout_group_cb (GObject *source_object, + gboolean *done = user_data; + g_autoptr (GError) error = NULL; + +- g_assert_true (meta_backend_set_keymap_layout_group_finish (backend, +- result, +- &error)); ++ g_assert_true (meta_backend_set_keymap_finish (backend, result, &error)); + g_assert_no_error (error); + + *done = TRUE; + } + ++static void ++trigger_error (const char *message) ++{ ++ g_error ("%s", message); ++} ++ + static void + meta_test_native_keyboard_map_set_layout_index (void) + { +@@ -290,6 +294,7 @@ meta_test_native_keyboard_map_set_layout_index (void) + g_autoptr (MetaKeymapDescription) keymap_description = NULL; + gboolean done = FALSE; + struct xkb_keymap *keymap; ++ gulong keymap_changed_handler_id; + + keymap_description = + meta_keymap_description_new_from_rules (NULL, +@@ -303,10 +308,15 @@ meta_test_native_keyboard_map_set_layout_index (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + ++ keymap_changed_handler_id = ++ g_signal_connect_swapped (backend, ++ "keymap-changed", ++ G_CALLBACK (trigger_error), ++ (gpointer) "Unexpected keymap-changed emission"); ++ + done = FALSE; +- meta_backend_set_keymap_layout_group_async (backend, 0, NULL, +- set_keymap_layout_group_cb, +- &done); ++ meta_backend_set_keymap_async (backend, keymap_description, 0, ++ NULL, set_keymap_layout_group_cb, &done); + while (!done) + g_main_context_iteration (NULL, TRUE); + +@@ -321,13 +331,14 @@ meta_test_native_keyboard_map_set_layout_index (void) + + g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); + done = FALSE; +- meta_backend_set_keymap_layout_group_async (backend, 1, NULL, +- set_keymap_layout_group_cb, +- &done); ++ meta_backend_set_keymap_async (backend, keymap_description, 1, ++ NULL, set_keymap_layout_group_cb, &done); + g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); + while (!done) + g_main_context_iteration (NULL, TRUE); + g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 1); ++ ++ g_signal_handler_disconnect (backend, keymap_changed_handler_id); + } + + static void +-- +2.54.0 + + +From 47acb440846860210d7b945e34dd87ee2c4ba167 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 10:56:52 +0200 +Subject: [PATCH 23/32] keymap-description: Add way to 'lock' a keymap + description + +This should be interpreted by other entities that tries to set the +keymap to avoid doing so. It's meant to allow a remote desktop service +to be in control of the current keymap and keyboard layout. + +This introduces a concept of keymap description owner. Only the same +owner can set a new keymap if a current keymap description is locked. + +Part-of: +(cherry picked from commit b1344b5f09d032ee1c44c31ef742b418ce74bb24) +--- + .../meta-keymap-description-private.h | 21 +++ + src/backends/meta-keymap-description.c | 68 ++++++++ + src/backends/native/meta-seat-impl.c | 13 ++ + src/meta/meta-keymap-description.h | 3 + + src/tests/keyboard-map-tests.c | 165 ++++++++++++++++++ + 5 files changed, 270 insertions(+) + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index 3c0d9cc924..5bf0b25505 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -21,6 +21,7 @@ + #include "meta/meta-keymap-description.h" + + #include "core/meta-sealed-fd.h" ++#include "core/util-private.h" + + typedef enum _MetaKeymapDescriptionSource + { +@@ -28,6 +29,16 @@ typedef enum _MetaKeymapDescriptionSource + META_KEYMAP_DESCRIPTION_SOURCE_FD, + } MetaKeymapDescriptionSource; + ++typedef struct _MetaKeymapDescriptionOwner MetaKeymapDescriptionOwner; ++ ++META_EXPORT_TEST ++MetaKeymapDescriptionOwner * meta_keymap_description_owner_new (void); ++ ++MetaKeymapDescriptionOwner * meta_keymap_description_owner_ref (MetaKeymapDescriptionOwner *owner); ++ ++META_EXPORT_TEST ++void meta_keymap_description_owner_unref (MetaKeymapDescriptionOwner *owner); ++ + MetaKeymapDescription * meta_keymap_description_new_from_fd (MetaSealedFd *sealed_fd, + enum xkb_keymap_format format); + +@@ -37,3 +48,13 @@ struct xkb_keymap * meta_keymap_description_create_xkb_keymap (MetaKeymapDescrip + GStrv *out_display_names, + GStrv *out_short_names, + GError **error); ++ ++META_EXPORT_TEST ++void meta_keymap_description_lock (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescriptionOwner *owner); ++ ++META_EXPORT_TEST ++void meta_keymap_description_unlock (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescriptionOwner *owner); ++ ++MetaKeymapDescriptionOwner * meta_keymap_description_get_owner (MetaKeymapDescription *keymap_description); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index 63bd539d57..b67bcf0a74 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -26,12 +26,20 @@ + #include "backends/meta-keymap-utils.h" + #include "core/meta-sealed-fd.h" + ++struct _MetaKeymapDescriptionOwner ++{ ++ gatomicrefcount ref_count; ++}; ++ + struct _MetaKeymapDescription + { + gatomicrefcount ref_count; + + MetaKeymapDescriptionSource source; + ++ gboolean is_locked; ++ MetaKeymapDescriptionOwner *owner; ++ + union { + struct { + char *rules; +@@ -62,6 +70,31 @@ strdup_or_empty (const char *string) + return string ? g_strdup (string) : g_strdup (""); + } + ++MetaKeymapDescriptionOwner * ++meta_keymap_description_owner_new (void) ++{ ++ MetaKeymapDescriptionOwner *owner; ++ ++ owner = g_new0 (MetaKeymapDescriptionOwner, 1); ++ g_atomic_ref_count_init (&owner->ref_count); ++ ++ return owner; ++} ++ ++MetaKeymapDescriptionOwner * ++meta_keymap_description_owner_ref (MetaKeymapDescriptionOwner *owner) ++{ ++ g_atomic_ref_count_inc (&owner->ref_count); ++ return owner; ++} ++ ++void ++meta_keymap_description_owner_unref (MetaKeymapDescriptionOwner *owner) ++{ ++ if (g_atomic_ref_count_dec (&owner->ref_count)) ++ g_free (owner); ++} ++ + MetaKeymapDescription * + meta_keymap_description_new_from_rules (const char *model, + const char *layout, +@@ -128,6 +161,9 @@ meta_keymap_description_unref (MetaKeymapDescription *keymap_description) + break; + } + ++ g_clear_pointer (&keymap_description->owner, ++ meta_keymap_description_owner_unref); ++ + g_free (keymap_description); + } + } +@@ -293,3 +329,35 @@ meta_keymap_description_create_xkb_keymap (MetaKeymapDescription *keymap_descri + + return xkb_keymap; + } ++ ++void ++meta_keymap_description_lock (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescriptionOwner *owner) ++{ ++ g_return_if_fail (!keymap_description->owner); ++ ++ keymap_description->is_locked = TRUE; ++ keymap_description->owner = meta_keymap_description_owner_ref (owner); ++} ++ ++void ++meta_keymap_description_unlock (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescriptionOwner *owner) ++{ ++ g_return_if_fail (!keymap_description->owner); ++ g_return_if_fail (!keymap_description->is_locked); ++ ++ keymap_description->owner = meta_keymap_description_owner_ref (owner); ++} ++ ++gboolean ++meta_keymap_description_is_locked (MetaKeymapDescription *keymap_description) ++{ ++ return keymap_description->is_locked; ++} ++ ++MetaKeymapDescriptionOwner * ++meta_keymap_description_get_owner (MetaKeymapDescription *keymap_description) ++{ ++ return keymap_description->owner; ++} +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 7c94a6c5cc..ab8e207d16 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3898,6 +3898,19 @@ set_keyboard_map (GTask *task) + g_auto (GStrv) short_names = NULL; + struct xkb_keymap *xkb_keymap = NULL; + ++ if (priv->keymap_description) ++ { ++ if (meta_keymap_description_is_locked (priv->keymap_description) && ++ meta_keymap_description_get_owner (keymap_description) != ++ meta_keymap_description_get_owner (priv->keymap_description)) ++ { ++ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "Keymap locked by other owner"); ++ g_rw_lock_writer_unlock (&seat_impl->state_lock); ++ return G_SOURCE_REMOVE; ++ } ++ } ++ + g_clear_pointer (&priv->keymap_description, + meta_keymap_description_unref); + priv->keymap_description = +diff --git a/src/meta/meta-keymap-description.h b/src/meta/meta-keymap-description.h +index 5140f63b1a..0f4de5ed2b 100644 +--- a/src/meta/meta-keymap-description.h ++++ b/src/meta/meta-keymap-description.h +@@ -42,5 +42,8 @@ MetaKeymapDescription * meta_keymap_description_ref (MetaKeymapDescription *keym + META_EXPORT + void meta_keymap_description_unref (MetaKeymapDescription *keymap_description); + ++META_EXPORT ++gboolean meta_keymap_description_is_locked (MetaKeymapDescription *keymap_description); ++ + G_DEFINE_AUTOPTR_CLEANUP_FUNC (MetaKeymapDescription, + meta_keymap_description_unref); +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index 7d233dbe23..1bbb87430b 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -21,6 +21,7 @@ + #include + + #include "backends/meta-backend-private.h" ++#include "backends/meta-keymap-description-private.h" + #include "backends/native/meta-keymap-native.h" + #include "backends/native/meta-seat-native.h" + #include "tests/meta-test-utils.h" +@@ -35,6 +36,12 @@ typedef struct + + static MetaContext *test_context; + ++static void ++set_true_cb (gboolean *value) ++{ ++ *value = TRUE; ++} ++ + static void + set_keymap_cb (GObject *source_object, + GAsyncResult *result, +@@ -50,6 +57,21 @@ set_keymap_cb (GObject *source_object, + *done = TRUE; + } + ++static void ++set_keymap_expect_error_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MetaBackend *backend = META_BACKEND (source_object); ++ gboolean *done = user_data; ++ g_autoptr (GError) error = NULL; ++ ++ g_assert_false (meta_backend_set_keymap_finish (backend, result, &error)); ++ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); ++ ++ *done = TRUE; ++} ++ + static void + await_mod_mask (MetaKeymapNative *keymap_native, + ModMaskTuple **awaited_mod_mask) +@@ -341,6 +363,147 @@ meta_test_native_keyboard_map_set_layout_index (void) + g_signal_handler_disconnect (backend, keymap_changed_handler_id); + } + ++static void ++meta_test_native_keyboard_map_lock_layout (void) ++{ ++ MetaBackend *backend = meta_context_get_backend (test_context); ++ g_autoptr (MetaKeymapDescription) keymap_description1 = NULL; ++ g_autoptr (MetaKeymapDescription) keymap_description2 = NULL; ++ g_autoptr (MetaKeymapDescription) keymap_description3 = NULL; ++ MetaKeymapDescriptionOwner *owner; ++ gboolean done = FALSE; ++ gboolean was_signalled; ++ struct xkb_keymap *keymap; ++ gulong keymap_changed_handler_id; ++ gulong keymap_layout_group_changed_handler_id; ++ ++ owner = meta_keymap_description_owner_new (); ++ ++ /* ++ * Set a locking keymap. ++ */ ++ ++ keymap_description1 = ++ meta_keymap_description_new_from_rules (NULL, ++ "us,se", ++ "dvorak-alt-intl,svdvorak", ++ NULL, ++ NULL, ++ NULL); ++ meta_keymap_description_lock (keymap_description1, owner); ++ meta_backend_set_keymap_async (backend, keymap_description1, 0, ++ NULL, set_keymap_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ keymap = xkb_keymap_ref (meta_backend_get_keymap (backend)); ++ g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), ++ ==, ++ "English (Dvorak, alt. intl.)"); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 1), ++ ==, ++ "Swedish (Svdvorak)"); ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); ++ ++ /* ++ * Set a new keymap without an owner. Should cause an error and not take ++ * effect. ++ */ ++ ++ keymap_changed_handler_id = ++ g_signal_connect_swapped (backend, ++ "keymap-changed", ++ G_CALLBACK (trigger_error), ++ (gpointer) "Unexpected keymap-changed emission"); ++ ++ keymap_description2 = ++ meta_keymap_description_new_from_rules (NULL, ++ "se,us", ++ "svdvorak,dvorak-alt-intl", ++ NULL, ++ NULL, ++ NULL); ++ done = FALSE; ++ meta_backend_set_keymap_async (backend, keymap_description2, 0, ++ NULL, set_keymap_expect_error_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ g_assert_true (keymap == meta_backend_get_keymap (backend)); ++ ++ /* ++ * Set the same keymap with a different layout index. Should take effect. ++ */ ++ ++ was_signalled = FALSE; ++ keymap_layout_group_changed_handler_id = ++ g_signal_connect_swapped (backend, ++ "keymap-layout-group-changed", ++ G_CALLBACK (set_true_cb), ++ &was_signalled); ++ ++ done = FALSE; ++ meta_backend_set_keymap_async (backend, keymap_description1, 1, ++ NULL, set_keymap_layout_group_cb, &done); ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 1); ++ g_assert_true (was_signalled); ++ ++ xkb_keymap_unref (keymap); ++ g_signal_handler_disconnect (backend, keymap_changed_handler_id); ++ g_signal_handler_disconnect (backend, keymap_layout_group_changed_handler_id); ++ ++ /* ++ * Set another keymap with the same owner. Should take effect. ++ */ ++ ++ keymap_description3 = ++ meta_keymap_description_new_from_rules (NULL, ++ "ua", ++ "", ++ NULL, ++ NULL, ++ NULL); ++ meta_keymap_description_unlock (keymap_description3, owner); ++ done = FALSE; ++ meta_backend_set_keymap_async (backend, keymap_description3, 0, ++ NULL, set_keymap_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ keymap = meta_backend_get_keymap (backend); ++ g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 1); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), ++ ==, ++ "Ukrainian"); ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); ++ ++ /* ++ * Set keymap again without owner. Should take effect. ++ */ ++ ++ done = FALSE; ++ meta_backend_set_keymap_async (backend, keymap_description2, 0, ++ NULL, set_keymap_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ keymap = meta_backend_get_keymap (backend); ++ g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), ++ ==, ++ "Swedish (Svdvorak)"); ++ g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 1), ++ ==, ++ "English (Dvorak, alt. intl.)"); ++ g_assert_cmpuint (meta_backend_get_keymap_layout_group (backend), ==, 0); ++ ++ meta_keymap_description_owner_unref (owner); ++} ++ + static void + record_modifier_state (ClutterKeymap *keymap, + ModMaskTuple **expected_mods) +@@ -460,6 +623,8 @@ init_tests (void) + meta_test_native_keyboard_map_change_layout); + g_test_add_func ("/backends/native/keyboard-map/set-layout-index", + meta_test_native_keyboard_map_set_layout_index); ++ g_test_add_func ("/backends/native/keyboard-map/lock-layout", ++ meta_test_native_keyboard_map_lock_layout); + g_test_add_func ("/backends/native/keyboard-map/modifiers", + meta_test_native_keyboard_map_modifiers); + } +-- +2.54.0 + + +From c5d98a94b453b7b869f9d8cc7a818e9f9698f98e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 15 Dec 2025 21:32:31 +0100 +Subject: [PATCH 24/32] backend: Only let owner reset keymap + +This allows enforcing only the owner of a locked keymap description can +reset it to something else. + +Part-of: +(cherry picked from commit 097ad5b10695cd006e8e50b51df5d6cdcee83eb1) +--- + src/backends/meta-backend-private.h | 12 +- + src/backends/meta-backend.c | 11 +- + .../meta-keymap-description-private.h | 5 + + src/backends/meta-keymap-description.c | 18 +++ + src/backends/native/meta-seat-impl.c | 2 + + src/tests/keyboard-map-tests.c | 123 ++++++++++++++++++ + 6 files changed, 163 insertions(+), 8 deletions(-) + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index c82d41032d..8b61738922 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -34,6 +34,7 @@ + #include "backends/meta-egl.h" + #include "backends/meta-input-mapper-private.h" + #include "backends/meta-input-settings-private.h" ++#include "backends/meta-keymap-description-private.h" + #include "backends/meta-monitor-manager-private.h" + #include "backends/meta-pointer-constraint.h" + #include "backends/meta-renderer.h" +@@ -213,14 +214,17 @@ struct xkb_keymap * meta_backend_get_keymap (MetaBackend *backend); + META_EXPORT_TEST + xkb_layout_index_t meta_backend_get_keymap_layout_group (MetaBackend *backend); + ++META_EXPORT_TEST + gboolean meta_backend_reset_keymap_finish (MetaBackend *backend, + GAsyncResult *result, + GError **error); + +-void meta_backend_reset_keymap_async (MetaBackend *backend, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); ++META_EXPORT_TEST ++void meta_backend_reset_keymap_async (MetaBackend *backend, ++ MetaKeymapDescriptionOwner *owner, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); + + gboolean meta_backend_is_lid_closed (MetaBackend *backend); + +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index 03ebf27ee4..143797213d 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -1851,10 +1851,11 @@ meta_backend_reset_keymap_finish (MetaBackend *backend, + } + + void +-meta_backend_reset_keymap_async (MetaBackend *backend, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) ++meta_backend_reset_keymap_async (MetaBackend *backend, ++ MetaKeymapDescriptionOwner *owner, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { + g_autoptr (MetaKeymapDescription) keymap_description = NULL; + uint32_t layout_index = 0; +@@ -1881,6 +1882,8 @@ meta_backend_reset_keymap_async (MetaBackend *backend, + layout_index = 0; + } + ++ meta_keymap_description_reset_owner (keymap_description, owner); ++ + task = g_task_new (G_OBJECT (backend), cancellable, callback, user_data); + g_task_set_source_tag (task, meta_backend_reset_keymap_async); + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index 5bf0b25505..f53092cd27 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -57,4 +57,9 @@ META_EXPORT_TEST + void meta_keymap_description_unlock (MetaKeymapDescription *keymap_description, + MetaKeymapDescriptionOwner *owner); + ++void meta_keymap_description_reset_owner (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescriptionOwner *owner); ++ + MetaKeymapDescriptionOwner * meta_keymap_description_get_owner (MetaKeymapDescription *keymap_description); ++ ++MetaKeymapDescriptionOwner * meta_keymap_description_resets_owner (MetaKeymapDescription *keymap_description); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index b67bcf0a74..60a06fc5da 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -39,6 +39,7 @@ struct _MetaKeymapDescription + + gboolean is_locked; + MetaKeymapDescriptionOwner *owner; ++ MetaKeymapDescriptionOwner *resets_owner; + + union { + struct { +@@ -163,6 +164,8 @@ meta_keymap_description_unref (MetaKeymapDescription *keymap_description) + + g_clear_pointer (&keymap_description->owner, + meta_keymap_description_owner_unref); ++ g_clear_pointer (&keymap_description->resets_owner, ++ meta_keymap_description_owner_unref); + + g_free (keymap_description); + } +@@ -350,6 +353,15 @@ meta_keymap_description_unlock (MetaKeymapDescription *keymap_description, + keymap_description->owner = meta_keymap_description_owner_ref (owner); + } + ++void ++meta_keymap_description_reset_owner (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescriptionOwner *owner) ++{ ++ g_clear_pointer (&keymap_description->resets_owner, ++ meta_keymap_description_owner_unref); ++ keymap_description->resets_owner = meta_keymap_description_owner_ref (owner); ++} ++ + gboolean + meta_keymap_description_is_locked (MetaKeymapDescription *keymap_description) + { +@@ -361,3 +373,9 @@ meta_keymap_description_get_owner (MetaKeymapDescription *keymap_description) + { + return keymap_description->owner; + } ++ ++MetaKeymapDescriptionOwner * ++meta_keymap_description_resets_owner (MetaKeymapDescription *keymap_description) ++{ ++ return keymap_description->resets_owner; ++} +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index ab8e207d16..2926bab779 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3902,6 +3902,8 @@ set_keyboard_map (GTask *task) + { + if (meta_keymap_description_is_locked (priv->keymap_description) && + meta_keymap_description_get_owner (keymap_description) != ++ meta_keymap_description_get_owner (priv->keymap_description) && ++ meta_keymap_description_resets_owner (keymap_description) != + meta_keymap_description_get_owner (priv->keymap_description)) + { + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index 1bbb87430b..c705562736 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -72,6 +72,36 @@ set_keymap_expect_error_cb (GObject *source_object, + *done = TRUE; + } + ++static void ++reset_keymap_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MetaBackend *backend = META_BACKEND (source_object); ++ gboolean *done = user_data; ++ g_autoptr (GError) error = NULL; ++ ++ g_assert_true (meta_backend_reset_keymap_finish (backend, result, &error)); ++ g_assert_no_error (error); ++ ++ *done = TRUE; ++} ++ ++static void ++reset_keymap_expect_error_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MetaBackend *backend = META_BACKEND (source_object); ++ gboolean *done = user_data; ++ g_autoptr (GError) error = NULL; ++ ++ g_assert_false (meta_backend_reset_keymap_finish (backend, result, &error)); ++ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); ++ ++ *done = TRUE; ++} ++ + static void + await_mod_mask (MetaKeymapNative *keymap_native, + ModMaskTuple **awaited_mod_mask) +@@ -504,6 +534,97 @@ meta_test_native_keyboard_map_lock_layout (void) + meta_keymap_description_owner_unref (owner); + } + ++static MetaKeymapDescription * ++on_reset_keymap_description (MetaBackend *backend, ++ MetaKeymapDescription *keymap_description) ++{ ++ return meta_keymap_description_ref (keymap_description); ++} ++ ++static void ++meta_test_native_keyboard_map_lock_layout_reset (void) ++{ ++ MetaBackend *backend = meta_context_get_backend (test_context); ++ g_autoptr (MetaKeymapDescription) keymap_description1 = NULL; ++ g_autoptr (MetaKeymapDescription) keymap_description2 = NULL; ++ MetaKeymapDescriptionOwner *owner; ++ MetaKeymapDescriptionOwner *other_owner; ++ gboolean done = FALSE; ++ gboolean was_signalled = FALSE; ++ gulong keymap_changed_handler_id; ++ gulong reset_keymap_handler_id; ++ ++ owner = meta_keymap_description_owner_new (); ++ other_owner = meta_keymap_description_owner_new (); ++ ++ keymap_description1 = ++ meta_keymap_description_new_from_rules (NULL, ++ "us,se", ++ "dvorak-alt-intl,svdvorak", ++ NULL, ++ NULL, ++ NULL); ++ meta_keymap_description_lock (keymap_description1, owner); ++ meta_backend_set_keymap_async (backend, keymap_description1, 0, ++ NULL, set_keymap_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ keymap_changed_handler_id = ++ g_signal_connect_swapped (backend, ++ "keymap-changed", ++ G_CALLBACK (trigger_error), ++ (gpointer) "Unexpected keymap-changed emission"); ++ ++ keymap_description2 = ++ meta_keymap_description_new_from_rules (NULL, ++ "se,us", ++ "svdvorak,dvorak-alt-intl", ++ NULL, ++ NULL, ++ NULL); ++ done = FALSE; ++ meta_backend_set_keymap_async (backend, keymap_description2, 0, ++ NULL, set_keymap_expect_error_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ reset_keymap_handler_id = ++ g_signal_connect (backend, ++ "reset-keymap-description", ++ G_CALLBACK (on_reset_keymap_description), ++ keymap_description2); ++ ++ done = FALSE; ++ meta_backend_reset_keymap_async (backend, other_owner, ++ NULL, reset_keymap_expect_error_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ g_signal_handler_disconnect (backend, keymap_changed_handler_id); ++ keymap_changed_handler_id = ++ g_signal_connect_swapped (backend, ++ "keymap-changed", ++ G_CALLBACK (set_true_cb), ++ &was_signalled); ++ ++ done = FALSE; ++ meta_backend_reset_keymap_async (backend, owner, ++ NULL, reset_keymap_cb, &done); ++ while (!done) ++ g_main_context_iteration (NULL, TRUE); ++ ++ g_assert_true (meta_backend_get_keymap_description (backend) == ++ keymap_description2); ++ ++ g_assert_true (was_signalled); ++ ++ g_signal_handler_disconnect (backend, keymap_changed_handler_id); ++ g_signal_handler_disconnect (backend, reset_keymap_handler_id); ++ meta_keymap_description_owner_unref (owner); ++ meta_keymap_description_owner_unref (other_owner); ++} ++ + static void + record_modifier_state (ClutterKeymap *keymap, + ModMaskTuple **expected_mods) +@@ -625,6 +746,8 @@ init_tests (void) + meta_test_native_keyboard_map_set_layout_index); + g_test_add_func ("/backends/native/keyboard-map/lock-layout", + meta_test_native_keyboard_map_lock_layout); ++ g_test_add_func ("/backends/native/keyboard-map/lock-layout-reset", ++ meta_test_native_keyboard_map_lock_layout_reset); + g_test_add_func ("/backends/native/keyboard-map/modifiers", + meta_test_native_keyboard_map_modifiers); + } +-- +2.54.0 + + +From d62e2aec5b902aa180b3d825bc6348d388b62ce2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 2 Oct 2025 11:34:12 +0200 +Subject: [PATCH 25/32] remote-desktop: Allow remote desktop servers to set the + keyboard layout + +This allows remote desktop servers to take control of the keyboard +layout by setting their own keymap. This is meant to for example allow +using the same keymap in the remote system as in the system where the +remote desktop client is running. + +For the time being, if the remote desktop server sets the keymap with a +'locked' property, it currently breaks the usage of input methods, as +they currently set what keyboard layout they work with, meaning that if +they can't override the keyboard layout, they wont work. This is a known +issue and deliberately not solved right now. + +Part-of: +(cherry picked from commit 0b8fae63112331043223f9aa6b234f4a99cd59b0) +--- + .../org.gnome.Mutter.RemoteDesktop.xml | 100 ++++++ + src/backends/meta-remote-desktop-session.c | 327 +++++++++++++++++- + 2 files changed, 426 insertions(+), 1 deletion(-) + +diff --git a/data/dbus-interfaces/org.gnome.Mutter.RemoteDesktop.xml b/data/dbus-interfaces/org.gnome.Mutter.RemoteDesktop.xml +index 815b1a3625..85cec80a9a 100644 +--- a/data/dbus-interfaces/org.gnome.Mutter.RemoteDesktop.xml ++++ b/data/dbus-interfaces/org.gnome.Mutter.RemoteDesktop.xml +@@ -367,6 +367,106 @@ + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/backends/meta-remote-desktop-session.c b/src/backends/meta-remote-desktop-session.c +index bda50e3110..82591bd9e7 100644 +--- a/src/backends/meta-remote-desktop-session.c ++++ b/src/backends/meta-remote-desktop-session.c +@@ -31,17 +31,19 @@ + #include + #include + ++#include "backends/meta-backend-private.h" + #include "backends/meta-dbus-session-watcher.h" + #include "backends/meta-dbus-session-manager.h" + #include "backends/meta-eis.h" ++#include "backends/meta-keymap-description-private.h" + #include "backends/meta-logical-monitor-private.h" + #include "backends/meta-screen-cast-session.h" + #include "backends/meta-remote-access-controller-private.h" + #include "cogl/cogl.h" + #include "core/display-private.h" ++#include "core/meta-sealed-fd.h" + #include "core/meta-selection-private.h" + #include "core/meta-selection-source-remote.h" +-#include "meta/meta-backend.h" + + #include "meta-dbus-remote-desktop.h" + +@@ -49,6 +51,20 @@ + + #define TRANSFER_REQUEST_CLEANUP_TIMEOUT_MS (s2ms (15)) + ++typedef enum _MetaRemoteDesktopKeymapFormat ++{ ++ META_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_V1 = 1, ++ META_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_V2 = 2, ++} MetaRemoteDesktopKeymapFormat; ++ ++typedef enum _MetaRemoteDesktopKeymapSource ++{ ++ META_REMOTE_DESKTOP_KEYMAP_SOURCE_EXTERNAL = 0, ++ META_REMOTE_DESKTOP_KEYMAP_SOURCE_SESSION = 1, ++} MetaRemoteDesktopKeymapSource; ++ ++#define SUPPORTED_KEYMAP_FORMATS (1 << META_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_V1) ++ + enum + { + PROP_0, +@@ -106,6 +122,14 @@ struct _MetaRemoteDesktopSession + GHashTable *mapping_ids; + + gulong monitors_changed_handler_id; ++ gulong keymap_changed_handler_id; ++ gulong keymap_layout_group_changed_handler_id; ++ gulong keymap_state_changed_handler_id; ++ ++ MetaKeymapDescription *keymap_description; ++ MetaKeymapDescriptionOwner *keymap_owner; ++ ++ GCancellable *cancellable; + }; + + static void initable_init_iface (GInitableIface *iface); +@@ -475,6 +499,8 @@ meta_remote_desktop_session_close (MetaDbusSession *dbus_session) + meta_dbus_session_manager_get_backend (session->session_manager); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); ++ ClutterSeat *seat = meta_backend_get_default_seat (backend); ++ ClutterKeymap *keymap = clutter_seat_get_keymap (seat); + + session->started = FALSE; + +@@ -491,6 +517,12 @@ meta_remote_desktop_session_close (MetaDbusSession *dbus_session) + + g_clear_signal_handler (&session->monitors_changed_handler_id, + monitor_manager); ++ g_clear_signal_handler (&session->keymap_changed_handler_id, ++ backend); ++ g_clear_signal_handler (&session->keymap_layout_group_changed_handler_id, ++ backend); ++ g_clear_signal_handler (&session->keymap_state_changed_handler_id, ++ keymap); + + g_clear_object (&session->virtual_pointer); + g_clear_object (&session->virtual_keyboard); +@@ -1998,6 +2030,276 @@ handle_connect_to_eis (MetaDBusRemoteDesktopSession *skeleton, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++static void ++update_current_keymap (MetaRemoteDesktopSession *session) ++{ ++ MetaBackend *backend = ++ meta_dbus_session_manager_get_backend (session->session_manager); ++ ClutterSeat *seat = meta_backend_get_default_seat (backend); ++ ClutterKeymap *keymap; ++ MetaKeymapDescription *keymap_description; ++ GVariantBuilder builder; ++ const char *layout_name; ++ MetaRemoteDesktopKeymapSource source; ++ ++ keymap = clutter_seat_get_keymap (seat); ++ keymap_description = meta_backend_get_keymap_description (backend); ++ ++ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); ++ ++ layout_name = clutter_keymap_get_current_display_name (keymap); ++ if (layout_name) ++ { ++ g_variant_builder_add (&builder, "{sv}", ++ "name", g_variant_new_string (layout_name)); ++ } ++ ++ if (keymap_description == session->keymap_description) ++ source = META_REMOTE_DESKTOP_KEYMAP_SOURCE_SESSION; ++ else ++ source = META_REMOTE_DESKTOP_KEYMAP_SOURCE_EXTERNAL; ++ g_variant_builder_add (&builder, "{sv}", ++ "source", g_variant_new_uint32 (source)); ++ ++ meta_dbus_remote_desktop_session_set_current_keymap ( ++ META_DBUS_REMOTE_DESKTOP_SESSION (session), ++ g_variant_builder_end (&builder)); ++} ++ ++typedef struct _SetKeymapData ++{ ++ MetaRemoteDesktopSession *session; ++ GDBusMethodInvocation *invocation; ++} SetKeymapData; ++ ++static void ++set_keymap_data_free (gpointer user_data) ++{ ++ SetKeymapData *data = user_data; ++ ++ g_free (data); ++} ++ ++G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetKeymapData, set_keymap_data_free) ++ ++static void ++set_keymap_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MetaBackend *backend = META_BACKEND (source_object); ++ g_autoptr (SetKeymapData) data = user_data; ++ MetaRemoteDesktopSession *session = data->session; ++ g_autoptr (GError) error = NULL; ++ ++ if (!meta_backend_set_keymap_finish (backend, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (data->invocation, error); ++ return; ++ } ++ ++ meta_dbus_remote_desktop_session_complete_set_keymap ( ++ META_DBUS_REMOTE_DESKTOP_SESSION (session), ++ data->invocation, ++ NULL); ++ ++ update_current_keymap (session); ++} ++ ++static void ++reset_keymap_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MetaBackend *backend = META_BACKEND (source_object); ++ g_autoptr (SetKeymapData) data = user_data; ++ MetaRemoteDesktopSession *session = data->session; ++ g_autoptr (GError) error = NULL; ++ ++ if (!meta_backend_reset_keymap_finish (backend, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (data->invocation, error); ++ return; ++ } ++ ++ g_clear_pointer (&session->keymap_description, ++ meta_keymap_description_unref); ++ ++ meta_dbus_remote_desktop_session_complete_set_keymap ( ++ META_DBUS_REMOTE_DESKTOP_SESSION (session), ++ data->invocation, ++ NULL); ++ ++ update_current_keymap (session); ++} ++ ++static gboolean ++verify_keymap_format (uint32_t keymap_format) ++{ ++ switch (keymap_format) ++ { ++ case META_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_V1: ++ case META_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_V2: ++ return TRUE; ++ } ++ return FALSE; ++} ++ ++static gboolean ++handle_set_keymap (MetaDBusRemoteDesktopSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ GUnixFDList *fd_list_in, ++ GVariant *arg_options) ++{ ++ MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); ++ MetaBackend *backend = ++ meta_dbus_session_manager_get_backend (session->session_manager); ++ uint32_t keymap_type = UINT32_MAX; ++ uint32_t keymap_format = UINT32_MAX; ++ g_autoptr (GError) error = NULL; ++ g_autoptr (GVariant) keymap_handle = NULL; ++ g_autoptr (MetaSealedFd) sealed_keymap = NULL; ++ g_autoptr (MetaKeymapDescription) keymap_description = NULL; ++ uint32_t layout_index = 0; ++ gboolean lock_keymap; ++ SetKeymapData *data; ++ ++ if (!g_variant_lookup (arg_options, "keymap-type", "u", &keymap_type)) ++ { ++ data = g_new0 (SetKeymapData, 1); ++ data->session = session; ++ data->invocation = invocation; ++ ++ meta_backend_reset_keymap_async (backend, ++ session->keymap_owner, ++ session->cancellable, ++ reset_keymap_cb, ++ data); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_variant_lookup (arg_options, "xkb-keymap-format", "u", &keymap_format); ++ ++ if (!verify_keymap_format (keymap_format)) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "Invalid keymap format"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (!((1 << keymap_format) & SUPPORTED_KEYMAP_FORMATS)) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_NOT_SUPPORTED, ++ "Non-supported keymap format"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ keymap_handle = g_variant_lookup_value (arg_options, "xkb-keymap", ++ G_VARIANT_TYPE_HANDLE); ++ if (!keymap_handle) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "No keymap handle"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ sealed_keymap = meta_sealed_fd_new_from_handle (keymap_handle, ++ fd_list_in, ++ &error); ++ ++ g_variant_lookup (arg_options, "xkb-keymap-layout-index", "u", &layout_index); ++ ++ keymap_description = meta_keymap_description_new_from_fd (sealed_keymap, ++ keymap_format); ++ if (g_variant_lookup (arg_options, "lock-keymap", "b", &lock_keymap) && ++ lock_keymap) ++ meta_keymap_description_lock (keymap_description, session->keymap_owner); ++ else ++ meta_keymap_description_unlock (keymap_description, session->keymap_owner); ++ ++ data = g_new0 (SetKeymapData, 1); ++ data->session = session; ++ data->invocation = invocation; ++ ++ g_clear_pointer (&session->keymap_description, ++ meta_keymap_description_unref); ++ session->keymap_description = ++ meta_keymap_description_ref (keymap_description); ++ ++ meta_backend_set_keymap_async (backend, ++ keymap_description, ++ layout_index, ++ session->cancellable, ++ set_keymap_cb, ++ data); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ ++} ++ ++static void ++set_keymap_layout_group_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) ++{ ++ MetaBackend *backend = META_BACKEND (source_object); ++ g_autoptr (SetKeymapData) data = user_data; ++ g_autoptr (GError) error = NULL; ++ ++ if (!meta_backend_set_keymap_finish (backend, result, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (data->invocation, error); ++ return; ++ } ++ ++ meta_dbus_remote_desktop_session_complete_set_keymap_layout_index ( ++ META_DBUS_REMOTE_DESKTOP_SESSION (data->session), ++ data->invocation); ++} ++ ++static gboolean ++handle_set_keymap_layout_index (MetaDBusRemoteDesktopSession *skeleton, ++ GDBusMethodInvocation *invocation, ++ uint32_t layout_index) ++{ ++ MetaRemoteDesktopSession *session = META_REMOTE_DESKTOP_SESSION (skeleton); ++ MetaBackend *backend = ++ meta_dbus_session_manager_get_backend (session->session_manager); ++ MetaKeymapDescription *keymap_description; ++ SetKeymapData *data; ++ ++ keymap_description = meta_backend_get_keymap_description (backend); ++ ++ if (!session->keymap_description || ++ keymap_description != session->keymap_description) ++ { ++ g_dbus_method_invocation_return_error (invocation, ++ G_DBUS_ERROR, ++ G_DBUS_ERROR_FAILED, ++ "Invalid current keymap"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ data = g_new0 (SetKeymapData, 1); ++ data->session = session; ++ data->invocation = invocation; ++ ++ meta_backend_set_keymap_async (backend, ++ session->keymap_description, ++ layout_index, ++ session->cancellable, ++ set_keymap_layout_group_cb, ++ data); ++ ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++} ++ + static gboolean + meta_remote_desktop_session_initable_init (GInitable *initable, + GCancellable *cancellable, +@@ -2029,6 +2331,17 @@ meta_remote_desktop_session_initable_init (GInitable *initable, + session, "num-lock-state", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + ++ session->keymap_changed_handler_id = ++ g_signal_connect_swapped (backend, "keymap-changed", ++ G_CALLBACK (update_current_keymap), session); ++ session->keymap_layout_group_changed_handler_id = ++ g_signal_connect_swapped (backend, "keymap-layout-group-changed", ++ G_CALLBACK (update_current_keymap), session); ++ session->keymap_state_changed_handler_id = ++ g_signal_connect_swapped (keymap, "state-changed", ++ G_CALLBACK (update_current_keymap), session); ++ update_current_keymap (session); ++ + session->mapping_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + return TRUE; +@@ -2062,6 +2375,8 @@ meta_remote_desktop_session_init_iface (MetaDBusRemoteDesktopSessionIface *iface + iface->handle_selection_write_done = handle_selection_write_done; + iface->handle_selection_read = handle_selection_read; + iface->handle_connect_to_eis = handle_connect_to_eis; ++ iface->handle_set_keymap = handle_set_keymap; ++ iface->handle_set_keymap_layout_index = handle_set_keymap_layout_index; + } + + static void +@@ -2079,6 +2394,9 @@ meta_remote_desktop_session_finalize (GObject *object) + + g_assert (!meta_remote_desktop_session_is_running (session)); + ++ g_cancellable_cancel (session->cancellable); ++ g_clear_object (&session->cancellable); ++ + g_clear_signal_handler (&session->owner_changed_handler_id, selection); + reset_current_selection_source (session); + cancel_selection_read (session); +@@ -2086,6 +2404,9 @@ meta_remote_desktop_session_finalize (GObject *object) + + g_clear_pointer (&session->mapping_ids, g_hash_table_unref); + ++ g_clear_pointer (&session->keymap_description, meta_keymap_description_unref); ++ g_clear_pointer (&session->keymap_owner, meta_keymap_description_owner_unref); ++ + g_clear_object (&session->handle); + g_free (session->peer_name); + g_free (session->session_id); +@@ -2154,6 +2475,10 @@ meta_remote_desktop_session_init (MetaRemoteDesktopSession *session) + ++global_session_number); + + session->transfer_requests = g_hash_table_new (NULL, NULL); ++ ++ session->keymap_owner = meta_keymap_description_owner_new (); ++ ++ session->cancellable = g_cancellable_new (); + } + + static void +-- +2.54.0 + + +From ec2bd8948e1014325f1c4e247863289666f33d7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 29 Sep 2025 16:36:43 +0200 +Subject: [PATCH 26/32] mdk: Allow mirroring the host keymap in the nested + instance + +Part-of: +(cherry picked from commit 5a78b12a3579b8c30a43a60f3f344f142db2626c) +--- + mdk/mdk-context.c | 19 +++++ + mdk/mdk-context.h | 2 + + mdk/mdk-main-window.ui | 4 + + mdk/mdk-main.c | 3 + + mdk/mdk-session.c | 179 +++++++++++++++++++++++++++++++++++++++++ + mdk/meson.build | 1 + + 6 files changed, 208 insertions(+) + +diff --git a/mdk/mdk-context.c b/mdk/mdk-context.c +index 38112ab0cb..a358719d53 100644 +--- a/mdk/mdk-context.c ++++ b/mdk/mdk-context.c +@@ -36,6 +36,7 @@ enum + + PROP_EMULATE_TOUCH, + PROP_INHIBIT_SYSTEM_SHORTCUTS, ++ PROP_USE_HOST_KEYMAP, + + N_PROPS + }; +@@ -67,6 +68,7 @@ struct _MdkContext + + gboolean emulate_touch; + gboolean inhibit_system_shortcuts; ++ gboolean use_host_keymap; + + GSettings *settings; + +@@ -324,6 +326,9 @@ mdk_context_set_property (GObject *object, + case PROP_INHIBIT_SYSTEM_SHORTCUTS: + context->inhibit_system_shortcuts = g_value_get_boolean (value); + break; ++ case PROP_USE_HOST_KEYMAP: ++ context->use_host_keymap = g_value_get_boolean (value); ++ break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; +@@ -346,6 +351,9 @@ mdk_context_get_property (GObject *object, + case PROP_INHIBIT_SYSTEM_SHORTCUTS: + g_value_set_boolean (value, context->inhibit_system_shortcuts); + break; ++ case PROP_USE_HOST_KEYMAP: ++ g_value_set_boolean (value, context->use_host_keymap); ++ break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; +@@ -386,6 +394,11 @@ mdk_context_class_init (MdkContextClass *klass) + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); ++ obj_props[PROP_USE_HOST_KEYMAP] = ++ g_param_spec_boolean ("use-host-keymap", NULL, NULL, ++ FALSE, ++ G_PARAM_READWRITE | ++ G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, obj_props); + + signals[READY] = g_signal_new ("ready", +@@ -593,6 +606,12 @@ mdk_context_get_inhibit_system_shortcuts (MdkContext *context) + return context->inhibit_system_shortcuts; + } + ++gboolean ++mdk_context_get_use_host_keymap (MdkContext *context) ++{ ++ return context->use_host_keymap; ++} ++ + GPtrArray * + mdk_context_get_launchers (MdkContext *context) + { +diff --git a/mdk/mdk-context.h b/mdk/mdk-context.h +index 29fb359741..e3f6a519b4 100644 +--- a/mdk/mdk-context.h ++++ b/mdk/mdk-context.h +@@ -38,6 +38,8 @@ gboolean mdk_context_get_emulate_touch (MdkContext *context); + + gboolean mdk_context_get_inhibit_system_shortcuts (MdkContext *context); + ++gboolean mdk_context_get_use_host_keymap (MdkContext *context); ++ + GPtrArray * mdk_context_get_launchers (MdkContext *context); + + void mdk_context_activate_launcher (MdkContext *context, +diff --git a/mdk/mdk-main-window.ui b/mdk/mdk-main-window.ui +index b420aa7bd2..a431770366 100644 +--- a/mdk/mdk-main-window.ui ++++ b/mdk/mdk-main-window.ui +@@ -13,6 +13,10 @@ + _Inhibit system shortcuts + app.toggle_inhibit_system_shortcuts + ++ ++ Use host _keymap ++ app.toggle_host_keymap ++ + + +
+diff --git a/mdk/mdk-main.c b/mdk/mdk-main.c +index 8e61373616..ff37dad4a2 100644 +--- a/mdk/mdk-main.c ++++ b/mdk/mdk-main.c +@@ -237,6 +237,7 @@ main (int argc, + { "about", activate_about, NULL, NULL, NULL }, + { "toggle_emulate_touch", .state = "false", }, + { "toggle_inhibit_system_shortcuts", .state = "false", }, ++ { "toggle_host_keymap", .state = "false", }, + { "launch", activate_launch, .parameter_type = "i", }, + { "edit_launchers", activate_edit_launchers, }, + }; +@@ -257,6 +258,8 @@ main (int argc, + app->context, "emulate-touch"); + bind_action_to_property (app, "toggle_inhibit_system_shortcuts", + app->context, "inhibit-system-shortcuts"); ++ bind_action_to_property (app, "toggle_host_keymap", ++ app->context, "use-host-keymap"); + + g_signal_connect (app, "startup", G_CALLBACK (startup), NULL); + g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); +diff --git a/mdk/mdk-session.c b/mdk/mdk-session.c +index 43793c8fc5..239d5dacd9 100644 +--- a/mdk/mdk-session.c ++++ b/mdk/mdk-session.c +@@ -19,10 +19,13 @@ + + #include "mdk-session.h" + ++#include + #include + #include + #include + #include ++#include ++#include + + #include "mdk-context.h" + #include "mdk-ei.h" +@@ -41,6 +44,17 @@ typedef enum _MdkScreenCastCursorMode + MDK_SCREEN_CAST_CURSOR_MODE_METADATA = 2, + } MdkScreenCastCursorMode; + ++typedef enum _MdkRemoteDesktopKeymapType ++{ ++ MDK_REMOTE_DESKTOP_KEYMAP_TYPE_XKB = 0, ++} MdkRemoteDesktopKeymapType; ++ ++typedef enum _MdkRemoteDesktopKeymapFormat ++{ ++ MDK_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_TEXT_V1 = 1, ++ MDK_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_TEXT_V2 = 2, ++} MdkRemoteDesktopKeymapFormat; ++ + enum + { + PROP_0, +@@ -73,6 +87,9 @@ struct _MdkSession + MdkDBusScreenCast *screen_cast_proxy; + MdkDBusRemoteDesktopSession *remote_desktop_session_proxy; + MdkDBusScreenCastSession *screen_cast_session_proxy; ++ ++ struct xkb_keymap *xkb_keymap; ++ int layout_index; + }; + + static void +@@ -89,6 +106,150 @@ on_session_closed (MdkDBusRemoteDesktopSession *remote_desktop_session_proxy, + g_signal_emit (session, signals[CLOSED], 0); + } + ++static void ++maybe_sync_keymap (MdkSession *session) ++{ ++ GdkDisplay *display = gdk_display_get_default (); ++ GdkSeat *seat = gdk_display_get_default_seat (display); ++ GdkDevice *keyboard = gdk_seat_get_keyboard (seat); ++ struct xkb_keymap *xkb_keymap; ++ int layout_index; ++ ++ if (!mdk_context_get_use_host_keymap (session->context)) ++ { ++ if (session->xkb_keymap) ++ { ++ GVariantBuilder options_builder; ++ ++ session->xkb_keymap = NULL; ++ ++ g_variant_builder_init (&options_builder, G_VARIANT_TYPE ("a{sv}")); ++ mdk_dbus_remote_desktop_session_call_set_keymap ( ++ session->remote_desktop_session_proxy, ++ g_variant_builder_end (&options_builder), ++ NULL, ++ NULL, NULL, NULL); ++ } ++ return; ++ } ++ ++ if (!GDK_IS_WAYLAND_DISPLAY (display)) ++ { ++ g_warning ("Changing keymap not supported when running from X11"); ++ return; ++ } ++ ++ xkb_keymap = gdk_wayland_device_get_xkb_keymap (keyboard); ++ layout_index = gdk_device_get_active_layout_index (keyboard); ++ ++ if (xkb_keymap == session->xkb_keymap && ++ layout_index == session->layout_index) ++ return; ++ ++ session->layout_index = layout_index; ++ ++ if (xkb_keymap != session->xkb_keymap) ++ { ++ g_autofree char *keymap_serialized = NULL; ++ g_autofd int fd = -1; ++ int fd_idx; ++ size_t keymap_size; ++ void *keymap_mem; ++ GVariantBuilder options_builder; ++ g_autoptr (GUnixFDList) fd_list = NULL; ++ g_autoptr (GError) error = NULL; ++ ++ session->xkb_keymap = xkb_keymap; ++ ++ keymap_serialized = xkb_keymap_get_as_string (xkb_keymap, ++ XKB_KEYMAP_FORMAT_TEXT_V1); ++ if (!keymap_serialized) ++ { ++ g_warning ("Failed to serialize current keymap."); ++ return; ++ } ++ ++ fd = memfd_create ("mdk-keymap", MFD_ALLOW_SEALING | MFD_CLOEXEC); ++ if (fd == -1) ++ { ++ g_warning ("Failed to create keymap memfd: %s", g_strerror (errno)); ++ return; ++ } ++ ++ keymap_size = strlen (keymap_serialized) + 1; ++ if (ftruncate (fd, keymap_size) == -1) ++ { ++ g_warning ("ftruncate of keymap fd failed: %s", g_strerror (errno)); ++ return; ++ } ++ ++ keymap_mem = mmap (NULL, keymap_size, PROT_WRITE, MAP_SHARED, fd, 0); ++ if (keymap_mem == MAP_FAILED) ++ { ++ g_warning ("Failed mmap keymap fd: %s", g_strerror (errno)); ++ return; ++ } ++ ++ strcpy (keymap_mem, keymap_serialized); ++ ++ if (munmap (keymap_mem, keymap_size) == -1) ++ { ++ g_warning ("Failed munmap keymap fd: %s", g_strerror (errno)); ++ return; ++ } ++ ++ fd_list = g_unix_fd_list_new (); ++ ++ fd_idx = g_unix_fd_list_append (fd_list, fd, &error); ++ if (fd_idx == -1) ++ { ++ g_warning ("Failed to append file descriptor to fd list: %s", ++ error->message); ++ return; ++ } ++ ++ layout_index = gdk_device_get_active_layout_index (keyboard); ++ ++ g_variant_builder_init (&options_builder, G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (&options_builder, "{sv}", ++ "keymap-type", ++ g_variant_new_uint32 (MDK_REMOTE_DESKTOP_KEYMAP_TYPE_XKB)); ++ g_variant_builder_add (&options_builder, "{sv}", ++ "xkb-keymap-format", ++ g_variant_new_uint32 (MDK_REMOTE_DESKTOP_KEYMAP_FORMAT_XKB_TEXT_V1)); ++ g_variant_builder_add (&options_builder, "{sv}", ++ "xkb-keymap", ++ g_variant_new_handle (fd_idx)); ++ g_variant_builder_add (&options_builder, "{sv}", ++ "xkb-keymap-layout-index", ++ g_variant_new_uint32 (layout_index)); ++ g_variant_builder_add (&options_builder, "{sv}", ++ "lock-keymap", ++ g_variant_new_boolean (TRUE)); ++ ++ mdk_dbus_remote_desktop_session_call_set_keymap ( ++ session->remote_desktop_session_proxy, ++ g_variant_builder_end (&options_builder), ++ fd_list, ++ NULL, NULL, NULL); ++ } ++ else ++ { ++ mdk_dbus_remote_desktop_session_call_set_keymap_layout_index ( ++ session->remote_desktop_session_proxy, ++ layout_index, ++ NULL, NULL, NULL); ++ } ++} ++ ++static void ++on_use_host_keymap_changed (MdkContext *context, ++ GParamSpec *pspec, ++ MdkSession *session) ++{ ++ maybe_sync_keymap (session); ++} ++ + static gboolean + init_session (MdkSession *session, + GCancellable *cancellable, +@@ -187,6 +348,12 @@ init_session (MdkSession *session, + G_CALLBACK (on_session_closed), + session); + ++ g_signal_connect_object (session->context, ++ "notify::use-host-keymap", ++ G_CALLBACK (on_use_host_keymap_changed), ++ session, ++ G_CONNECT_DEFAULT); ++ + return TRUE; + } + +@@ -196,6 +363,9 @@ mdk_session_initable_init (GInitable *initable, + GError **error) + { + MdkSession *session = MDK_SESSION (initable); ++ GdkDisplay *display = gdk_display_get_default (); ++ GdkSeat *seat = gdk_display_get_default_seat (display); ++ GdkDevice *keyboard = gdk_seat_get_keyboard (seat); + + g_debug ("Initializing session"); + +@@ -230,6 +400,15 @@ mdk_session_initable_init (GInitable *initable, + error)) + return FALSE; + ++ g_signal_connect_object (keyboard, "notify::layout-names", ++ G_CALLBACK (maybe_sync_keymap), ++ session, ++ G_CONNECT_SWAPPED); ++ g_signal_connect_object (keyboard, "notify::active-layout-index", ++ G_CALLBACK (maybe_sync_keymap), ++ session, ++ G_CONNECT_SWAPPED); ++ + return TRUE; + } + +diff --git a/mdk/meson.build b/mdk/meson.build +index d3e4184ca4..59a39b9c57 100644 +--- a/mdk/meson.build ++++ b/mdk/meson.build +@@ -91,6 +91,7 @@ executable('mutter-devkit', + libpipewire_dep, + libdrm_dep, + libei_dep, ++ xkbcommon_dep, + ], + install_dir: libexecdir, + install: true, +-- +2.54.0 + + +From fc4743e8812280b181ff3097b37ad619c431d24d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 3 Oct 2025 15:41:09 +0200 +Subject: [PATCH 27/32] keymap-description: Add 'direct_equal()' API + +This is meant to be used by Javascript via GI to check whether one +instance is exactly the same as another, to effectively check if was one +self that set the keymap description in question. + +Part-of: +(cherry picked from commit 3e458cc611026bd4095012c488584b612b85ea4b) +--- + src/backends/meta-keymap-description.c | 7 +++++++ + src/meta/meta-keymap-description.h | 4 ++++ + 2 files changed, 11 insertions(+) + +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index 60a06fc5da..4d077626df 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -177,6 +177,13 @@ meta_keymap_description_get_source (MetaKeymapDescription *keymap_description) + return keymap_description->source; + } + ++gboolean ++meta_keymap_description_direct_equal (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescription *other) ++{ ++ return keymap_description == other; ++} ++ + static char * + maybe_derive_short_name (struct rxkb_context *rxkb_context, + const char *layout_name) +diff --git a/src/meta/meta-keymap-description.h b/src/meta/meta-keymap-description.h +index 0f4de5ed2b..d4f9431ef8 100644 +--- a/src/meta/meta-keymap-description.h ++++ b/src/meta/meta-keymap-description.h +@@ -45,5 +45,9 @@ void meta_keymap_description_unref (MetaKeymapDescription *keymap_description); + META_EXPORT + gboolean meta_keymap_description_is_locked (MetaKeymapDescription *keymap_description); + ++META_EXPORT ++gboolean meta_keymap_description_direct_equal (MetaKeymapDescription *keymap_description, ++ MetaKeymapDescription *other); ++ + G_DEFINE_AUTOPTR_CLEANUP_FUNC (MetaKeymapDescription, + meta_keymap_description_unref); +-- +2.54.0 + + +From f2c21f79e5587ad1c204dad787bb3de7bd0ba7d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 3 Oct 2025 15:42:19 +0200 +Subject: [PATCH 28/32] tests/keyboard-map-tests: Assert keymap description + sanity on signal + +When signalled that the keymap changed, make sure the currently returned +keymap description is the one that was changed to. + +Part-of: +(cherry picked from commit 34f9765e27056ea90b800538c2d77189ece3e810) +--- + src/tests/keyboard-map-tests.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index c705562736..a26b1a7bf4 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -150,6 +150,14 @@ on_keymap_changed (MetaBackend *backend, + *expected_next_handler = on_keymap_state_changed; + } + ++static void ++on_keymap_changed2 (MetaBackend *backend, ++ MetaKeymapDescription *expected_keymap_description) ++{ ++ g_assert_true (expected_keymap_description == ++ meta_backend_get_keymap_description (backend)); ++} ++ + static void + meta_test_native_keyboard_map_set_async (void) + { +@@ -168,6 +176,7 @@ meta_test_native_keyboard_map_set_async (void) + gpointer expected_next_handler; + gulong await_mod_mask_handler_id; + gulong keymap_changed_handler_id; ++ gulong keymap_changed_handler_id2; + gulong keymap_state_changed_handler_id; + + await_mod_mask_handler_id = +@@ -215,6 +224,13 @@ meta_test_native_keyboard_map_set_async (void) + NULL, + NULL, + NULL); ++ ++ keymap_changed_handler_id2 = ++ g_signal_connect (backend, ++ "keymap-changed", ++ G_CALLBACK (on_keymap_changed2), ++ keymap_description); ++ + meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + +@@ -240,6 +256,7 @@ meta_test_native_keyboard_map_set_async (void) + meta_wait_for_update (test_context); + + g_signal_handler_disconnect (backend, keymap_changed_handler_id); ++ g_signal_handler_disconnect (backend, keymap_changed_handler_id2); + g_signal_handler_disconnect (keymap, keymap_state_changed_handler_id); + } + +-- +2.54.0 + + +From 2ed2a27f02aed240cc7791b7345d5b7fad5d770c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 28 Jan 2026 16:48:38 +0100 +Subject: [PATCH 29/32] backend: Harmonize on keymap naming convention + +Instead of sometimes calling it keyboard map or keymap, harmonize an +using the term keymap everywhere. One piece of API was renamed to +get_xkb_keymap() to distinguish between returning a ClutterKeymap, and a +xkb_keymap. + +Part-of: +(cherry picked from commit 2fe20346d03391412c37a54d687c9302eafa61ad) +--- + src/backends/native/meta-backend-native.c | 24 +++--- + .../native/meta-keymap-native-private.h | 16 ++-- + src/backends/native/meta-keymap-native.c | 16 ++-- + src/backends/native/meta-seat-impl.c | 50 +++++------ + src/backends/native/meta-seat-impl.h | 20 ++--- + src/backends/native/meta-seat-native.c | 84 +++++++++---------- + src/backends/native/meta-seat-native.h | 22 ++--- + .../native/meta-virtual-input-device-native.c | 3 +- + src/tests/keyboard-map-tests.c | 2 +- + 9 files changed, 119 insertions(+), 118 deletions(-) + +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index 7d68894cb8..66105d6cb4 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -318,15 +318,15 @@ meta_backend_native_get_current_logical_monitor (MetaBackend *backend) + } + + static void +-set_keyboard_map_cb (GObject *source_object, +- GAsyncResult *result, +- gpointer user_data) ++set_keymap_cb (GObject *source_object, ++ GAsyncResult *result, ++ gpointer user_data) + { + MetaSeatNative *seat_native = META_SEAT_NATIVE (source_object); + g_autoptr (GTask) task = G_TASK (user_data); + g_autoptr (GError) error = NULL; + +- if (!meta_seat_native_set_keyboard_map_finish (seat_native, result, &error)) ++ if (!meta_seat_native_set_keymap_finish (seat_native, result, &error)) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; +@@ -345,12 +345,12 @@ meta_backend_native_set_keymap_async (MetaBackend *backend, + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); +- meta_seat_native_set_keyboard_map_async (META_SEAT_NATIVE (seat), +- description, +- layout_index, +- g_task_get_cancellable (task), +- set_keyboard_map_cb, +- task); ++ meta_seat_native_set_keymap_async (META_SEAT_NATIVE (seat), ++ description, ++ layout_index, ++ g_task_get_cancellable (task), ++ set_keymap_cb, ++ task); + + } + +@@ -361,7 +361,7 @@ meta_backend_native_get_keymap (MetaBackend *backend) + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); +- return meta_seat_native_get_keyboard_map (META_SEAT_NATIVE (seat)); ++ return meta_seat_native_get_xkb_keymap (META_SEAT_NATIVE (seat)); + } + + static MetaKeymapDescription * +@@ -371,7 +371,7 @@ meta_backend_native_get_keymap_description (MetaBackend *backend) + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); +- return meta_seat_native_get_keyboard_map_description (META_SEAT_NATIVE (seat)); ++ return meta_seat_native_get_keymap_description (META_SEAT_NATIVE (seat)); + } + + static xkb_layout_index_t +diff --git a/src/backends/native/meta-keymap-native-private.h b/src/backends/native/meta-keymap-native-private.h +index e119b91c31..326451a282 100644 +--- a/src/backends/native/meta-keymap-native-private.h ++++ b/src/backends/native/meta-keymap-native-private.h +@@ -25,15 +25,15 @@ + #error "This header cannot be included directly. Use "backends/native/meta-input-thread.h"" + #endif /* META_INPUT_THREAD_H_INSIDE */ + +-void meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, +- MetaSeatImpl *seat_impl, +- MetaKeymapDescription *keymap_description, +- struct xkb_keymap *xkb_keymap, +- struct xkb_state *xkb_state, +- GStrv display_names, +- GStrv short_names); ++void meta_keymap_native_set_keymap_in_impl (MetaKeymapNative *keymap, ++ MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ struct xkb_keymap *xkb_keymap, ++ struct xkb_state *xkb_state, ++ GStrv display_names, ++ GStrv short_names); + +-struct xkb_keymap * meta_keymap_native_get_keyboard_map_in_impl (MetaKeymapNative *keymap); ++struct xkb_keymap * meta_keymap_native_get_xkb_keymap_in_impl (MetaKeymapNative *keymap); + + void meta_keymap_native_update_in_impl (MetaKeymapNative *keymap, + MetaSeatImpl *seat_impl, +diff --git a/src/backends/native/meta-keymap-native.c b/src/backends/native/meta-keymap-native.c +index f55b8bd056..f11fd44866 100644 +--- a/src/backends/native/meta-keymap-native.c ++++ b/src/backends/native/meta-keymap-native.c +@@ -203,13 +203,13 @@ update_keymap_in_main (gpointer user_data) + } + + void +-meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, +- MetaSeatImpl *seat_impl, +- MetaKeymapDescription *keymap_description, +- struct xkb_keymap *xkb_keymap, +- struct xkb_state *xkb_state, +- GStrv display_names, +- GStrv short_names) ++meta_keymap_native_set_keymap_in_impl (MetaKeymapNative *keymap, ++ MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ struct xkb_keymap *xkb_keymap, ++ struct xkb_state *xkb_state, ++ GStrv display_names, ++ GStrv short_names) + { + UpdateKeymapData *data; + +@@ -231,7 +231,7 @@ meta_keymap_native_set_keyboard_map_in_impl (MetaKeymapNative *keymap, + } + + struct xkb_keymap * +-meta_keymap_native_get_keyboard_map_in_impl (MetaKeymapNative *keymap) ++meta_keymap_native_get_xkb_keymap_in_impl (MetaKeymapNative *keymap) + { + return keymap->impl.keymap; + } +diff --git a/src/backends/native/meta-seat-impl.c b/src/backends/native/meta-seat-impl.c +index 2926bab779..ca7a9aed5a 100644 +--- a/src/backends/native/meta-seat-impl.c ++++ b/src/backends/native/meta-seat-impl.c +@@ -3028,7 +3028,7 @@ meta_seat_impl_set_keyboard_numlock_in_impl (MetaSeatImpl *seat_impl, + MetaKeymapNative *keymap; + + keymap = seat_impl->keymap; +- xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (keymap); ++ xkb_keymap = meta_keymap_native_get_xkb_keymap_in_impl (keymap); + + numlock = (1 << xkb_keymap_mod_get_index (xkb_keymap, "Mod2")); + +@@ -3180,7 +3180,7 @@ update_keyboard_leds (MetaSeatImpl *seat_impl) + + G_STATIC_ASSERT (G_N_ELEMENTS (led_map) == N_KEYBOARD_LEDS); + +- xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (seat_impl->keymap); ++ xkb_keymap = meta_keymap_native_get_xkb_keymap_in_impl (seat_impl->keymap); + if (!xkb_keymap) + return; + +@@ -3223,7 +3223,7 @@ input_thread (MetaSeatImpl *seat_impl) + + seat_impl->keymap = g_object_new (META_TYPE_KEYMAP_NATIVE, NULL); + +- xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (seat_impl->keymap); ++ xkb_keymap = meta_keymap_native_get_xkb_keymap_in_impl (seat_impl->keymap); + + if (xkb_keymap) + { +@@ -3715,7 +3715,7 @@ meta_seat_impl_update_xkb_state_in_impl (MetaSeatImpl *seat_impl) + + g_rw_lock_writer_lock (&seat_impl->state_lock); + +- xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (seat_impl->keymap); ++ xkb_keymap = meta_keymap_native_get_xkb_keymap_in_impl (seat_impl->keymap); + meta_seat_impl_update_xkb_state_in_impl_unlocked (seat_impl, + xkb_keymap, + seat_impl->layout_idx); +@@ -3820,15 +3820,15 @@ meta_seat_impl_reclaim_devices (MetaSeatImpl *seat_impl) + } + + gboolean +-meta_seat_impl_set_keyboard_map_finish (MetaSeatImpl *seat_impl, +- GAsyncResult *result, +- GError **error) ++meta_seat_impl_set_keymap_finish (MetaSeatImpl *seat_impl, ++ GAsyncResult *result, ++ GError **error) + { + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_is_valid (result, seat_impl), FALSE); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == +- meta_seat_impl_set_keyboard_map_async, FALSE); ++ meta_seat_impl_set_keymap_async, FALSE); + + return g_task_propagate_boolean (task, error); + } +@@ -3878,7 +3878,7 @@ update_layout_index_unlocked (MetaSeatImpl *seat_impl, + } + + static gboolean +-set_keyboard_map (GTask *task) ++set_keymap (GTask *task) + { + MetaSeatImpl *seat_impl = g_task_get_source_object (task); + MetaSeatImplPrivate *priv = +@@ -3936,13 +3936,13 @@ set_keyboard_map (GTask *task) + data->layout_index); + + keymap = seat_impl->keymap; +- meta_keymap_native_set_keyboard_map_in_impl (keymap, +- seat_impl, +- keymap_description, +- xkb_keymap, +- seat_impl->xkb, +- g_steal_pointer (&display_names), +- g_steal_pointer (&short_names)); ++ meta_keymap_native_set_keymap_in_impl (keymap, ++ seat_impl, ++ keymap_description, ++ xkb_keymap, ++ seat_impl->xkb, ++ g_steal_pointer (&display_names), ++ g_steal_pointer (&short_names)); + xkb_keymap_unref (xkb_keymap); + } + else +@@ -3958,7 +3958,7 @@ set_keyboard_map (GTask *task) + } + + /** +- * meta_seat_impl_set_keyboard_map_async: (skip) ++ * meta_seat_impl_set_keymap_async: (skip) + * @seat_impl: the #ClutterSeat created by the evdev backend + * @keymap: the new keymap + * @cancellable: a #GCancellable +@@ -3971,12 +3971,12 @@ set_keyboard_map (GTask *task) + * is pressed when calling this function. + */ + void +-meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, +- MetaKeymapDescription *keymap_description, +- xkb_layout_index_t layout_index, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) ++meta_seat_impl_set_keymap_async (MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ xkb_layout_index_t layout_index, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { + GTask *task; + SetKeymapData *data; +@@ -3985,13 +3985,13 @@ meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, + g_return_if_fail (keymap_description); + + task = g_task_new (seat_impl, cancellable, callback, user_data); +- g_task_set_source_tag (task, meta_seat_impl_set_keyboard_map_async); ++ g_task_set_source_tag (task, meta_seat_impl_set_keymap_async); + + data = g_new0 (SetKeymapData, 1); + data->keymap_description = meta_keymap_description_ref (keymap_description); + data->layout_index = layout_index; + g_task_set_task_data (task, data, set_keymap_data_free); +- meta_seat_impl_run_input_task (seat_impl, task, (GSourceFunc) set_keyboard_map); ++ meta_seat_impl_run_input_task (seat_impl, task, (GSourceFunc) set_keymap); + g_object_unref (task); + } + +diff --git a/src/backends/native/meta-seat-impl.h b/src/backends/native/meta-seat-impl.h +index c4692cac19..2a31db19b7 100644 +--- a/src/backends/native/meta-seat-impl.h ++++ b/src/backends/native/meta-seat-impl.h +@@ -185,16 +185,16 @@ void meta_seat_impl_reclaim_devices (MetaSeatImpl *seat_impl); + + struct xkb_state * meta_seat_impl_get_xkb_state_in_impl (MetaSeatImpl *seat_impl); + +-gboolean meta_seat_impl_set_keyboard_map_finish (MetaSeatImpl *seat_impl, +- GAsyncResult *result, +- GError **error); +- +-void meta_seat_impl_set_keyboard_map_async (MetaSeatImpl *seat_impl, +- MetaKeymapDescription *keymap_description, +- xkb_layout_index_t layout_index, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); ++gboolean meta_seat_impl_set_keymap_finish (MetaSeatImpl *seat_impl, ++ GAsyncResult *result, ++ GError **error); ++ ++void meta_seat_impl_set_keymap_async (MetaSeatImpl *seat_impl, ++ MetaKeymapDescription *keymap_description, ++ xkb_layout_index_t layout_index, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); + + void meta_seat_impl_set_keyboard_repeat_in_impl (MetaSeatImpl *seat_impl, + gboolean repeat, +diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c +index 2722b1d970..dbb0a3534a 100644 +--- a/src/backends/native/meta-seat-native.c ++++ b/src/backends/native/meta-seat-native.c +@@ -66,11 +66,11 @@ static guint signals[N_SIGNALS]; + + G_DEFINE_TYPE (MetaSeatNative, meta_seat_native, CLUTTER_TYPE_SEAT) + +-static gboolean meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, +- MetaKeymapDescription *description, +- xkb_layout_index_t layout_index, +- GCancellable *cancellable, +- GError **error); ++static gboolean meta_seat_native_set_keymap_sync (MetaSeatNative *seat_native, ++ MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, ++ GCancellable *cancellable, ++ GError **error); + + static gboolean + meta_seat_native_handle_event_post (ClutterSeat *seat, +@@ -226,9 +226,9 @@ meta_seat_native_constructed (GObject *object) + NULL, + NULL, + NULL); +- if (!meta_seat_native_set_keyboard_map_sync (seat, +- keymap_description, 0, +- NULL, &error)) ++ if (!meta_seat_native_set_keymap_sync (seat, ++ keymap_description, 0, ++ NULL, &error)) + g_warning ("Failed to set keyboard map: %s", error->message); + + if (G_OBJECT_CLASS (meta_seat_native_parent_class)->constructed) +@@ -575,15 +575,15 @@ meta_seat_native_reclaim_devices (MetaSeatNative *seat) + } + + gboolean +-meta_seat_native_set_keyboard_map_finish (MetaSeatNative *seat_native, +- GAsyncResult *result, +- GError **error) ++meta_seat_native_set_keymap_finish (MetaSeatNative *seat_native, ++ GAsyncResult *result, ++ GError **error) + { + GTask *task = G_TASK (result); + + g_return_val_if_fail (g_task_is_valid (result, seat_native), FALSE); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == +- meta_seat_native_set_keyboard_map_async, FALSE); ++ meta_seat_native_set_keymap_async, FALSE); + + return g_task_propagate_boolean (task, error); + } +@@ -597,7 +597,7 @@ set_impl_keyboard_map_cb (GObject *source_object, + g_autoptr (GTask) task = G_TASK (user_data); + g_autoptr (GError) error = NULL; + +- if (!meta_seat_impl_set_keyboard_map_finish (seat_impl, result, &error)) ++ if (!meta_seat_impl_set_keymap_finish (seat_impl, result, &error)) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; +@@ -607,7 +607,7 @@ set_impl_keyboard_map_cb (GObject *source_object, + } + + /** +- * meta_seat_native_set_keyboard_map_async: (skip) ++ * meta_seat_native_set_keymap_async: (skip) + * @seat: the #ClutterSeat created by the evdev backend + * @keymap: the new keymap + * +@@ -617,24 +617,24 @@ set_impl_keyboard_map_cb (GObject *source_object, + * is pressed when calling this function. + */ + void +-meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, +- MetaKeymapDescription *description, +- xkb_layout_index_t layout_index, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data) ++meta_seat_native_set_keymap_async (MetaSeatNative *seat, ++ MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data) + { + g_autoptr (GTask) task = NULL; + + task = g_task_new (G_OBJECT (seat), cancellable, callback, user_data); +- g_task_set_source_tag (task, meta_seat_native_set_keyboard_map_async); ++ g_task_set_source_tag (task, meta_seat_native_set_keymap_async); + +- meta_seat_impl_set_keyboard_map_async (seat->impl, +- description, +- layout_index, +- cancellable, +- set_impl_keyboard_map_cb, +- g_object_ref (task)); ++ meta_seat_impl_set_keymap_async (seat->impl, ++ description, ++ layout_index, ++ cancellable, ++ set_impl_keyboard_map_cb, ++ g_object_ref (task)); + } + + static void +@@ -647,7 +647,7 @@ sync_set_impl_keyboard_map_cb (GObject *source_object, + GTask *task = G_TASK (user_data); + GMainLoop *main_loop = g_task_get_task_data (task); + +- if (!meta_seat_impl_set_keyboard_map_finish (seat_impl, result, &error)) ++ if (!meta_seat_impl_set_keymap_finish (seat_impl, result, &error)) + { + g_task_return_error (task, g_steal_pointer (&error)); + g_main_loop_quit (main_loop); +@@ -659,11 +659,11 @@ sync_set_impl_keyboard_map_cb (GObject *source_object, + } + + static gboolean +-meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, +- MetaKeymapDescription *description, +- xkb_layout_index_t layout_index, +- GCancellable *cancellable, +- GError **error) ++meta_seat_native_set_keymap_sync (MetaSeatNative *seat_native, ++ MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, ++ GCancellable *cancellable, ++ GError **error) + { + g_autoptr (GMainContext) main_context = NULL; + g_autoptr (GMainLoop) main_loop = NULL; +@@ -677,12 +677,12 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (task, main_loop, NULL); + +- meta_seat_impl_set_keyboard_map_async (seat_native->impl, +- description, +- layout_index, +- cancellable, +- sync_set_impl_keyboard_map_cb, +- task); ++ meta_seat_impl_set_keymap_async (seat_native->impl, ++ description, ++ layout_index, ++ cancellable, ++ sync_set_impl_keyboard_map_cb, ++ task); + g_main_loop_run (main_loop); + g_main_context_pop_thread_default (main_context); + +@@ -708,7 +708,7 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + } + + /** +- * meta_seat_native_get_keyboard_map: (skip) ++ * meta_seat_native_get_keymap: (skip) + * @seat: the #ClutterSeat created by the evdev backend + * + * Retrieves the #xkb_keymap in use by the evdev backend. +@@ -716,7 +716,7 @@ meta_seat_native_set_keyboard_map_sync (MetaSeatNative *seat_native, + * Return value: the #xkb_keymap. + */ + struct xkb_keymap * +-meta_seat_native_get_keyboard_map (MetaSeatNative *seat) ++meta_seat_native_get_xkb_keymap (MetaSeatNative *seat) + { + g_return_val_if_fail (META_IS_SEAT_NATIVE (seat), NULL); + +@@ -724,7 +724,7 @@ meta_seat_native_get_keyboard_map (MetaSeatNative *seat) + } + + MetaKeymapDescription * +-meta_seat_native_get_keyboard_map_description (MetaSeatNative *seat_native) ++meta_seat_native_get_keymap_description (MetaSeatNative *seat_native) + { + g_return_val_if_fail (seat_native->keymap_description, NULL); + +diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h +index 7fbe7dd3e7..ce3551d4ad 100644 +--- a/src/backends/native/meta-seat-native.h ++++ b/src/backends/native/meta-seat-native.h +@@ -101,21 +101,21 @@ void meta_seat_native_set_device_callbacks (MetaOpenDeviceCallback open_callba + void meta_seat_native_release_devices (MetaSeatNative *seat); + void meta_seat_native_reclaim_devices (MetaSeatNative *seat); + +-void meta_seat_native_set_keyboard_map_async (MetaSeatNative *seat, +- MetaKeymapDescription *description, +- xkb_layout_index_t layout_index, +- GCancellable *cancellable, +- GAsyncReadyCallback callback, +- gpointer user_data); ++void meta_seat_native_set_keymap_async (MetaSeatNative *seat, ++ MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, ++ GCancellable *cancellable, ++ GAsyncReadyCallback callback, ++ gpointer user_data); + +-gboolean meta_seat_native_set_keyboard_map_finish (MetaSeatNative *seat_native, +- GAsyncResult *result, +- GError **error); ++gboolean meta_seat_native_set_keymap_finish (MetaSeatNative *seat_native, ++ GAsyncResult *result, ++ GError **error); + + META_EXPORT_TEST +-struct xkb_keymap * meta_seat_native_get_keyboard_map (MetaSeatNative *seat); ++struct xkb_keymap * meta_seat_native_get_xkb_keymap (MetaSeatNative *seat); + +-MetaKeymapDescription * meta_seat_native_get_keyboard_map_description (MetaSeatNative *seat_native); ++MetaKeymapDescription * meta_seat_native_get_keymap_description (MetaSeatNative *seat_native); + + xkb_layout_index_t meta_seat_native_get_keyboard_layout_index (MetaSeatNative *seat); + +diff --git a/src/backends/native/meta-virtual-input-device-native.c b/src/backends/native/meta-virtual-input-device-native.c +index e33925d7ba..777b4241eb 100644 +--- a/src/backends/native/meta-virtual-input-device-native.c ++++ b/src/backends/native/meta-virtual-input-device-native.c +@@ -483,7 +483,8 @@ pick_keycode_for_keyval_in_current_group_in_impl (ClutterVirtualInputDevice *vir + + seat = clutter_virtual_input_device_get_seat (virtual_device); + keymap = clutter_seat_get_keymap (seat); +- xkb_keymap = meta_keymap_native_get_keyboard_map_in_impl (META_KEYMAP_NATIVE (keymap)); ++ xkb_keymap = ++ meta_keymap_native_get_xkb_keymap_in_impl (META_KEYMAP_NATIVE (keymap)); + state = meta_seat_impl_get_xkb_state_in_impl (seat_native->impl); + + layout = xkb_state_serialize_layout (state, XKB_STATE_LAYOUT_EFFECTIVE); +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index a26b1a7bf4..b55dfc0a16 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -670,7 +670,7 @@ meta_test_native_keyboard_map_modifiers (void) + ClutterSeat *seat = meta_backend_get_default_seat (backend); + MetaSeatNative *seat_native = META_SEAT_NATIVE (seat); + struct xkb_keymap *xkb_keymap = +- meta_seat_native_get_keyboard_map (seat_native); ++ meta_seat_native_get_xkb_keymap (seat_native); + xkb_mod_mask_t shift_mask = + 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_SHIFT); + xkb_mod_mask_t alt_mask = +-- +2.54.0 + + +From 92e1763f133db6b5909a4989df2ad8c26f2c1fd2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 28 Jan 2026 22:15:39 +0100 +Subject: [PATCH 30/32] backend: Rename get_keymap() to get_xkb_keymap() + +This makes it clearer it's not a ClutterKeymap that is returned. + +Part-of: +(cherry picked from commit 149973014c5abf25c058a081ee6fdab605bed836) +--- + src/backends/meta-backend-private.h | 4 ++-- + src/backends/meta-backend.c | 4 ++-- + src/backends/meta-eis-client.c | 2 +- + src/backends/meta-input-capture-session.c | 2 +- + src/backends/native/meta-backend-native.c | 4 ++-- + src/core/keybindings.c | 4 ++-- + src/tests/keyboard-map-tests.c | 20 ++++++++++---------- + src/wayland/meta-wayland-keyboard.c | 6 ++++-- + 8 files changed, 24 insertions(+), 22 deletions(-) + +diff --git a/src/backends/meta-backend-private.h b/src/backends/meta-backend-private.h +index 8b61738922..45c02f2204 100644 +--- a/src/backends/meta-backend-private.h ++++ b/src/backends/meta-backend-private.h +@@ -130,7 +130,7 @@ struct _MetaBackendClass + xkb_layout_index_t layout_index, + GTask *task); + +- struct xkb_keymap * (* get_keymap) (MetaBackend *backend); ++ struct xkb_keymap * (* get_xkb_keymap) (MetaBackend *backend); + + MetaKeymapDescription * (* get_keymap_description) (MetaBackend *backend); + +@@ -209,7 +209,7 @@ void meta_backend_finish_touch_sequence (MetaBackend *backend, + MetaSequenceState state); + + META_EXPORT_TEST +-struct xkb_keymap * meta_backend_get_keymap (MetaBackend *backend); ++struct xkb_keymap * meta_backend_get_xkb_keymap (MetaBackend *backend); + + META_EXPORT_TEST + xkb_layout_index_t meta_backend_get_keymap_layout_group (MetaBackend *backend); +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index 143797213d..7c2a520a0d 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -1810,9 +1810,9 @@ meta_backend_set_keymap_async (MetaBackend *backend, + } + + struct xkb_keymap * +-meta_backend_get_keymap (MetaBackend *backend) ++meta_backend_get_xkb_keymap (MetaBackend *backend) + { +- return META_BACKEND_GET_CLASS (backend)->get_keymap (backend); ++ return META_BACKEND_GET_CLASS (backend)->get_xkb_keymap (backend); + } + + /** +diff --git a/src/backends/meta-eis-client.c b/src/backends/meta-eis-client.c +index 167e5ca030..d017c3b09e 100644 +--- a/src/backends/meta-eis-client.c ++++ b/src/backends/meta-eis-client.c +@@ -242,7 +242,7 @@ configure_keyboard (MetaEisClient *client, + eis_device_configure_capability (eis_device, EIS_DEVICE_CAP_KEYBOARD); + + xkb_keymap = +- meta_backend_get_keymap (meta_eis_get_backend (client->eis)); ++ meta_backend_get_xkb_keymap (meta_eis_get_backend (client->eis)); + if (!xkb_keymap) + return; + +diff --git a/src/backends/meta-input-capture-session.c b/src/backends/meta-input-capture-session.c +index 67d50c8e8c..15acbe3222 100644 +--- a/src/backends/meta-input-capture-session.c ++++ b/src/backends/meta-input-capture-session.c +@@ -244,7 +244,7 @@ ensure_xkb_keymap_file (MetaInputCaptureSession *session, + if (session->keymap_file) + return session->keymap_file; + +- keymap = meta_backend_get_keymap (backend); ++ keymap = meta_backend_get_xkb_keymap (backend); + if (!keymap) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, +diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c +index 66105d6cb4..15e05a50be 100644 +--- a/src/backends/native/meta-backend-native.c ++++ b/src/backends/native/meta-backend-native.c +@@ -355,7 +355,7 @@ meta_backend_native_set_keymap_async (MetaBackend *backend, + } + + static struct xkb_keymap * +-meta_backend_native_get_keymap (MetaBackend *backend) ++meta_backend_native_get_xkb_keymap (MetaBackend *backend) + { + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + ClutterSeat *seat; +@@ -903,7 +903,7 @@ meta_backend_native_class_init (MetaBackendNativeClass *klass) + backend_class->get_current_logical_monitor = meta_backend_native_get_current_logical_monitor; + + backend_class->set_keymap_async = meta_backend_native_set_keymap_async; +- backend_class->get_keymap = meta_backend_native_get_keymap; ++ backend_class->get_xkb_keymap = meta_backend_native_get_xkb_keymap; + backend_class->get_keymap_description = meta_backend_native_get_keymap_description; + backend_class->get_keymap_layout_group = meta_backend_native_get_keymap_layout_group; + backend_class->update_stage = meta_backend_native_update_stage; +diff --git a/src/core/keybindings.c b/src/core/keybindings.c +index 6f879eed8e..486da03689 100644 +--- a/src/core/keybindings.c ++++ b/src/core/keybindings.c +@@ -239,7 +239,7 @@ key_combo_key (MetaResolvedKeyCombo *resolved_combo, + static void + reload_modmap (MetaKeyBindingManager *keys) + { +- struct xkb_keymap *keymap = meta_backend_get_keymap (keys->backend); ++ struct xkb_keymap *keymap = meta_backend_get_xkb_keymap (keys->backend); + struct xkb_state *scratch_state; + xkb_mod_mask_t scroll_lock_mask; + xkb_mod_mask_t dummy_mask; +@@ -800,7 +800,7 @@ reload_active_keyboard_layouts (MetaKeyBindingManager *keys) + + clear_active_keyboard_layouts (keys); + +- keymap = meta_backend_get_keymap (keys->backend); ++ keymap = meta_backend_get_xkb_keymap (keys->backend); + layout_index = meta_backend_get_keymap_layout_group (keys->backend); + primary_layout = (MetaKeyBindingKeyboardLayout) { + .keymap = xkb_keymap_ref (keymap), +diff --git a/src/tests/keyboard-map-tests.c b/src/tests/keyboard-map-tests.c +index b55dfc0a16..c31b176c7c 100644 +--- a/src/tests/keyboard-map-tests.c ++++ b/src/tests/keyboard-map-tests.c +@@ -165,7 +165,7 @@ meta_test_native_keyboard_map_set_async (void) + ClutterSeat *seat = meta_backend_get_default_seat (backend); + ClutterKeymap *keymap = clutter_seat_get_keymap (seat); + g_autoptr (ClutterVirtualInputDevice) virtual_keyboard = NULL; +- struct xkb_keymap *xkb_keymap = meta_backend_get_keymap (backend); ++ struct xkb_keymap *xkb_keymap = meta_backend_get_xkb_keymap (backend); + xkb_mod_mask_t alt_mask = + 1 << xkb_keymap_mod_get_index (xkb_keymap, XKB_MOD_NAME_ALT); + ModMaskTuple expected_mods = { alt_mask, 0, 0 }; +@@ -234,12 +234,12 @@ meta_test_native_keyboard_map_set_async (void) + meta_backend_set_keymap_async (backend, keymap_description, 0, + NULL, set_keymap_cb, &done); + +- g_assert_true (xkb_keymap == meta_backend_get_keymap (backend)); ++ g_assert_true (xkb_keymap == meta_backend_get_xkb_keymap (backend)); + + while (!done || expected_next_handler) + g_main_context_iteration (NULL, TRUE); + +- new_xkb_keymap = meta_backend_get_keymap (backend); ++ new_xkb_keymap = meta_backend_get_xkb_keymap (backend); + g_assert_true (new_xkb_keymap != xkb_keymap); + g_assert_cmpuint (xkb_keymap_num_layouts (new_xkb_keymap), ==, 1); + g_assert_cmpstr (xkb_keymap_layout_get_name (new_xkb_keymap, 0), +@@ -266,7 +266,7 @@ meta_test_native_keyboard_map_change_layout (void) + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + g_autoptr (ClutterVirtualInputDevice) virtual_keyboard = NULL; +- struct xkb_keymap *xkb_keymap = meta_backend_get_keymap (backend); ++ struct xkb_keymap *xkb_keymap = meta_backend_get_xkb_keymap (backend); + g_autoptr (MetaKeymapDescription) keymap_description = NULL; + struct xkb_keymap *new_xkb_keymap; + gboolean done = FALSE; +@@ -289,7 +289,7 @@ meta_test_native_keyboard_map_change_layout (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- new_xkb_keymap = meta_backend_get_keymap (backend); ++ new_xkb_keymap = meta_backend_get_xkb_keymap (backend); + g_assert_true (new_xkb_keymap != xkb_keymap); + g_assert_cmpuint (xkb_keymap_num_layouts (new_xkb_keymap), ==, 2); + g_assert_cmpstr (xkb_keymap_layout_get_name (new_xkb_keymap, 0), +@@ -389,7 +389,7 @@ meta_test_native_keyboard_map_set_layout_index (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- keymap = meta_backend_get_keymap (backend); ++ keymap = meta_backend_get_xkb_keymap (backend); + g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); + g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), + ==, +@@ -443,7 +443,7 @@ meta_test_native_keyboard_map_lock_layout (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- keymap = xkb_keymap_ref (meta_backend_get_keymap (backend)); ++ keymap = xkb_keymap_ref (meta_backend_get_xkb_keymap (backend)); + g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); + g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), + ==, +@@ -477,7 +477,7 @@ meta_test_native_keyboard_map_lock_layout (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- g_assert_true (keymap == meta_backend_get_keymap (backend)); ++ g_assert_true (keymap == meta_backend_get_xkb_keymap (backend)); + + /* + * Set the same keymap with a different layout index. Should take effect. +@@ -521,7 +521,7 @@ meta_test_native_keyboard_map_lock_layout (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- keymap = meta_backend_get_keymap (backend); ++ keymap = meta_backend_get_xkb_keymap (backend); + g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 1); + g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), + ==, +@@ -538,7 +538,7 @@ meta_test_native_keyboard_map_lock_layout (void) + while (!done) + g_main_context_iteration (NULL, TRUE); + +- keymap = meta_backend_get_keymap (backend); ++ keymap = meta_backend_get_xkb_keymap (backend); + g_assert_cmpuint (xkb_keymap_num_layouts (keymap), ==, 2); + g_assert_cmpstr (xkb_keymap_layout_get_name (keymap, 0), + ==, +diff --git a/src/wayland/meta-wayland-keyboard.c b/src/wayland/meta-wayland-keyboard.c +index 451c9d834d..a6329785fd 100644 +--- a/src/wayland/meta-wayland-keyboard.c ++++ b/src/wayland/meta-wayland-keyboard.c +@@ -209,7 +209,8 @@ on_keymap_changed (MetaBackend *backend, + { + MetaWaylandKeyboard *keyboard = data; + +- meta_wayland_keyboard_take_keymap (keyboard, meta_backend_get_keymap (backend)); ++ meta_wayland_keyboard_take_keymap (keyboard, ++ meta_backend_get_xkb_keymap (backend)); + } + + static void +@@ -454,7 +455,8 @@ meta_wayland_keyboard_enable (MetaWaylandKeyboard *keyboard) + g_signal_connect (backend, "keymap-layout-group-changed", + G_CALLBACK (on_keymap_layout_group_changed), keyboard); + +- meta_wayland_keyboard_take_keymap (keyboard, meta_backend_get_keymap (backend)); ++ meta_wayland_keyboard_take_keymap (keyboard, ++ meta_backend_get_xkb_keymap (backend)); + + meta_wayland_keyboard_set_focus (keyboard, seat->input_focus); + } +-- +2.54.0 + + +From 5f7591a2fd4e362ed50484dceaefec8cddffec56 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 19 May 2026 23:50:29 +0200 +Subject: [PATCH 31/32] Let X11 backend gracefully handle rules keymap + descriptions + +This means ones set from remote desktop servers wont work, but this is +not supported anyway, so lets not bother. +--- + .../meta-keymap-description-private.h | 6 ++ + src/backends/meta-keymap-description.c | 24 ++++++++ + src/backends/x11/cm/meta-backend-x11-cm.c | 61 +++++++++---------- + src/backends/x11/meta-backend-x11.c | 6 +- + src/backends/x11/meta-keymap-x11.c | 3 +- + .../x11/nested/meta-backend-x11-nested.c | 20 ++---- + 6 files changed, 69 insertions(+), 51 deletions(-) + +diff --git a/src/backends/meta-keymap-description-private.h b/src/backends/meta-keymap-description-private.h +index f53092cd27..616bc94b2c 100644 +--- a/src/backends/meta-keymap-description-private.h ++++ b/src/backends/meta-keymap-description-private.h +@@ -63,3 +63,9 @@ void meta_keymap_description_reset_owner (MetaKeymapDescription *keymap_des + MetaKeymapDescriptionOwner * meta_keymap_description_get_owner (MetaKeymapDescription *keymap_description); + + MetaKeymapDescriptionOwner * meta_keymap_description_resets_owner (MetaKeymapDescription *keymap_description); ++ ++gboolean meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, ++ const char **model, ++ const char **layout, ++ const char **variant, ++ const char **options); +diff --git a/src/backends/meta-keymap-description.c b/src/backends/meta-keymap-description.c +index 4d077626df..29b1ad2c16 100644 +--- a/src/backends/meta-keymap-description.c ++++ b/src/backends/meta-keymap-description.c +@@ -386,3 +386,27 @@ meta_keymap_description_resets_owner (MetaKeymapDescription *keymap_description) + { + return keymap_description->resets_owner; + } ++ ++gboolean ++meta_keymap_description_get_rules (MetaKeymapDescription *keymap_description, ++ const char **model, ++ const char **layout, ++ const char **variant, ++ const char **options) ++{ ++ switch (keymap_description->source) ++ { ++ case META_KEYMAP_DESCRIPTION_SOURCE_RULES: ++ { ++ *model = keymap_description->rules.model; ++ *layout = keymap_description->rules.layout; ++ *variant = keymap_description->rules.variant; ++ *options = keymap_description->rules.options; ++ return TRUE; ++ } ++ case META_KEYMAP_DESCRIPTION_SOURCE_FD: ++ return FALSE; ++ } ++ ++ g_assert_not_reached (); ++} +diff --git a/src/backends/x11/cm/meta-backend-x11-cm.c b/src/backends/x11/cm/meta-backend-x11-cm.c +index bc8ad6dfe2..18daf62ac1 100644 +--- a/src/backends/x11/cm/meta-backend-x11-cm.c ++++ b/src/backends/x11/cm/meta-backend-x11-cm.c +@@ -386,42 +386,42 @@ apply_keymap (MetaBackendX11 *x11) + } + + static void +-meta_backend_x11_cm_set_keymap_async (MetaBackend *backend, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GTask *task) ++meta_backend_x11_cm_set_keymap_async (MetaBackend *backend, ++ MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, ++ GTask *task) + { + MetaBackendX11 *x11 = META_BACKEND_X11 (backend); + MetaBackendX11Cm *x11_cm = META_BACKEND_X11_CM (x11); ++ Display *xdisplay = meta_backend_x11_get_xdisplay (x11); ++ const char *models; ++ const char *layouts; ++ const char *variants; ++ const char *options; ++ gboolean changed = FALSE; ++ ++ if (!meta_keymap_description_get_rules (description, ++ &models, ++ &layouts, ++ &variants, ++ &options)) ++ { ++ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "X11 backend cant set layout from file descriptor"); ++ g_object_unref (task); ++ return; ++ } + +- g_free (x11_cm->keymap_layouts); +- x11_cm->keymap_layouts = g_strdup (layouts); +- g_free (x11_cm->keymap_variants); +- x11_cm->keymap_variants = g_strdup (variants); +- g_free (x11_cm->keymap_options); +- x11_cm->keymap_options = g_strdup (options); +- g_free (x11_cm->keymap_model); +- x11_cm->keymap_model = g_strdup (model); +- +- apply_keymap (x11); ++ changed |= g_set_str (&x11_cm->keymap_layouts, layouts); ++ changed |= g_set_str (&x11_cm->keymap_variants, variants); ++ changed |= g_set_str (&x11_cm->keymap_options, options); ++ changed |= g_set_str (&x11_cm->keymap_model, models); + +- g_task_return_boolean (task, TRUE); +- g_object_unref (task); +-} +- +-static void +-meta_backend_x11_cm_set_keymap_layout_group_async (MetaBackend *backend, +- xkb_layout_index_t idx, +- GTask *task) +-{ +- MetaBackendX11 *x11 = META_BACKEND_X11 (backend); +- MetaBackendX11Cm *x11_cm = META_BACKEND_X11_CM (x11); +- Display *xdisplay = meta_backend_x11_get_xdisplay (x11); ++ if (changed) ++ apply_keymap (x11); + +- x11_cm->locked_group = idx; +- XkbLockGroup (xdisplay, XkbUseCoreKbd, idx); ++ x11_cm->locked_group = layout_index; ++ XkbLockGroup (xdisplay, XkbUseCoreKbd, layout_index); + + g_task_return_boolean (task, TRUE); + g_object_unref (task); +@@ -567,7 +567,6 @@ meta_backend_x11_cm_class_init (MetaBackendX11CmClass *klass) + backend_class->update_stage = meta_backend_x11_cm_update_stage; + backend_class->select_stage_events = meta_backend_x11_cm_select_stage_events; + backend_class->set_keymap_async = meta_backend_x11_cm_set_keymap_async; +- backend_class->set_keymap_layout_group_async = meta_backend_x11_cm_set_keymap_layout_group_async; + + backend_x11_class->handle_host_xevent = meta_backend_x11_cm_handle_host_xevent; + backend_x11_class->translate_device_event = meta_backend_x11_cm_translate_device_event; +diff --git a/src/backends/x11/meta-backend-x11.c b/src/backends/x11/meta-backend-x11.c +index 9e119f2c93..59fa358d30 100644 +--- a/src/backends/x11/meta-backend-x11.c ++++ b/src/backends/x11/meta-backend-x11.c +@@ -876,7 +876,7 @@ meta_backend_x11_get_current_logical_monitor (MetaBackend *backend) + } + + static struct xkb_keymap * +-meta_backend_x11_get_keymap (MetaBackend *backend) ++meta_backend_x11_get_xkb_keymap (MetaBackend *backend) + { + MetaBackendX11 *x11 = META_BACKEND_X11 (backend); + MetaBackendX11Private *priv = meta_backend_x11_get_instance_private (x11); +@@ -931,7 +931,7 @@ init_xkb_state (MetaBackendX11 *x11) + int32_t device_id; + struct xkb_state *state; + +- keymap = meta_backend_get_keymap (META_BACKEND (x11)); ++ keymap = meta_backend_get_xkb_keymap (META_BACKEND (x11)); + + device_id = xkb_x11_get_core_keyboard_device_id (priv->xcb); + state = xkb_x11_state_new_from_device (keymap, priv->xcb, device_id); +@@ -1096,7 +1096,7 @@ meta_backend_x11_class_init (MetaBackendX11Class *klass) + backend_class->ungrab_keyboard = meta_backend_x11_ungrab_keyboard; + backend_class->finish_touch_sequence = meta_backend_x11_finish_touch_sequence; + backend_class->get_current_logical_monitor = meta_backend_x11_get_current_logical_monitor; +- backend_class->get_keymap = meta_backend_x11_get_keymap; ++ backend_class->get_xkb_keymap = meta_backend_x11_get_xkb_keymap; + backend_class->get_keymap_layout_group = meta_backend_x11_get_keymap_layout_group; + } + +diff --git a/src/backends/x11/meta-keymap-x11.c b/src/backends/x11/meta-keymap-x11.c +index 01107af07c..cdcb69ac45 100644 +--- a/src/backends/x11/meta-keymap-x11.c ++++ b/src/backends/x11/meta-keymap-x11.c +@@ -227,7 +227,8 @@ update_modifiers (MetaKeymapX11 *keymap_x11, + keymap_x11->current_group, + xkb_event->state.base_mods, + xkb_event->state.latched_mods, +- xkb_event->state.locked_mods); ++ xkb_event->state.locked_mods, ++ TRUE); + + if (num_lock_state != old_num_lock_state) + { +diff --git a/src/backends/x11/nested/meta-backend-x11-nested.c b/src/backends/x11/nested/meta-backend-x11-nested.c +index 24377a90be..5cf7fe8667 100644 +--- a/src/backends/x11/nested/meta-backend-x11-nested.c ++++ b/src/backends/x11/nested/meta-backend-x11-nested.c +@@ -149,21 +149,10 @@ meta_backend_x11_nested_select_stage_events (MetaBackend *backend) + } + + static void +-meta_backend_x11_nested_set_keymap_async (MetaBackend *backend, +- const char *layouts, +- const char *variants, +- const char *options, +- const char *model, +- GTask *task) +-{ +- g_task_return_boolean (task, TRUE); +- g_object_unref (task); +-} +- +-static void +-meta_backend_x11_nested_set_keymap_layout_group_async (MetaBackend *backend, +- xkb_layout_index_t idx, +- GTask *task) ++meta_backend_x11_nested_set_keymap_async (MetaBackend *backend, ++ MetaKeymapDescription *description, ++ xkb_layout_index_t layout_index, ++ GTask *task) + { + g_task_return_boolean (task, TRUE); + g_object_unref (task); +@@ -292,7 +281,6 @@ meta_backend_x11_nested_class_init (MetaBackendX11NestedClass *klass) + backend_class->update_stage = meta_backend_x11_nested_update_stage; + backend_class->select_stage_events = meta_backend_x11_nested_select_stage_events; + backend_class->set_keymap_async = meta_backend_x11_nested_set_keymap_async; +- backend_class->set_keymap_layout_group_async = meta_backend_x11_nested_set_keymap_layout_group_async; + backend_class->is_lid_closed = meta_backend_x11_nested_is_lid_closed; + backend_class->set_pointer_constraint = meta_backend_x11_nested_set_pointer_constraint; + +-- +2.54.0 + + +From 2eea8d03d233a5c20078baaf5fba1aec566ce614 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 20 May 2026 00:05:45 +0200 +Subject: [PATCH 32/32] mdk/session: Don't depend on newer GTK + +A symbol needed for keymap changes doesn't exist in RHEL10, so lets +build without it, and fall back to using layout index 0. +--- + mdk/mdk-session.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/mdk/mdk-session.c b/mdk/mdk-session.c +index 239d5dacd9..bf253b1b91 100644 +--- a/mdk/mdk-session.c ++++ b/mdk/mdk-session.c +@@ -140,7 +140,12 @@ maybe_sync_keymap (MdkSession *session) + } + + xkb_keymap = gdk_wayland_device_get_xkb_keymap (keyboard); ++#if GTK_CHECK_VERSION (4,18,0) + layout_index = gdk_device_get_active_layout_index (keyboard); ++#else ++ g_warning_once ("Only able to set the first layout"); ++ layout_index = 0; ++#endif + + if (xkb_keymap == session->xkb_keymap && + layout_index == session->layout_index) +@@ -208,7 +213,11 @@ maybe_sync_keymap (MdkSession *session) + return; + } + ++#if GTK_CHECK_VERSION (4,18,0) + layout_index = gdk_device_get_active_layout_index (keyboard); ++#else ++ layout_index = 0; ++#endif + + g_variant_builder_init (&options_builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&options_builder, "{sv}", +-- +2.54.0 +