[gnome-flashback] common: add support for modifiers-only keybindings



commit 971c7ac6a56d05ba86efa27e9b01829ce9dc0775
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date:   Wed Oct 14 04:03:05 2015 +0300

    common: add support for modifiers-only keybindings
    
    More precisely - support for ISO_Next_Group that will be used to
    switch input sources.

 gnome-flashback/libcommon/gf-keybindings.c         |  440 ++++++++++++++++++--
 gnome-flashback/libcommon/gf-keybindings.h         |    2 +-
 .../libinput-sources/gf-input-source-manager.c     |    2 +-
 gnome-flashback/libshell/flashback-shell.c         |    2 +-
 4 files changed, 416 insertions(+), 30 deletions(-)
---
diff --git a/gnome-flashback/libcommon/gf-keybindings.c b/gnome-flashback/libcommon/gf-keybindings.c
index cf2ed9d..8313b05 100644
--- a/gnome-flashback/libcommon/gf-keybindings.c
+++ b/gnome-flashback/libcommon/gf-keybindings.c
@@ -24,11 +24,21 @@
 
 #include "gf-keybindings.h"
 
+#define DESKTOP_INPUT_SOURCES_SCHEMA "org.gnome.desktop.input-sources"
+
+#define KEY_XKB_OPTIONS "xkb-options"
+
 struct _GfKeybindings
 {
   GObject     parent;
 
   GHashTable *keybindings;
+  GHashTable *iso_next_group_keybindings;
+
+  gboolean    iso_next_group;
+
+  GSettings  *settings;
+  gchar      *iso_next_group_option;
 
   Display    *xdisplay;
   Window      xwindow;
@@ -59,12 +69,24 @@ typedef struct
 enum
 {
   SIGNAL_ACCELERATOR_ACTIVATED,
+  SIGNAL_MODIFIERS_ACCELERATOR_ACTIVATED,
 
   LAST_SIGNAL
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
+enum
+{
+  PROP_0,
+
+  PROP_ISO_NEXT_GROUP,
+
+  LAST_PROP
+};
+
+static GParamSpec *properties[LAST_PROP] = { NULL };
+
 G_DEFINE_TYPE (GfKeybindings, gf_keybindings, G_TYPE_OBJECT)
 
 static gboolean
@@ -141,6 +163,9 @@ change_keygrab (GfKeybindings *keybindings,
   keycode = keybinding->keycode;
   mask = keybinding->mask;
 
+  if (keycode == 0)
+    return;
+
   while (ignored_mask <= keybindings->ignored_mask)
     {
       if (ignored_mask & ~(keybindings->ignored_mask))
@@ -224,12 +249,183 @@ get_keybinding (GfKeybindings *keybindings,
   return keybinding;
 }
 
+static gint
+get_keycodes_for_keysym (GfKeybindings *keybindings,
+                         gint           keysym,
+                         gint         **keycodes)
+{
+  GArray *retval;
+  gint min_keycodes;
+  gint max_keycodes;
+  gint keysyms_per_keycode;
+  KeySym *keymap;
+  gint n_keycodes;
+  gint keycode;
+
+  retval = g_array_new (FALSE, FALSE, sizeof (gint));
+
+  XDisplayKeycodes (keybindings->xdisplay, &min_keycodes, &max_keycodes);
+
+  keymap = XGetKeyboardMapping (keybindings->xdisplay, min_keycodes,
+                                max_keycodes - min_keycodes + 1,
+                                &keysyms_per_keycode);
+
+  keycode = min_keycodes;
+  while (keycode <= max_keycodes)
+    {
+      const KeySym *syms;
+      gint i;
+
+      syms = keymap + (keycode - min_keycodes) * keysyms_per_keycode;
+      i = 0;
+
+      while (i < keysyms_per_keycode)
+        {
+          if (syms[i] == (uint) keysym)
+            g_array_append_val (retval, keycode);
+
+          ++i;
+        }
+
+      ++keycode;
+    }
+
+  XFree (keymap);
+
+  n_keycodes = retval->len;
+  *keycodes = (gint *) g_array_free (retval, n_keycodes == 0 ? TRUE : FALSE);
+
+  return n_keycodes;
+}
+
+static void
+reload_iso_next_group_keybindings (GfKeybindings *keybindings)
+{
+  gint *keycodes;
+  gint n_keycodes;
+  gint i;
+
+  g_hash_table_remove_all (keybindings->iso_next_group_keybindings);
+
+  if (keybindings->iso_next_group_option == NULL)
+    return;
+
+  n_keycodes = get_keycodes_for_keysym (keybindings, XK_ISO_Next_Group,
+                                        &keycodes);
+
+  if (g_strcmp0 (keybindings->iso_next_group_option, "toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "lalt_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "lwin_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "rwin_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "lshift_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "rshift_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "lctrl_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "rctrl_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "sclk_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "menu_toggle") == 0 ||
+      g_strcmp0 (keybindings->iso_next_group_option, "caps_toggle") == 0)
+    {
+      for (i = 0; i < n_keycodes; i++)
+        {
+          Keybinding *keybinding;
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], 0, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i), keybinding);
+        }
+    }
+  else if (g_strcmp0 (keybindings->iso_next_group_option, "shift_caps_toggle") == 0 ||
+           g_strcmp0 (keybindings->iso_next_group_option, "shifts_toggle") == 0)
+    {
+      for (i = 0; i < n_keycodes; i++)
+        {
+          Keybinding *keybinding;
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ShiftMask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i), keybinding);
+        }
+    }
+  else if (g_strcmp0 (keybindings->iso_next_group_option, "alt_caps_toggle") == 0 ||
+           g_strcmp0 (keybindings->iso_next_group_option, "alt_space_toggle") == 0)
+    {
+      for (i = 0; i < n_keycodes; i++)
+        {
+          Keybinding *keybinding;
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], Mod1Mask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i), keybinding);
+        }
+    }
+  else if (g_strcmp0 (keybindings->iso_next_group_option, "ctrl_shift_toggle") == 0 ||
+           g_strcmp0 (keybindings->iso_next_group_option, "lctrl_lshift_toggle") == 0 ||
+           g_strcmp0 (keybindings->iso_next_group_option, "rctrl_rshift_toggle") == 0)
+    {
+      for (i = 0; i < n_keycodes; i++)
+        {
+          Keybinding *keybinding;
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ShiftMask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i), keybinding);
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ControlMask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i + n_keycodes), keybinding);
+        }
+    }
+  else if (g_strcmp0 (keybindings->iso_next_group_option, "ctrl_alt_toggle") == 0)
+    {
+      for (i = 0; i < n_keycodes; i++)
+        {
+          Keybinding *keybinding;
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], Mod1Mask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i), keybinding);
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ControlMask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i + n_keycodes), keybinding);
+        }
+    }
+  else if (g_strcmp0 (keybindings->iso_next_group_option, "alt_shift_toggle") == 0 ||
+           g_strcmp0 (keybindings->iso_next_group_option, "lalt_lshift_toggle") == 0)
+    {
+      for (i = 0; i < n_keycodes; i++)
+        {
+          Keybinding *keybinding;
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], Mod1Mask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i), keybinding);
+
+          keybinding = keybinding_new (NULL, 0, 0, keycodes[i], ShiftMask, 0);
+          g_hash_table_insert (keybindings->iso_next_group_keybindings,
+                               GINT_TO_POINTER (i + n_keycodes), keybinding);
+        }
+    }
+
+  g_free (keycodes);
+}
+
 static void
 ungrab_keybindings (GfKeybindings *keybindings)
 {
   GHashTableIter iter;
   gpointer value;
 
+  g_hash_table_iter_init (&iter, keybindings->iso_next_group_keybindings);
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    {
+      Keybinding *keybinding;
+
+      keybinding = (Keybinding *) value;
+
+      change_keygrab (keybindings, FALSE, keybinding);
+    }
+
   g_hash_table_iter_init (&iter, keybindings->keybindings);
   while (g_hash_table_iter_next (&iter, NULL, &value))
     {
@@ -278,6 +474,8 @@ reload_keybindings (GfKeybindings *keybindings)
   GHashTableIter iter;
   gpointer value;
 
+  reload_iso_next_group_keybindings (keybindings);
+
   g_hash_table_iter_init (&iter, keybindings->keybindings);
   while (g_hash_table_iter_next (&iter, NULL, &value))
     {
@@ -319,6 +517,16 @@ regrab_keybindings (GfKeybindings *keybindings)
   GHashTableIter iter;
   gpointer value;
 
+  g_hash_table_iter_init (&iter, keybindings->iso_next_group_keybindings);
+  while (g_hash_table_iter_next (&iter, NULL, &value))
+    {
+      Keybinding *keybinding;
+
+      keybinding = (Keybinding *) value;
+
+      change_keygrab (keybindings, TRUE, keybinding);
+    }
+
   g_hash_table_iter_init (&iter, keybindings->keybindings);
   while (g_hash_table_iter_next (&iter, NULL, &value))
     {
@@ -330,6 +538,90 @@ regrab_keybindings (GfKeybindings *keybindings)
     }
 }
 
+static gboolean
+process_iso_next_group (GfKeybindings *keybindings,
+                        XEvent        *event)
+{
+  gboolean processed;
+  guint state;
+  GList *values;
+  GList *l;
+
+  if (event->type == KeyRelease)
+    return FALSE;
+
+  processed = FALSE;
+  state = event->xkey.state & 0xff & ~(keybindings->ignored_mask);
+  values = g_hash_table_get_values (keybindings->iso_next_group_keybindings);
+
+  for (l = values; l; l = l->next)
+    {
+      Keybinding *keybinding;
+
+      keybinding = (Keybinding *) l->data;
+
+      if (keybinding->keycode == event->xkey.keycode &&
+          keybinding->mask == state)
+        {
+          gboolean freeze;
+
+          g_signal_emit (keybindings,
+                         signals[SIGNAL_MODIFIERS_ACCELERATOR_ACTIVATED],
+                         0, &freeze);
+
+          if (!freeze)
+            XUngrabKeyboard (keybindings->xdisplay, event->xkey.time);
+
+          processed = TRUE;
+          break;
+        }
+    }
+
+  g_list_free (values);
+
+  return processed;
+}
+
+static gboolean
+process_event (GfKeybindings *keybindings,
+               XEvent        *event)
+{
+  gboolean processed;
+  guint state;
+  GList *values;
+  GList *l;
+
+  if (event->type == KeyRelease)
+    return FALSE;
+
+  processed = FALSE;
+  state = event->xkey.state & 0xff & ~(keybindings->ignored_mask);
+  values = g_hash_table_get_values (keybindings->keybindings);
+
+  for (l = values; l; l = l->next)
+    {
+      Keybinding *keybinding;
+
+      keybinding = (Keybinding *) l->data;
+
+      if (keybinding->keycode == event->xkey.keycode &&
+          keybinding->mask == state)
+        {
+          XUngrabKeyboard (keybindings->xdisplay, event->xkey.time);
+          g_signal_emit (keybindings, signals[SIGNAL_ACCELERATOR_ACTIVATED],
+                         0, keybinding->action);
+
+
+          processed = TRUE;
+          break;
+        }
+    }
+
+  g_list_free (values);
+
+  return processed;
+}
+
 static GdkFilterReturn
 filter_func (GdkXEvent *xevent,
              GdkEvent  *event,
@@ -337,8 +629,6 @@ filter_func (GdkXEvent *xevent,
 {
   GfKeybindings *keybindings;
   XEvent *ev;
-  GList *values;
-  GList *l;
 
   keybindings = GF_KEYBINDINGS (user_data);
   ev = (XEvent *) xevent;
@@ -363,43 +653,81 @@ filter_func (GdkXEvent *xevent,
         }
     }
 
+  if (ev->type != KeyPress && ev->type != KeyRelease)
+    return GDK_FILTER_CONTINUE;
+
+  if (process_iso_next_group (keybindings, ev))
+    return GDK_FILTER_REMOVE;
+
   XAllowEvents (keybindings->xdisplay, AsyncKeyboard, ev->xkey.time);
 
-  if (ev->type != KeyPress)
-    return GDK_FILTER_CONTINUE;
+  if (process_event (keybindings, ev))
+    return GDK_FILTER_REMOVE;
 
-  values = g_hash_table_get_values (keybindings->keybindings);
+  return GDK_FILTER_CONTINUE;
+}
 
-  for (l = values; l; l = l->next)
-    {
-      Keybinding *keybinding;
-      guint state;
+static guint
+get_next_action (void)
+{
+  static guint action;
 
-      keybinding = (Keybinding *) l->data;
-      state = ev->xkey.state & 0xff & ~(keybindings->ignored_mask);
+  return ++action;
+}
 
-      if (keybinding->keycode == ev->xkey.keycode &&
-          keybinding->mask == state)
-        {
-          XUngrabKeyboard (keybindings->xdisplay, ev->xkey.time);
-          g_signal_emit (keybindings, signals[SIGNAL_ACCELERATOR_ACTIVATED],
-                         0, keybinding->action);
+static void
+xkb_options_changed_cb (GSettings *settings,
+                        gchar     *key,
+                        gpointer   user_data)
+{
+  GfKeybindings *keybindings;
+  gchar **xkb_options;
+  gchar **p;
+  gchar *option;
+
+  keybindings = GF_KEYBINDINGS (user_data);
+  xkb_options = g_settings_get_strv (settings, KEY_XKB_OPTIONS);
+  option = NULL;
 
+  for (p = xkb_options; p && *p; ++p)
+    {
+      if (g_str_has_prefix (*p, "grp:"))
+        {
+          option = (*p + 4);
           break;
         }
     }
 
-  g_list_free (values);
+  if (g_strcmp0 (option, keybindings->iso_next_group_option) == 0)
+    {
+      g_strfreev (xkb_options);
+      return;
+    }
 
-  return GDK_FILTER_CONTINUE;
+  g_free (keybindings->iso_next_group_option);
+  keybindings->iso_next_group_option = g_strdup (option);
+  g_strfreev (xkb_options);
+
+  reload_iso_next_group_keybindings (keybindings);
 }
 
-static guint
-get_next_action (void)
+static void
+gf_keybindings_constructed (GObject *object)
 {
-  static guint action;
+  GfKeybindings *keybindings;
 
-  return ++action;
+  keybindings = GF_KEYBINDINGS (object);
+
+  G_OBJECT_CLASS (gf_keybindings_parent_class)->constructed (object);
+
+  if (keybindings->iso_next_group)
+    {
+      keybindings->settings = g_settings_new (DESKTOP_INPUT_SOURCES_SCHEMA);
+
+      g_signal_connect (keybindings->settings, "changed::" KEY_XKB_OPTIONS,
+                        G_CALLBACK (xkb_options_changed_cb), keybindings);
+      xkb_options_changed_cb (keybindings->settings, NULL, keybindings);
+    }
 }
 
 static void
@@ -415,6 +743,14 @@ gf_keybindings_dispose (GObject *object)
       keybindings->keybindings = NULL;
     }
 
+  if (keybindings->iso_next_group_keybindings != NULL)
+    {
+      g_hash_table_destroy (keybindings->iso_next_group_keybindings);
+      keybindings->iso_next_group_keybindings = NULL;
+    }
+
+  g_clear_object (&keybindings->settings);
+
   G_OBJECT_CLASS (gf_keybindings_parent_class)->dispose (object);
 }
 
@@ -427,31 +763,77 @@ gf_keybindings_finalize (GObject *object)
 
   gdk_window_remove_filter (NULL, filter_func, keybindings);
 
+  g_free (keybindings->iso_next_group_option);
+
   G_OBJECT_CLASS (gf_keybindings_parent_class)->finalize (object);
 }
 
 static void
+gf_keybindings_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  GfKeybindings *keybindings;
+
+  keybindings = GF_KEYBINDINGS (object);
+
+  switch (prop_id)
+    {
+      case PROP_ISO_NEXT_GROUP:
+        keybindings->iso_next_group = g_value_get_boolean (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
 gf_keybindings_class_init (GfKeybindingsClass *keybindings_class)
 {
   GObjectClass *object_class;
 
   object_class = G_OBJECT_CLASS (keybindings_class);
 
+  object_class->constructed = gf_keybindings_constructed;
   object_class->dispose = gf_keybindings_dispose;
   object_class->finalize = gf_keybindings_finalize;
+  object_class->set_property = gf_keybindings_set_property;
 
   /**
    * GfKeybindings::accelerator-activated:
    * @keybindings: the object on which the signal is emitted
    * @action: keybinding action from gf_keybindings_grab
    *
-   * The ::accelerator-activated signal is emitted each time when keybinding
-   * is activated by user.
+   * The ::accelerator-activated signal will be emitted when a keybinding is
+   * activated.
    */
   signals[SIGNAL_ACCELERATOR_ACTIVATED] =
     g_signal_new ("accelerator-activated",
                   G_TYPE_FROM_CLASS (keybindings_class), G_SIGNAL_RUN_LAST,
                   0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
+
+  /**
+   * GfKeybindings::modifiers-accelerator-activated:
+   * @keybindings: the object on which the signal is emitted
+   *
+   * The ::modifiers-accelerator-activated signal will be emitted when a
+   * special modifiers-only keybinding is activated.
+   */
+  signals[SIGNAL_MODIFIERS_ACCELERATOR_ACTIVATED] =
+    g_signal_new ("modifiers-accelerator-activated",
+                  G_TYPE_FROM_CLASS (keybindings_class), G_SIGNAL_RUN_LAST,
+                  0, g_signal_accumulator_first_wins, NULL, NULL,
+                  G_TYPE_BOOLEAN, 0);
+
+  properties[PROP_ISO_NEXT_GROUP] =
+    g_param_spec_boolean ("iso-next-group", "iso-next-group", "iso-next-group",
+                          FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
+                          G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
 }
 
 static void
@@ -464,6 +846,8 @@ gf_keybindings_init (GfKeybindings *keybindings)
 
   keybindings->keybindings = g_hash_table_new_full (NULL, NULL, NULL,
                                                     keybinding_free);
+  keybindings->iso_next_group_keybindings = g_hash_table_new_full (NULL, NULL, NULL,
+                                                                   keybinding_free);
 
   display = gdk_display_get_default ();
   xkb_major = XkbMajorVersion;
@@ -502,9 +886,11 @@ gf_keybindings_init (GfKeybindings *keybindings)
  * Returns: (transfer full): a newly created #GfKeybindings.
  */
 GfKeybindings *
-gf_keybindings_new (void)
+gf_keybindings_new (gboolean iso_next_group)
 {
-  return g_object_new (GF_TYPE_KEYBINDINGS, NULL);
+  return g_object_new (GF_TYPE_KEYBINDINGS,
+                       "iso-next-group", iso_next_group,
+                       NULL);
 }
 
 /**
diff --git a/gnome-flashback/libcommon/gf-keybindings.h b/gnome-flashback/libcommon/gf-keybindings.h
index 571b89e..d8bb035 100644
--- a/gnome-flashback/libcommon/gf-keybindings.h
+++ b/gnome-flashback/libcommon/gf-keybindings.h
@@ -25,7 +25,7 @@ G_BEGIN_DECLS
 #define GF_TYPE_KEYBINDINGS gf_keybindings_get_type ()
 G_DECLARE_FINAL_TYPE (GfKeybindings, gf_keybindings, GF, KEYBINDINGS, GObject)
 
-GfKeybindings   *gf_keybindings_new           (void);
+GfKeybindings   *gf_keybindings_new           (gboolean       iso_next_group);
 
 guint            gf_keybindings_grab          (GfKeybindings *keybindings,
                                                const gchar   *accelerator);
diff --git a/gnome-flashback/libinput-sources/gf-input-source-manager.c 
b/gnome-flashback/libinput-sources/gf-input-source-manager.c
index 0832601..b9fcd7c 100644
--- a/gnome-flashback/libinput-sources/gf-input-source-manager.c
+++ b/gnome-flashback/libinput-sources/gf-input-source-manager.c
@@ -265,7 +265,7 @@ static void
 keybindings_init (GfInputSourceManager *manager)
 {
   manager->wm_keybindings = g_settings_new (DESKTOP_WM_KEYBINDINGS_SCHEMA);
-  manager->keybindings = gf_keybindings_new ();
+  manager->keybindings = gf_keybindings_new (TRUE);
 
   g_signal_connect (manager->wm_keybindings,
                     "changed::" KEY_SWITCH_INPUT_SOURCE,
diff --git a/gnome-flashback/libshell/flashback-shell.c b/gnome-flashback/libshell/flashback-shell.c
index 3d978c4..7e51f52 100644
--- a/gnome-flashback/libshell/flashback-shell.c
+++ b/gnome-flashback/libshell/flashback-shell.c
@@ -465,7 +465,7 @@ flashback_shell_init (FlashbackShell *shell)
   shell->grabbed_accelerators = g_hash_table_new_full (NULL, NULL, NULL, g_free);
   shell->grabbers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
-  shell->keybindings = gf_keybindings_new ();
+  shell->keybindings = gf_keybindings_new (FALSE);
   g_signal_connect (shell->keybindings, "accelerator-activated",
                     G_CALLBACK (accelerator_activated), shell);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]