Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/vino/ChangeLog,v
retrieving revision 1.186
diff -u -p -r1.186 ChangeLog
--- ChangeLog	22 Dec 2006 02:05:38 -0000	1.186
+++ ChangeLog	28 Dec 2006 20:59:18 -0000
@@ -1,3 +1,12 @@
+2006-12-28  Dan Winship  <danw@novell.com>
+
+	* configure.in: check for XKB
+
+	* server/vino-input.c: If the server supports XKB, use that rather
+	than core X calls to get the keyboard mapping. Add support for
+	multiple keyboard groups, and watch xkb events to notice when the
+	keyboard layout changes.
+
 2006-12-21  Jonh Wendell <jwendell@cvs.gnome.org>
 	Added initial d-bus support, fixing bug #319798
 
Index: configure.in
===================================================================
RCS file: /cvs/gnome/vino/configure.in,v
retrieving revision 1.122
diff -u -p -r1.122 configure.in
--- configure.in	22 Dec 2006 02:05:38 -0000	1.122
+++ configure.in	28 Dec 2006 20:59:18 -0000
@@ -185,6 +185,15 @@ AC_CHECK_HEADER(X11/extensions/XTest.h, 
 AC_SUBST(XTEST_LIBS)
 
 #
+# Check for XKB extension
+#
+AC_CHECK_HEADER(X11/XKBlib.h, [
+    AC_CHECK_LIB(X11, XkbQueryExtension,
+      [AC_DEFINE(HAVE_XKB, 1, [Defined if the XKB X extension is present])]
+      ,, $X_LIBS)
+  ])
+
+#
 # Check for Avahi
 #
 AC_ARG_ENABLE(avahi,
Index: server/vino-input.c
===================================================================
RCS file: /cvs/gnome/vino/server/vino-input.c,v
retrieving revision 1.6
diff -u -p -r1.6 vino-input.c
--- server/vino-input.c	6 Dec 2006 10:09:21 -0000	1.6
+++ server/vino-input.c	28 Dec 2006 20:59:19 -0000
@@ -31,6 +31,69 @@
  *       x0rfbserver, the original native X vnc server (Jens Wagner)
  */
 
+/*
+ * Theory of keyboard operation
+ * 
+ * The remote VNC client sends us a series of key press and release
+ * events identified as X keysyms. (Even Windows clients use X
+ * keysyms; that's just how the protocol works.) We then use our
+ * knowledge of the keyboard layout to translate the keysyms into
+ * keycodes, and use XTEST to send events for the appropriate
+ * keycodes.
+ * 
+ * 
+ * Keyboard layouts
+ * 
+ * The XKEYBOARD extension describes the keyboard as having up to 4
+ * "groups" (layouts), and each key on the keyboard has some number of
+ * "levels" corresponding to combinations of modifier keys. Different
+ * keys may have different numbers of levels, with different meanings.
+ * The "Alt-Gr" key on the keyboard is bound to the keysym
+ * ISO_Level3_Shift, which is a modifer that switches to a level
+ * called "Level3" on some keys.
+ * 
+ * The core X protocol (on non-XKB servers) has a simpler model, in
+ * which there are normally 2 groups, and each group has 2 levels
+ * (unshifted and shifted). The "Alt-Gr" key is bound to the keysym
+ * Mode_switch, which is a modifier key that switches to the second
+ * group when it is held down.
+ * 
+ * Our model is a simplification of the XKB model that also fits in
+ * well with the core model; there are between 1 and 4 groups
+ * (keyboard layouts), and each group has exactly 4 levels (plain,
+ * Shift or NumLock, Alt-Gr, and Shift+Alt-Gr).
+ * 
+ * 
+ * Details of key event processing
+ * 
+ * When a modifier keypress is received, vino sends a KeyPress to the
+ * server, and when the corresponding keyrelease event is received, it
+ * sends a KeyRelease to the server. But for non-modifier keys, it
+ * always sends both a KeyPress and a KeyRelease together when it
+ * receives just the keypress event. (This is because we assume that
+ * the client side will handle "key repeat", and if we left the key
+ * pressed down on the server as well, we'd get double repeating.)
+ * 
+ * On servers that support XKB, we also have to worry about multiple
+ * keyboard layouts. We need to keep track of what keysyms are
+ * available in what layouts, and what layout (or "group") the server
+ * is currently using. When a keysym arrives, we look for a way to
+ * type that keysym in the current group, but if it's only available
+ * in a different group, we have to change groups temporarily to type
+ * it (in the same way we have to temporarily change modifiers
+ * sometimes). Doing this programmatically is a little bit tricky; XKB
+ * provides XkbLatchGroup(), but if the layout doesn't use
+ * "XkbWrapIntoRange" semantics, then that can only switch to
+ * higher-numbered groups. Alternatively, we could find the
+ * group-switching keys on the keyboard and use them the way we use
+ * the Shift and Alt-Gr keys, but there are several different ways
+ * that group switching could be set up, and we'd need to support all
+ * of them. Anyway, all of the layouts in the xkeyboard-config package
+ * use XkbWrapIntoRange, so XkbLatchGroup() works for us in that case,
+ * so we just require that, and don't support multiple groups if the
+ * layout uses XkbRedirectIntoRange or XkbClampIntoRange.
+ */
+
 #include <config.h>
 
 #include "vino-input.h"
@@ -42,13 +105,12 @@
 #ifdef HAVE_XTEST
 #include <X11/extensions/XTest.h>
 #endif
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
 
 #include "vino-util.h"
 
-/* See <X11/keysymdef.h> - "Latin 1: Byte 3 and 4 = 0"
- */
-#define VINO_IS_LATIN1_KEYSYM(k) ((k) != NoSymbol && ((k) & 0xff00) == 0)
-
 #define VINO_IS_MODIFIER_KEYSYM(k) (((k) >= XK_Shift_L && (k) <= XK_Hyper_R) || \
                                      (k) == XK_Mode_switch                   || \
                                      (k) == XK_ISO_Level3_Shift)
@@ -60,81 +122,301 @@ typedef enum
   VINO_ALT_GR      = 1 << 2
 } VinoModifierState;
 
-#define VINO_LEFT_OR_RIGHT_SHIFT (VINO_LEFT_SHIFT | VINO_RIGHT_SHIFT)
+typedef enum
+{
+  VINO_LEVEL_PLAIN        = 0,
+  VINO_LEVEL_SHIFT        = 1,
+  VINO_LEVEL_ALTGR        = 2,
+  VINO_LEVEL_SHIFT_ALTGR  = 3
+} VinoLevel;
+
+#define VINO_NUM_LEVELS 4
+#define VINO_NUM_GROUPS 4
+
+#define VINO_LEVEL(state) ((((state) & (VINO_LEFT_SHIFT | VINO_RIGHT_SHIFT)) ? VINO_LEVEL_SHIFT : 0) | \
+			   (((state) & VINO_ALT_GR) ? VINO_LEVEL_ALTGR : 0))
+
+#define VINO_LEVEL_IS_SHIFT(level)    (level & VINO_LEVEL_SHIFT)
+#define VINO_LEVEL_IS_ALTGR(level)    (level & VINO_LEVEL_ALTGR)
 
 typedef struct
 {
-  guint8            button_mask;
+  KeyCode  keycode;
+  guint    level;
+  gboolean keypad;
+} VinoKeybinding;
 
-  VinoModifierState modifier_state;
-  guint8            modifiers [0x100];
-  KeyCode           keycodes [0x100];
-  KeyCode           left_shift_keycode;
-  KeyCode           right_shift_keycode;
-  KeyCode           alt_gr_keycode;
+typedef struct
+{
+  guint8             button_mask;
+  VinoModifierState  modifier_state;
+#ifdef HAVE_XKB
+  int                current_group;
+#endif
+
+  GHashTable        *keybindings;
+  KeyCode            left_shift_keycode;
+  KeyCode            right_shift_keycode;
+  KeyCode            alt_gr_keycode;
+
+  KeySym             alt_gr_keysym;
+
+#ifdef HAVE_XKB
+  int                xkb_num_groups;
+  int                xkb_event_type;
+#endif
 
-  guint             initialized : 1;
-  guint             xtest_supported : 1;
+  guint              initialized : 1;
+  guint              xtest_supported : 1;
+  guint              xkb_supported : 1;
 } VinoInputData;
 
 /* Data is per-display, but we only handle a single display.
  */
 static VinoInputData global_input_data = { 0, };
 
-/* Set up a keysym -> keycode + modifier mapping.
- *
- * RFB transmits the KeySym for a keypress, but we may only inject
- * keycodes using XTest. Thus, we must ensure that the modifier
- * state is such that the keycode we inject maps to the KeySym
- * we received from the client.
- */
 #ifdef HAVE_XTEST
+static void vino_input_initialize_keycodes (Display *xdisplay);
+
 static void
-vino_input_initialize_keycodes (GdkDisplay *display)
+vino_input_initialize_keycodes_core (Display *xdisplay)
 {
-  Display *xdisplay;
   int      min_keycodes, max_keycodes;
   int      keysyms_per_keycode;
-  int      keycode;
-
-  xdisplay = GDK_DISPLAY_XDISPLAY (display);
+  KeySym   sym;
+  int      keycode, level;
 
-  memset (global_input_data.keycodes,   0, sizeof (global_input_data.keycodes));
-  memset (global_input_data.modifiers, -1, sizeof (global_input_data.modifiers));
+  global_input_data.alt_gr_keysym = XK_Mode_switch;
 
   XDisplayKeycodes (xdisplay, &min_keycodes, &max_keycodes);
+  XGetKeyboardMapping (xdisplay, min_keycodes, 0, &keysyms_per_keycode);
 
-  g_assert (min_keycodes >= 8);
-  g_assert (max_keycodes <= 255);
+  /* We iterate by level first, then by keycode, to ensure that we
+   * find an unshifted match for each keysym, if possible.
+   */
+  for (level = 0; level < MAX (keysyms_per_keycode, VINO_NUM_LEVELS); level++)
+    {
+      for (keycode = min_keycodes; keycode < max_keycodes; keycode++)
+	{
+	  VinoKeybinding *binding;
 
-  XGetKeyboardMapping (xdisplay, min_keycodes, 0, &keysyms_per_keycode);
+	  sym = XKeycodeToKeysym (xdisplay, keycode, level);
+	  if (sym == NoSymbol)
+	    continue;
+
+	  if (g_hash_table_lookup (global_input_data.keybindings,
+				   GUINT_TO_POINTER (sym)))
+	    continue;
+
+	  binding = g_new (VinoKeybinding, VINO_NUM_GROUPS);
+	  g_hash_table_insert (global_input_data.keybindings,
+			       GUINT_TO_POINTER (sym), binding);
+
+	  /* There's only one group in plain X, so use "binding[0]". */
+	  binding[0].keycode = keycode;
+	  binding[0].level = level;
 
-  dprintf (INPUT, "Initializing keysym to keycode/modifier mapping\n");
+	  dprintf (INPUT, "\t0x%.2x (%s) -> key %d level %d\n", (guint)sym,
+		   XKeysymToString (sym), keycode, level);
+	}
+    }
+}
 
-  for (keycode = min_keycodes; keycode < max_keycodes; keycode++)
+#ifdef HAVE_XKB
+static void
+vino_input_initialize_keycodes_xkb (Display *xdisplay)
+{
+  XkbDescPtr xkb;
+  int levelmap[XkbMaxKeyTypes][VINO_NUM_LEVELS], kc, kt, level, group;
+  int LevelThreeMask;
+  VinoKeybinding *binding;
+  KeySym sym;
+
+  global_input_data.alt_gr_keysym = XK_ISO_Level3_Shift;
+
+  xkb = XkbGetMap (xdisplay, XkbAllClientInfoMask, XkbUseCoreKbd);
+  g_assert (xkb != NULL);
+
+  LevelThreeMask = XkbKeysymToModifiers (xdisplay, XK_ISO_Level3_Shift);
+
+  /* XKB's levels don't map to VinoLevel, and in fact, may be
+   * different for different types of keys (eg, the second level on
+   * the function keys is Ctrl+Alt, not Shift). So we create
+   * "levelmap" to map VinoLevel values to keytype-specific levels.
+   */
+  for (kt = 0; kt < xkb->map->num_types; kt++)
     {
-      guint8 modifier;
+      XkbKeyTypePtr type;
+      XkbModsPtr mods;
+      int ktl;
+
+      levelmap[kt][VINO_LEVEL_PLAIN] = 0;
+      levelmap[kt][VINO_LEVEL_SHIFT] = -1;
+      levelmap[kt][VINO_LEVEL_ALTGR] = -1;
+      levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] = -1;
 
-      for (modifier = 0; modifier < keysyms_per_keycode; modifier++)
+      type = &xkb->map->types[kt];
+      for (ktl = 0; ktl < type->map_count; ktl++)
 	{
-	  guint32 keysym = XKeycodeToKeysym (xdisplay, keycode, modifier);
+	  if (!type->map[ktl].active)
+	    continue;
+
+	  mods = &type->map[ktl].mods;
+	  if (mods->mask == ShiftMask)
+	    levelmap[kt][VINO_LEVEL_SHIFT] = type->map[ktl].level;
+	  else if (mods->mask == LevelThreeMask)
+	    levelmap[kt][VINO_LEVEL_ALTGR] = type->map[ktl].level;
+	  else if (mods->mask == (ShiftMask | LevelThreeMask))
+	    levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] = type->map[ktl].level;
+	}
 
-	  if (VINO_IS_LATIN1_KEYSYM (keysym))
+      if (levelmap[kt][VINO_LEVEL_SHIFT] == -1)
+	levelmap[kt][VINO_LEVEL_SHIFT] = levelmap[kt][VINO_LEVEL_PLAIN];
+      if (levelmap[kt][VINO_LEVEL_ALTGR] == -1)
+	levelmap[kt][VINO_LEVEL_ALTGR] = levelmap[kt][VINO_LEVEL_PLAIN];
+      if (levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] == -1)
+	levelmap[kt][VINO_LEVEL_SHIFT_ALTGR] = levelmap[kt][VINO_LEVEL_SHIFT];
+    }
+
+  /* Now map out the keysyms. As in the core case, we iterate levels
+   * before keycodes, so our first match for a keysym in each group
+   * will be unshifted if possible.
+   */
+  for (group = 0; group < VINO_NUM_GROUPS; group++)
+    {
+      for (level = 0; level < VINO_NUM_LEVELS; level++)
+	{
+	  for (kc = xkb->min_key_code; kc <= xkb->max_key_code; kc++)
 	    {
-	      if (global_input_data.keycodes [keysym] != 0)
+	      int ngroups, kgroup;
+
+	      ngroups = XkbKeyNumGroups (xkb, kc);
+	      if (ngroups == 0)
 		continue;
 
-	      global_input_data.keycodes  [keysym] = keycode;
-	      global_input_data.modifiers [keysym] = modifier;
+	      /* If the key has fewer groups than the keyboard as a
+	       * whole does, figure out which of the key's groups
+	       * ("kgroup") will end up being used when the keyboard
+	       * as a whole is using group "group".
+	       */
+	      if (group >= ngroups)
+		{
+		  if (xkb->map->key_sym_map[kc].group_info & XkbRedirectIntoRange)
+		    kgroup = 0;
+		  else if (xkb->map->key_sym_map[kc].group_info & XkbClampIntoRange)
+		    kgroup = ngroups - 1;
+		  else
+		    kgroup = group % ngroups;
+		}
+	      else
+		kgroup = group;
+
+	      kt = xkb->map->key_sym_map[kc].kt_index[kgroup];
+	      sym = XkbKeySymEntry (xkb, kc, levelmap[kt][level], kgroup);
+	      if (!sym)
+		continue;
 
-	      dprintf (INPUT, "\t0x%.2x -> %d %d\n", keysym, keycode, modifier);
+	      binding = g_hash_table_lookup (global_input_data.keybindings,
+					     GUINT_TO_POINTER (sym));
+	      if (!binding)
+		{
+		  binding = g_new0 (VinoKeybinding, VINO_NUM_GROUPS);
+		  g_hash_table_insert (global_input_data.keybindings,
+				       GUINT_TO_POINTER (sym), binding);
+		}
+
+	      if (!binding[group].keycode)
+		{
+		  binding[group].keycode = kc;
+		  binding[group].level = level;
+		}
+
+	      if (kgroup == group)
+		{
+		  dprintf (INPUT, "\t0x%.2x (%s) -> key %d group %d level %d\n",
+			   (guint)sym, XKeysymToString (sym), kc, group, level);
+		}
 	    }
 	}
     }
 
-  global_input_data.left_shift_keycode  = XKeysymToKeycode (xdisplay, XK_Shift_L);
-  global_input_data.right_shift_keycode = XKeysymToKeycode (xdisplay, XK_Shift_R);
-  global_input_data.alt_gr_keycode      = XKeysymToKeycode (xdisplay, XK_Mode_switch);
+  /* Figure out how switching between groups works; we only support
+   * XkbWrapIntoRange. (In theory, xkb->ctrls->group_info should equal
+   * XkbWrapIntoRange (ie, 0) when that's the case, but in practice
+   * there seems to be junk in the lower 4 bits, so we test for the
+   * absence of the other two flags instead.)
+   */
+  if (XkbGetControls (xdisplay, XkbGroupsWrapMask, xkb) == Success)
+    {
+      if (!(xkb->ctrls->groups_wrap & (XkbClampIntoRange | XkbRedirectIntoRange)))
+	{
+	  dprintf (INPUT, "%d groups configured\n", xkb->ctrls->num_groups);
+	  global_input_data.xkb_num_groups = xkb->ctrls->num_groups;
+	}
+      else if (xkb->ctrls->num_groups > 1)
+	{
+	  dprintf (INPUT, "groups_wrap is not WrapIntoRange, can't use\n");
+	  global_input_data.xkb_num_groups = -1;
+	}
+    }
+
+  XkbFreeKeyboard (xkb, 0, True);
+}
+
+static GdkFilterReturn
+xkb_event_filter (GdkXEvent *xevent, GdkEvent *event, gpointer user_data)
+{
+  VinoInputData *data = user_data;
+  XkbStateNotifyEvent *state;
+
+  if (((XEvent *)xevent)->type == data->xkb_event_type)
+    {
+      switch (((XkbAnyEvent *)xevent)->xkb_type)
+	{
+	case XkbStateNotify:
+	  state = (XkbStateNotifyEvent *)xevent;
+
+	  if (state->changed & XkbGroupStateMask)
+	    {
+	      data->current_group = state->group;
+	      dprintf (INPUT, "current_group -> %d\n", data->current_group);
+	    }
+	  break;
+
+	case XkbMapNotify:
+	  XkbRefreshKeyboardMapping ((XkbMapNotifyEvent *)xevent);
+	  /* fall through */
+
+	case XkbControlsNotify:
+	  vino_input_initialize_keycodes (((XkbAnyEvent *)xevent)->display);
+	  break;
+	}
+    }
+
+  return GDK_FILTER_CONTINUE;
+}
+#endif
+
+static void
+vino_input_initialize_keycodes (Display *xdisplay)
+{
+  dprintf (INPUT, "Initializing keysym to keycode/modifier mapping\n");
+
+  if (global_input_data.keybindings)
+    g_hash_table_destroy (global_input_data.keybindings);
+  global_input_data.keybindings =
+    g_hash_table_new_full (NULL, NULL, NULL, g_free);
+
+#ifdef HAVE_XKB
+  if (global_input_data.xkb_supported)
+    vino_input_initialize_keycodes_xkb (xdisplay);
+#endif
+  if (!global_input_data.xkb_supported)
+    vino_input_initialize_keycodes_core (xdisplay);
+
+  global_input_data.left_shift_keycode   = XKeysymToKeycode (xdisplay, XK_Shift_L);
+  global_input_data.right_shift_keycode  = XKeysymToKeycode (xdisplay, XK_Shift_R);
+  global_input_data.alt_gr_keycode       = XKeysymToKeycode (xdisplay, global_input_data.alt_gr_keysym);
 }
 #endif /* HAVE_XTEST */
 
@@ -156,14 +438,40 @@ vino_input_init (GdkDisplay *display)
       global_input_data.xtest_supported = TRUE;
     }
 
-  vino_input_initialize_keycodes (display);
+#ifdef HAVE_XKB
+  if (XkbQueryExtension (xdisplay, NULL, &global_input_data.xkb_event_type,
+			 NULL, NULL, NULL))
+    {
+      XkbStateRec state;
+
+      dprintf (INPUT, "Using XKB\n");
+
+      XkbGetState (xdisplay, XkbUseCoreKbd, &state);
+      global_input_data.current_group = state.group;
+
+      XkbSelectEventDetails (xdisplay, XkbUseCoreKbd, XkbStateNotify,
+			     XkbGroupStateMask, XkbGroupStateMask);
+      XkbSelectEventDetails (xdisplay, XkbUseCoreKbd, XkbMapNotify,
+			     XkbAllClientInfoMask, XkbAllClientInfoMask);
+      XkbSelectEventDetails (xdisplay, XkbUseCoreKbd, XkbControlsNotify,
+			     XkbGroupsWrapMask, XkbGroupsWrapMask);
+
+      gdk_window_add_filter (NULL, xkb_event_filter, &global_input_data);
+
+      global_input_data.xkb_supported = TRUE;
+    }
+  else
+#endif
+    global_input_data.xkb_supported = FALSE;
+
+  vino_input_initialize_keycodes (xdisplay);
 
   global_input_data.initialized = TRUE;
 
   return global_input_data.xtest_supported;
 #else
   return global_input_data.xtest_supported = FALSE;
-#endif /* HAVE_XSHM */
+#endif /* HAVE_XTEST */
 }
 
 void
@@ -221,20 +529,24 @@ vino_input_update_modifier_state (VinoIn
     }
 }
 
+/* If @key_press is %TRUE, adjusts the modifier state on the keyboard
+ * such that it's possible to type @binding. If @key_press is %FALSE,
+ * undoes that.
+ */
 static void
-vino_input_fake_modifier (GdkScreen         *screen,
-			  VinoInputData     *input_data,
-			  guint8             modifier,
-			  gboolean           key_press)
+vino_input_fake_modifiers (Display           *xdisplay,
+			   VinoInputData     *input_data,
+			   VinoKeybinding    *binding,
+			   gboolean           key_press)
 {
-  Display           *xdisplay;
-  VinoModifierState  modifier_state = input_data->modifier_state;
+  VinoModifierState modifier_state = input_data->modifier_state;
+  int cur_level;
 
-  xdisplay = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
+  cur_level = VINO_LEVEL (modifier_state);
 
-  if ((modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && modifier != 1)
+  if (VINO_LEVEL_IS_SHIFT (cur_level) && !VINO_LEVEL_IS_SHIFT (binding->level))
     {
-      dprintf (INPUT, "Shift is down, but we don't want it to be\n");
+      dprintf (INPUT, "Faking Shift %s\n", key_press ? "release" : "press");
 
       if (modifier_state & VINO_LEFT_SHIFT)
 	XTestFakeKeyEvent (xdisplay,
@@ -249,35 +561,83 @@ vino_input_fake_modifier (GdkScreen     
 			   CurrentTime);
     }
 
-  if (!(modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && modifier == 1)
+  if (!VINO_LEVEL_IS_SHIFT (cur_level) && VINO_LEVEL_IS_SHIFT (binding->level))
     {
-      dprintf (INPUT, "Shift isn't down, but we want it to be\n");
+      dprintf (INPUT, "Faking Shift %s\n", key_press ? "press" : "release");
 
       XTestFakeKeyEvent (xdisplay,
 			 input_data->left_shift_keycode,
 			 key_press,
 			 CurrentTime);
     }
-      
-  if ((modifier_state & VINO_ALT_GR) && modifier != 2)
+
+  if (VINO_LEVEL_IS_ALTGR (cur_level) != VINO_LEVEL_IS_ALTGR (binding->level))
     {
-      dprintf (INPUT, "Alt is down, but we don't want it to be\n");
+      gboolean want_pressed = VINO_LEVEL_IS_ALTGR (binding->level) ? key_press : !key_press;
+
+      dprintf (INPUT, "Faking Alt-Gr %s\n", want_pressed ? "press" : "release");
 
       XTestFakeKeyEvent (xdisplay,
 			 input_data->alt_gr_keycode,
-			 !key_press,
+			 want_pressed,
 			 CurrentTime);
     }
-      
-  if (!(modifier_state & VINO_ALT_GR) && modifier == 2)
+}
+
+static gboolean
+vino_input_fake_keypress (Display *xdisplay, guint32 keysym)
+{
+  VinoKeybinding *binding = g_hash_table_lookup (global_input_data.keybindings,
+						 GUINT_TO_POINTER (keysym));
+
+  if (binding)
     {
-      dprintf (INPUT, "Alt isn't down, but we want it to be\n");
+      int group;
 
-      XTestFakeKeyEvent (xdisplay,
-			 input_data->alt_gr_keycode,
-			 key_press,
-			 CurrentTime);
+#ifdef HAVE_XKB
+      if (global_input_data.xkb_supported)
+	{
+	  if (binding[global_input_data.current_group].keycode)
+	    group = global_input_data.current_group;
+	  else if (global_input_data.xkb_num_groups > 1)
+	    {
+	      for (group = 0; group < VINO_NUM_GROUPS; group ++)
+		{
+		  if (binding[group].keycode)
+		    {
+		      dprintf (INPUT, "Latching group to %d\n", group);
+		      XkbLatchGroup (xdisplay, XkbUseCoreKbd,
+				     (group - global_input_data.current_group) %
+				     global_input_data.xkb_num_groups);
+		      break;
+		    }
+		}
+	      if (group == VINO_NUM_GROUPS)
+		return FALSE;
+	    }
+	  else
+	    return FALSE;
+	}
+      else
+#endif
+	group = 0;
+
+      vino_input_fake_modifiers (xdisplay, &global_input_data,
+				 &binding[group], TRUE);
+
+      dprintf (INPUT, "Injecting keysym 0x%.2x (%s) press/release (via keycode %d, level %d)\n",
+	       keysym, XKeysymToString (keysym), binding[group].keycode, binding[group].level);
+
+      XTestFakeKeyEvent (xdisplay, binding[group].keycode, TRUE,  CurrentTime);
+      XTestFakeKeyEvent (xdisplay, binding[group].keycode, FALSE, CurrentTime);
+
+      vino_input_fake_modifiers (xdisplay, &global_input_data,
+				 &binding[group], FALSE);
+
+      return TRUE;
     }
+  else
+    return FALSE;
 }
 #endif /* HAVE_XTEST */
 
@@ -289,6 +649,9 @@ vino_input_handle_key_event (GdkScreen *
 #ifdef HAVE_XTEST
   Display *xdisplay;
 
+  dprintf (INPUT, "Got key %s for %s\n", key_press ? "press" : "release",
+	   XKeysymToString (keysym));
+
   /* 
    * We inject a key press/release pair for all key presses 
    * and ignore key releases. The exception is modifiers.
@@ -308,28 +671,12 @@ vino_input_handle_key_event (GdkScreen *
 				    keysym, key_press);
 
   vino_input_update_modifier_state (&global_input_data,
-				    VINO_ALT_GR, XK_Mode_switch,
+				    VINO_ALT_GR, global_input_data.alt_gr_keysym,
 				    keysym, key_press);
 
-  if (VINO_IS_LATIN1_KEYSYM (keysym))
+  if (!VINO_IS_MODIFIER_KEYSYM (keysym))
     {
-      KeyCode keycode  = global_input_data.keycodes [keysym];
-      guint8  modifier = global_input_data.modifiers [keysym];
-
-      if (keycode != NoSymbol)
-	{
-	  g_assert (key_press != FALSE);
-
-	  vino_input_fake_modifier (screen, &global_input_data, modifier, TRUE);
-
-	  dprintf (INPUT, "Injecting keysym 0x%.2x %s (keycode %d, modifier %d)\n",
-		   keysym, key_press ? "press" : "release", keycode, modifier);
-
-	  XTestFakeKeyEvent (xdisplay, keycode, TRUE,  CurrentTime);
-	  XTestFakeKeyEvent (xdisplay, keycode, FALSE, CurrentTime);
-
-	  vino_input_fake_modifier (screen, &global_input_data, modifier, FALSE);
-	}
+      vino_input_fake_keypress (xdisplay, keysym);
     }
   else if (keysym != XK_Caps_Lock)
     {
@@ -337,15 +684,11 @@ vino_input_handle_key_event (GdkScreen *
 
       if ((keycode = XKeysymToKeycode (xdisplay, keysym)) != NoSymbol)
 	{
-	  dprintf (INPUT, "Injecting keysym 0x%.2x %s (keycode %d)\n",
-		   keysym, key_press ? "press" : "release", keycode);
+	  dprintf (INPUT, "Injecting keysym 0x%.2x (%s) %s (keycode %d)\n",
+		   keysym, XKeysymToString (keysym),
+		   key_press ? "press" : "release", keycode);
 
 	  XTestFakeKeyEvent (xdisplay, keycode, key_press, CurrentTime);
-
-	  if (key_press && !VINO_IS_MODIFIER_KEYSYM (keysym))
-	    {
-	      XTestFakeKeyEvent (xdisplay, keycode, FALSE, CurrentTime);
-	    }
 	}
     }
 
