[gnome-settings-daemon] keyboard: Add modifiers-only shortcuts to switch input sources



commit 4b81759704519c97dcb0366649df015e3ff4d708
Author: Rui Matos <tiagomatos gmail com>
Date:   Thu Sep 20 16:16:40 2012 +0200

    keyboard: Add modifiers-only shortcuts to switch input sources
    
    This adds an out of process helper similar to gsd-locate-pointer to
    allow for grabbing of certain pre-defined modifier-only shortcut
    combos.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=681685

 data/gsd-enums.h                                   |   15 +
 ...e.settings-daemon.peripherals.gschema.xml.in.in |    4 +
 plugins/keyboard/Makefile.am                       |   23 ++
 plugins/keyboard/gsd-input-sources-switcher.c      |  365 ++++++++++++++++++++
 plugins/keyboard/gsd-keyboard-manager.c            |   51 +++
 5 files changed, 458 insertions(+), 0 deletions(-)
---
diff --git a/data/gsd-enums.h b/data/gsd-enums.h
index 528622c..6c4edd4 100644
--- a/data/gsd-enums.h
+++ b/data/gsd-enums.h
@@ -121,4 +121,19 @@ typedef enum
   GSD_NUM_LOCK_STATE_OFF
 } GsdNumLockState;
 
+typedef enum
+{
+  GSD_INPUT_SOURCES_SWITCHER_OFF,
+  GSD_INPUT_SOURCES_SWITCHER_SHIFT_L,
+  GSD_INPUT_SOURCES_SWITCHER_ALT_L,
+  GSD_INPUT_SOURCES_SWITCHER_CTRL_L,
+  GSD_INPUT_SOURCES_SWITCHER_SHIFT_R,
+  GSD_INPUT_SOURCES_SWITCHER_ALT_R,
+  GSD_INPUT_SOURCES_SWITCHER_CTRL_R,
+  GSD_INPUT_SOURCES_SWITCHER_ALT_SHIFT_L,
+  GSD_INPUT_SOURCES_SWITCHER_ALT_SHIFT_R,
+  GSD_INPUT_SOURCES_SWITCHER_CTRL_SHIFT_L,
+  GSD_INPUT_SOURCES_SWITCHER_CTRL_SHIFT_R
+} GsdInputSourcesSwitcher;
+
 #endif /* __gsd_enums_h__ */
diff --git a/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in b/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
index cbdf120..c2c2f0c 100644
--- a/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
+++ b/data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
@@ -106,6 +106,10 @@
       <summary>NumLock state</summary>
       <description>The remembered state of the NumLock LED.</description>
     </key>
+    <key name="input-sources-switcher" enum="org.gnome.settings-daemon.GsdInputSourcesSwitcher">
+      <default>'off'</default>
+      <summary>Modifiers-only input sources switcher shortcut</summary>
+    </key>
   </schema>
   <schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.settings-daemon.peripherals.mouse" path="/org/gnome/settings-daemon/peripherals/mouse/">
     <key name="locate-pointer" type="b">
diff --git a/plugins/keyboard/Makefile.am b/plugins/keyboard/Makefile.am
index 0c1452e..445dd1f 100644
--- a/plugins/keyboard/Makefile.am
+++ b/plugins/keyboard/Makefile.am
@@ -27,6 +27,7 @@ libkeyboard_la_CPPFLAGS = \
 	-I$(top_srcdir)/data				\
 	-I$(top_srcdir)/plugins/common			\
 	-DDATADIR=\""$(pkgdatadir)"\"			\
+	-DLIBEXECDIR=\""$(libexecdir)"\"		\
 	-DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\" \
 	$(AM_CPPFLAGS)
 
@@ -75,6 +76,28 @@ check-local: test-make-xkb-source-id
 	$(builddir)/test-make-xkb-source-id > /dev/null
 endif
 
+libexec_PROGRAMS += gsd-input-sources-switcher
+
+gsd_input_sources_switcher_SOURCES = 	\
+	gsd-input-sources-switcher.c	\
+	$(NULL)
+
+gsd_input_sources_switcher_CPPFLAGS =	\
+	-I$(top_srcdir)/data		\
+	-I$(top_srcdir)/plugins/common	\
+	$(AM_CPPFLAGS)			\
+	$(NULL)
+
+gsd_input_sources_switcher_CFLAGS =	\
+	$(SETTINGS_PLUGIN_CFLAGS)	\
+	$(AM_CFLAGS)			\
+	$(NULL)
+
+gsd_input_sources_switcher_LDADD  = 	\
+	$(top_builddir)/plugins/common/libcommon.la	\
+	$(SETTINGS_PLUGIN_LIBS)		\
+	$(NULL)
+
 EXTRA_DIST = 			\
 	$(icons_DATA)		\
 	$(plugin_in_files)	\
diff --git a/plugins/keyboard/gsd-input-sources-switcher.c b/plugins/keyboard/gsd-input-sources-switcher.c
new file mode 100644
index 0000000..75b0111
--- /dev/null
+++ b/plugins/keyboard/gsd-input-sources-switcher.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * Written by: Rui Matos <rmatos redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+
+#include "gsd-enums.h"
+#include "gsd-keygrab.h"
+
+#define GNOME_DESKTOP_INPUT_SOURCES_DIR "org.gnome.desktop.input-sources"
+#define KEY_CURRENT_INPUT_SOURCE "current"
+#define KEY_INPUT_SOURCES        "sources"
+
+#define GSD_KEYBOARD_DIR "org.gnome.settings-daemon.peripherals.keyboard"
+#define KEY_SWITCHER "input-sources-switcher"
+
+static GSettings *input_sources_settings;
+
+static Key *the_keys = NULL;
+static guint n_keys = 0;
+
+static void
+do_switch (void)
+{
+  GVariant *sources;
+  gint i, n;
+
+  /* FIXME: this is racy with the g-s-d media-keys plugin. Instead we
+     should have a DBus API on g-s-d and poke it from here.*/
+  sources = g_settings_get_value (input_sources_settings, KEY_INPUT_SOURCES);
+
+  n = g_variant_n_children (sources);
+  if (n < 2)
+    goto out;
+
+  i = g_settings_get_uint (input_sources_settings, KEY_CURRENT_INPUT_SOURCE) + 1;
+  if (i >= n)
+    i = 0;
+
+  g_settings_set_uint (input_sources_settings, KEY_CURRENT_INPUT_SOURCE, i);
+
+ out:
+  g_variant_unref (sources);
+}
+
+static void
+init_keys (void)
+{
+  GSettings *settings;
+
+  settings = g_settings_new (GSD_KEYBOARD_DIR);
+
+  switch (g_settings_get_enum (settings, KEY_SWITCHER))
+    {
+    case GSD_INPUT_SOURCES_SWITCHER_SHIFT_L:
+      n_keys = 1;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Shift_L;
+      the_keys[0].state = 0;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_ALT_L:
+      n_keys = 1;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Alt_L;
+      the_keys[0].state = 0;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_CTRL_L:
+      n_keys = 1;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Control_L;
+      the_keys[0].state = 0;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_SHIFT_R:
+      n_keys = 1;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Shift_R;
+      the_keys[0].state = 0;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_ALT_R:
+      n_keys = 2;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Alt_R;
+      the_keys[0].state = 0;
+      the_keys[1].keysym = GDK_KEY_ISO_Level3_Shift;
+      the_keys[1].state = 0;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_CTRL_R:
+      n_keys = 1;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Control_R;
+      the_keys[0].state = 0;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_ALT_SHIFT_L:
+      n_keys = 2;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Shift_L;
+      the_keys[0].state = GDK_MOD1_MASK;
+      the_keys[1].keysym = GDK_KEY_Alt_L;
+      the_keys[1].state = GDK_SHIFT_MASK;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_ALT_SHIFT_R:
+      n_keys = 4;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Shift_R;
+      the_keys[0].state = GDK_MOD1_MASK;
+      the_keys[1].keysym = GDK_KEY_Alt_R;
+      the_keys[1].state = GDK_SHIFT_MASK;
+      the_keys[2].keysym = GDK_KEY_Shift_R;
+      the_keys[2].state = GDK_MOD5_MASK;
+      the_keys[3].keysym = GDK_KEY_ISO_Level3_Shift;
+      the_keys[3].state = GDK_SHIFT_MASK;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_CTRL_SHIFT_L:
+      n_keys = 2;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Shift_L;
+      the_keys[0].state = GDK_CONTROL_MASK;
+      the_keys[1].keysym = GDK_KEY_Control_L;
+      the_keys[1].state = GDK_SHIFT_MASK;
+      break;
+
+    case GSD_INPUT_SOURCES_SWITCHER_CTRL_SHIFT_R:
+      n_keys = 2;
+      the_keys = g_new0 (Key, n_keys);
+
+      the_keys[0].keysym = GDK_KEY_Shift_R;
+      the_keys[0].state = GDK_CONTROL_MASK;
+      the_keys[1].keysym = GDK_KEY_Control_R;
+      the_keys[1].state = GDK_SHIFT_MASK;
+      break;
+    }
+
+  g_object_unref (settings);
+}
+
+static void
+free_keys (void)
+{
+  gint i;
+
+  for (i = 0; i < n_keys; ++i)
+    g_free (the_keys[i].keycodes);
+
+  g_free (the_keys);
+}
+
+static gboolean
+match_modifier (const Key *key,
+                XIEvent   *xiev)
+{
+  Key meta;
+
+  meta = *key;
+
+  switch (key->keysym)
+    {
+    case GDK_KEY_Shift_L:
+    case GDK_KEY_Shift_R:
+      if (xiev->evtype == XI_KeyRelease)
+        meta.state |= GDK_SHIFT_MASK;
+      break;
+
+    case GDK_KEY_Control_L:
+    case GDK_KEY_Control_R:
+      if (xiev->evtype == XI_KeyRelease)
+        meta.state |= GDK_CONTROL_MASK;
+      break;
+
+    case GDK_KEY_ISO_Level3_Shift:
+      if (xiev->evtype == XI_KeyRelease)
+        meta.state |= GDK_MOD5_MASK;
+      break;
+
+    case GDK_KEY_Alt_L:
+    case GDK_KEY_Alt_R:
+      if (key->state == GDK_SHIFT_MASK)
+        meta.keysym = key->keysym == GDK_KEY_Alt_L ? GDK_KEY_Meta_L : GDK_KEY_Meta_R;
+
+      if (xiev->evtype == XI_KeyRelease)
+        meta.state |= GDK_MOD1_MASK;
+      break;
+    }
+
+  return match_xi2_key (&meta, (XIDeviceEvent *) xiev);
+}
+
+static gboolean
+matches_key (XIEvent *xiev)
+{
+  gint i;
+
+  for (i = 0; i < n_keys; ++i)
+    if (match_modifier (&the_keys[i], xiev))
+      return TRUE;
+
+  return FALSE;
+}
+
+/* Owen magic, ported to XI2 */
+static GdkFilterReturn
+filter (XEvent   *xevent,
+        GdkEvent *event,
+        gpointer  data)
+{
+  XIEvent *xiev;
+  XIDeviceEvent *xev;
+
+  if (xevent->type != GenericEvent)
+    return GDK_FILTER_CONTINUE;
+
+  xiev = (XIEvent *) xevent->xcookie.data;
+
+  if (xiev->evtype != XI_KeyPress &&
+      xiev->evtype != XI_KeyRelease)
+    return GDK_FILTER_CONTINUE;
+
+  xev = (XIDeviceEvent *) xiev;
+
+  if (matches_key (xiev))
+    {
+      if (xiev->evtype == XI_KeyPress)
+        {
+          XIAllowEvents (xev->display,
+                         xev->deviceid,
+                         XISyncDevice,
+                         xev->time);
+        }
+      else
+        {
+          do_switch ();
+          XIUngrabDevice (xev->display,
+                          xev->deviceid,
+                          xev->time);
+        }
+    }
+  else
+    {
+      XIAllowEvents (xev->display,
+                     xev->deviceid,
+                     XIReplayDevice,
+                     xev->time);
+    }
+
+  return GDK_FILTER_CONTINUE;
+}
+
+static void
+grab_key (Key        *key,
+          GdkDisplay *display,
+          GSList     *screens)
+{
+  GdkKeymapKey *keys;
+  gboolean has_entries;
+  GArray *keycodes;
+  gint n, i;
+
+  has_entries = gdk_keymap_get_entries_for_keyval (gdk_keymap_get_default (),
+                                                   key->keysym,
+                                                   &keys,
+                                                   &n);
+  if (!has_entries)
+    return;
+
+  keycodes = g_array_sized_new (TRUE, TRUE, sizeof (guint), n);
+  for (i = 0; i < n; ++i)
+    g_array_append_val (keycodes, keys[i].keycode);
+
+  key->keycodes = (guint *) g_array_free (keycodes, FALSE);
+
+  gdk_x11_display_error_trap_push (display);
+
+  grab_key_unsafe (key, GSD_KEYGRAB_ALLOW_UNMODIFIED | GSD_KEYGRAB_SYNCHRONOUS, screens);
+
+  gdk_x11_display_error_trap_pop_ignored (display);
+
+  g_free (keys);
+}
+
+static void
+set_input_sources_switcher (void)
+{
+  GdkDisplay *display;
+  gint n_screens;
+  GSList *screens, *l;
+  gint i;
+
+  display = gdk_display_get_default ();
+  n_screens = gdk_display_get_n_screens (display);
+  screens = NULL;
+  for (i = 0; i < n_screens; ++i)
+    screens = g_slist_prepend (screens, gdk_display_get_screen (display, i));
+
+  for (i = 0; i < n_keys; ++i)
+    grab_key (&the_keys[i], display, screens);
+
+  for (l = screens; l; l = l->next)
+    {
+      GdkScreen *screen;
+
+      screen = (GdkScreen *) l->data;
+      gdk_window_add_filter (gdk_screen_get_root_window (screen),
+                             (GdkFilterFunc) filter,
+                             screen);
+    }
+
+  g_slist_free (screens);
+}
+
+int
+main (int argc, char *argv[])
+{
+  gtk_init (&argc, &argv);
+
+  init_keys ();
+  if (n_keys == 0)
+    {
+      g_warning ("No shortcut defined, exiting");
+      return -1;
+    }
+  input_sources_settings = g_settings_new (GNOME_DESKTOP_INPUT_SOURCES_DIR);
+
+  set_input_sources_switcher ();
+  gtk_main ();
+
+  g_object_unref (input_sources_settings);
+  free_keys ();
+
+  return 0;
+}
diff --git a/plugins/keyboard/gsd-keyboard-manager.c b/plugins/keyboard/gsd-keyboard-manager.c
index d06cf33..65d3273 100644
--- a/plugins/keyboard/gsd-keyboard-manager.c
+++ b/plugins/keyboard/gsd-keyboard-manager.c
@@ -71,6 +71,8 @@
 #define KEY_BELL_DURATION  "bell-duration"
 #define KEY_BELL_MODE      "bell-mode"
 
+#define KEY_SWITCHER "input-sources-switcher"
+
 #define GNOME_DESKTOP_INTERFACE_DIR "org.gnome.desktop.interface"
 
 #define KEY_GTK_IM_MODULE    "gtk-im-module"
@@ -107,6 +109,9 @@ struct GsdKeyboardManagerPrivate
         GdkDeviceManager *device_manager;
         guint device_added_id;
         guint device_removed_id;
+
+        gboolean input_sources_switcher_spawned;
+        GPid input_sources_switcher_pid;
 };
 
 static void     gsd_keyboard_manager_class_init  (GsdKeyboardManagerClass *klass);
@@ -1003,6 +1008,47 @@ apply_all_settings (GsdKeyboardManager *manager)
 }
 
 static void
+set_input_sources_switcher (GsdKeyboardManager *manager,
+                            gboolean            state)
+{
+        if (state) {
+                GError *error = NULL;
+                char *args[2];
+
+                if (manager->priv->input_sources_switcher_spawned)
+                        set_input_sources_switcher (manager, FALSE);
+
+                args[0] = LIBEXECDIR "/gsd-input-sources-switcher";
+                args[1] = NULL;
+
+                g_spawn_async (NULL, args, NULL,
+                               0, NULL, NULL,
+                               &manager->priv->input_sources_switcher_pid, &error);
+
+                manager->priv->input_sources_switcher_spawned = (error == NULL);
+
+                if (error) {
+                        g_warning ("Couldn't spawn %s: %s", args[0], error->message);
+                        g_error_free (error);
+                }
+        } else if (manager->priv->input_sources_switcher_spawned) {
+                kill (manager->priv->input_sources_switcher_pid, SIGHUP);
+                g_spawn_close_pid (manager->priv->input_sources_switcher_pid);
+                manager->priv->input_sources_switcher_spawned = FALSE;
+        }
+}
+
+static gboolean
+enable_switcher (GsdKeyboardManager *manager)
+{
+        GsdInputSourcesSwitcher switcher;
+
+        switcher = g_settings_get_enum (manager->priv->settings, KEY_SWITCHER);
+
+        return switcher != GSD_INPUT_SOURCES_SWITCHER_OFF;
+}
+
+static void
 settings_changed (GSettings          *settings,
                   const char         *key,
                   GsdKeyboardManager *manager)
@@ -1023,6 +1069,8 @@ settings_changed (GSettings          *settings,
 		 g_strcmp0 (key, KEY_DELAY) == 0) {
 		g_debug ("Key repeat setting '%s' changed, applying key repeat settings", key);
 		apply_repeat (manager);
+        } else if (g_strcmp0 (key, KEY_SWITCHER) == 0) {
+                set_input_sources_switcher (manager, enable_switcher (manager));
 	} else {
 		g_warning ("Unhandled settings change, key '%s'", key);
 	}
@@ -1174,6 +1222,7 @@ start_keyboard_idle_cb (GsdKeyboardManager *manager)
                           G_CALLBACK (apply_input_sources_settings), manager);
 
 	install_xkb_filter (manager);
+        set_input_sources_switcher (manager, enable_switcher (manager));
 
         gnome_settings_profile_end (NULL);
 
@@ -1223,6 +1272,8 @@ gsd_keyboard_manager_stop (GsdKeyboardManager *manager)
         }
 
 	remove_xkb_filter (manager);
+
+        set_input_sources_switcher (manager, FALSE);
 }
 
 static void



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