/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * Copyright (C) 2012-2013 D. R. Commander.  All Rights Reserved.
 * Copyright (C) 2013 Brian P. Hinz
 * Copyright (C) 2013 Pierre Ossman <ossman@cendio.se> for Cendio AB
 *
 * This 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 of the License, or
 * (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
 * USA.
 */

//
// Keysyms - defines X keysyms for non-character keys.  All keysyms
// corresponding to characters should be generated by calling
// UnicodeToKeysym.ucs2keysym().
//

package com.tigervnc.rfb;

import java.awt.event.KeyEvent;
import com.tigervnc.vncviewer.VncViewer;

public class Keysyms {

  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;
  public static final int Clear = 0xFF0B;
  public static final int Return = 0xFF0D;
  public static final int Pause = 0xFF13;
  public static final int Scroll_Lock = 0xFF14;
  public static final int Sys_Req = 0xFF15;
  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 */
  public static final int Henkan = 0xFF23;  /* Alias for Henkan_Mode */
  public static final int Romaji = 0xFF24;  /* to Romaji */
  public static final int Hiragana = 0xFF25;  /* to Hiragana */
  public static final int Katakana = 0xFF26;  /* to Katakana */
  public static final int Hiragana_Katakana = 0xFF27;  /* Hiragana/Katakana toggle */
  public static final int Zenkaku = 0xFF28;  /* to Zenkaku */
  public static final int Hankaku = 0xFF29;  /* to Hankaku */
  public static final int Zenkaku_Hankaku = 0xFF2A;  /* Zenkaku/Hankaku toggle */
  public static final int Touroku = 0xFF2B;  /* Add to Dictionary */
  public static final int Massyo = 0xFF2C;  /* Delete from Dictionary */
  public static final int Kana_Lock = 0xFF2D;  /* Kana Lock */
  public static final int Kana_Shift = 0xFF2E;  /* Kana Shift */
  public static final int Eisu_Shift = 0xFF2F;  /* Alphanumeric Shift */
  public static final int Eisu_toggle = 0xFF30;  /* Alphanumeric toggle */
  public static final int Kanji_Bangou = 0xFF37;  /* Codeinput */
  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;
  public static final int Right = 0xFF53;
  public static final int Down = 0xFF54;
  public static final int Prior = 0xFF55;
  public static final int Page_Up = 0xFF55;
  public static final int Next = 0xFF56;
  public static final int Page_Down = 0xFF56;
  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;
  public static final int Insert = 0xFF63;
  public static final int Undo = 0xFF65;
  public static final int Redo = 0xFF66;
  public static final int Menu = 0xFF67;
  public static final int Find = 0xFF68;
  public static final int Cancel = 0xFF69;
  public static final int Help = 0xFF6A;
  public static final int Break = 0xFF6B;
  public static final int Mode_switch = 0xFF7E;
  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;
  public static final int F4 = 0xFFC1;
  public static final int F5 = 0xFFC2;
  public static final int F6 = 0xFFC3;
  public static final int F7 = 0xFFC4;
  public static final int F8 = 0xFFC5;
  public static final int F9 = 0xFFC6;
  public static final int F10 = 0xFFC7;
  public static final int F11 = 0xFFC8;
  public static final int F12 = 0xFFC9;
  public static final int F13 = 0xFFCA;
  public static final int F14 = 0xFFCB;
  public static final int F15 = 0xFFCC;
  public static final int F16 = 0xFFCD;
  public static final int F17 = 0xFFCE;
  public static final int F18 = 0xFFCF;
  public static final int F19 = 0xFFD0;
  public static final int F20 = 0xFFD1;
  public static final int F21 = 0xFFD2;
  public static final int F22 = 0xFFD3;
  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 Super_R = 0xFFEC;
  public static final int Hyper_L = 0xFFED;
  public static final int Hyper_R = 0xFFEE;

  /*
   * 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");
}
