diff options
author | Rui Matos <tiagomatos@gmail.com> | 2012-09-20 16:16:40 +0200 |
---|---|---|
committer | Rui Matos <tiagomatos@gmail.com> | 2012-09-23 17:36:13 +0200 |
commit | 4b81759704519c97dcb0366649df015e3ff4d708 (patch) | |
tree | 3eca9da0bef93a63731375f18694756e17fa434e | |
parent | 45d0ca402ed8783fa4028e0a4fc27df74bbfbe75 (diff) | |
download | gnome-settings-daemon-4b81759704519c97dcb0366649df015e3ff4d708.tar.gz |
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
-rw-r--r-- | data/gsd-enums.h | 15 | ||||
-rw-r--r-- | data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in | 4 | ||||
-rw-r--r-- | plugins/keyboard/Makefile.am | 23 | ||||
-rw-r--r-- | plugins/keyboard/gsd-input-sources-switcher.c | 365 | ||||
-rw-r--r-- | 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 528622c6..6c4edd44 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 cbdf1204..c2c2f0c8 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 0c1452e9..445dd1fa 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 00000000..75b01110 --- /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 d06cf33d..65d3273d 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 |