Another overhaul of the key event handlers. There was a fundamental flaw in the previous implementation due to the fact that java key modifiers associated with a key_pressed or key_released event are reported with respect to each particular event.  Thus, for example a key sequence of CTRL press, letter press, CTRL release, letter release never sends the corresponding release for the CTRL+letter down event.  Key events are now synchronized on a monitor object to help ensure that the key sequence is preserved.  This implementation mirrors the fltk viewer quite closely and, as far as I can tell, is capable of sending all of the same keysyms.

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@5147 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/java/com/tigervnc/rfb/Keysyms.java b/java/com/tigervnc/rfb/Keysyms.java
index 4cf1784..e5ece69 100644
--- a/java/com/tigervnc/rfb/Keysyms.java
+++ b/java/com/tigervnc/rfb/Keysyms.java
@@ -21,14 +21,22 @@
 //
 // Keysyms - defines X keysyms for non-character keys.  All keysyms
 // corresponding to characters should be generated by calling
-// UnicodeToKeysym.translate().
+// UnicodeToKeysym.ucs2keysym().
 //
 
 package com.tigervnc.rfb;
 
+import java.awt.event.KeyEvent;
+import com.tigervnc.vncviewer.VncViewer;
+
 public class Keysyms {
 
-  public static final int ISO_Level3_Shift = 0xFE03;
+  public static final int VoidSymbol = 0xffffff;
+  /*
+   * TTY Functions, cleverly chosen to map to ascii, for convenience of
+   * programming, but could have been arbitrary (at the cost of lookup
+   * tables in client code.
+   */
   public static final int BackSpace = 0xFF08;
   public static final int Tab = 0xFF09;
   public static final int Linefeed = 0xFF0A;
@@ -40,12 +48,14 @@
   public static final int Escape = 0xFF1B;
   public static final int Delete = 0xFFFF;
 
+  /* International & multi-key character composition */
   public static final int Multi_key = 0xFF20;  /* Multi-key character compose */
   public static final int Codeinput = 0xFF37;
   public static final int SingleCandidate = 0xFF3C;
   public static final int MultipleCandidate = 0xFF3D;
   public static final int PreviousCandidate = 0xFF3E;
 
+  /* Japanese keyboard support */
   public static final int Kanji = 0xFF21;  /* Kanji, Kanji convert */
   public static final int Muhenkan = 0xFF22;  /* Cancel Conversion */
   public static final int Henkan_Mode = 0xFF23;  /* Start/Stop Conversion */
@@ -67,6 +77,7 @@
   public static final int Zen_Koho = 0xFF3D;  /* Multiple/All Candidate(s) */
   public static final int Mae_Koho = 0xFF3E;  /* Previous Candidate */
 
+  /* Cursor control & motion */
   public static final int Home = 0xFF50;
   public static final int Left = 0xFF51;
   public static final int Up = 0xFF52;
@@ -79,6 +90,7 @@
   public static final int End = 0xFF57;
   public static final int Begin = 0xFF58;
 
+  /* Misc Functions */
   public static final int Select = 0xFF60;
   public static final int Print = 0xFF61;
   public static final int Execute = 0xFF62;
@@ -94,6 +106,42 @@
   public static final int script_switch = 0xFF7E;
   public static final int Num_Lock = 0xFF7F;
 
+  /* Keypad Functions, keypad numbers cleverly chosen to map to ascii */
+  public static final int KP_Enter = 0xFF8D;
+  public static final int KP_Home = 0xFF95;
+  public static final int KP_Left = 0xFF96;
+  public static final int KP_Up = 0xFF97;
+  public static final int KP_Right = 0xFF98;
+  public static final int KP_Down = 0xFF99;
+  public static final int KP_Page_Up = 0xFF9A;
+  public static final int KP_Page_Down = 0xFF9B;
+  public static final int KP_End = 0xFF9C;
+  public static final int KP_Begin = 0xFF9D;
+  public static final int KP_Insert = 0xFF9E;
+  public static final int KP_Delete = 0xFF9F;
+  public static final int KP_Equal = 0xFFBD;
+  public static final int KP_0 = 0xFFB0;
+  public static final int KP_1 = 0xFFB1;
+  public static final int KP_2 = 0xFFB2;
+  public static final int KP_3 = 0xFFB3;
+  public static final int KP_4 = 0xFFB4;
+  public static final int KP_5 = 0xFFB5;
+  public static final int KP_6 = 0xFFB6;
+  public static final int KP_7 = 0xFFB7;
+  public static final int KP_8 = 0xFFB8;
+  public static final int KP_9 = 0xFFB9;
+  public static final int KP_Decimal = 0xFFAE;
+  public static final int KP_Add = 0xFFAB;
+  public static final int KP_Subtract = 0xFFAD;
+  public static final int KP_Multiply = 0xFFAA;
+  public static final int KP_Divide = 0xFFAF;
+
+  /*
+   * Auxilliary Functions; note the duplicate definitions for left and right
+   * function keys;  Sun keyboards and a few other manufactures have such
+   * function key groups on the left and/or right sides of the keyboard.
+   * We've not found a keyboard with more than 35 function keys total.
+   */
   public static final int F1 = 0xFFBE;
   public static final int F2 = 0xFFBF;
   public static final int F3 = 0xFFC0;
@@ -119,43 +167,224 @@
   public static final int F23 = 0xFFD4;
   public static final int F24 = 0xFFD5;
 
+  /* Modifiers */
   public static final int Shift_L = 0xFFE1;
   public static final int Shift_R = 0xFFE2;
   public static final int Control_L = 0xFFE3;
   public static final int Control_R = 0xFFE4;
+  public static final int Caps_Lock = 0xFFE5;
+  public static final int Shift_Lock = 0xFFE6;
+
   public static final int Meta_L = 0xFFE7;
   public static final int Meta_R = 0xFFE8;
   public static final int Alt_L = 0xFFE9;
   public static final int Alt_R = 0xFFEA;
-
   public static final int Super_L = 0xFFEB;
-  public static final int Caps_Lock = 0xFFE5;
+  public static final int Super_R = 0xFFEC;
+  public static final int Hyper_L = 0xFFED;
+  public static final int Hyper_R = 0xFFEE;
 
-  public static final int KP_Enter = 0xFF8D;
-  public static final int KP_Home = 0xFF95;
-  public static final int KP_Left = 0xFF96;
-  public static final int KP_Up = 0xFF97;
-  public static final int KP_Right = 0xFF98;
-  public static final int KP_Down = 0xFF99;
-  public static final int KP_Page_Up = 0xFF9A;
-  public static final int KP_Page_Down = 0xFF9B;
-  public static final int KP_End = 0xFF9C;
-  public static final int KP_Begin = 0xFF9D;
-  public static final int KP_Insert = 0xFF9E;
-  public static final int KP_Delete = 0xFF9F;
-  public static final int KP_0 = 0xFFB0;
-  public static final int KP_1 = 0xFFB1;
-  public static final int KP_2 = 0xFFB2;
-  public static final int KP_3 = 0xFFB3;
-  public static final int KP_4 = 0xFFB4;
-  public static final int KP_5 = 0xFFB5;
-  public static final int KP_6 = 0xFFB6;
-  public static final int KP_7 = 0xFFB7;
-  public static final int KP_8 = 0xFFB8;
-  public static final int KP_9 = 0xFFB9;
-  public static final int KP_Decimal = 0xFFAE;
-  public static final int KP_Add = 0xFFAB;
-  public static final int KP_Subtract = 0xFFAD;
-  public static final int KP_Multiply = 0xFFAA;
-  public static final int KP_Divide = 0xFFAF;
+  /*
+   * ISO 9995 Function and Modifier Keys
+   * Byte 3 = 0xFE
+   */
+  public static final int ISO_Level3_Shift = 0xFE03;
+
+  /*
+   * Dead Modifier Keys
+   */
+  public static final int Dead_Grave = 0xfe50; /*                               COMBINING GRAVE ACCENT */
+  public static final int Dead_Acute = 0xfe51; /*                               COMBINING ACUTE ACCENT */
+  public static final int Dead_Circumflex = 0xfe52; /*                               COMBINING CIRCUMFLEX ACCENT */
+  public static final int Dead_Tilde = 0xfe53; /*                               COMBINING TILDE */
+  public static final int Dead_Macron = 0xfe54; /*                               COMBINING MACRON */
+  public static final int Dead_Breve = 0xfe55; /*                               COMBINING BREVE */
+  public static final int Dead_AboveDot = 0xfe56; /*                               COMBINING DOT ABOVE */
+  public static final int Dead_Diaeresis = 0xfe57; /*                               COMBINING DIAERESIS */
+  public static final int Dead_AboveRing = 0xfe58; /*                               COMBINING RING ABOVE */
+  public static final int Dead_DoubleAcute = 0xfe59; /*                               COMBINING DOUBLE ACUTE ACCENT */
+  public static final int Dead_Caron = 0xfe5a; /*                               COMBINING CARON */
+  public static final int Dead_Cedilla = 0xfe5b; /*                               COMBINING CEDILLA */
+  public static final int Dead_Ogonek = 0xfe5c; /*                               COMBINING OGONEK */
+  public static final int Dead_Iota = 0xfe5d; /*                               MODIFIER LETTER SMALL IOTA */
+  public static final int Dead_Voiced_Sound = 0xfe5e; /*                               COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK */
+  public static final int Dead_SemiVoiced_Sound = 0xfe5f; /*                               COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+  public static final int Dead_BelowDot = 0xfe60; /*                               COMBINING DOT BELOW */
+
+  private static class KeySymbol {
+    public KeySymbol(int keycode_, int[] keysym_) {
+      keycode = keycode_;
+      keysym = new int[5];
+      System.arraycopy(keysym_, 0, keysym, 0, 5);
+    }
+    int keycode;
+    int[] keysym;
+  }
+
+  private static KeySymbol[] keySymbols = {
+    /*                 KEYCODE                                                    LOCATION                          */
+    /*                                                UNKNOWN     STANDARD          LEFT        RIGHT       NUMPAD  */
+    new KeySymbol(KeyEvent.VK_BACK_SPACE,   new int[]{VoidSymbol, BackSpace,        VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_TAB,          new int[]{VoidSymbol, Tab,              VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_ENTER,        new int[]{VoidSymbol, Return,           VoidSymbol, VoidSymbol, KP_Enter}),
+    new KeySymbol(KeyEvent.VK_ESCAPE,       new int[]{VoidSymbol, Escape,           VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_CONTROL,      new int[]{VoidSymbol, Control_L,        Control_L,  Control_R,  VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_ALT_GRAPH,    new int[]{VoidSymbol, ISO_Level3_Shift, VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_ALT,          new int[]{VoidSymbol, ISO_Level3_Shift, Alt_L,      Alt_R,      VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_SHIFT,        new int[]{VoidSymbol, Shift_L,          Shift_L,    Shift_R,    VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_META,         new int[]{VoidSymbol, Meta_L,           Meta_L,     Meta_R,     VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_NUMPAD0,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_0}),
+    new KeySymbol(KeyEvent.VK_NUMPAD1,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_1}),
+    new KeySymbol(KeyEvent.VK_NUMPAD2,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_2}),
+    new KeySymbol(KeyEvent.VK_NUMPAD3,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_3}),
+    new KeySymbol(KeyEvent.VK_NUMPAD4,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_4}),
+    new KeySymbol(KeyEvent.VK_NUMPAD5,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_5}),
+    new KeySymbol(KeyEvent.VK_NUMPAD6,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_6}),
+    new KeySymbol(KeyEvent.VK_NUMPAD7,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_7}),
+    new KeySymbol(KeyEvent.VK_NUMPAD8,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_8}),
+    new KeySymbol(KeyEvent.VK_NUMPAD9,      new int[]{VoidSymbol, VoidSymbol,       VoidSymbol, VoidSymbol, KP_9}),
+    new KeySymbol(KeyEvent.VK_DECIMAL,      new int[]{VoidSymbol, KP_Decimal,       VoidSymbol, VoidSymbol, KP_Decimal}),
+    new KeySymbol(KeyEvent.VK_ADD,          new int[]{VoidSymbol, KP_Add,           VoidSymbol, VoidSymbol, KP_Add}),
+    new KeySymbol(KeyEvent.VK_SUBTRACT,     new int[]{VoidSymbol, KP_Subtract,      VoidSymbol, VoidSymbol, KP_Subtract}),
+    new KeySymbol(KeyEvent.VK_MULTIPLY,     new int[]{VoidSymbol, KP_Multiply,      VoidSymbol, VoidSymbol, KP_Multiply}),
+    new KeySymbol(KeyEvent.VK_DIVIDE,       new int[]{VoidSymbol, KP_Divide,        VoidSymbol, VoidSymbol, KP_Divide}),
+    new KeySymbol(KeyEvent.VK_DELETE,       new int[]{VoidSymbol, Delete,           VoidSymbol, VoidSymbol, KP_Delete}),
+    new KeySymbol(KeyEvent.VK_CLEAR,        new int[]{VoidSymbol, Clear,            VoidSymbol, VoidSymbol, KP_Begin}),
+    new KeySymbol(KeyEvent.VK_HOME,         new int[]{VoidSymbol, Home,             VoidSymbol, VoidSymbol, KP_Home}),
+    new KeySymbol(KeyEvent.VK_END,          new int[]{VoidSymbol, End,              VoidSymbol, VoidSymbol, KP_End}),
+    new KeySymbol(KeyEvent.VK_PAGE_UP,      new int[]{VoidSymbol, Page_Up,          VoidSymbol, VoidSymbol, KP_Page_Up}),
+    new KeySymbol(KeyEvent.VK_PAGE_DOWN,    new int[]{VoidSymbol, Page_Down,        VoidSymbol, VoidSymbol, KP_Page_Down}),
+    new KeySymbol(KeyEvent.VK_UP,           new int[]{VoidSymbol, Up,               VoidSymbol, VoidSymbol, KP_Up}),
+    new KeySymbol(KeyEvent.VK_DOWN,         new int[]{VoidSymbol, Down,             VoidSymbol, VoidSymbol, KP_Down}),
+    new KeySymbol(KeyEvent.VK_LEFT,         new int[]{VoidSymbol, Left,             VoidSymbol, VoidSymbol, KP_Left}),
+    new KeySymbol(KeyEvent.VK_RIGHT,        new int[]{VoidSymbol, Right,            VoidSymbol, VoidSymbol, KP_Right}),
+    new KeySymbol(KeyEvent.VK_BEGIN,        new int[]{VoidSymbol, Begin,            VoidSymbol, VoidSymbol, KP_Begin}),
+    new KeySymbol(KeyEvent.VK_KP_LEFT,      new int[]{VoidSymbol, KP_Left,          VoidSymbol, VoidSymbol, KP_Left}),
+    new KeySymbol(KeyEvent.VK_KP_UP,        new int[]{VoidSymbol, KP_Up,            VoidSymbol, VoidSymbol, KP_Up}),
+    new KeySymbol(KeyEvent.VK_KP_RIGHT,     new int[]{VoidSymbol, KP_Right,         VoidSymbol, VoidSymbol, KP_Right}),
+    new KeySymbol(KeyEvent.VK_KP_DOWN,      new int[]{VoidSymbol, KP_Down,          VoidSymbol, VoidSymbol, KP_Down}),
+    new KeySymbol(KeyEvent.VK_PRINTSCREEN,  new int[]{VoidSymbol, Print,            VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_SCROLL_LOCK,  new int[]{VoidSymbol, Scroll_Lock,      VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_CAPS_LOCK,    new int[]{VoidSymbol, Caps_Lock,        VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_NUM_LOCK,     new int[]{VoidSymbol, Num_Lock,         VoidSymbol, VoidSymbol, Num_Lock}),
+    new KeySymbol(KeyEvent.VK_INSERT,       new int[]{VoidSymbol, Insert,           VoidSymbol, VoidSymbol, KP_Insert}),
+    new KeySymbol(KeyEvent.VK_AGAIN,        new int[]{VoidSymbol, Redo,             VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_UNDO,         new int[]{VoidSymbol, Undo,             VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_FIND,         new int[]{VoidSymbol, Find,             VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_STOP,         new int[]{VoidSymbol, Cancel,           VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_HELP,         new int[]{VoidSymbol, Help,             VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_WINDOWS,      new int[]{VoidSymbol, Super_L,          Super_L,    Super_R,    VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_CONTEXT_MENU, new int[]{VoidSymbol, Menu,             VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_KANJI,        new int[]{VoidSymbol, Kanji,            VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_KATAKANA,     new int[]{VoidSymbol, Katakana,         VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_HIRAGANA,     new int[]{VoidSymbol, Hiragana,         VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_PREVIOUS_CANDIDATE, 
+                                            new int[]{VoidSymbol, PreviousCandidate,VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_CODE_INPUT,   new int[]{VoidSymbol, Codeinput,        VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_JAPANESE_ROMAN, 
+                                            new int[]{VoidSymbol, Romaji,           VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_KANA_LOCK,    new int[]{VoidSymbol, Kana_Lock,        VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_ABOVEDOT,new int[]{VoidSymbol, Dead_AboveDot,    VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_ABOVERING,
+                                            new int[]{VoidSymbol, Dead_AboveRing,   VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_ACUTE,   new int[]{VoidSymbol, Dead_Acute,       VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_BREVE,   new int[]{VoidSymbol, Dead_Breve,       VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_CARON,   new int[]{VoidSymbol, Dead_Caron,       VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_CIRCUMFLEX,
+                                            new int[]{VoidSymbol, Dead_Circumflex,  VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_DIAERESIS,
+                                            new int[]{VoidSymbol, Dead_Diaeresis,   VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_DOUBLEACUTE,
+                                            new int[]{VoidSymbol, Dead_DoubleAcute, VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_GRAVE,   new int[]{VoidSymbol, Dead_Grave,       VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_IOTA,    new int[]{VoidSymbol, Dead_Iota,        VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_MACRON,  new int[]{VoidSymbol, Dead_Macron,      VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_OGONEK,  new int[]{VoidSymbol, Dead_Ogonek,      VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_SEMIVOICED_SOUND,
+                                            new int[]{VoidSymbol, Dead_SemiVoiced_Sound,VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_TILDE,   new int[]{VoidSymbol, Dead_Tilde,       VoidSymbol, VoidSymbol, VoidSymbol}),
+    new KeySymbol(KeyEvent.VK_DEAD_VOICED_SOUND,
+                                            new int[]{VoidSymbol, Dead_Voiced_Sound, VoidSymbol, VoidSymbol, VoidSymbol}),
+  };
+
+  public static int translateKeyEvent(KeyEvent ev) {
+    int location = ev.getKeyLocation();
+    int keyCode = ev.getKeyCode();
+
+    // First check for function keys
+    if (keyCode >= KeyEvent.VK_F1 && keyCode <= KeyEvent.VK_F12)
+      return Keysyms.F1 + keyCode - KeyEvent.VK_F1;
+    if (keyCode >= KeyEvent.VK_F13 && keyCode <= KeyEvent.VK_F24)
+      return Keysyms.F13 + keyCode - KeyEvent.VK_F13;
+
+    // Numpad numbers
+    if (keyCode >= KeyEvent.VK_0 && keyCode <= KeyEvent.VK_9 &&
+        location == KeyEvent.KEY_LOCATION_NUMPAD)
+      return Keysyms.KP_0 + keyCode - KeyEvent.VK_0;
+
+    if (VncViewer.os.startsWith("mac os x")) {
+      // Alt on OS X behaves more like AltGr on other systems, and to get
+      // sane behaviour we should translate things in that manner for the
+      // remote VNC server. However that means we lose the ability to use
+      // Alt as a shortcut modifier. Do what RealVNC does and hijack the
+      // left command key as an Alt replacement.
+      switch (keyCode) {
+      case KeyEvent.VK_META:
+        if (location == KeyEvent.KEY_LOCATION_LEFT)
+          return Alt_L;
+        else
+          return Super_L;
+      case KeyEvent.VK_ALT:
+        if (location == KeyEvent.KEY_LOCATION_LEFT)
+          return Alt_L;
+        else
+          return ISO_Level3_Shift;
+      }
+    }
+
+    // Then other special keys
+    if (keyCode == KeyEvent.VK_PAUSE)
+      return (ev.isControlDown() ? Break : Pause);
+    else if (keyCode == KeyEvent.VK_PRINTSCREEN)
+      return (ev.isControlDown() ? Sys_Req : Print);
+    else
+      for(int i = 0; i < keySymbols.length; i++)
+        if (keySymbols[i].keycode == keyCode)
+          return (keySymbols[i].keysym)[location];
+
+    // Unknown special key?
+    if (KeyEvent.getKeyText(keyCode).isEmpty()) {
+      vlog.error("Unknown key code:");
+      String fmt = ev.paramString().replaceAll("%","%%");
+      vlog.error(String.format(fmt.replaceAll(",","%n       ")));
+      return VoidSymbol;
+    }
+
+    char keyChar = ev.getKeyChar();
+    if (!ev.isControlDown() && keyChar != KeyEvent.CHAR_UNDEFINED)
+      return UnicodeToKeysym.ucs2keysym(Character.toString(keyChar).codePointAt(0));
+
+    int key = keyChar;
+    if (ev.isControlDown()) {
+      // For CTRL-<letter>, CTRL is sent separately, so just send <letter>.
+      if ((key >= 1 && key <= 26 && !ev.isShiftDown()) ||
+          // CTRL-{, CTRL-|, CTRL-} also map to ASCII 96-127
+          (key >= 27 && key <= 29 && ev.isShiftDown()))
+        key += 96;
+      // For CTRL-SHIFT-<letter>, send capital <letter> to emulate behavior
+      // of Linux.  For CTRL-@, send @.  For CTRL-_, send _.  For CTRL-^,
+      // send ^.
+      else if (key < 32)
+        key += 64;
+      // Windows and Mac sometimes return CHAR_UNDEFINED with CTRL-SHIFT
+      // combinations, so best we can do is send the key code if it is
+      // a valid ASCII symbol.
+      else if (ev.getKeyChar() == KeyEvent.CHAR_UNDEFINED && keyCode >= 0 &&
+               keyCode <= 127)
+        key = keyCode;
+    }
+    return UnicodeToKeysym.ucs2keysym(key);
+  }
+
+  static LogWriter vlog = new LogWriter("Keysyms");
 }