Index: configure.in
===================================================================
RCS file: /cvs/gnome/vino/configure.in,v
retrieving revision 1.110
diff -u -p -r1.110 configure.in
--- configure.in	15 Aug 2006 12:32:30 -0000	1.110
+++ configure.in	14 Sep 2006 15:51:12 -0000
@@ -172,6 +172,15 @@ AC_CHECK_HEADER(X11/extensions/XTest.h, 
       XTEST_LIBS="-lXtst"],, $X_LIBS)
   ])
 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
Index: server/vino-input.c
===================================================================
RCS file: /cvs/gnome/vino/server/vino-input.c,v
retrieving revision 1.5
diff -u -p -r1.5 vino-input.c
--- server/vino-input.c	16 Nov 2005 08:18:43 -0000	1.5
+++ server/vino-input.c	14 Sep 2006 15:51:12 -0000
@@ -42,109 +42,538 @@
 #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)
 
 typedef enum
 {
-  VINO_LEFT_SHIFT  = 1 << 0,
-  VINO_RIGHT_SHIFT = 1 << 1,
-  VINO_ALT_GR      = 1 << 2
+  VINO_LEFT_SHIFT   = 1 << 0,
+  VINO_RIGHT_SHIFT  = 1 << 1,
+  VINO_LEFT_CONTROL = 1 << 2,
+  VINO_RIGHT_ALT    = 1 << 3,
+  VINO_ALT_GR       = 1 << 4
 } VinoModifierState;
 
 #define VINO_LEFT_OR_RIGHT_SHIFT (VINO_LEFT_SHIFT | VINO_RIGHT_SHIFT)
 
+typedef struct {
+  KeyCode keycode;
+  int     level;
+} VinoKeybinding;
+
 typedef struct
 {
   guint8            button_mask;
-
   VinoModifierState modifier_state;
-  guint8            modifiers [0x100];
-  KeyCode           keycodes [0x100];
+#ifdef HAVE_XKB
+  int               current_group;
+#endif
+
+  GHashTable       *keybindings;
+  GHashTable       *decompositions;
   KeyCode           left_shift_keycode;
   KeyCode           right_shift_keycode;
+  KeyCode           left_control_keycode;
   KeyCode           alt_gr_keycode;
+  KeySym            alt_gr_keysym;
 
   guint             initialized : 1;
   guint             xtest_supported : 1;
+  guint             xkb_supported : 1;
+
+#ifdef HAVE_XKB
+  int               num_groups;
+  int               xkb_event_type;
+#endif
 } 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.
+static struct {
+  guint32 composed;
+  guint32 decomposed[3];
+} decompositions[] = {
+  { XK_Cabovedot, { XK_dead_abovedot, 'C', 0 } },
+  { XK_Eabovedot, { XK_dead_abovedot, 'E', 0 } },
+  { XK_Gabovedot, { XK_dead_abovedot, 'G', 0 } },
+  { XK_Iabovedot, { XK_dead_abovedot, 'I', 0 } },
+  { XK_Zabovedot, { XK_dead_abovedot, 'Z', 0 } },
+  { XK_cabovedot, { XK_dead_abovedot, 'c', 0 } },
+  { XK_eabovedot, { XK_dead_abovedot, 'e', 0 } },
+  { XK_gabovedot, { XK_dead_abovedot, 'g', 0 } },
+  { XK_idotless,  { XK_dead_abovedot, 'i', 0 } },
+  { XK_zabovedot, { XK_dead_abovedot, 'z', 0 } },
+  { XK_abovedot,  { XK_dead_abovedot, XK_dead_abovedot, 0 } },
+
+  { XK_Aring,  { XK_dead_abovering, 'A', 0 } },
+  { XK_Uring,  { XK_dead_abovering, 'U', 0 } },
+  { XK_aring,  { XK_dead_abovering, 'a', 0 } },
+  { XK_uring,  { XK_dead_abovering, 'u', 0 } },
+  { XK_degree, { XK_dead_abovering, XK_dead_abovering, 0 } },
+
+  { XK_Aacute, { XK_dead_acute, 'A', 0 } },
+  { XK_Cacute, { XK_dead_acute, 'C', 0 } },
+  { XK_Eacute, { XK_dead_acute, 'E', 0 } },
+  { XK_Iacute, { XK_dead_acute, 'I', 0 } },
+  { XK_Lacute, { XK_dead_acute, 'L', 0 } },
+  { XK_Nacute, { XK_dead_acute, 'N', 0 } },
+  { XK_Oacute, { XK_dead_acute, 'O', 0 } },
+  { XK_Racute, { XK_dead_acute, 'R', 0 } },
+  { XK_Sacute, { XK_dead_acute, 'S', 0 } },
+  { XK_Uacute, { XK_dead_acute, 'U', 0 } },
+  { XK_Yacute, { XK_dead_acute, 'Y', 0 } },
+  { XK_Zacute, { XK_dead_acute, 'Z', 0 } },
+  { XK_aacute, { XK_dead_acute, 'a', 0 } },
+  { XK_cacute, { XK_dead_acute, 'c', 0 } },
+  { XK_eacute, { XK_dead_acute, 'e', 0 } },
+  { XK_iacute, { XK_dead_acute, 'i', 0 } },
+  { XK_lacute, { XK_dead_acute, 'l', 0 } },
+  { XK_nacute, { XK_dead_acute, 'n', 0 } },
+  { XK_oacute, { XK_dead_acute, 'o', 0 } },
+  { XK_racute, { XK_dead_acute, 'r', 0 } },
+  { XK_sacute, { XK_dead_acute, 's', 0 } },
+  { XK_uacute, { XK_dead_acute, 'u', 0 } },
+  { XK_yacute, { XK_dead_acute, 'y', 0 } },
+  { XK_zacute, { XK_dead_acute, 'z', 0 } },
+  { XK_acute,  { XK_dead_acute, XK_dead_acute, 0 } },
+
+  { XK_Abreve, { XK_dead_breve, 'A', 0 } },
+  { XK_Gbreve, { XK_dead_breve, 'G', 0 } },
+  { XK_Ubreve, { XK_dead_breve, 'U', 0 } },
+  { XK_abreve, { XK_dead_breve, 'a', 0 } },
+  { XK_gbreve, { XK_dead_breve, 'g', 0 } },
+  { XK_ubreve, { XK_dead_breve, 'u', 0 } },
+  { XK_breve,  { XK_dead_breve, XK_dead_breve, 0 } },
+
+  { XK_Ccaron, { XK_dead_caron, 'C', 0 } },
+  { XK_Dcaron, { XK_dead_caron, 'D', 0 } },
+  { XK_Ecaron, { XK_dead_caron, 'E', 0 } },
+  { XK_Lcaron, { XK_dead_caron, 'L', 0 } },
+  { XK_Ncaron, { XK_dead_caron, 'N', 0 } },
+  { XK_Rcaron, { XK_dead_caron, 'R', 0 } },
+  { XK_Scaron, { XK_dead_caron, 'S', 0 } },
+  { XK_Tcaron, { XK_dead_caron, 'T', 0 } },
+  { XK_Zcaron, { XK_dead_caron, 'Z', 0 } },
+  { XK_ccaron, { XK_dead_caron, 'c', 0 } },
+  { XK_dcaron, { XK_dead_caron, 'd', 0 } },
+  { XK_ecaron, { XK_dead_caron, 'e', 0 } },
+  { XK_lcaron, { XK_dead_caron, 'l', 0 } },
+  { XK_ncaron, { XK_dead_caron, 'n', 0 } },
+  { XK_rcaron, { XK_dead_caron, 'r', 0 } },
+  { XK_scaron, { XK_dead_caron, 's', 0 } },
+  { XK_tcaron, { XK_dead_caron, 't', 0 } },
+  { XK_zcaron, { XK_dead_caron, 'z', 0 } },
+  { XK_caron,  { XK_dead_caron, XK_dead_caron, 0 } },
+
+  { XK_Ccedilla, { XK_dead_cedilla, 'C', 0 } },
+  { XK_Gcedilla, { XK_dead_cedilla, 'G', 0 } },
+  { XK_Kcedilla, { XK_dead_cedilla, 'K', 0 } },
+  { XK_Lcedilla, { XK_dead_cedilla, 'L', 0 } },
+  { XK_Ncedilla, { XK_dead_cedilla, 'N', 0 } },
+  { XK_Rcedilla, { XK_dead_cedilla, 'R', 0 } },
+  { XK_Scedilla, { XK_dead_cedilla, 'S', 0 } },
+  { XK_Tcedilla, { XK_dead_cedilla, 'T', 0 } },
+  { XK_ccedilla, { XK_dead_cedilla, 'c', 0 } },
+  { XK_gcedilla, { XK_dead_cedilla, 'g', 0 } },
+  { XK_kcedilla, { XK_dead_cedilla, 'k', 0 } },
+  { XK_lcedilla, { XK_dead_cedilla, 'l', 0 } },
+  { XK_ncedilla, { XK_dead_cedilla, 'n', 0 } },
+  { XK_rcedilla, { XK_dead_cedilla, 'r', 0 } },
+  { XK_scedilla, { XK_dead_cedilla, 's', 0 } },
+  { XK_tcedilla, { XK_dead_cedilla, 't', 0 } },
+  { XK_cedilla,  { XK_dead_cedilla, XK_dead_cedilla, 0 } },
+
+  { XK_Acircumflex, { XK_dead_circumflex, 'A', 0 } },
+  { XK_Ccircumflex, { XK_dead_circumflex, 'C', 0 } },
+  { XK_Ecircumflex, { XK_dead_circumflex, 'E', 0 } },
+  { XK_Gcircumflex, { XK_dead_circumflex, 'G', 0 } },
+  { XK_Hcircumflex, { XK_dead_circumflex, 'H', 0 } },
+  { XK_Icircumflex, { XK_dead_circumflex, 'I', 0 } },
+  { XK_Jcircumflex, { XK_dead_circumflex, 'J', 0 } },
+  { XK_Ocircumflex, { XK_dead_circumflex, 'O', 0 } },
+  { XK_Scircumflex, { XK_dead_circumflex, 'S', 0 } },
+  { XK_Ucircumflex, { XK_dead_circumflex, 'U', 0 } },
+  { XK_acircumflex, { XK_dead_circumflex, 'a', 0 } },
+  { XK_ccircumflex, { XK_dead_circumflex, 'c', 0 } },
+  { XK_ecircumflex, { XK_dead_circumflex, 'e', 0 } },
+  { XK_gcircumflex, { XK_dead_circumflex, 'g', 0 } },
+  { XK_hcircumflex, { XK_dead_circumflex, 'h', 0 } },
+  { XK_icircumflex, { XK_dead_circumflex, 'i', 0 } },
+  { XK_jcircumflex, { XK_dead_circumflex, 'j', 0 } },
+  { XK_ocircumflex, { XK_dead_circumflex, 'o', 0 } },
+  { XK_scircumflex, { XK_dead_circumflex, 's', 0 } },
+  { XK_ucircumflex, { XK_dead_circumflex, 'u', 0 } },
+  { XK_asciicircum, { XK_dead_circumflex, XK_dead_circumflex, 0 } },
+
+  { XK_Adiaeresis, { XK_dead_diaeresis, 'A', 0 } },
+  { XK_Ediaeresis, { XK_dead_diaeresis, 'E', 0 } },
+  { XK_Idiaeresis, { XK_dead_diaeresis, 'I', 0 } },
+  { XK_Odiaeresis, { XK_dead_diaeresis, 'O', 0 } },
+  { XK_Udiaeresis, { XK_dead_diaeresis, 'U', 0 } },
+  { XK_adiaeresis, { XK_dead_diaeresis, 'a', 0 } },
+  { XK_ediaeresis, { XK_dead_diaeresis, 'e', 0 } },
+  { XK_idiaeresis, { XK_dead_diaeresis, 'i', 0 } },
+  { XK_odiaeresis, { XK_dead_diaeresis, 'o', 0 } },
+  { XK_udiaeresis, { XK_dead_diaeresis, 'u', 0 } },
+  { XK_ydiaeresis, { XK_dead_diaeresis, 'y', 0 } },
+  { XK_diaeresis,  { XK_dead_diaeresis, XK_dead_diaeresis, 0 } },
+
+  { XK_Odoubleacute, { XK_dead_doubleacute, 'O', 0 } },
+  { XK_Udoubleacute, { XK_dead_doubleacute, 'U', 0 } },
+  { XK_odoubleacute, { XK_dead_doubleacute, 'o', 0 } },
+  { XK_udoubleacute, { XK_dead_doubleacute, 'u', 0 } },
+  { XK_doubleacute,  { XK_dead_doubleacute, XK_dead_doubleacute, 0 } },
+
+  { XK_Agrave, { XK_dead_grave, 'A', 0 } },
+  { XK_Egrave, { XK_dead_grave, 'E', 0 } },
+  { XK_Igrave, { XK_dead_grave, 'I', 0 } },
+  { XK_Ograve, { XK_dead_grave, 'O', 0 } },
+  { XK_Ugrave, { XK_dead_grave, 'U', 0 } },
+  { XK_agrave, { XK_dead_grave, 'a', 0 } },
+  { XK_egrave, { XK_dead_grave, 'e', 0 } },
+  { XK_igrave, { XK_dead_grave, 'i', 0 } },
+  { XK_ograve, { XK_dead_grave, 'o', 0 } },
+  { XK_ugrave, { XK_dead_grave, 'u', 0 } },
+  { XK_grave,  { XK_dead_grave, XK_dead_grave, 0 } },
+
+  { XK_Amacron, { XK_dead_macron, 'A', 0 } },
+  { XK_Emacron, { XK_dead_macron, 'E', 0 } },
+  { XK_Imacron, { XK_dead_macron, 'I', 0 } },
+  { XK_Omacron, { XK_dead_macron, 'O', 0 } },
+  { XK_Umacron, { XK_dead_macron, 'U', 0 } },
+  { XK_amacron, { XK_dead_macron, 'a', 0 } },
+  { XK_emacron, { XK_dead_macron, 'e', 0 } },
+  { XK_imacron, { XK_dead_macron, 'i', 0 } },
+  { XK_omacron, { XK_dead_macron, 'o', 0 } },
+  { XK_umacron, { XK_dead_macron, 'u', 0 } },
+  { XK_macron,  { XK_dead_macron, XK_dead_macron, 0 } },
+
+  { XK_Aogonek, { XK_dead_ogonek, 'A', 0 } },
+  { XK_Eogonek, { XK_dead_ogonek, 'E', 0 } },
+  { XK_Iogonek, { XK_dead_ogonek, 'I', 0 } },
+  { XK_Uogonek, { XK_dead_ogonek, 'U', 0 } },
+  { XK_aogonek, { XK_dead_ogonek, 'a', 0 } },
+  { XK_eogonek, { XK_dead_ogonek, 'e', 0 } },
+  { XK_iogonek, { XK_dead_ogonek, 'i', 0 } },
+  { XK_uogonek, { XK_dead_ogonek, 'u', 0 } },
+  { XK_ogonek,  { XK_dead_ogonek, XK_dead_ogonek, 0 } },
+
+  { XK_Atilde, 	 { XK_dead_tilde, 'A', 0 } },
+  { XK_Itilde, 	 { XK_dead_tilde, 'I', 0 } },
+  { XK_Ntilde, 	 { XK_dead_tilde, 'N', 0 } },
+  { XK_Otilde, 	 { XK_dead_tilde, 'O', 0 } },
+  { XK_Utilde, 	 { XK_dead_tilde, 'U', 0 } },
+  { XK_atilde, 	 { XK_dead_tilde, 'a', 0 } },
+  { XK_itilde, 	 { XK_dead_tilde, 'i', 0 } },
+  { XK_ntilde, 	 { XK_dead_tilde, 'n', 0 } },
+  { XK_otilde, 	 { XK_dead_tilde, 'o', 0 } },
+  { XK_utilde, 	 { XK_dead_tilde, 'u', 0 } },
+  { XK_asciitilde, { XK_dead_tilde, XK_dead_tilde, 0 } },
+
+  { XK_Greek_ALPHAaccent,   { XK_dead_acute, XK_Greek_ALPHA, 0 } },
+  { XK_Greek_EPSILONaccent, { XK_dead_acute, XK_Greek_EPSILON, 0 } },
+  { XK_Greek_ETAaccent,     { XK_dead_acute, XK_Greek_ETA, 0 } },
+  { XK_Greek_IOTAaccent,    { XK_dead_acute, XK_Greek_IOTA, 0 } },
+  { XK_Greek_OMICRONaccent, { XK_dead_acute, XK_Greek_OMICRON, 0 } },
+  { XK_Greek_UPSILONaccent, { XK_dead_acute, XK_Greek_UPSILON, 0 } },
+  { XK_Greek_OMEGAaccent,   { XK_dead_acute, XK_Greek_OMEGA, 0 } },
+  { XK_Greek_alphaaccent,   { XK_dead_acute, XK_Greek_alpha, 0 } },
+  { XK_Greek_epsilonaccent, { XK_dead_acute, XK_Greek_epsilon, 0 } },
+  { XK_Greek_etaaccent,     { XK_dead_acute, XK_Greek_eta, 0 } },
+  { XK_Greek_iotaaccent,    { XK_dead_acute, XK_Greek_iota, 0 } },
+  { XK_Greek_omicronaccent, { XK_dead_acute, XK_Greek_omicron, 0 } },
+  { XK_Greek_upsilonaccent, { XK_dead_acute, XK_Greek_upsilon, 0 } },
+  { XK_Greek_omegaaccent,   { XK_dead_acute, XK_Greek_omega, 0 } },
+
+  { XK_Greek_IOTAdieresis,    { XK_dead_diaeresis, XK_Greek_IOTA, 0 } },
+  { XK_Greek_UPSILONdieresis, { XK_dead_diaeresis, XK_Greek_UPSILON, 0 } },
+  { XK_Greek_iotadieresis,    { XK_dead_diaeresis, XK_Greek_iota, 0 } },
+  { XK_Greek_upsilondieresis, { XK_dead_diaeresis, XK_Greek_upsilon, 0 } },
+
+  { XK_Greek_iotaaccentdieresis,    { XK_dead_acute, XK_dead_diaeresis, XK_Greek_iota } },
+  { XK_Greek_upsilonaccentdieresis, { XK_dead_acute, XK_dead_diaeresis, XK_Greek_upsilon } }
+};
+static const int num_decompositions = G_N_ELEMENTS (decompositions);
+
+/* Set up a keysym -> keycode + group + level mapping.
  *
  * RFB transmits the KeySym for a keypress, but we may only inject
- * keycodes using XTest. Thus, we must ensure that the modifier
+ * keycodes using XTest. Thus, we must ensure that the group/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 (GdkDisplay *display)
+vino_input_initialize_keycodes_core (Display *xdisplay)
 {
-  Display *xdisplay;
   int      min_keycodes, max_keycodes;
   int      keysyms_per_keycode;
   KeySym  *keymap;
   int      keycode;
 
-  xdisplay = GDK_DISPLAY_XDISPLAY (display);
-
-  memset (global_input_data.keycodes,   0, sizeof (global_input_data.keycodes));
-  memset (global_input_data.modifiers, -1, sizeof (global_input_data.modifiers));
+  /* The Core X protocol describes itself as having 2 groups with 2
+   * levels, where Alt-Gr (bound to Mode_switch) switches groups when
+   * it is held down. For our purposes, we think of this as being
+   * 1 group with 4 levels (where Alt-Gr, as in the XKB case, shifts
+   * to levels 3 and 4).
+   */
 
-  XDisplayKeycodes (xdisplay, &min_keycodes, &max_keycodes);
+  global_input_data.alt_gr_keysym = XK_Mode_switch;
 
-  g_assert (min_keycodes >= 8);
-  g_assert (max_keycodes <= 255);
+  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);
 
+  XDisplayKeycodes (xdisplay, &min_keycodes, &max_keycodes);
   keymap = XGetKeyboardMapping (xdisplay,
 				min_keycodes,
 				max_keycodes - min_keycodes + 1,
 				&keysyms_per_keycode);
-
   g_assert (keymap != NULL);
 
-  dprintf (INPUT, "Initializing keysym to keycode/modifier mapping\n");
-
   for (keycode = min_keycodes; keycode < max_keycodes; keycode++)
     {
-      int    keycode_index = (keycode - min_keycodes) * keysyms_per_keycode;
-      guint8 modifier;
+      int keycode_index = (keycode - min_keycodes) * keysyms_per_keycode;
+      int level;
 
-      for (modifier = 0; modifier < keysyms_per_keycode; modifier++)
+      for (level = 0; level < MAX (keysyms_per_keycode, 4); level++)
 	{
-	  guint32 keysym = keymap [keycode_index + modifier];
+	  guint32 keysym;
+	  VinoKeybinding *binding;
 
-	  if (VINO_IS_LATIN1_KEYSYM (keysym) &&
-	      XKeysymToKeycode (xdisplay, keysym) == keycode)
+	  keysym = keymap [keycode_index + level];
+	  if (g_hash_table_lookup (global_input_data.keybindings,
+				   GUINT_TO_POINTER (keysym)))
+	    continue;
+
+	  binding = g_new0 (VinoKeybinding, 4);
+	  binding[0].keycode = keycode;
+	  binding[0].level   = level;
+
+	  g_hash_table_insert (global_input_data.keybindings,
+			       GUINT_TO_POINTER (keysym),
+			       binding);
+
+	  dprintf (INPUT, "\t0x%.2x (%s) -> key %d level %d\n", keysym,
+		   XKeysymToString (keysym), keycode, level);
+	}
+    }
+
+  XFree (keymap);
+}
+
+#ifdef HAVE_XKB
+static void
+vino_input_initialize_keycodes_xkb (Display *xdisplay)
+{
+  XkbDescPtr xkb;
+  int levelmap[XkbMaxKeyTypes * 4], kc, i;
+  int LevelThreeMask;
+  VinoKeybinding *binding;
+  KeySym sym;
+
+  /* The situation in XKB is much more complicated than in Core X;
+   * there can be 4 separate "groups" (keyboard layouts) configured,
+   * and the "levels" on different keys may not all be the same. (Eg,
+   * normally a key's second level corresponds to "Shift", but the
+   * second level on some keys (like "F1") might correspond
+   * "Control+Alt".)
+   */
+
+  global_input_data.alt_gr_keysym = XK_ISO_Level3_Shift;
+
+  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);
+
+  xkb = XkbGetMap (xdisplay, XkbAllClientInfoMask, XkbUseCoreKbd);
+  g_assert (xkb != NULL);
+
+  LevelThreeMask = XkbKeysymToModifiers (xdisplay, XK_ISO_Level3_Shift);
+
+  /* Figure out which levels on each of the keytypes correspond to
+   * Shift, Alt-Gr, and Shift + Alt-Gr
+   */
+  for (i = 0; i < xkb->map->num_types; i++)
+    {
+      XkbKeyTypePtr type;
+      XkbModsPtr mods;
+      int ktl;
+
+      levelmap[i * 4] = 0;
+      levelmap[i * 4 + 1] = -1;
+      levelmap[i * 4 + 2] = -1;
+      levelmap[i * 4 + 3] = -1;
+
+      type = &xkb->map->types[i];
+      for (ktl = 0; ktl < type->map_count; ktl++)
+	{
+	  if (!type->map[ktl].active)
+	    continue;
+
+	  mods = &type->map[ktl].mods;
+	  if (mods->mask == ShiftMask)
+	    levelmap[i * 4 + 1] = type->map[ktl].level;
+	  else if (mods->mask == LevelThreeMask)
+	    levelmap[i * 4 + 2] = type->map[ktl].level;
+	  else if (mods->mask == (ShiftMask | LevelThreeMask))
+	    levelmap[i * 4 + 3] = type->map[ktl].level;
+	}
+    }
+
+  /* Now map out the keysyms */
+  for (kc = xkb->min_key_code; kc <= xkb->max_key_code; kc++)
+    {
+      int ngroups, group, kgroup, kt, lev;
+
+      ngroups = XkbKeyNumGroups (xkb, kc);
+      if (ngroups == 0)
+	continue;
+
+      for (group = 0; group < 4; 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];
+
+	  for (lev = 0; lev < 4; lev++)
 	    {
-	      if (global_input_data.keycodes [keysym] != 0)
+	      int klev = levelmap[kt * 4 + lev];
+	      if (klev == -1)
 		continue;
 
-	      global_input_data.keycodes  [keysym] = keycode;
-	      global_input_data.modifiers [keysym] = modifier;
+	      sym = XkbKeySymEntry (xkb, kc, klev, 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, 4);
+		  g_hash_table_insert (global_input_data.keybindings,
+				       GUINT_TO_POINTER (sym), binding);
+		}
+
+	      binding[group].keycode = kc;
+	      binding[group].level   = lev;
+
+	      if (kgroup == group)
+		{
+		  dprintf (INPUT, "\t0x%.2x (%s) -> key %d group %d level %d\n",
+			   (guint)sym, XKeysymToString (sym), kc, group, lev);
+		}
 	    }
 	}
     }
 
-  XFree (keymap);
+  /* Figure out how switching between groups works; we only support
+   * XkbWrapIntoRange. (In theory, xkb->ctrls->group_info should ==
+   * XkbWrapIntoRange (0) when that's in use, but in practice there
+   * seems to be junk in the lower 4 bits, so we test for the absence
+   * of the other two flags.)
+   */
+  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.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.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;
+	      printf ("current_group -> %d\n", data->current_group);
+	    }
+	  break;
 
-  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);
+	case XkbMapNotify:
+	  XkbRefreshKeyboardMapping ((XkbMapNotifyEvent *)xevent);
+	  /* fall through */
+
+	case XkbControlsNotify:
+	  g_hash_table_destroy (global_input_data.keybindings);
+	  global_input_data.keybindings =
+	    g_hash_table_new_full (NULL, NULL, NULL, g_free);
+
+	  vino_input_initialize_keycodes_xkb (((XkbAnyEvent *)xevent)->display);
+	  break;
+	}
+    }
+
+  return GDK_FILTER_CONTINUE;
+}
+#endif
+
+static void
+vino_input_initialize_keycodes (Display *xdisplay)
+{
+  int i;
+
+  global_input_data.decompositions = g_hash_table_new (NULL, NULL);
+  for (i = 0; i < num_decompositions; i++)
+    {
+      g_hash_table_insert (global_input_data.decompositions,
+			   GUINT_TO_POINTER (decompositions[i].composed),
+			   decompositions[i].decomposed);
+    }
+
+  dprintf (INPUT, "Initializing keysym to keycode/modifier mapping\n");
+
+#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.left_control_keycode = XKeysymToKeycode (xdisplay, XK_Control_L);
+  global_input_data.alt_gr_keycode       = XKeysymToKeycode (xdisplay, global_input_data.alt_gr_keysym);
 }
 #endif /* HAVE_XTEST */
 
@@ -166,7 +595,33 @@ 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;
 
@@ -232,19 +687,19 @@ vino_input_update_modifier_state (VinoIn
 }
 
 static void
-vino_input_fake_modifier (GdkScreen         *screen,
-			  VinoInputData     *input_data,
-			  guint8             modifier,
-			  gboolean           key_press)
+vino_input_fake_modifiers (GdkScreen         *screen,
+			   VinoInputData     *input_data,
+			   int                level,
+			   gboolean           key_press)
 {
   Display           *xdisplay;
   VinoModifierState  modifier_state = input_data->modifier_state;
 
   xdisplay = GDK_DISPLAY_XDISPLAY (gdk_screen_get_display (screen));
 
-  if ((modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && modifier != 1)
+  if ((modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && !(level & 1))
     {
-      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,
@@ -259,9 +714,9 @@ vino_input_fake_modifier (GdkScreen     
 			   CurrentTime);
     }
 
-  if (!(modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && modifier == 1)
+  if (!(modifier_state & VINO_LEFT_OR_RIGHT_SHIFT) && (level & 1))
     {
-      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,
@@ -269,9 +724,9 @@ vino_input_fake_modifier (GdkScreen     
 			 CurrentTime);
     }
       
-  if ((modifier_state & VINO_ALT_GR) && modifier != 2)
+  if ((modifier_state & VINO_ALT_GR) && !(level & 2))
     {
-      dprintf (INPUT, "Alt is down, but we don't want it to be\n");
+      dprintf (INPUT, "Faking Alt-Gr %s\n", key_press ? "release" : "press");
 
       XTestFakeKeyEvent (xdisplay,
 			 input_data->alt_gr_keycode,
@@ -279,9 +734,9 @@ vino_input_fake_modifier (GdkScreen     
 			 CurrentTime);
     }
       
-  if (!(modifier_state & VINO_ALT_GR) && modifier == 2)
+  if (!(modifier_state & VINO_ALT_GR) && (level & 2))
     {
-      dprintf (INPUT, "Alt isn't down, but we want it to be\n");
+      dprintf (INPUT, "Faking Alt-Gr %s\n", key_press ? "press" : "release");
 
       XTestFakeKeyEvent (xdisplay,
 			 input_data->alt_gr_keycode,
@@ -289,6 +744,82 @@ vino_input_fake_modifier (GdkScreen     
 			 CurrentTime);
     }
 }
+
+static gboolean
+vino_input_fake_keypress (GdkScreen *screen, Display *xdisplay, guint32 keysym)
+{
+  VinoKeybinding *binding = g_hash_table_lookup (global_input_data.keybindings,
+						 GUINT_TO_POINTER (keysym));
+
+  if (binding)
+    {
+      int group;
+
+#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.num_groups > 1)
+	    {
+	      for (group = 0; group < 4; 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.num_groups);
+		      break;
+		    }
+		}
+	      if (group == 4)
+		return FALSE;
+	    }
+	  else
+	    return FALSE;
+	}
+      else
+	group = 0;
+#endif
+
+      vino_input_fake_modifiers (screen, &global_input_data,
+				 binding[group].level, TRUE);
+
+      dprintf (INPUT, "Injecting keysym 0x%.2x (%s) press/release (keycode %d, group %d, level %d)\n",
+	       keysym, XKeysymToString (keysym), binding[group].keycode,
+	       group, binding[group].level);
+
+      XTestFakeKeyEvent (xdisplay, binding[group].keycode, TRUE,  CurrentTime);
+      XTestFakeKeyEvent (xdisplay, binding[group].keycode, FALSE, CurrentTime);
+
+      vino_input_fake_modifiers (screen, &global_input_data,
+				 binding[group].level, FALSE);
+
+      return TRUE;
+    }
+  else
+    {
+      guint32 *decomposition =
+	g_hash_table_lookup (global_input_data.decompositions,
+			     GUINT_TO_POINTER (keysym));
+      int i;
+
+      if (decomposition)
+	{
+	  for (i = 0; i < 3 && decomposition[i]; i++)
+	    {
+	      if (!vino_input_fake_keypress (screen, xdisplay,
+					     decomposition[i]))
+		return FALSE;
+	    }
+
+	  return TRUE;
+	}
+    }
+
+  return FALSE;
+}
 #endif /* HAVE_XTEST */
 
 void
@@ -296,6 +827,9 @@ vino_input_handle_key_event (GdkScreen *
 			     guint32    keysym,
 			     gboolean   key_press)
 {
+  dprintf (INPUT, "Got key %s for %s\n", key_press ? "press" : "release",
+	   XKeysymToString (keysym));
+
 #ifdef HAVE_XTEST
   Display *xdisplay;
 
@@ -318,44 +852,50 @@ vino_input_handle_key_event (GdkScreen *
 				    keysym, key_press);
 
   vino_input_update_modifier_state (&global_input_data,
-				    VINO_ALT_GR, XK_Mode_switch,
+				    VINO_LEFT_CONTROL, XK_Control_L,
 				    keysym, key_press);
 
-  if (VINO_IS_LATIN1_KEYSYM (keysym))
-    {
-      KeyCode keycode  = global_input_data.keycodes [keysym];
-      guint8  modifier = global_input_data.modifiers [keysym];
+  vino_input_update_modifier_state (&global_input_data,
+				    VINO_ALT_GR, global_input_data.alt_gr_keysym,
+				    keysym, key_press);
 
-      if (keycode != NoSymbol)
-	{
-	  g_assert (key_press != FALSE);
+  if (!VINO_IS_MODIFIER_KEYSYM (keysym))
+    {
+      vino_input_fake_keypress (screen, xdisplay, keysym);
+    }
+  else if (key_press && keysym == XK_Alt_R &&
+	   (global_input_data.modifier_state & VINO_LEFT_CONTROL))
+    {
+      dprintf (INPUT, "Translating Ctrl+Alt press into Alt-Gr\n");
 
-	  vino_input_fake_modifier (screen, &global_input_data, modifier, TRUE);
+      XTestFakeKeyEvent (xdisplay, global_input_data.left_control_keycode,
+			 FALSE,  CurrentTime);
+      XTestFakeKeyEvent (xdisplay, global_input_data.alt_gr_keycode,
+			 TRUE, CurrentTime);
 
-	  dprintf (INPUT, "Injecting keysym 0x%.2x %s (keycode %d, modifier %d)\n",
-		   keysym, key_press ? "press" : "release", keycode, modifier);
+      global_input_data.modifier_state |= (VINO_RIGHT_ALT | VINO_ALT_GR);
+    }
+  else if (!key_press && keysym == XK_Control_L &&
+	   (global_input_data.modifier_state & VINO_RIGHT_ALT))
+    {
+      dprintf (INPUT, "Translating Ctrl+Alt release into Alt-Gr\n");
 
-	  XTestFakeKeyEvent (xdisplay, keycode, TRUE,  CurrentTime);
-	  XTestFakeKeyEvent (xdisplay, keycode, FALSE, CurrentTime);
+      XTestFakeKeyEvent (xdisplay, global_input_data.alt_gr_keycode,
+			 FALSE, CurrentTime);
 
-	  vino_input_fake_modifier (screen, &global_input_data, modifier, FALSE);
-	}
+      global_input_data.modifier_state &= ~(VINO_RIGHT_ALT | VINO_ALT_GR);
     }
   else if (keysym != XK_Caps_Lock)
     {
-      KeyCode keycode;
+      KeyCode keycode = XKeysymToKeycode (xdisplay, keysym);
 
-      if ((keycode = XKeysymToKeycode (xdisplay, keysym)) != NoSymbol)
+      if (keycode != 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);
-	    }
 	}
     }
 
