f92192
From a9c7fb96d9d03547d53c6fdd27a1cf6f4d00b17d Mon Sep 17 00:00:00 2001
f92192
From: Andrea Azzarone <azzaronea@gmail.com>
f92192
Date: Fri, 13 Jul 2018 14:49:38 +0200
f92192
Subject: [PATCH] clutter/x11: Implement keycode remap to keysyms on virtual
f92192
 key devices
f92192
f92192
Keycode lookup can fail for serveral reasons, e.g. if there is no combination of
f92192
modifiers and keycodes that can produce the target keysym with the current
f92192
keyboard layout.
f92192
f92192
In case the keycode lookup fails, remap temporarily the keysym to an unused
f92192
keycodes.
f92192
f92192
Fixes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/109
f92192
---
f92192
 clutter/clutter/x11/clutter-keymap-x11.c      | 154 ++++++++++++++++++
f92192
 clutter/clutter/x11/clutter-keymap-x11.h      |   6 +-
f92192
 .../x11/clutter-virtual-input-device-x11.c    |  19 ++-
f92192
 3 files changed, 173 insertions(+), 6 deletions(-)
f92192
f92192
diff --git a/clutter/clutter/x11/clutter-keymap-x11.c b/clutter/clutter/x11/clutter-keymap-x11.c
f92192
index c34e676a4..744fab979 100644
f92192
--- a/clutter/clutter/x11/clutter-keymap-x11.c
f92192
+++ b/clutter/clutter/x11/clutter-keymap-x11.c
f92192
@@ -81,6 +81,9 @@ struct _ClutterKeymapX11
f92192
   int current_group;
f92192
 #endif
f92192
 
f92192
+  GHashTable *reserved_keycodes;
f92192
+  GQueue *available_keycodes;
f92192
+
f92192
   guint caps_lock_state : 1;
f92192
   guint num_lock_state  : 1;
f92192
   guint has_direction   : 1;
f92192
@@ -441,15 +444,98 @@ clutter_keymap_x11_set_property (GObject      *gobject,
f92192
     }
f92192
 }
f92192
 
f92192
+static void
f92192
+clutter_keymap_x11_refresh_reserved_keycodes (ClutterKeymapX11 *keymap_x11)
f92192
+{
f92192
+  Display *dpy = clutter_x11_get_default_display ();
f92192
+  GHashTableIter iter;
f92192
+  gpointer key, value;
f92192
+
f92192
+  g_hash_table_iter_init (&iter, keymap_x11->reserved_keycodes);
f92192
+  while (g_hash_table_iter_next (&iter, &key, &value))
f92192
+    {
f92192
+      guint reserved_keycode = GPOINTER_TO_UINT (key);
f92192
+      guint reserved_keysym = GPOINTER_TO_UINT (value);
f92192
+      guint actual_keysym = XkbKeycodeToKeysym (dpy, reserved_keycode, 0, 0);
f92192
+
f92192
+      /* If an available keycode is no longer mapped to the stored keysym, then
f92192
+       * the keycode should not be considered available anymore and should be
f92192
+       * removed both from the list of available and reserved keycodes.
f92192
+       */
f92192
+      if (reserved_keysym != actual_keysym)
f92192
+        {
f92192
+          g_hash_table_iter_remove (&iter);
f92192
+          g_queue_remove (keymap_x11->available_keycodes, key);
f92192
+        }
f92192
+    }
f92192
+}
f92192
+
f92192
+static gboolean
f92192
+clutter_keymap_x11_replace_keycode (ClutterKeymapX11 *keymap_x11,
f92192
+                                    KeyCode           keycode,
f92192
+                                    KeySym            keysym)
f92192
+{
f92192
+  if (CLUTTER_BACKEND_X11 (keymap_x11->backend)->use_xkb)
f92192
+    {
f92192
+      Display *dpy = clutter_x11_get_default_display ();
f92192
+      XkbDescPtr xkb = get_xkb (keymap_x11);
f92192
+      XkbMapChangesRec changes;
f92192
+
f92192
+      XFlush (dpy);
f92192
+
f92192
+      xkb->device_spec = XkbUseCoreKbd;
f92192
+      memset (&changes, 0, sizeof(changes));
f92192
+
f92192
+      if (keysym != NoSymbol)
f92192
+        {
f92192
+          int types[XkbNumKbdGroups] = { XkbOneLevelIndex };
f92192
+          XkbChangeTypesOfKey (xkb, keycode, 1, XkbGroup1Mask, types, &changes);
f92192
+          XkbKeySymEntry (xkb, keycode, 0, 0) = keysym;
f92192
+        }
f92192
+      else
f92192
+        {
f92192
+          /* Reset to NoSymbol */
f92192
+          XkbChangeTypesOfKey (xkb, keycode, 0, XkbGroup1Mask, NULL, &changes);
f92192
+        }
f92192
+
f92192
+      changes.changed = XkbKeySymsMask | XkbKeyTypesMask;
f92192
+      changes.first_key_sym = keycode;
f92192
+      changes.num_key_syms = 1;
f92192
+      changes.first_type = 0;
f92192
+      changes.num_types = xkb->map->num_types;
f92192
+      XkbChangeMap (dpy, xkb, &changes);
f92192
+
f92192
+      XFlush (dpy);
f92192
+
f92192
+      return TRUE;
f92192
+    }
f92192
+
f92192
+  return FALSE;
f92192
+}
f92192
+
f92192
 static void
f92192
 clutter_keymap_x11_finalize (GObject *gobject)
f92192
 {
f92192
   ClutterKeymapX11 *keymap;
f92192
   ClutterEventTranslator *translator;
f92192
+  GHashTableIter iter;
f92192
+  gpointer key, value;
f92192
 
f92192
   keymap = CLUTTER_KEYMAP_X11 (gobject);
f92192
   translator = CLUTTER_EVENT_TRANSLATOR (keymap);
f92192
 
f92192
+  clutter_keymap_x11_refresh_reserved_keycodes (keymap);
f92192
+  g_hash_table_iter_init (&iter, keymap->reserved_keycodes);
f92192
+  while (g_hash_table_iter_next (&iter, &key, &value))
f92192
+    {
f92192
+      guint keycode = GPOINTER_TO_UINT (key);
f92192
+      clutter_keymap_x11_replace_keycode (keymap, keycode, NoSymbol);
f92192
+    }
f92192
+
f92192
+  g_hash_table_destroy (keymap->reserved_keycodes);
f92192
+  g_queue_free (keymap->available_keycodes);
f92192
+
f92192
+
f92192
 #ifdef HAVE_XKB
f92192
   _clutter_backend_remove_event_translator (keymap->backend, translator);
f92192
 
f92192
@@ -483,6 +569,8 @@ clutter_keymap_x11_init (ClutterKeymapX11 *keymap)
f92192
 {
f92192
   keymap->current_direction = PANGO_DIRECTION_NEUTRAL;
f92192
   keymap->current_group = -1;
f92192
+  keymap->reserved_keycodes = g_hash_table_new (NULL, NULL);
f92192
+  keymap->available_keycodes = g_queue_new ();
f92192
 }
f92192
 
f92192
 static ClutterTranslateReturn
f92192
@@ -766,6 +854,72 @@ clutter_keymap_x11_get_entries_for_keyval (ClutterKeymapX11  *keymap_x11,
f92192
     }
f92192
 }
f92192
 
f92192
+static guint
f92192
+clutter_keymap_x11_get_available_keycode (ClutterKeymapX11 *keymap_x11)
f92192
+{
f92192
+  if (CLUTTER_BACKEND_X11 (keymap_x11->backend)->use_xkb)
f92192
+    {
f92192
+      clutter_keymap_x11_refresh_reserved_keycodes (keymap_x11);
f92192
+
f92192
+      if (g_hash_table_size (keymap_x11->reserved_keycodes) < 5)
f92192
+        {
f92192
+          Display *dpy = clutter_x11_get_default_display ();
f92192
+          XkbDescPtr xkb = get_xkb (keymap_x11);
f92192
+          guint i;
f92192
+
f92192
+          for (i = xkb->max_key_code; i >= xkb->min_key_code; --i)
f92192
+            {
f92192
+              if (XkbKeycodeToKeysym (dpy, i, 0, 0) == NoSymbol)
f92192
+                return i;
f92192
+            }
f92192
+        }
f92192
+
f92192
+      return GPOINTER_TO_UINT (g_queue_pop_head (keymap_x11->available_keycodes));
f92192
+    }
f92192
+
f92192
+  return 0;
f92192
+}
f92192
+
f92192
+gboolean clutter_keymap_x11_reserve_keycode (ClutterKeymapX11 *keymap_x11,
f92192
+                                             guint             keyval,
f92192
+                                             guint            *keycode_out)
f92192
+{
f92192
+  g_return_val_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap_x11), FALSE);
f92192
+  g_return_val_if_fail (keyval != 0, FALSE);
f92192
+  g_return_val_if_fail (keycode_out != NULL, FALSE);
f92192
+
f92192
+  *keycode_out = clutter_keymap_x11_get_available_keycode (keymap_x11);
f92192
+
f92192
+  if (*keycode_out == NoSymbol)
f92192
+    {
f92192
+      g_warning ("Cannot reserve a keycode for keyval %d: no available keycode", keyval);
f92192
+      return FALSE;
f92192
+    }
f92192
+
f92192
+  if (!clutter_keymap_x11_replace_keycode (keymap_x11, *keycode_out, keyval))
f92192
+    {
f92192
+      g_warning ("Failed to remap keycode %d to keyval %d", *keycode_out, keyval);
f92192
+      return FALSE;
f92192
+    }
f92192
+
f92192
+  g_hash_table_insert (keymap_x11->reserved_keycodes, GUINT_TO_POINTER (*keycode_out), GUINT_TO_POINTER (keyval));
f92192
+  g_queue_remove (keymap_x11->available_keycodes, GUINT_TO_POINTER (*keycode_out));
f92192
+
f92192
+  return TRUE;
f92192
+}
f92192
+
f92192
+void clutter_keymap_x11_release_keycode_if_needed (ClutterKeymapX11 *keymap_x11,
f92192
+                                                   guint             keycode)
f92192
+{
f92192
+  g_return_if_fail (CLUTTER_IS_KEYMAP_X11 (keymap_x11));
f92192
+
f92192
+  if (!g_hash_table_contains (keymap_x11->reserved_keycodes, GUINT_TO_POINTER (keycode)) ||
f92192
+      g_queue_index (keymap_x11->available_keycodes, GUINT_TO_POINTER (keycode)) != -1)
f92192
+    return;
f92192
+
f92192
+  g_queue_push_tail (keymap_x11->available_keycodes, GUINT_TO_POINTER (keycode));
f92192
+}
f92192
+
f92192
 void
f92192
 clutter_keymap_x11_latch_modifiers (ClutterKeymapX11 *keymap_x11,
f92192
                                     uint32_t          level,
f92192
diff --git a/clutter/clutter/x11/clutter-keymap-x11.h b/clutter/clutter/x11/clutter-keymap-x11.h
f92192
index 4b5b403c8..4decb44ee 100644
f92192
--- a/clutter/clutter/x11/clutter-keymap-x11.h
f92192
+++ b/clutter/clutter/x11/clutter-keymap-x11.h
f92192
@@ -58,7 +58,11 @@ gboolean clutter_keymap_x11_keycode_for_keyval (ClutterKeymapX11 *keymap_x11,
f92192
 void     clutter_keymap_x11_latch_modifiers (ClutterKeymapX11 *keymap_x11,
f92192
                                              uint32_t          level,
f92192
                                              gboolean          enable);
f92192
-
f92192
+gboolean clutter_keymap_x11_reserve_keycode (ClutterKeymapX11 *keymap_x11,
f92192
+                                             guint             keyval,
f92192
+                                             guint            *keycode_out);
f92192
+void     clutter_keymap_x11_release_keycode_if_needed (ClutterKeymapX11 *keymap_x11,
f92192
+                                                       guint             keycode);
f92192
 G_END_DECLS
f92192
 
f92192
 #endif /* __CLUTTER_KEYMAP_X11_H__ */
f92192
diff --git a/clutter/clutter/x11/clutter-virtual-input-device-x11.c b/clutter/clutter/x11/clutter-virtual-input-device-x11.c
f92192
index e16ba3fd0..cab26c38c 100644
f92192
--- a/clutter/clutter/x11/clutter-virtual-input-device-x11.c
f92192
+++ b/clutter/clutter/x11/clutter-virtual-input-device-x11.c
f92192
@@ -143,8 +143,13 @@ clutter_virtual_input_device_x11_notify_keyval (ClutterVirtualInputDevice *virtu
f92192
 
f92192
   if (!clutter_keymap_x11_keycode_for_keyval (keymap, keyval, &keycode, &level))
f92192
     {
f92192
-      g_warning ("No keycode found for keyval %x in current group", keyval);
f92192
-      return;
f92192
+      level = 0;
f92192
+
f92192
+      if (!clutter_keymap_x11_reserve_keycode (keymap, keyval, &keycode))
f92192
+        {
f92192
+          g_warning ("No keycode found for keyval %x in current group", keyval);
f92192
+          return;
f92192
+        }
f92192
     }
f92192
 
f92192
   if (!_clutter_keymap_x11_get_is_modifier (keymap, keycode) &&
f92192
@@ -155,9 +160,13 @@ clutter_virtual_input_device_x11_notify_keyval (ClutterVirtualInputDevice *virtu
f92192
                      (KeyCode) keycode,
f92192
                      key_state == CLUTTER_KEY_STATE_PRESSED, 0);
f92192
 
f92192
-  if (!_clutter_keymap_x11_get_is_modifier (keymap, keycode) &&
f92192
-      key_state == CLUTTER_KEY_STATE_RELEASED)
f92192
-    clutter_keymap_x11_latch_modifiers (keymap, level, FALSE);
f92192
+
f92192
+  if (key_state == CLUTTER_KEY_STATE_RELEASED)
f92192
+    {
f92192
+      if (!_clutter_keymap_x11_get_is_modifier (keymap, keycode))
f92192
+        clutter_keymap_x11_latch_modifiers (keymap, level, FALSE);
f92192
+      clutter_keymap_x11_release_keycode_if_needed (keymap, keycode);
f92192
+    }
f92192
 }
f92192
 
f92192
 static void
f92192
-- 
f92192
2.26.2
f92192