diff --git a/0001-gobject-utils-Log-when-executing-deferred-tasks.patch b/0001-gobject-utils-Log-when-executing-deferred-tasks.patch new file mode 100644 index 0000000..a83c690 --- /dev/null +++ b/0001-gobject-utils-Log-when-executing-deferred-tasks.patch @@ -0,0 +1,116 @@ +From ab8482bab7981321f0f2fbd401907ac34028bb1a Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 20 Apr 2021 09:27:01 -0400 +Subject: [PATCH 1/4] gobject-utils: Log when executing deferred tasks + +At the moment, the code defers execution until "later" in +various parts of the code to ensure a flood of related events +doesn't lead to a flood of duplicated work. + +But, its on the called code to log at the moment. + +This commit adds logging to the generic part of the code to +for clarity. +--- + compositor/kiosk-gobject-utils.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/compositor/kiosk-gobject-utils.c b/compositor/kiosk-gobject-utils.c +index a0dfcd8..c38db7e 100644 +--- a/compositor/kiosk-gobject-utils.c ++++ b/compositor/kiosk-gobject-utils.c +@@ -1,83 +1,87 @@ + #include "config.h" + #include "kiosk-gobject-utils.h" + + #define COALESCE_INTERVAL 250 /* milliseconds */ + + static void + on_task_wait_complete (GObject *self, + GTask *task) + { + KioskObjectCallback callback; + gpointer user_data; + gboolean completed; + g_autofree char *data_key = NULL; + ++ g_debug ("KioskGObjectUtils: Executing deferred task '%s'", g_task_get_name (task)); ++ + callback = g_object_get_data (G_OBJECT (task), "callback"); + user_data = g_object_get_data (G_OBJECT (task), "user-data"); + + completed = g_task_propagate_boolean (task, NULL); + + if (completed) { + callback (self, user_data); + } + + data_key = g_strdup_printf ("kiosk-gobject-utils-%p-%p-task", + callback, user_data); + + g_object_set_data (G_OBJECT (self), data_key, NULL); + } + + static gboolean + on_coalesce_timeout (GTask *task) + { + if (!g_task_return_error_if_cancelled (task)) { + g_task_return_boolean (task, TRUE); + } + + return G_SOURCE_REMOVE; + } + + void + kiosk_gobject_utils_queue_defer_callback (GObject *self, + const char *name, + GCancellable *cancellable, + KioskObjectCallback callback, + gpointer user_data) + { + g_autofree char *data_key = NULL; + g_autoptr (GSource) timeout_source = NULL; + GTask *task; + + g_return_if_fail (G_IS_OBJECT (self)); + g_return_if_fail (callback != NULL); + + data_key = g_strdup_printf ("kiosk-gobject-utils-%p-%p-task", + callback, user_data); + + task = g_object_get_data (G_OBJECT (self), data_key); + + if (task != NULL) { + return; + } + + timeout_source = g_timeout_source_new (COALESCE_INTERVAL); + +- if (name != NULL) { +- g_source_set_name (timeout_source, name); +- } +- + task = g_task_new (self, + cancellable, + (GAsyncReadyCallback) on_task_wait_complete, + NULL); ++ ++ if (name != NULL) { ++ g_task_set_name (task, name); ++ g_debug ("KioskGObjectUtils: Deferring task '%s' for %dms", name, COALESCE_INTERVAL); ++ } ++ + g_task_attach_source (task, timeout_source, G_SOURCE_FUNC (on_coalesce_timeout)); + + g_object_set_data (G_OBJECT (task), "callback", callback); + g_object_set_data (G_OBJECT (task), "user-data", user_data); + + g_object_set_data_full (G_OBJECT (self), + data_key, + task, + (GDestroyNotify) + g_object_unref); + } +-- +2.30.2 + diff --git a/0002-input-sources-manager-Fix-overzealous-rename-mistake.patch b/0002-input-sources-manager-Fix-overzealous-rename-mistake.patch index d4c1624..4eda3ca 100644 --- a/0002-input-sources-manager-Fix-overzealous-rename-mistake.patch +++ b/0002-input-sources-manager-Fix-overzealous-rename-mistake.patch @@ -1,4 +1,4 @@ -From 840bac654aef7aa0e241be8ac0052f5a5c67e4d5 Mon Sep 17 00:00:00 2001 +From f553708a756682d625f51e7c0f3d2a31f39442bb Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 15 Apr 2021 14:39:55 -0400 Subject: [PATCH 2/4] input-sources-manager: Fix overzealous rename mistake @@ -184,5 +184,5 @@ index 58d7a4c..a1a4cfa 100644 old_input_source_group = kiosk_input_sources_manager_get_selected_input_source_group (self); -- -2.31.1 +2.30.2 diff --git a/0003-compositor-Add-signal-for-reporting-X-server-events.patch b/0003-compositor-Add-signal-for-reporting-X-server-events.patch new file mode 100644 index 0000000..8a9cb41 --- /dev/null +++ b/0003-compositor-Add-signal-for-reporting-X-server-events.patch @@ -0,0 +1,229 @@ +From a944f0d27a42028ec18edb17f65957780c400104 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 15 Apr 2021 13:28:00 -0400 +Subject: [PATCH 3/4] compositor: Add signal for reporting X server events + +The keyboard layout handling code currently doesn't notice +when the keyboard layout is changed using libxklavier, out from +under it. + +As a first step toward fixing that problem, this commit adds a +new signal "x-server-event" to KioskCompositor, so that the +InputSourcesManager can watch for root window property changes. +--- + compositor/kiosk-compositor.c | 22 +++++++++++++++++++++- + 1 file changed, 21 insertions(+), 1 deletion(-) + +diff --git a/compositor/kiosk-compositor.c b/compositor/kiosk-compositor.c +index dad7776..14f5de3 100644 +--- a/compositor/kiosk-compositor.c ++++ b/compositor/kiosk-compositor.c +@@ -9,60 +9,67 @@ + #include + #include + #include + #include + #include + #include + #include + + #include "kiosk-backgrounds.h" + #include "kiosk-input-sources-manager.h" + #include "kiosk-service.h" + + #include "org.gnome.DisplayManager.Manager.h" + + struct _KioskCompositor + { + MetaPlugin parent; + + /* weak references */ + MetaDisplay *display; + ClutterBackend *backend; + ClutterActor *stage; + + /* strong references */ + GCancellable *cancellable; + KioskBackgrounds *backgrounds; + KioskInputSourcesManager *input_sources_manager; + KioskService *service; + }; + ++enum { ++ X_SERVER_EVENT, ++ NUMBER_OF_SIGNALS ++}; ++ ++static guint signals [NUMBER_OF_SIGNALS] = { 0, }; ++ + G_DEFINE_TYPE (KioskCompositor, kiosk_compositor, META_TYPE_PLUGIN) + + static void kiosk_compositor_dispose (GObject *object); + + static void + kiosk_compositor_dispose (GObject *object) + { + KioskCompositor *self = KIOSK_COMPOSITOR (object); + + if (self->cancellable != NULL) { + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + } + + g_clear_weak_pointer (&self->stage); + g_clear_weak_pointer (&self->display); + g_clear_weak_pointer (&self->backend); + + g_clear_object (&self->backgrounds); + + G_OBJECT_CLASS (kiosk_compositor_parent_class)->dispose (object); + } + + static void + register_with_display_manager (KioskCompositor *self) + { + g_autoptr (GDBusConnection) system_bus = NULL; + g_autoptr (GdmManager) display_manager = NULL; + GVariantBuilder builder; + g_autoptr (GError) error = NULL; +@@ -329,62 +336,64 @@ kiosk_compositor_show_tile_preview (MetaPlugin *plugin, + g_assert (META_PLUGIN_CLASS (kiosk_compositor_parent_class)->show_tile_preview == NULL); + } + + static void + kiosk_compositor_hide_tile_preview (MetaPlugin *plugin) + { + g_assert (META_PLUGIN_CLASS (kiosk_compositor_parent_class)->hide_tile_preview == NULL); + } + + static void + kiosk_compositor_show_window_menu (MetaPlugin *plugin, + MetaWindow *window, + MetaWindowMenuType menu, + int x, + int y) + { + g_assert (META_PLUGIN_CLASS (kiosk_compositor_parent_class)->show_window_menu == NULL); + } + + static void + kiosk_compositor_show_window_menu_for_rect (MetaPlugin *plugin, + MetaWindow *window, + MetaWindowMenuType menu, + MetaRectangle *rect) + { + g_assert (META_PLUGIN_CLASS (kiosk_compositor_parent_class)->show_window_menu_for_rect == NULL); + } + + static gboolean + kiosk_compositor_xevent_filter (MetaPlugin *plugin, +- XEvent *xev) ++ XEvent *x_server_event) + { ++ KioskCompositor *self = KIOSK_COMPOSITOR (plugin); ++ g_signal_emit (G_OBJECT (self), signals[X_SERVER_EVENT], 0, x_server_event); + return FALSE; + } + + static gboolean + kiosk_compositor_keybinding_filter (MetaPlugin *plugin, + MetaKeyBinding *binding) + { + return FALSE; + } + + static void + kiosk_compositor_confirm_display_change (MetaPlugin *plugin) + { + KioskCompositor *self = KIOSK_COMPOSITOR (plugin); + + meta_plugin_complete_display_change (META_PLUGIN (self), TRUE); + } + + static const MetaPluginInfo info = { + .name = "GNOME Kiosk", + .version = VERSION, + .author = "Various", + .license = "GPLv2+", + .description = "Provides Kiosk compositor plugin for mutter" + }; + + static const MetaPluginInfo * + kiosk_compositor_plugin_info (MetaPlugin *plugin) + { + +@@ -420,60 +429,71 @@ kiosk_compositor_class_init (KioskCompositorClass *compositor_class) + + plugin_class->start = kiosk_compositor_start; + plugin_class->map = kiosk_compositor_map; + plugin_class->minimize = kiosk_compositor_minimize; + plugin_class->unminimize = kiosk_compositor_unminimize; + plugin_class->size_changed = kiosk_compositor_size_changed; + plugin_class->size_change = kiosk_compositor_size_change; + plugin_class->destroy = kiosk_compositor_destroy; + + plugin_class->switch_workspace = kiosk_compositor_switch_workspace; + + plugin_class->kill_window_effects = kiosk_compositor_kill_window_effects; + plugin_class->kill_switch_workspace = kiosk_compositor_kill_switch_workspace; + + plugin_class->show_tile_preview = kiosk_compositor_show_tile_preview; + plugin_class->hide_tile_preview = kiosk_compositor_hide_tile_preview; + plugin_class->show_window_menu = kiosk_compositor_show_window_menu; + plugin_class->show_window_menu_for_rect = kiosk_compositor_show_window_menu_for_rect; + + plugin_class->xevent_filter = kiosk_compositor_xevent_filter; + plugin_class->keybinding_filter = kiosk_compositor_keybinding_filter; + + plugin_class->confirm_display_change = kiosk_compositor_confirm_display_change; + + plugin_class->plugin_info = kiosk_compositor_plugin_info; + + plugin_class->create_close_dialog = kiosk_compositor_create_close_dialog; + plugin_class->create_inhibit_shortcuts_dialog = kiosk_compositor_create_inhibit_shortcuts_dialog; + + plugin_class->locate_pointer = kiosk_compositor_locate_pointer; ++ ++ signals [X_SERVER_EVENT] = ++ g_signal_new ("x-server-event", ++ G_TYPE_FROM_CLASS (object_class), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, ++ NULL, ++ g_cclosure_marshal_VOID__POINTER, ++ G_TYPE_NONE, ++ 1, G_TYPE_POINTER); + } + + static void + kiosk_compositor_init (KioskCompositor *compositor) + { + g_debug ("KioskCompositor: Initializing"); + } + + KioskBackgrounds * + kiosk_compositor_get_backgrounds (KioskCompositor *self) + { + g_return_val_if_fail (KIOSK_IS_COMPOSITOR (self), NULL); + + return KIOSK_BACKGROUNDS (self->backgrounds); + } + + KioskInputSourcesManager * + kiosk_compositor_get_input_sources_manager (KioskCompositor *self) + { + g_return_val_if_fail (KIOSK_IS_COMPOSITOR (self), NULL); + + return KIOSK_INPUT_SOURCES_MANAGER (self->input_sources_manager); + } + + KioskService * + kiosk_compositor_get_service (KioskCompositor *self) + { + g_return_val_if_fail (KIOSK_IS_COMPOSITOR (self), NULL); + + return KIOSK_SERVICE (self->service); +-- +2.30.2 + diff --git a/0004-input-sources-manager-Support-libxklavier-managed-ke.patch b/0004-input-sources-manager-Support-libxklavier-managed-ke.patch new file mode 100644 index 0000000..e365d28 --- /dev/null +++ b/0004-input-sources-manager-Support-libxklavier-managed-ke.patch @@ -0,0 +1,1490 @@ +From 5b23d5f86f554373d4207d4f95c50bc86892e634 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 15 Apr 2021 14:43:17 -0400 +Subject: [PATCH 4/4] input-sources-manager: Support libxklavier managed + keyboard layouts + +Mutter currently doesn't allow libxklavier to change the layout out from +under it. + +This commit fixes that on X being careful to make sure the current layout +state, as gnome-kiosk sees it, stays in sync with X server state. +--- + compositor/kiosk-input-source-group.c | 47 +- + compositor/kiosk-input-source-group.h | 5 +- + compositor/kiosk-input-sources-manager.c | 143 ++++++ + compositor/kiosk-input-sources-manager.h | 2 + + compositor/kiosk-x-keyboard-manager.c | 565 +++++++++++++++++++++++ + compositor/kiosk-x-keyboard-manager.h | 29 ++ + meson.build | 1 + + 7 files changed, 786 insertions(+), 6 deletions(-) + create mode 100644 compositor/kiosk-x-keyboard-manager.c + create mode 100644 compositor/kiosk-x-keyboard-manager.h + +diff --git a/compositor/kiosk-input-source-group.c b/compositor/kiosk-input-source-group.c +index a0c924e..c33ca05 100644 +--- a/compositor/kiosk-input-source-group.c ++++ b/compositor/kiosk-input-source-group.c +@@ -1,67 +1,67 @@ + #include "config.h" + #include "kiosk-input-source-group.h" + + #include + #include + + #include + + #include + + #define GNOME_DESKTOP_USE_UNSTABLE_API + #include + #include + + #include "kiosk-gobject-utils.h" + #include "kiosk-input-sources-manager.h" + + #define KIOSK_INPUT_SOURCE_GROUP_MAX_LAYOUTS 3 + + struct _KioskInputSourceGroup + { + GObject parent; + + /* weak references */ + KioskInputSourcesManager *input_sources_manager; + KioskInputEngineManager *input_engine_manager; ++ KioskXKeyboardManager *x_keyboard_manager; + + /* strong references */ + char *input_engine_name; + GPtrArray *layouts; + GPtrArray *variants; + char *options; + + /* state */ + xkb_layout_index_t layout_index; + }; +- + enum + { + PROP_INPUT_SOURCES_MANAGER = 1, + NUMBER_OF_PROPERTIES + }; + + static GParamSpec *kiosk_input_source_group_properties[NUMBER_OF_PROPERTIES] = { NULL, }; + + G_DEFINE_TYPE (KioskInputSourceGroup, kiosk_input_source_group, G_TYPE_OBJECT) + + static void kiosk_input_source_group_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *param_spec); + static void kiosk_input_source_group_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *param_spec); + + static void kiosk_input_source_group_constructed (GObject *object); + static void kiosk_input_source_group_dispose (GObject *object); + + KioskInputSourceGroup * + kiosk_input_source_group_new (KioskInputSourcesManager *input_sources_manager) + { + GObject *object; + + object = g_object_new (KIOSK_TYPE_INPUT_SOURCE_GROUP, + "input-sources-manager", input_sources_manager, + NULL); +@@ -213,121 +213,156 @@ kiosk_input_source_group_set_input_engine (KioskInputSourceGroup *self, + const char *engine_name) + { + g_debug ("KioskInputSourceGroup: Setting input engine to '%s'", engine_name); + + g_free (self->input_engine_name); + self->input_engine_name = g_strdup (engine_name); + + g_ptr_array_set_size (self->layouts, 0); + g_ptr_array_set_size (self->variants, 0); + + g_ptr_array_add (self->layouts, NULL); + g_ptr_array_add (self->variants, NULL); + + return TRUE; + } + + const char * + kiosk_input_source_group_get_input_engine (KioskInputSourceGroup *self) + { + return self->input_engine_name; + } + + void + kiosk_input_source_group_set_options (KioskInputSourceGroup *self, + const char *options) + { + g_free (self->options); + self->options = g_strdup (options); + } + ++const char * ++kiosk_input_source_group_get_options (KioskInputSourceGroup *self) ++{ ++ return self->options; ++} ++ + gboolean + kiosk_input_source_group_activate (KioskInputSourceGroup *self) + { + size_t number_of_layouts; + g_autofree char *layouts = NULL; + g_autofree char *variants = NULL; ++ gboolean keymap_already_set = FALSE; ++ gboolean layout_group_already_locked = FALSE; + + g_debug ("KioskInputSourceGroup: Activating input source"); + + if (self->input_engine_name != NULL) { + kiosk_input_source_group_ensure_layout_for_input_engine (self); + } + + number_of_layouts = kiosk_input_source_group_get_number_of_layouts (self); + + if (number_of_layouts == 0) { + return FALSE; + } + + layouts = g_strjoinv (",", (GStrv) self->layouts->pdata); + variants = g_strjoinv (",", (GStrv) self->variants->pdata); + + if (self->input_engine_name != NULL) { + gboolean activated; + + activated = kiosk_input_engine_manager_activate_engine (self->input_engine_manager, self->input_engine_name); + + if (!activated) { + g_debug ("KioskInputSourceGroup: Could not activate input engine '%s'", self->input_engine_name); + return FALSE; + } + } else { + kiosk_input_engine_manager_activate_engine (self->input_engine_manager, NULL); + } + +- g_debug ("KioskInputSourceGroup: Setting keyboard mapping to [%s] (%s) [%s]", +- layouts, variants, self->options); ++ if (self->x_keyboard_manager != NULL) { ++ keymap_already_set = kiosk_x_keyboard_manager_keymap_is_active (self->x_keyboard_manager, (const char * const *) self->layouts->pdata, (const char * const *) self->variants->pdata, self->options); ++ layout_group_already_locked = kiosk_x_keyboard_manager_layout_group_is_locked (self->x_keyboard_manager, self->layout_index); + +- meta_backend_set_keymap (meta_get_backend (), layouts, variants, self->options); +- meta_backend_lock_layout_group (meta_get_backend (), self->layout_index); ++ } ++ ++ if (!keymap_already_set) { ++ g_debug ("KioskInputSourceGroup: Setting keyboard mapping to [%s] (%s) [%s]", ++ layouts, variants, self->options); ++ ++ meta_backend_set_keymap (meta_get_backend (), layouts, variants, self->options); ++ } ++ ++ if (!layout_group_already_locked) { ++ g_debug ("KioskInputSourceGroup: Locking layout to index %d", self->layout_index); ++ meta_backend_lock_layout_group (meta_get_backend (), self->layout_index); ++ } ++ ++ if (keymap_already_set && layout_group_already_locked) { ++ g_debug ("KioskInputSourceGroup: Input source already active"); ++ } + + return TRUE; + } + + static ssize_t + get_index_of_layout (KioskInputSourceGroup *self, + const char *layout_name) + { + g_auto (GStrv) layouts; + size_t i; + + layouts = kiosk_input_source_group_get_layouts (self); + for (i = 0; layouts[i] != NULL; i++) { + if (g_strcmp0 (layout_name, layouts[i]) == 0) { + return (ssize_t) i; + } + } + + return -1; + } + ++gboolean ++kiosk_input_source_group_only_has_layouts (KioskInputSourceGroup *self, ++ const char * const *layouts_to_check) ++{ ++ g_auto (GStrv) layouts; ++ ++ layouts = kiosk_input_source_group_get_layouts (self); ++ ++ return g_strv_equal (layouts_to_check, (const char * const *) layouts); ++} ++ + gboolean + kiosk_input_source_group_switch_to_layout (KioskInputSourceGroup *self, + const char *layout_name) + { + g_autofree char *active_layout = NULL; + ssize_t layout_index; + + layout_index = get_index_of_layout (self, layout_name); + + if (layout_index < 0) { + return FALSE; + } + + g_debug ("KioskInputSourceGroup: Switching to layout %s", layout_name); + + active_layout = kiosk_input_source_group_get_selected_layout (self); + + self->layout_index = layout_index; + + g_debug ("KioskInputSourceGroup: Switching from layout '%s' to next layout '%s'", + active_layout, layout_name); + + meta_backend_lock_layout_group (meta_get_backend (), self->layout_index); + + return TRUE; + } + + void + kiosk_input_source_group_switch_to_first_layout (KioskInputSourceGroup *self) + { +@@ -492,49 +527,51 @@ kiosk_input_source_group_get_property (GObject *object, + GValue *value, + GParamSpec *param_spec) + { + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); + break; + } + } + + static void + kiosk_input_source_group_init (KioskInputSourceGroup *self) + { + g_debug ("KioskInputSourceGroup: Initializing"); + + self->layouts = g_ptr_array_new_full (KIOSK_INPUT_SOURCE_GROUP_MAX_LAYOUTS + 1, g_free); + self->variants = g_ptr_array_new_full (KIOSK_INPUT_SOURCE_GROUP_MAX_LAYOUTS + 1, g_free); + + g_ptr_array_add (self->layouts, NULL); + g_ptr_array_add (self->variants, NULL); + } + + static void + kiosk_input_source_group_constructed (GObject *object) + { + KioskInputSourceGroup *self = KIOSK_INPUT_SOURCE_GROUP (object); + + G_OBJECT_CLASS (kiosk_input_source_group_parent_class)->constructed (object); + + g_set_weak_pointer (&self->input_engine_manager, kiosk_input_sources_manager_get_input_engine_manager (self->input_sources_manager)); ++ g_set_weak_pointer (&self->x_keyboard_manager, kiosk_input_sources_manager_get_x_keyboard_manager (self->input_sources_manager)); + } + + static void + kiosk_input_source_group_dispose (GObject *object) + { + KioskInputSourceGroup *self = KIOSK_INPUT_SOURCE_GROUP (object); + + g_debug ("KioskInputSourceGroup: Disposing"); + + g_clear_pointer (&self->options, g_free); + + g_clear_pointer (&self->variants, g_ptr_array_unref); + g_clear_pointer (&self->layouts, g_ptr_array_unref); + ++ g_clear_weak_pointer (&self->x_keyboard_manager); + g_clear_weak_pointer (&self->input_engine_manager); + g_clear_weak_pointer (&self->input_sources_manager); + + G_OBJECT_CLASS (kiosk_input_source_group_parent_class)->dispose (object); + } +diff --git a/compositor/kiosk-input-source-group.h b/compositor/kiosk-input-source-group.h +index cec8b2f..d255da4 100644 +--- a/compositor/kiosk-input-source-group.h ++++ b/compositor/kiosk-input-source-group.h +@@ -1,38 +1,41 @@ + #pragma once + + #include + + typedef struct _KioskInputSourcesManager KioskInputSourcesManager; + + G_BEGIN_DECLS + + #define KIOSK_TYPE_INPUT_SOURCE_GROUP (kiosk_input_source_group_get_type ()) + + G_DECLARE_FINAL_TYPE (KioskInputSourceGroup, + kiosk_input_source_group, + KIOSK, INPUT_SOURCE_GROUP, + GObject) + + KioskInputSourceGroup *kiosk_input_source_group_new (KioskInputSourcesManager *manager); + gboolean kiosk_input_source_group_add_layout (KioskInputSourceGroup *input_sources, + const char *layout, + const char *variant); + char *kiosk_input_source_group_get_selected_layout (KioskInputSourceGroup *input_sources); + char **kiosk_input_source_group_get_layouts (KioskInputSourceGroup *input_sources); + + gboolean kiosk_input_source_group_set_input_engine (KioskInputSourceGroup *input_sources, + const char *engine_name); + const char *kiosk_input_source_group_get_input_engine (KioskInputSourceGroup *input_sources); + + void kiosk_input_source_group_set_options (KioskInputSourceGroup *input_sources, +- const char *options); ++ const char *options); ++const char *kiosk_input_source_group_get_options (KioskInputSourceGroup *self); + + gboolean kiosk_input_source_group_activate (KioskInputSourceGroup *input_sources); + ++gboolean kiosk_input_source_group_only_has_layouts (KioskInputSourceGroup *self, ++ const char * const *layouts_to_check); + gboolean kiosk_input_source_group_switch_to_layout (KioskInputSourceGroup *input_sources, + const char *layout_name); + void kiosk_input_source_group_switch_to_first_layout (KioskInputSourceGroup *input_sources); + void kiosk_input_source_group_switch_to_last_layout (KioskInputSourceGroup *input_sources); + gboolean kiosk_input_source_group_switch_to_next_layout (KioskInputSourceGroup *input_sources); + gboolean kiosk_input_source_group_switch_to_previous_layout (KioskInputSourceGroup *input_sources); + G_END_DECLS +diff --git a/compositor/kiosk-input-sources-manager.c b/compositor/kiosk-input-sources-manager.c +index a1a4cfa..7bb67b0 100644 +--- a/compositor/kiosk-input-sources-manager.c ++++ b/compositor/kiosk-input-sources-manager.c +@@ -1,83 +1,85 @@ + #include "config.h" + #include "kiosk-input-sources-manager.h" + + #include + #include + + #include + #include + #include + #include + + #include + #include + + #define GNOME_DESKTOP_USE_UNSTABLE_API + #include + #include + + #include "org.freedesktop.locale1.h" + #include "kiosk-compositor.h" + #include "kiosk-dbus-utils.h" + #include "kiosk-gobject-utils.h" + #include "kiosk-input-engine-manager.h" + #include "kiosk-input-source-group.h" ++#include "kiosk-x-keyboard-manager.h" + + #define SD_LOCALE1_BUS_NAME "org.freedesktop.locale1" + #define SD_LOCALE1_OBJECT_PATH "/org/freedesktop/locale1" + + #define KIOSK_INPUT_SOURCES_SCHEMA "org.gnome.desktop.input-sources" + #define KIOSK_INPUT_SOURCES_SETTING "sources" + #define KIOSK_INPUT_OPTIONS_SETTING "xkb-options" + + #define KIOSK_INPUT_SOURCE_OBJECTS_PATH_PREFIX "/org/gnome/Kiosk/InputSources" + #define KIOSK_KEYBINDINGS_SCHEMA "org.gnome.desktop.wm.keybindings" + #define KIOSK_SWITCH_INPUT_SOURCES_KEYBINDING "switch-input-source" + #define KIOSK_SWITCH_INPUT_SOURCES_BACKWARD_KEYBINDING "switch-input-source-backward" + + #define KIOSK_DBUS_INPUT_SOURCES_MANGER_INPUT_SOURCE_INTERFACE "org.gnome.Kiosk.InputSources.InputSource" + + struct _KioskInputSourcesManager + { + GObject parent; + + /* weak references */ + KioskCompositor *compositor; + MetaDisplay *display; + + KioskDBusInputSourcesManager *dbus_service; + GDBusObjectManagerServer *dbus_object_manager; + + /* strong references */ + GCancellable *cancellable; + KioskInputEngineManager *input_engine_manager; ++ KioskXKeyboardManager *x_keyboard_manager; + SdLocale1 *locale_proxy; + GnomeXkbInfo *xkb_info; + GSettings *input_sources_settings; + GSettings *key_binding_settings; + GPtrArray *input_source_groups; + + /* state */ + ssize_t input_source_groups_index; + + /* flags */ + guint32 overriding_configuration : 1; + }; + + enum + { + PROP_COMPOSITOR = 1, + NUMBER_OF_PROPERTIES + }; + static GParamSpec *kiosk_input_sources_manager_properties[NUMBER_OF_PROPERTIES] = { NULL, }; + + G_DEFINE_TYPE (KioskInputSourcesManager, kiosk_input_sources_manager, G_TYPE_OBJECT) + + static void kiosk_input_sources_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *param_spec); + static void kiosk_input_sources_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *param_spec); +@@ -690,60 +692,66 @@ on_dbus_service_handle_select_input_source (KioskInputSourcesManager *self, + static gboolean + on_dbus_service_handle_select_next_input_source (KioskInputSourcesManager *self, + GDBusMethodInvocation *invocation) + { + g_debug ("KioskService: Handling SelectNextInputSource() call"); + + kiosk_input_sources_manager_switch_to_next_input_source (self); + kiosk_dbus_input_sources_manager_complete_select_next_input_source (self->dbus_service, invocation); + + return TRUE; + } + + static gboolean + on_dbus_service_handle_select_previous_input_source (KioskInputSourcesManager *self, + GDBusMethodInvocation *invocation) + { + g_debug ("KioskService: Handling SelectPreviousInputSource() call"); + + kiosk_input_sources_manager_switch_to_previous_input_source (self); + kiosk_dbus_input_sources_manager_complete_select_previous_input_source (self->dbus_service, invocation); + + return TRUE; + } + + KioskInputEngineManager * + kiosk_input_sources_manager_get_input_engine_manager (KioskInputSourcesManager *self) + { + return self->input_engine_manager; + } + ++KioskXKeyboardManager * ++kiosk_input_sources_manager_get_x_keyboard_manager (KioskInputSourcesManager *self) ++{ ++ return self->x_keyboard_manager; ++} ++ + void + kiosk_input_sources_manager_clear_input_sources (KioskInputSourcesManager *self) + { + g_debug ("KioskInputSourcesManager: Clearing selected keyboard mappings"); + + g_ptr_array_set_size (self->input_source_groups, 0); + self->input_source_groups_index = 0; + } + + static void + kiosk_input_sources_manager_add_input_source_group (KioskInputSourcesManager *self, + KioskInputSourceGroup *input_source_group) + { + g_ptr_array_add (self->input_source_groups, g_object_ref (input_source_group)); + } + + static KioskInputSourceGroup * + kiosk_input_sources_manager_add_new_input_source_group (KioskInputSourcesManager *self, + const char *options) + { + g_autoptr (KioskInputSourceGroup) input_source_group = NULL; + + g_debug ("KioskInputSourcesManager: Adding new, empty keyboard mapping with options '%s'", + options); + + input_source_group = kiosk_input_source_group_new (self); + kiosk_input_source_group_set_options (input_source_group, options); + + kiosk_input_sources_manager_add_input_source_group (self, input_source_group); + +@@ -1340,60 +1348,180 @@ on_input_engine_manager_active_engine_changed (KioskInputSourcesManager *self) + + active_input_engine = kiosk_input_engine_manager_get_active_engine (self->input_engine_manager); + + if (active_input_engine == NULL) { + g_debug ("KioskInputSourcesManager: Input engine deactivated, activating first available input source"); + activate_first_available_input_source_group (self); + return; + } + + activate_input_source_group_with_engine (self, active_input_engine); + } + + static void + kiosk_input_sources_manager_start_input_engine_manager (KioskInputSourcesManager *self) + { + self->input_engine_manager = kiosk_input_engine_manager_new (self); + + g_signal_connect_object (G_OBJECT (self->input_engine_manager), + "notify::is-loaded", + G_CALLBACK (on_input_engine_manager_is_loaded_changed), + self, + G_CONNECT_SWAPPED); + + g_signal_connect_object (G_OBJECT (self->input_engine_manager), + "notify::active-engine", + G_CALLBACK (on_input_engine_manager_active_engine_changed), + self, + G_CONNECT_SWAPPED); + } + ++static void ++process_x_keyboard_manager_selected_layout_change (KioskInputSourcesManager *self) ++{ ++ const char *selected_layout; ++ ++ selected_layout = kiosk_x_keyboard_manager_get_selected_layout (self->x_keyboard_manager); ++ ++ if (selected_layout == NULL) { ++ return; ++ } ++ ++ g_debug ("KioskInputSourcesManager: X server changed active layout to %s", selected_layout); ++ ++ activate_input_source_group_with_layout (self, selected_layout); ++ ++ sync_dbus_service (self); ++} ++ ++static void ++on_x_keyboard_manager_selected_layout_changed (KioskInputSourcesManager *self) ++{ ++ /* We defer processing the layout change for a bit, because often in practice there is more than ++ * one layout change at the same time, and only the last one is the desired one ++ */ ++ kiosk_gobject_utils_queue_defer_callback (G_OBJECT (self), ++ "[kiosk-input-sources-manager] process_x_keyboard_manager_selected_layout_change", ++ self->cancellable, ++ KIOSK_OBJECT_CALLBACK (process_x_keyboard_manager_selected_layout_change), ++ NULL); ++} ++ ++static gboolean ++layouts_match_selected_input_source_group (KioskInputSourcesManager *self, ++ const char * const *layouts, ++ const char *options) ++{ ++ KioskInputSourceGroup *input_source_group; ++ ++ g_auto (GStrv) current_layouts = NULL; ++ const char *input_source_group_options; ++ const char *input_engine_name; ++ ++ input_source_group = kiosk_input_sources_manager_get_selected_input_source_group (self); ++ ++ if (input_source_group == NULL) { ++ return FALSE; ++ } ++ ++ input_engine_name = kiosk_input_source_group_get_input_engine (input_source_group); ++ ++ if (input_engine_name != NULL) { ++ return FALSE; ++ } ++ ++ current_layouts = kiosk_input_source_group_get_layouts (input_source_group); ++ ++ if (!g_strv_equal ((const char * const *) current_layouts, layouts)) { ++ return FALSE; ++ } ++ ++ input_source_group_options = kiosk_input_source_group_get_options (input_source_group); ++ ++ if (g_strcmp0 (input_source_group_options, options) != 0) { ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static void ++on_x_keyboard_manager_layouts_changed (KioskInputSourcesManager *self) ++{ ++ const char * const *new_layouts; ++ const char *selected_layout; ++ const char *options; ++ gboolean layouts_match; ++ size_t i; ++ ++ new_layouts = kiosk_x_keyboard_manager_get_layouts (self->x_keyboard_manager); ++ options = kiosk_x_keyboard_manager_get_options (self->x_keyboard_manager); ++ layouts_match = layouts_match_selected_input_source_group (self, new_layouts, options); ++ ++ if (layouts_match) { ++ return; ++ } ++ ++ g_debug ("KioskInputSorcesManager: X server keyboard layouts changed"); ++ ++ self->overriding_configuration = TRUE; ++ kiosk_input_sources_manager_clear_input_sources (self); ++ ++ for (i = 0; new_layouts[i] != NULL; i++) { ++ kiosk_input_sources_manager_add_layout (self, new_layouts[i], options); ++ } ++ ++ selected_layout = kiosk_x_keyboard_manager_get_selected_layout (self->x_keyboard_manager); ++ ++ if (selected_layout != NULL) { ++ activate_best_available_input_source_group (self, NULL, selected_layout); ++ } ++} ++ ++static void ++kiosk_input_source_manager_start_x_keyboard_manager (KioskInputSourcesManager *self) ++{ ++ g_debug ("KioskInputSourcesManager: Starting X Keyboard Manager"); ++ self->x_keyboard_manager = kiosk_x_keyboard_manager_new (self->compositor); ++ ++ g_signal_connect_object (G_OBJECT (self->x_keyboard_manager), ++ "notify::selected-layout", ++ G_CALLBACK (on_x_keyboard_manager_selected_layout_changed), ++ self, ++ G_CONNECT_SWAPPED); ++ g_signal_connect_object (G_OBJECT (self->x_keyboard_manager), ++ "notify::layouts", ++ G_CALLBACK (on_x_keyboard_manager_layouts_changed), ++ self, ++ G_CONNECT_SWAPPED); ++} ++ + static void + kiosk_input_sources_manager_handle_dbus_service (KioskInputSourcesManager *self) + { + KioskService *service; + + service = kiosk_compositor_get_service (self->compositor); + + g_set_weak_pointer (&self->dbus_service, KIOSK_DBUS_INPUT_SOURCES_MANAGER (kiosk_service_get_input_sources_manager_skeleton (service))); + g_set_weak_pointer (&self->dbus_object_manager, kiosk_service_get_input_sources_object_manager (service)); + + g_signal_connect_object (G_OBJECT (self->dbus_service), + "handle-set-input-sources", + G_CALLBACK (on_dbus_service_handle_set_input_sources), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (self->dbus_service), + "handle-set-input-sources-from-locales", + G_CALLBACK (on_dbus_service_handle_set_input_sources_from_locales), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (self->dbus_service), + "handle-set-input-sources-from-session-configuration", + G_CALLBACK (on_dbus_service_handle_set_input_sources_from_session_configuration), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (self->dbus_service), + "handle-select-input-source", + G_CALLBACK (on_dbus_service_handle_select_input_source), + self, + G_CONNECT_SWAPPED); +@@ -1407,63 +1535,78 @@ kiosk_input_sources_manager_handle_dbus_service (KioskInputSourcesManager *self) + G_CALLBACK (on_dbus_service_handle_select_previous_input_source), + self, + G_CONNECT_SWAPPED); + } + + static void + kiosk_input_sources_manager_constructed (GObject *object) + { + KioskInputSourcesManager *self = KIOSK_INPUT_SOURCES_MANAGER (object); + + g_debug ("KioskInputSourcesManager: Initializing"); + + G_OBJECT_CLASS (kiosk_input_sources_manager_parent_class)->constructed (object); + + g_set_weak_pointer (&self->display, meta_plugin_get_display (META_PLUGIN (self->compositor))); + + self->cancellable = g_cancellable_new (); + + self->xkb_info = gnome_xkb_info_new (); + self->input_source_groups = g_ptr_array_new_full (1, g_object_unref); + + kiosk_input_sources_manager_handle_dbus_service (self); + + kiosk_input_sources_manager_start_input_engine_manager (self); + + kiosk_input_sources_manager_connect_to_localed (self); + + kiosk_input_sources_manager_add_key_bindings (self); + + kiosk_input_sources_manager_set_input_sources_from_session_configuration (self); ++ ++ /* We start the X keyboard manager after we've already loaded and locked in ++ * GSettings etc, so the session settings take precedence over xorg.conf ++ */ ++ if (!meta_is_wayland_compositor ()) { ++ g_debug ("KioskInputSourcesManager: Will start X keyboard manager shortly"); ++ kiosk_gobject_utils_queue_defer_callback (G_OBJECT (self), ++ "[kiosk-input-sources-manager] kiosk_input_source_manager_start_x_keyboard_manager", ++ self->cancellable, ++ KIOSK_OBJECT_CALLBACK (kiosk_input_source_manager_start_x_keyboard_manager), ++ NULL); ++ } else { ++ g_debug ("KioskInputSourcesManager: Won't start X keyboard manager on wayland"); ++ } + } + + static void + kiosk_input_sources_manager_init (KioskInputSourcesManager *self) + { + } + + static void + kiosk_input_sources_manager_dispose (GObject *object) + { + KioskInputSourcesManager *self = KIOSK_INPUT_SOURCES_MANAGER (object); + + if (self->cancellable != NULL) { + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + } + + kiosk_input_sources_manager_clear_input_sources (self); + + g_clear_object (&self->input_engine_manager); ++ g_clear_object (&self->x_keyboard_manager); + + g_clear_object (&self->xkb_info); + + g_clear_object (&self->locale_proxy); + + kiosk_input_sources_manager_remove_key_bindings (self); + g_clear_weak_pointer (&self->dbus_service); + g_clear_weak_pointer (&self->dbus_object_manager); + g_clear_weak_pointer (&self->display); + g_clear_weak_pointer (&self->compositor); + + G_OBJECT_CLASS (kiosk_input_sources_manager_parent_class)->dispose (object); + } +diff --git a/compositor/kiosk-input-sources-manager.h b/compositor/kiosk-input-sources-manager.h +index 0b1a738..8a44c3a 100644 +--- a/compositor/kiosk-input-sources-manager.h ++++ b/compositor/kiosk-input-sources-manager.h +@@ -1,35 +1,37 @@ + #pragma once + + #include + + #include "kiosk-input-source-group.h" + #include "kiosk-input-engine-manager.h" ++#include "kiosk-x-keyboard-manager.h" + + typedef struct _KioskCompositor KioskCompositor; + + G_BEGIN_DECLS + + #define KIOSK_TYPE_INPUT_SOURCES_MANAGER (kiosk_input_sources_manager_get_type ()) + + G_DECLARE_FINAL_TYPE (KioskInputSourcesManager, + kiosk_input_sources_manager, + KIOSK, INPUT_SOURCES_MANAGER, + GObject) + + KioskInputSourcesManager *kiosk_input_sources_manager_new (KioskCompositor *compositor); + KioskInputEngineManager *kiosk_input_sources_manager_get_input_engine_manager (KioskInputSourcesManager *manager); ++KioskXKeyboardManager *kiosk_input_sources_manager_get_x_keyboard_manager (KioskInputSourcesManager *manager); + + void kiosk_input_sources_manager_clear_input_sources (KioskInputSourcesManager *self); + gboolean kiosk_input_sources_manager_set_input_sources_from_locales (KioskInputSourcesManager *self, + const char * const *locales, + const char *options); + gboolean kiosk_input_sources_manager_set_input_sources_from_session_configuration (KioskInputSourcesManager *manager); + + void kiosk_input_sources_manager_add_layout (KioskInputSourcesManager *self, + const char *layout, + const char *options); + void kiosk_input_sources_manager_add_input_engine (KioskInputSourcesManager *self, + const char *engine_name, + const char *options); + + G_END_DECLS +diff --git a/compositor/kiosk-x-keyboard-manager.c b/compositor/kiosk-x-keyboard-manager.c +new file mode 100644 +index 0000000..ad18d39 +--- /dev/null ++++ b/compositor/kiosk-x-keyboard-manager.c +@@ -0,0 +1,565 @@ ++#include "config.h" ++#include "kiosk-x-keyboard-manager.h" ++ ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "kiosk-compositor.h" ++#include "kiosk-gobject-utils.h" ++ ++struct _KioskXKeyboardManager ++{ ++ GObject parent; ++ ++ /* weak references */ ++ KioskCompositor *compositor; ++ MetaBackend *backend; ++ MetaDisplay *display; ++ Display *x_server_display; ++ ++ /* strong references */ ++ GCancellable *cancellable; ++ GBytes *xkb_rules_names_data; ++ char **layouts; ++ char *options; ++ ++ /* state */ ++ Window x_server_root_window; ++ Atom xkb_rules_names_atom; ++ int xkb_event_base; ++ ++ size_t layout_index; ++ ssize_t pending_layout_index; ++ ++ /* flags */ ++ guint32 xkb_rules_names_data_changed: 1; ++}; ++ ++enum ++{ ++ PROP_COMPOSITOR = 1, ++ PROP_LAYOUTS, ++ PROP_SELECTED_LAYOUT, ++ NUMBER_OF_PROPERTIES ++}; ++ ++static GParamSpec *kiosk_x_keyboard_manager_properties[NUMBER_OF_PROPERTIES] = { NULL, }; ++ ++G_DEFINE_TYPE (KioskXKeyboardManager, kiosk_x_keyboard_manager, G_TYPE_OBJECT) ++ ++static void kiosk_x_keyboard_manager_set_property (GObject *object, ++ guint property_id, ++ const GValue *value, ++ GParamSpec *param_spec); ++static void kiosk_x_keyboard_manager_get_property (GObject *object, ++ guint property_id, ++ GValue *value, ++ GParamSpec *param_spec); ++ ++static void kiosk_x_keyboard_manager_constructed (GObject *object); ++static void kiosk_x_keyboard_manager_dispose (GObject *object); ++ ++KioskXKeyboardManager * ++kiosk_x_keyboard_manager_new (KioskCompositor *compositor) ++{ ++ GObject *object; ++ ++ object = g_object_new (KIOSK_TYPE_X_KEYBOARD_MANAGER, ++ "compositor", compositor, ++ NULL); ++ ++ return KIOSK_X_KEYBOARD_MANAGER (object); ++} ++ ++static void ++kiosk_x_keyboard_manager_class_init (KioskXKeyboardManagerClass *x_keyboard_manager_class) ++{ ++ GObjectClass *object_class = G_OBJECT_CLASS (x_keyboard_manager_class); ++ ++ object_class->constructed = kiosk_x_keyboard_manager_constructed; ++ object_class->set_property = kiosk_x_keyboard_manager_set_property; ++ object_class->get_property = kiosk_x_keyboard_manager_get_property; ++ object_class->dispose = kiosk_x_keyboard_manager_dispose; ++ ++ kiosk_x_keyboard_manager_properties[PROP_COMPOSITOR] = g_param_spec_object ("compositor", ++ "compositor", ++ "compositor", ++ KIOSK_TYPE_COMPOSITOR, ++ G_PARAM_CONSTRUCT_ONLY ++ | G_PARAM_WRITABLE ++ | G_PARAM_STATIC_NAME ++ | G_PARAM_STATIC_NICK ++ | G_PARAM_STATIC_BLURB); ++ kiosk_x_keyboard_manager_properties[PROP_SELECTED_LAYOUT] = g_param_spec_string ("selected-layout", ++ "selected-layout", ++ "selected-layout", ++ NULL, ++ G_PARAM_READABLE ++ | G_PARAM_STATIC_NAME ++ | G_PARAM_STATIC_NICK ++ | G_PARAM_STATIC_BLURB); ++ ++ kiosk_x_keyboard_manager_properties[PROP_LAYOUTS] = g_param_spec_boxed ("layouts", ++ "layouts", ++ "layouts", ++ G_TYPE_STRV, ++ G_PARAM_READABLE ++ | G_PARAM_STATIC_NAME ++ | G_PARAM_STATIC_NICK ++ | G_PARAM_STATIC_BLURB); ++ ++ g_object_class_install_properties (object_class, NUMBER_OF_PROPERTIES, kiosk_x_keyboard_manager_properties); ++} ++ ++static void ++kiosk_x_keyboard_manager_set_property (GObject *object, ++ guint property_id, ++ const GValue *value, ++ GParamSpec *param_spec) ++{ ++ KioskXKeyboardManager *self = KIOSK_X_KEYBOARD_MANAGER (object); ++ ++ switch (property_id) { ++ case PROP_COMPOSITOR: ++ g_set_weak_pointer (&self->compositor, g_value_get_object (value)); ++ break; ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); ++ break; ++ } ++} ++ ++static void ++kiosk_x_keyboard_manager_get_property (GObject *object, ++ guint property_id, ++ GValue *value, ++ GParamSpec *param_spec) ++{ ++ KioskXKeyboardManager *self = KIOSK_X_KEYBOARD_MANAGER (object); ++ ++ switch (property_id) { ++ case PROP_SELECTED_LAYOUT: ++ g_value_set_string (value, self->layouts[self->layout_index]); ++ break; ++ case PROP_LAYOUTS: ++ g_value_set_boxed (value, self->layouts); ++ break; ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, param_spec); ++ break; ++ } ++} ++ ++static char ** ++qualify_layouts_with_variants (KioskXKeyboardManager *self, ++ const char * const *layouts, ++ const char * const *variants) ++{ ++ size_t number_of_layouts = 0; ++ size_t number_of_variants = 0; ++ char **fully_qualified_layouts = NULL; ++ ++ size_t i, j; ++ ++ g_return_val_if_fail (KIOSK_IS_X_KEYBOARD_MANAGER (self), FALSE); ++ ++ number_of_layouts = g_strv_length ((GStrv) layouts); ++ number_of_variants = g_strv_length ((GStrv) variants); ++ ++ if (number_of_layouts < number_of_variants) { ++ g_debug ("KioskXKeyboardManager: There is a layout variant mismatch"); ++ return NULL; ++ } ++ ++ fully_qualified_layouts = g_new0 (char *, number_of_layouts + 1); ++ ++ for (i = 0, j = 0; layouts[i] != NULL; i++) { ++ const char *layout = layouts[i]; ++ const char *variant = ""; ++ ++ if (variants[j] != NULL) { ++ variant = variants[j++]; ++ } ++ ++ if (variant[0] == '\0') { ++ fully_qualified_layouts[i] = g_strdup (layout); ++ } else { ++ fully_qualified_layouts[i] = g_strdup_printf ("%s+%s", layout, variant); ++ } ++ } ++ ++ return fully_qualified_layouts; ++} ++ ++static void ++kiosk_x_keyboard_manager_set_layout_index (KioskXKeyboardManager *self, ++ size_t layout_index) ++{ ++ size_t number_of_layouts; ++ ++ if (self->layout_index == layout_index) { ++ return; ++ } ++ ++ g_debug ("KioskXKeyboardManager: X server is using layout with index %ld", ++ layout_index); ++ ++ number_of_layouts = g_strv_length (self->layouts); ++ ++ if (layout_index >= number_of_layouts) { ++ layout_index = 0; ++ } ++ ++ self->layout_index = layout_index; ++ g_object_notify (G_OBJECT (self), "selected-layout"); ++} ++ ++static gboolean ++kiosk_x_keyboard_manager_read_current_layout_index (KioskXKeyboardManager *self) ++{ ++ XkbStateRec xkb_state = { 0 }; ++ int status; ++ ++ status = XkbGetState (self->x_server_display, XkbUseCoreKbd, &xkb_state); ++ ++ if (status != Success) { ++ g_debug ("KioskXKeyboardManager: Could not read current layout index"); ++ return FALSE; ++ } ++ ++ kiosk_x_keyboard_manager_set_layout_index (self, xkb_state.locked_group); ++ return FALSE; ++} ++ ++static gboolean ++kiosk_x_keyboard_manager_read_xkb_rules_names_data (KioskXKeyboardManager *self) ++{ ++ g_autoptr (GBytes) new_xkb_rules_names_data = NULL; ++ g_autoptr (GVariant) input_source_group = NULL; ++ size_t number_of_layouts = 0; ++ g_autofree char *layouts_string = NULL; ++ g_autofree char *variants_string = NULL; ++ g_autofree char *options = NULL; ++ g_auto (GStrv) layouts = NULL; ++ g_auto (GStrv) variants = NULL; ++ g_auto (GStrv) qualified_layouts = NULL; ++ int status; ++ Atom returned_type = 0; ++ int returned_format = 0; ++ gulong number_of_bytes_read = 0; ++ gulong number_of_bytes_unread = 0; ++ guchar *property_values; ++ size_t i; ++ enum { ++ RULES_NAME = 0, ++ MODEL, ++ LAYOUTS, ++ VARIANTS, ++ OPTIONS ++ } property_value_index; ++ ++ self->xkb_rules_names_data_changed = TRUE; ++ ++ g_debug ("KioskXKeyboardManager: Reading active keyboard layouts from X server"); ++ ++ status = XGetWindowProperty (self->x_server_display, ++ self->x_server_root_window, ++ self->xkb_rules_names_atom, ++ 0, 1024, FALSE, XA_STRING, ++ &returned_type, ++ &returned_format, ++ &number_of_bytes_read, ++ &number_of_bytes_unread, ++ &property_values); ++ ++ if (status != Success) { ++ g_debug ("KioskXKeyboardManager: Could not read active keyboard layouts from X server"); ++ return FALSE; ++ } ++ ++ if (returned_type != XA_STRING || ++ returned_format != 8 || ++ number_of_bytes_unread != 0) { ++ g_debug ("KioskXKeyboardManager: Active keyboard layouts propery from X server is corrupted"); ++ return FALSE; ++ } ++ ++ new_xkb_rules_names_data = g_bytes_new_with_free_func (property_values, ++ number_of_bytes_read, ++ (GDestroyNotify) XFree, ++ NULL); ++ ++ property_value_index = 0; ++ for (i = 0; i < number_of_bytes_read; i++) { ++ g_autofree char *value = g_strdup ((char *) property_values + i); ++ size_t value_length = strlen (value); ++ ++ switch (property_value_index) { ++ case RULES_NAME: ++ case MODEL: ++ break; ++ case LAYOUTS: ++ layouts_string = g_steal_pointer (&value); ++ g_debug ("KioskXKeyboardManager: Read layouts '%s'", layouts_string); ++ break; ++ case VARIANTS: ++ variants_string = g_steal_pointer (&value); ++ g_debug ("KioskXKeyboardManager: Read variants '%s'", variants_string); ++ break; ++ case OPTIONS: ++ options = g_steal_pointer (&value); ++ g_debug ("KioskXKeyboardManager: Read options '%s'", options); ++ break; ++ } ++ ++ i += value_length; ++ property_value_index++; ++ } ++ ++ if (self->xkb_rules_names_data != NULL && g_bytes_equal (self->xkb_rules_names_data, new_xkb_rules_names_data)) { ++ g_debug ("KioskXKeyboardManager: XKB rules names data is unchanged"); ++ return FALSE; ++ } ++ ++ g_clear_pointer (&self->xkb_rules_names_data, g_bytes_unref); ++ self->xkb_rules_names_data = g_steal_pointer (&new_xkb_rules_names_data); ++ ++ layouts = g_strsplit (layouts_string, ",", -1); ++ variants = g_strsplit (variants_string, ",", -1); ++ ++ qualified_layouts = qualify_layouts_with_variants (self, (const char * const *)layouts, (const char * const *)variants); ++ ++ if (qualified_layouts == NULL) { ++ g_debug ("KioskXKeyboardManager: Unable to qualify layouts with variants"); ++ return FALSE; ++ } ++ ++ number_of_layouts = g_strv_length (qualified_layouts); ++ ++ if (number_of_layouts == 0) { ++ g_debug ("KioskXKeyboardManager: No layouts found"); ++ return FALSE; ++ } ++ ++ g_clear_pointer (&self->layouts, g_strfreev); ++ self->layouts = g_steal_pointer (&qualified_layouts); ++ self->options = g_steal_pointer (&options); ++ ++ g_object_freeze_notify (G_OBJECT (self)); ++ g_object_notify (G_OBJECT (self), "layouts"); ++ kiosk_x_keyboard_manager_read_current_layout_index (self); ++ g_object_thaw_notify (G_OBJECT (self)); ++ ++ return TRUE; ++} ++ ++static void ++monitor_x_server_display_for_changes (KioskXKeyboardManager *self) ++{ ++ int major = XkbMajorVersion; ++ int minor = XkbMinorVersion; ++ XWindowAttributes attributes; ++ ++ XGetWindowAttributes (self->x_server_display, self->x_server_root_window, &attributes); ++ ++ if (!(attributes.your_event_mask & PropertyChangeMask)) { ++ XSelectInput (self->x_server_display, ++ self->x_server_root_window, ++ attributes.your_event_mask | PropertyChangeMask); ++ } ++ ++ XkbQueryExtension (self->x_server_display, NULL, &self->xkb_event_base, NULL, &major, &minor); ++ self->xkb_rules_names_atom = XInternAtom (self->x_server_display, "_XKB_RULES_NAMES", False); ++} ++ ++static void ++kiosk_x_keyboard_manager_handle_x_server_property_notify (KioskXKeyboardManager *self, ++ XPropertyEvent *x_server_event) ++{ ++ if (x_server_event->window != self->x_server_root_window) { ++ return; ++ } ++ ++ if (x_server_event->atom != self->xkb_rules_names_atom) { ++ return; ++ } ++ ++ g_debug ("KioskXKeyboardManager: XKB rules names property changed in X server"); ++ kiosk_x_keyboard_manager_read_xkb_rules_names_data (self); ++} ++ ++static void ++kiosk_x_keyboard_manager_handle_xkb_event (KioskXKeyboardManager *self, ++ XkbEvent *x_server_event) ++{ ++ size_t layout_index; ++ ++ layout_index = XkbStateGroup (&x_server_event->state); ++ switch (x_server_event->any.xkb_type) { ++ case XkbStateNotify: ++ if (!(x_server_event->state.changed & XkbGroupStateMask)) { ++ return; ++ } ++ ++ /* Mutter immediately reverts all layout changes coming from ++ * the outside, so we hide the event from it. ++ */ ++ x_server_event->state.changed &= ~XkbGroupLockMask; ++ ++ if (self->xkb_rules_names_data_changed) { ++ g_debug ("KioskXKeyboardManager: Ignoring spurious group change following layout change"); ++ self->xkb_rules_names_data_changed = FALSE; ++ return; ++ ++ } ++ g_debug ("KioskXKeyboardManager: Approving keyboard group change to group %lu", layout_index); ++ kiosk_x_keyboard_manager_set_layout_index (self, layout_index); ++ break; ++ } ++} ++ ++static void ++on_x_server_event (KioskXKeyboardManager *self, ++ XEvent *x_server_event) ++{ ++ ++ if (self->x_server_display == NULL) { ++ self->x_server_display = x_server_event->xany.display; ++ self->x_server_root_window = DefaultRootWindow (self->x_server_display); ++ monitor_x_server_display_for_changes (self); ++ } ++ ++ switch (x_server_event->type) { ++ case PropertyNotify: ++ kiosk_x_keyboard_manager_handle_x_server_property_notify (self, &x_server_event->xproperty); ++ break; ++ default: ++ if (x_server_event->type == self->xkb_event_base) { ++ kiosk_x_keyboard_manager_handle_xkb_event (self, (XkbEvent *) x_server_event); ++ } ++ break; ++ } ++} ++ ++const char * const * ++kiosk_x_keyboard_manager_get_layouts (KioskXKeyboardManager *self) ++{ ++ g_return_val_if_fail (KIOSK_IS_X_KEYBOARD_MANAGER (self), NULL); ++ ++ return (const char * const *) self->layouts; ++} ++ ++const char * ++kiosk_x_keyboard_manager_get_selected_layout (KioskXKeyboardManager *self) ++{ ++ g_return_val_if_fail (KIOSK_IS_X_KEYBOARD_MANAGER (self), NULL); ++ ++ if (self->layouts == NULL) { ++ return NULL; ++ } ++ ++ g_debug ("KioskXKeyboardManager: Selected layout is '%s'", self->layouts[self->layout_index]); ++ ++ return self->layouts[self->layout_index]; ++} ++ ++const char * ++kiosk_x_keyboard_manager_get_options (KioskXKeyboardManager *self) ++{ ++ g_return_val_if_fail (KIOSK_IS_X_KEYBOARD_MANAGER (self), NULL); ++ ++ return self->options; ++} ++ ++gboolean ++kiosk_x_keyboard_manager_keymap_is_active (KioskXKeyboardManager *self, ++ const char * const *layouts, ++ const char * const *variants, ++ const char *options) ++{ ++ g_auto (GStrv) qualified_layouts = NULL; ++ ++ if (g_strcmp0 (options, self->options) != 0) { ++ return FALSE; ++ } ++ ++ qualified_layouts = qualify_layouts_with_variants (self, layouts, variants); ++ ++ if (qualified_layouts == NULL) { ++ return FALSE; ++ } ++ ++ if (!g_strv_equal ((const char * const *) qualified_layouts, (const char * const *) self->layouts)) { ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++gboolean ++kiosk_x_keyboard_manager_layout_group_is_locked (KioskXKeyboardManager *self, ++ xkb_layout_index_t layout_index) ++{ ++ return self->layout_index == layout_index; ++} ++ ++static void ++kiosk_x_keyboard_manager_init (KioskXKeyboardManager *self) ++{ ++} ++ ++static void ++kiosk_x_keyboard_manager_constructed (GObject *object) ++{ ++ KioskXKeyboardManager *self = KIOSK_X_KEYBOARD_MANAGER (object); ++ ++ g_debug ("KioskXKeyboardManager: Initializing"); ++ ++ G_OBJECT_CLASS (kiosk_x_keyboard_manager_parent_class)->constructed (object); ++ ++ self->cancellable = g_cancellable_new (); ++ ++ g_set_weak_pointer (&self->backend, meta_get_backend ()); ++ g_set_weak_pointer (&self->display, meta_plugin_get_display (META_PLUGIN (self->compositor))); ++ ++ self->pending_layout_index = -1; ++ ++ g_signal_connect_object (G_OBJECT (self->compositor), ++ "x-server-event", ++ G_CALLBACK (on_x_server_event), ++ self, ++ G_CONNECT_SWAPPED); ++} ++ ++static void ++kiosk_x_keyboard_manager_dispose (GObject *object) ++{ ++ KioskXKeyboardManager *self = KIOSK_X_KEYBOARD_MANAGER (object); ++ ++ g_debug ("KioskXKeyboardManager: Disposing"); ++ ++ if (self->cancellable != NULL) { ++ g_cancellable_cancel (self->cancellable); ++ g_clear_object (&self->cancellable); ++ } ++ ++ g_clear_pointer (&self->xkb_rules_names_data, g_bytes_unref); ++ g_clear_pointer (&self->layouts, g_strfreev); ++ g_clear_pointer (&self->options, g_free); ++ ++ g_clear_weak_pointer (&self->display); ++ g_clear_weak_pointer (&self->backend); ++ g_clear_weak_pointer (&self->compositor); ++ ++ G_OBJECT_CLASS (kiosk_x_keyboard_manager_parent_class)->dispose (object); ++} +diff --git a/compositor/kiosk-x-keyboard-manager.h b/compositor/kiosk-x-keyboard-manager.h +new file mode 100644 +index 0000000..bae498f +--- /dev/null ++++ b/compositor/kiosk-x-keyboard-manager.h +@@ -0,0 +1,29 @@ ++#pragma once ++ ++#include ++#include ++ ++typedef struct _KioskCompositor KioskCompositor; ++ ++G_BEGIN_DECLS ++ ++#define KIOSK_TYPE_X_KEYBOARD_MANAGER (kiosk_x_keyboard_manager_get_type ()) ++ ++G_DECLARE_FINAL_TYPE (KioskXKeyboardManager, ++ kiosk_x_keyboard_manager, ++ KIOSK, X_KEYBOARD_MANAGER, ++ GObject) ++ ++KioskXKeyboardManager *kiosk_x_keyboard_manager_new (KioskCompositor *compositor); ++ ++const char * const *kiosk_x_keyboard_manager_get_layouts (KioskXKeyboardManager *manager); ++const char *kiosk_x_keyboard_manager_get_selected_layout (KioskXKeyboardManager *manager); ++const char *kiosk_x_keyboard_manager_get_options (KioskXKeyboardManager *manager); ++gboolean kiosk_x_keyboard_manager_keymap_is_active (KioskXKeyboardManager *manager, ++ const char * const *layouts, ++ const char * const *variants, ++ const char *options); ++gboolean kiosk_x_keyboard_manager_layout_group_is_locked (KioskXKeyboardManager *manager, ++ xkb_layout_index_t layout_index); ++ ++G_END_DECLS +diff --git a/meson.build b/meson.build +index 2b5640a..44afcea 100644 +--- a/meson.build ++++ b/meson.build +@@ -86,60 +86,61 @@ sources = gnome.gdbus_codegen(dbus_interface, dbus_interface_file, + namespace: 'Kiosk', + interface_prefix: 'org.gnome', + annotations: [ + [ dbus_interface, 'org.gtk.GDBus.C.Name', 'ShellDBusService' ] + ] + ) + dbus_interface_sources_map += { dbus_interface: sources } + + compositor_dependencies = [] + compositor_dependencies += c_compiler.find_library('m') + compositor_dependencies += dependency('gio-2.0') + compositor_dependencies += dependency('glib-2.0') + compositor_dependencies += dependency('gnome-desktop-3.0') + compositor_dependencies += dependency('gobject-2.0') + compositor_dependencies += dependency('ibus-1.0') + compositor_dependencies += dependency('mutter-cogl-8') + compositor_dependencies += dependency('mutter-cogl-pango-8') + compositor_dependencies += dependency('mutter-clutter-8') + compositor_dependencies += mutter_dependency + + compositor_sources = [] + compositor_sources += 'compositor/kiosk-backgrounds.c' + compositor_sources += 'compositor/kiosk-compositor.c' + compositor_sources += 'compositor/kiosk-dbus-utils.c' + compositor_sources += 'compositor/kiosk-gobject-utils.c' + compositor_sources += 'compositor/kiosk-input-sources-manager.c' + compositor_sources += 'compositor/kiosk-input-engine-manager.c' + compositor_sources += 'compositor/kiosk-input-source-group.c' + compositor_sources += 'compositor/kiosk-service.c' + compositor_sources += 'compositor/kiosk-shell-service.c' ++compositor_sources += 'compositor/kiosk-x-keyboard-manager.c' + compositor_sources += 'compositor/main.c' + + foreach dbus_interface, sources: dbus_interface_sources_map + compositor_sources += sources + endforeach + + executable('gnome-kiosk', compositor_sources, + dependencies: compositor_dependencies, + build_rpath: mutter_libdir, + install_rpath: mutter_libdir, + install: true + ) + + desktop_config_data = configuration_data() + desktop_config_data.set('bindir', bindir) + + desktop_file = configure_file( + input: 'compositor/data/org.gnome.Kiosk.desktop.in.in', + output: 'org.gnome.Kiosk.desktop.in', + configuration: desktop_config_data + ) + + i18n.merge_file('desktop', + input: desktop_file, + output: 'org.gnome.Kiosk.desktop', + po_dir: po_dir, + install: true, + install_dir: desktop_data_dir, + type: 'desktop' + ) +-- +2.30.2 + diff --git a/gnome-kiosk.spec b/gnome-kiosk.spec index d539f29..9fc9613 100644 --- a/gnome-kiosk.spec +++ b/gnome-kiosk.spec @@ -12,7 +12,7 @@ Name: gnome-kiosk Version: 40~alpha -Release: 3%{?dist} +Release: 5%{?dist} Summary: Window management and application launching for GNOME License: GPLv2+ @@ -41,10 +41,10 @@ Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas Patch10001: 0001-compositor-Be-less-aggressive-about-full-screening-w.patch # https://gitlab.gnome.org/halfline/gnome-kiosk/-/merge_requests/2 -Patch20001: 0001-compositor-Add-signal-for-reporting-X-server-events.patch +Patch20001: 0001-gobject-utils-Log-when-executing-deferred-tasks.patch Patch20002: 0002-input-sources-manager-Fix-overzealous-rename-mistake.patch -Patch20003: 0003-input-sources-manager-Add-function-to-load-input-sou.patch -Patch20004: 0004-wip-Better-support-libxklavier.patch +Patch20003: 0003-compositor-Add-signal-for-reporting-X-server-events.patch +Patch20004: 0004-input-sources-manager-Support-libxklavier-managed-ke.patch %description GNOME Kiosk provides a desktop enviroment suitable for fixed purpose, or @@ -88,6 +88,12 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.gnome.Kiosk.Searc %{_datadir}/wayland-sessions/org.gnome.Kiosk.SearchApp.Session.desktop %changelog +* Wed Apr 21 2021 Ray Strode - 40~alpha-5 +- Fix keyboard layouts getting out of sync in anaconda + +* Tue Apr 20 2021 Ray Strode - 40~alpha-4 +- Fix infinite loop + * Mon Apr 19 2021 Ray Strode - 40~alpha-3 - Fix crash