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