From 09b90bb8fcdcc2a15ef5d3dcc0a0eaecdb8be112 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 15 Apr 2021 14:43:17 -0400 Subject: [PATCH 4/4] wip! Better support libxklavier Mutter currently doesn't allow libxklavier to change the group out from under it. This commit fixes that, but also makes sure that current layout state stays in sync with X server state. --- compositor/kiosk-input-source-group.c | 17 + compositor/kiosk-input-source-group.h | 5 +- compositor/kiosk-input-sources-manager.c | 137 ++++++ compositor/kiosk-x-keyboard-manager.c | 546 +++++++++++++++++++++++ compositor/kiosk-x-keyboard-manager.h | 22 + meson.build | 1 + 6 files changed, 727 insertions(+), 1 deletion(-) 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..6e2712f 100644 --- a/compositor/kiosk-input-source-group.c +++ b/compositor/kiosk-input-source-group.c @@ -213,60 +213,66 @@ 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; 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; @@ -274,60 +280,71 @@ kiosk_input_source_group_activate (KioskInputSourceGroup *self) } 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); meta_backend_set_keymap (meta_get_backend (), layouts, variants, self->options); meta_backend_lock_layout_group (meta_get_backend (), self->layout_index); 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) { 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 4b4ef62..2015887 100644 --- a/compositor/kiosk-input-sources-manager.c +++ b/compositor/kiosk-input-sources-manager.c @@ -1,83 +1,88 @@ #include "config.h" #include "kiosk-input-sources-manager.h" #include #include #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); @@ -1363,60 +1368,184 @@ 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) +{ + 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); + 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 +process_x_keyboard_manager_layouts_change (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 +on_x_keyboard_manager_layouts_changed (KioskInputSourcesManager *self) +{ + kiosk_gobject_utils_queue_defer_callback (G_OBJECT (self), + "[kiosk-input-sources-manager] process_x_keyboard_manager_layouts_change", + self->cancellable, + KIOSK_OBJECT_CALLBACK (process_x_keyboard_manager_layouts_change), + NULL); +} + +static void +kiosk_input_source_manager_start_x_keyboard_manager (KioskInputSourcesManager *self) +{ + if (meta_is_wayland_compositor ()) { + return; + } + + 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); @@ -1430,63 +1559,71 @@ 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); + + 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); } 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-x-keyboard-manager.c b/compositor/kiosk-x-keyboard-manager.c new file mode 100644 index 0000000..94b8f0b --- /dev/null +++ b/compositor/kiosk-x-keyboard-manager.c @@ -0,0 +1,546 @@ +#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; + MetaX11Display *x11_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; + + /* flags */ + guint32 watching_x_server_root_window : 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 void +kiosk_x_keyboard_manager_read_current_layout_index (KioskXKeyboardManager *self) +{ + XkbStateRec xkb_state = { 0 }; + int status; + + meta_x11_error_trap_push (self->x11_display); + status = XkbGetState (self->x_server_display, XkbUseCoreKbd, &xkb_state); + meta_x11_error_trap_pop (self->x11_display); + + if (status != Success) { + return; + } + + kiosk_x_keyboard_manager_set_layout_index (self, xkb_state.locked_group); +} + +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; + + 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); + + 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); + + 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); + break; + case VARIANTS: + variants_string = g_steal_pointer (&value); + break; + case OPTIONS: + options = g_steal_pointer (&value); + break; + } + + i += value_length; + property_value_index++; + } + + 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) { + return FALSE; + } + + number_of_layouts = g_strv_length (qualified_layouts); + + if (number_of_layouts == 0) { + 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 +on_x_server_xkb_rules_names_data_changed (KioskXKeyboardManager *self) +{ + g_debug ("KioskXKeyboardManager: X server keyboard layouts changed"); + + kiosk_x_keyboard_manager_read_xkb_rules_names_data (self); +} + +static void +monitor_x_server_display_for_property_changes (KioskXKeyboardManager *self, + Display *x_server_display) +{ + XWindowAttributes attributes; + + if (self->watching_x_server_root_window) { + return; + } + + XGetWindowAttributes (x_server_display, self->x_server_root_window, &attributes); + + if (!(attributes.your_event_mask & PropertyChangeMask)) { + XSelectInput (x_server_display, + self->x_server_root_window, + attributes.your_event_mask | PropertyChangeMask); + } + + self->watching_x_server_root_window = TRUE; +} + +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 data changed in X server"); + kiosk_gobject_utils_queue_defer_callback (G_OBJECT (self), + "[kiosk-input-sources-manager] on_x_server_xkb_rules_names_data_changed", + self->cancellable, + KIOSK_OBJECT_CALLBACK (on_x_server_xkb_rules_names_data_changed), + NULL); +} + +static void +kiosk_x_keyboard_manager_handle_xkb_event (KioskXKeyboardManager *self, + XkbEvent *x_server_event) +{ + g_debug ("KioskXKeyboardManager: Got XKB event"); + size_t layout_index; + + layout_index = XkbStateGroup (&x_server_event->state); + switch (x_server_event->any.xkb_type) { + case XkbStateNotify: + g_debug ("KioskXKeyboardManager: Approving keyboard group change to group %lu", layout_index); + meta_backend_lock_layout_group (self->backend, 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) +{ + monitor_x_server_display_for_property_changes (self, x_server_event->xany.display); + + 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; + } + + 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; +} + +static void +kiosk_x_keyboard_manager_init (KioskXKeyboardManager *self) +{ +} + +static void +kiosk_x_keyboard_manager_initialize_xkb_extension (KioskXKeyboardManager *self) +{ + int major = XkbMajorVersion; + int minor = XkbMinorVersion; + + 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_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))); + g_set_weak_pointer (&self->x11_display, meta_display_get_x11_display (self->display)); + + self->x_server_display = meta_x11_display_get_xdisplay (self->x11_display); + g_object_add_weak_pointer (G_OBJECT (self->x11_display), (gpointer *) &self->x_server_display); + + self->x_server_root_window = meta_x11_display_get_xroot (self->x11_display); + g_object_add_weak_pointer (G_OBJECT (self->x11_display), (gpointer *) &self->x_server_root_window); + + kiosk_x_keyboard_manager_initialize_xkb_extension (self); + + 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); + + if (self->x11_display != NULL) { + g_object_remove_weak_pointer (G_OBJECT (self->x11_display), + (gpointer *) &self->x_server_display); + g_object_remove_weak_pointer (G_OBJECT (self->x11_display), + (gpointer *) &self->x_server_root_window); + } + g_clear_weak_pointer (&self->x11_display); + + 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..b402465 --- /dev/null +++ b/compositor/kiosk-x-keyboard-manager.h @@ -0,0 +1,22 @@ +#pragma once + +#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); + +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