From 8dfcfa0607754caab5032532ccc9d97b4393708e Mon Sep 17 00:00:00 2001
From: Carlos Garnacho <carlosg@gnome.org>
Date: Fri, 29 Jun 2018 14:31:23 +0200
Subject: [PATCH] clutter/x11: Implement keycode lookup from keysyms on virtual
 key devices

Unfortunately XKeysymToKeycode() falls short in that it coalesces keysyms
into keycodes pertaining to the first level (i.e. lowercase). Add a
ClutterKeymapX11 method (much alike its GdkKeymap counterpart) to look up
all matches for the given keysym.

Two other helper methods have been added so the virtual device can fetch
the current keyboard group, and latch modifiers for key emission. Combining
all this, the virtual device is now able to handle keycodes in further
levels.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/135

(cherry picked from commit 85284acb000ddc70afcf716b6c198b4b5bf5741e)
---
 clutter/clutter/x11/clutter-keymap-x11.c      | 178 +++++++++++++++++-
 clutter/clutter/x11/clutter-keymap-x11.h      |   8 +
 .../x11/clutter-virtual-input-device-x11.c    |  22 ++-
 3 files changed, 204 insertions(+), 4 deletions(-)

diff --git a/clutter/clutter/x11/clutter-keymap-x11.c b/clutter/clutter/x11/clutter-keymap-x11.c
index 914e31434..c34e676a4 100644
--- a/clutter/clutter/x11/clutter-keymap-x11.c
+++ b/clutter/clutter/x11/clutter-keymap-x11.c
@@ -38,6 +38,14 @@
 
 typedef struct _ClutterKeymapX11Class   ClutterKeymapX11Class;
 typedef struct _DirectionCacheEntry     DirectionCacheEntry;
+typedef struct _ClutterKeymapKey        ClutterKeymapKey;
+
+struct _ClutterKeymapKey
+{
+  guint keycode;
+  guint group;
+  guint level;
+};
 
 struct _DirectionCacheEntry
 {
@@ -59,6 +67,7 @@ struct _ClutterKeymapX11
 
   ClutterModifierType num_lock_mask;
   ClutterModifierType scroll_lock_mask;
+  ClutterModifierType level3_shift_mask;
 
   PangoDirection current_direction;
 
@@ -69,6 +78,7 @@ struct _ClutterKeymapX11
   Atom current_group_atom;
   guint current_cache_serial;
   DirectionCacheEntry group_direction_cache[4];
+  int current_group;
 #endif
 
   guint caps_lock_state : 1;
@@ -198,6 +208,9 @@ get_xkb (ClutterKeymapX11 *keymap_x11)
   if (keymap_x11->scroll_lock_mask == 0)
     keymap_x11->scroll_lock_mask = XkbKeysymToModifiers (backend_x11->xdpy,
                                                          XK_Scroll_Lock);
+  if (keymap_x11->level3_shift_mask == 0)
+    keymap_x11->level3_shift_mask = XkbKeysymToModifiers (backend_x11->xdpy,
+                                                          XK_ISO_Level3_Shift);
 
   return keymap_x11->xkb_desc;
 }
@@ -469,6 +482,7 @@ static void
 clutter_keymap_x11_init (ClutterKeymapX11 *keymap)
 {
   keymap->current_direction = PANGO_DIRECTION_NEUTRAL;
+  keymap->current_group = -1;
 }
 
 static ClutterTranslateReturn
@@ -498,7 +512,8 @@ clutter_keymap_x11_translate_event (ClutterEventTranslator *translator,
         {
         case XkbStateNotify:
           CLUTTER_NOTE (EVENT, "Updating keyboard state");
-          update_direction (keymap_x11, XkbStateGroup (&xkb_event->state));
+          keymap_x11->current_group = XkbStateGroup (&xkb_event->state);
+          update_direction (keymap_x11, keymap_x11->current_group);
           update_locked_mods (keymap_x11, xkb_event->state.locked_mods);
           retval = CLUTTER_TRANSLATE_REMOVE;
           break;
@@ -665,3 +680,164 @@ _clutter_keymap_x11_get_direction (ClutterKeymapX11 *keymap)
 #endif
     return PANGO_DIRECTION_NEUTRAL;
 }
+
+static gboolean
+clutter_keymap_x11_get_entries_for_keyval (ClutterKeymapX11  *keymap_x11,
+                                           guint              keyval,
+                                           ClutterKeymapKey **keys,
+                                           gint              *n_keys)
+{
+#ifdef HAVE_XKB
+  if (CLUTTER_BACKEND_X11 (keymap_x11->backend)->use_xkb)
+    {
+      XkbDescRec *xkb = get_xkb (keymap_x11);
+      GArray *retval;
+      gint keycode;
+
+      keycode = keymap_x11->min_keycode;
+      retval = g_array_new (FALSE, FALSE, sizeof (ClutterKeymapKey));
+
+      while (keycode <= keymap_x11->max_keycode)
+        {
+          gint max_shift_levels = XkbKeyGroupsWidth (xkb, keycode);
+          gint group = 0;
+          gint level = 0;
+          gint total_syms = XkbKeyNumSyms (xkb, keycode);
+          gint i = 0;
+          KeySym *entry;
+
+          /* entry is an array with all syms for group 0, all
+           * syms for group 1, etc. and for each group the
+           * shift level syms are in order
+           */
+          entry = XkbKeySymsPtr (xkb, keycode);
+
+          while (i < total_syms)
+            {
+              g_assert (i == (group * max_shift_levels + level));
+
+              if (entry[i] == keyval)
+                {
+                  ClutterKeymapKey key;
+
+                  key.keycode = keycode;
+                  key.group = group;
+                  key.level = level;
+
+                  g_array_append_val (retval, key);
+
+                  g_assert (XkbKeySymEntry (xkb, keycode, level, group) ==
+                            keyval);
+                }
+
+              ++level;
+
+              if (level == max_shift_levels)
+                {
+                  level = 0;
+                  ++group;
+                }
+
+              ++i;
+            }
+
+          ++keycode;
+        }
+
+      if (retval->len > 0)
+        {
+          *keys = (ClutterKeymapKey*) retval->data;
+          *n_keys = retval->len;
+        }
+      else
+        {
+          *keys = NULL;
+          *n_keys = 0;
+        }
+
+      g_array_free (retval, retval->len > 0 ? FALSE : TRUE);
+
+      return *n_keys > 0;
+    }
+  else
+#endif
+    {
+      return FALSE;
+    }
+}
+
+void
+clutter_keymap_x11_latch_modifiers (ClutterKeymapX11 *keymap_x11,
+                                    uint32_t          level,
+                                    gboolean          enable)
+{
+#ifdef HAVE_XKB
+  ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (keymap_x11->backend);
+  uint32_t modifiers[] = {
+    0,
+    ShiftMask,
+    keymap_x11->level3_shift_mask,
+    keymap_x11->level3_shift_mask | ShiftMask,
+  };
+  uint32_t value = 0;
+
+  if (!backend_x11->use_xkb)
+    return;
+
+  level = CLAMP (level, 0, G_N_ELEMENTS (modifiers) - 1);
+
+  if (enable)
+    value = modifiers[level];
+  else
+    value = 0;
+
+  XkbLatchModifiers (clutter_x11_get_default_display (),
+                     XkbUseCoreKbd, modifiers[level],
+                     value);
+#endif
+}
+
+static uint32_t
+clutter_keymap_x11_get_current_group (ClutterKeymapX11 *keymap_x11)
+{
+  ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (keymap_x11->backend);
+  XkbStateRec state_rec;
+
+  if (keymap_x11->current_group >= 0)
+    return keymap_x11->current_group;
+
+  XkbGetState (backend_x11->xdpy, XkbUseCoreKbd, &state_rec);
+  return XkbStateGroup (&state_rec);
+}
+
+gboolean
+clutter_keymap_x11_keycode_for_keyval (ClutterKeymapX11 *keymap_x11,
+                                       guint             keyval,
+                                       guint            *keycode_out,
+                                       guint            *level_out)
+{
+  ClutterKeymapKey *keys;
+  gint i, n_keys, group;
+  gboolean found = FALSE;
+
+  g_return_val_if_fail (keycode_out != NULL, FALSE);
+  g_return_val_if_fail (level_out != NULL, FALSE);
+
+  group = clutter_keymap_x11_get_current_group (keymap_x11);
+
+  if (!clutter_keymap_x11_get_entries_for_keyval (keymap_x11, keyval, &keys, &n_keys))
+    return FALSE;
+
+  for (i = 0; i < n_keys && !found; i++)
+    {
+      if (keys[i].group == group)
+        {
+          *keycode_out = keys[i].keycode;
+          *level_out = keys[i].level;
+          found = TRUE;
+        }
+    }
+
+  g_free (keys);
+  return found;
+}
diff --git a/clutter/clutter/x11/clutter-keymap-x11.h b/clutter/clutter/x11/clutter-keymap-x11.h
index ad673a2a7..4b5b403c8 100644
--- a/clutter/clutter/x11/clutter-keymap-x11.h
+++ b/clutter/clutter/x11/clutter-keymap-x11.h
@@ -51,6 +51,14 @@ gboolean _clutter_keymap_x11_get_is_modifier     (ClutterKeymapX11    *keymap,
 
 PangoDirection _clutter_keymap_x11_get_direction (ClutterKeymapX11    *keymap);
 
+gboolean clutter_keymap_x11_keycode_for_keyval (ClutterKeymapX11 *keymap_x11,
+                                                guint             keyval,
+                                                guint            *keycode_out,
+                                                guint            *level_out);
+void     clutter_keymap_x11_latch_modifiers (ClutterKeymapX11 *keymap_x11,
+                                             uint32_t          level,
+                                             gboolean          enable);
+
 G_END_DECLS
 
 #endif /* __CLUTTER_KEYMAP_X11_H__ */
diff --git a/clutter/clutter/x11/clutter-virtual-input-device-x11.c b/clutter/clutter/x11/clutter-virtual-input-device-x11.c
index 416c944b3..b86ded0d0 100644
--- a/clutter/clutter/x11/clutter-virtual-input-device-x11.c
+++ b/clutter/clutter/x11/clutter-virtual-input-device-x11.c
@@ -32,6 +32,8 @@
 
 #include "clutter-virtual-input-device.h"
 #include "x11/clutter-virtual-input-device-x11.h"
+#include "x11/clutter-backend-x11.h"
+#include "x11/clutter-keymap-x11.h"
 
 struct _ClutterVirtualInputDeviceX11
 {
@@ -135,11 +137,25 @@ clutter_virtual_input_device_x11_notify_keyval (ClutterVirtualInputDevice *virtu
 						uint32_t                   keyval,
 						ClutterKeyState            key_state)
 {
-  KeyCode keycode;
+  ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (clutter_get_default_backend ());
+  ClutterKeymapX11 *keymap = backend_x11->keymap;
+  uint32_t keycode, level;
+
+  if (!clutter_keymap_x11_keycode_for_keyval (keymap, keyval, &keycode, &level))
+    {
+      g_warning ("No keycode found for keyval %x in current group", keyval);
+      return;
+    }
+
+  if (key_state == CLUTTER_KEY_STATE_PRESSED)
+    clutter_keymap_x11_latch_modifiers (keymap, level, TRUE);
 
-  keycode = XKeysymToKeycode (clutter_x11_get_default_display (), keyval);
   XTestFakeKeyEvent (clutter_x11_get_default_display (),
-                     keycode, key_state == CLUTTER_KEY_STATE_PRESSED, 0);
+                     (KeyCode) keycode,
+                     key_state == CLUTTER_KEY_STATE_PRESSED, 0);
+
+  if (key_state == CLUTTER_KEY_STATE_RELEASED)
+    clutter_keymap_x11_latch_modifiers (keymap, level, FALSE);
 }
 
 static void
-- 
2.19.0.rc0

