1 /* Copyright (C) 2017 Brian P. Hinz 2 * 3 * This is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This software is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this software; if not, write to the Free Software 15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 16 * USA. 17 */ 18 19 package com.tigervnc.vncviewer; 20 21 import java.awt.*; 22 import java.awt.event.*; 23 import java.util.*; 24 import javax.swing.*; 25 26 import com.tigervnc.rfb.*; 27 28 import static java.awt.event.KeyEvent.*; 29 import static com.tigervnc.rfb.Keysymdef.*; 30 31 public class KeyMap { 32 33 public final static int NoSymbol = 0; 34 35 private static final HashMap<Integer, Character> code_map_java_to_char; 36 static { 37 // Certain KeyStrokes fail to produce a valid character (CTRL+ALT+... 38 // on Windows and Mac almost never does). For these cases, the best 39 // we can try to do is to map to the ASCII symbol from the keyCode. 40 code_map_java_to_char = new HashMap<Integer, Character>(); 41 for (int c=32; c<0x7f; c++) { 42 int keyCode = KeyEvent.getExtendedKeyCodeForChar(c); 43 if (keyCode != KeyEvent.VK_UNDEFINED) 44 // Not all ASCII characters have VK_ constants, see vk_to_ascii() code_map_java_to_char.put(keyCode, (char)c)45 code_map_java_to_char.put(keyCode, (char)c); 46 } 47 } 48 49 private static int[][] vkey_map = { 50 /* KEYCODE LOCATION */ 51 /* UNKNOWN STANDARD LEFT RIGHT NUMPAD */ 52 { VK_BACK_SPACE, NoSymbol, XK_BackSpace, NoSymbol, NoSymbol, NoSymbol }, 53 { VK_TAB, NoSymbol, XK_Tab, NoSymbol, NoSymbol, NoSymbol }, 54 { VK_CANCEL, NoSymbol, XK_Cancel, NoSymbol, NoSymbol, NoSymbol }, 55 { VK_ENTER, NoSymbol, XK_Return, NoSymbol, NoSymbol, XK_KP_Enter }, 56 { VK_SHIFT, NoSymbol, XK_Shift_L, XK_Shift_L, XK_Shift_R, NoSymbol }, 57 { VK_CONTROL, NoSymbol, XK_Control_L, XK_Control_L, XK_Control_R, NoSymbol }, 58 { VK_ALT, NoSymbol, XK_Alt_L, XK_Alt_L, XK_Alt_R, NoSymbol }, 59 /* VK_PAUSE left out on purpose because interpretation depends on state of CTRL. See further down. */ 60 { VK_CAPS_LOCK, NoSymbol, XK_Caps_Lock, NoSymbol, NoSymbol, NoSymbol }, 61 { VK_ESCAPE, NoSymbol, XK_Escape, NoSymbol, NoSymbol, NoSymbol }, 62 { VK_END, NoSymbol, XK_End, NoSymbol, NoSymbol, XK_KP_End }, 63 { VK_HOME, NoSymbol, XK_Home, NoSymbol, NoSymbol, XK_KP_Home }, 64 { VK_LEFT, NoSymbol, XK_Left, NoSymbol, NoSymbol, XK_KP_Left }, 65 { VK_UP, NoSymbol, XK_Up, NoSymbol, NoSymbol, XK_KP_Up }, 66 { VK_RIGHT, NoSymbol, XK_Right, NoSymbol, NoSymbol, XK_KP_Right }, 67 { VK_DOWN, NoSymbol, XK_Down, NoSymbol, NoSymbol, XK_KP_Down }, 68 /* VK_PRINTSCREEN left out on purpose because interpretation depends on state of CTRL. See further down. */ 69 { VK_PAGE_UP, NoSymbol, XK_Page_Up, NoSymbol, NoSymbol, XK_KP_Page_Up }, 70 { VK_PAGE_DOWN, NoSymbol, XK_Page_Down, NoSymbol, NoSymbol, XK_KP_Page_Down }, 71 { VK_BEGIN, NoSymbol, XK_Begin, NoSymbol, NoSymbol, XK_KP_Begin }, 72 { VK_KP_LEFT, NoSymbol, XK_KP_Left, NoSymbol, NoSymbol, XK_KP_Left }, 73 { VK_KP_UP, NoSymbol, XK_KP_Up, NoSymbol, NoSymbol, XK_KP_Up }, 74 { VK_KP_RIGHT, NoSymbol, XK_KP_Right, NoSymbol, NoSymbol, XK_KP_Right }, 75 { VK_KP_DOWN, NoSymbol, XK_KP_Down, NoSymbol, NoSymbol, XK_KP_Down }, 76 { VK_INSERT, NoSymbol, XK_Insert, NoSymbol, NoSymbol, XK_KP_Insert }, 77 { VK_DELETE, NoSymbol, XK_Delete, NoSymbol, NoSymbol, XK_KP_Delete }, 78 { VK_WINDOWS, NoSymbol, NoSymbol, XK_Super_L, XK_Super_R, NoSymbol }, 79 { VK_CONTEXT_MENU, NoSymbol, XK_Menu, NoSymbol, NoSymbol, NoSymbol }, 80 { VK_NUMPAD0, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_0 }, 81 { VK_NUMPAD1, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_1 }, 82 { VK_NUMPAD2, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_2 }, 83 { VK_NUMPAD3, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_3 }, 84 { VK_NUMPAD4, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_4 }, 85 { VK_NUMPAD5, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_5 }, 86 { VK_NUMPAD6, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_6 }, 87 { VK_NUMPAD7, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_7 }, 88 { VK_NUMPAD8, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_8 }, 89 { VK_NUMPAD9, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_9 }, 90 { VK_MULTIPLY, NoSymbol, XK_KP_Multiply, NoSymbol, NoSymbol, XK_KP_Multiply }, 91 { VK_ADD, NoSymbol, XK_KP_Add, NoSymbol, NoSymbol, XK_KP_Add }, 92 { VK_SUBTRACT, NoSymbol, XK_KP_Subtract, NoSymbol, NoSymbol, XK_KP_Subtract }, 93 { VK_DIVIDE, NoSymbol, XK_KP_Divide, NoSymbol, NoSymbol, XK_KP_Divide }, 94 { VK_SEPARATER, NoSymbol, XK_KP_Separator, NoSymbol, NoSymbol, XK_KP_Separator }, 95 { VK_DECIMAL, NoSymbol, XK_KP_Decimal, NoSymbol, NoSymbol, XK_KP_Decimal }, 96 { VK_F1, NoSymbol, XK_F1, XK_L1, XK_R1, NoSymbol }, 97 { VK_F2, NoSymbol, XK_F2, XK_L2, XK_R2, NoSymbol }, 98 { VK_F3, NoSymbol, XK_F3, XK_L3, XK_R3, NoSymbol }, 99 { VK_F4, NoSymbol, XK_F4, XK_L4, XK_R4, NoSymbol }, 100 { VK_F5, NoSymbol, XK_F5, XK_L5, XK_R5, NoSymbol }, 101 { VK_F6, NoSymbol, XK_F6, XK_L6, XK_R6, NoSymbol }, 102 { VK_F7, NoSymbol, XK_F7, XK_L7, XK_R7, NoSymbol }, 103 { VK_F8, NoSymbol, XK_F8, XK_L8, XK_R8, NoSymbol }, 104 { VK_F9, NoSymbol, XK_F9, XK_L9, XK_R9, NoSymbol }, 105 { VK_F10, NoSymbol, XK_F10, XK_L10, XK_R10, NoSymbol }, 106 { VK_F11, NoSymbol, XK_F11, NoSymbol, XK_R11, NoSymbol }, 107 { VK_F12, NoSymbol, XK_F12, NoSymbol, XK_R12, NoSymbol }, 108 { VK_F13, NoSymbol, XK_F13, NoSymbol, XK_R13, NoSymbol }, 109 { VK_F14, NoSymbol, XK_F14, NoSymbol, XK_R14, NoSymbol }, 110 { VK_F15, NoSymbol, XK_F15, NoSymbol, XK_R15, NoSymbol }, 111 { VK_F16, NoSymbol, XK_F16, NoSymbol, NoSymbol, NoSymbol }, 112 { VK_F17, NoSymbol, XK_F17, NoSymbol, NoSymbol, NoSymbol }, 113 { VK_F18, NoSymbol, XK_F18, NoSymbol, NoSymbol, NoSymbol }, 114 { VK_F19, NoSymbol, XK_F19, NoSymbol, NoSymbol, NoSymbol }, 115 { VK_F20, NoSymbol, XK_F20, NoSymbol, NoSymbol, NoSymbol }, 116 { VK_F21, NoSymbol, XK_F21, NoSymbol, NoSymbol, NoSymbol }, 117 { VK_F22, NoSymbol, XK_F22, NoSymbol, NoSymbol, NoSymbol }, 118 { VK_F23, NoSymbol, XK_F23, NoSymbol, NoSymbol, NoSymbol }, 119 { VK_F24, NoSymbol, XK_F24, NoSymbol, NoSymbol, NoSymbol }, 120 { VK_NUM_LOCK, NoSymbol, XK_Num_Lock, NoSymbol, NoSymbol, XK_Num_Lock }, 121 { VK_SCROLL_LOCK, NoSymbol, XK_Scroll_Lock, NoSymbol, NoSymbol, NoSymbol }, 122 { VK_ALT_GRAPH, NoSymbol, XK_ISO_Level3_Shift, NoSymbol, NoSymbol, NoSymbol }, 123 { VK_META, NoSymbol, NoSymbol, XK_Meta_L, XK_Meta_R, NoSymbol }, 124 { VK_MODECHANGE, NoSymbol, XK_Mode_switch, NoSymbol, NoSymbol, NoSymbol }, 125 { VK_CLEAR, NoSymbol, XK_Clear, NoSymbol, NoSymbol, XK_KP_Begin }, 126 { VK_AGAIN, NoSymbol, XK_Redo, NoSymbol, NoSymbol, NoSymbol }, 127 { VK_UNDO, NoSymbol, XK_Undo, NoSymbol, NoSymbol, NoSymbol }, 128 { VK_FIND, NoSymbol, XK_Find, NoSymbol, NoSymbol, NoSymbol }, 129 { VK_STOP, NoSymbol, XK_Cancel, NoSymbol, NoSymbol, NoSymbol }, 130 { VK_HELP, NoSymbol, XK_Help, NoSymbol, NoSymbol, NoSymbol }, 131 { VK_KANJI, NoSymbol, XK_Kanji, NoSymbol, NoSymbol, NoSymbol }, 132 { VK_KATAKANA, NoSymbol, XK_Katakana, NoSymbol, NoSymbol, NoSymbol }, 133 { VK_HIRAGANA, NoSymbol, XK_Hiragana, NoSymbol, NoSymbol, NoSymbol }, 134 { VK_PREVIOUS_CANDIDATE, NoSymbol, XK_PreviousCandidate, NoSymbol, NoSymbol, NoSymbol }, 135 { VK_CODE_INPUT, NoSymbol, XK_Codeinput, NoSymbol, NoSymbol, NoSymbol }, 136 { VK_JAPANESE_ROMAN, NoSymbol, XK_Romaji, NoSymbol, NoSymbol, NoSymbol }, 137 { VK_KANA_LOCK, NoSymbol, XK_Kana_Lock, NoSymbol, NoSymbol, NoSymbol }, 138 { VK_DEAD_ABOVEDOT, NoSymbol, XK_dead_abovedot, NoSymbol, NoSymbol, NoSymbol }, 139 { VK_DEAD_ABOVERING, NoSymbol, XK_dead_abovering, NoSymbol, NoSymbol, NoSymbol }, 140 { VK_DEAD_ACUTE, NoSymbol, XK_dead_acute, NoSymbol, NoSymbol, NoSymbol }, 141 { VK_DEAD_BREVE, NoSymbol, XK_dead_breve, NoSymbol, NoSymbol, NoSymbol }, 142 { VK_DEAD_CARON, NoSymbol, XK_dead_caron, NoSymbol, NoSymbol, NoSymbol }, 143 { VK_DEAD_CEDILLA, NoSymbol, XK_dead_cedilla, NoSymbol, NoSymbol, NoSymbol }, 144 { VK_DEAD_CIRCUMFLEX, NoSymbol, XK_dead_circumflex, NoSymbol, NoSymbol, NoSymbol }, 145 { VK_DEAD_DIAERESIS, NoSymbol, XK_dead_diaeresis, NoSymbol, NoSymbol, NoSymbol }, 146 { VK_DEAD_DOUBLEACUTE, NoSymbol, XK_dead_doubleacute, NoSymbol, NoSymbol, NoSymbol }, 147 { VK_DEAD_GRAVE, NoSymbol, XK_dead_grave, NoSymbol, NoSymbol, NoSymbol }, 148 { VK_DEAD_IOTA, NoSymbol, XK_dead_iota, NoSymbol, NoSymbol, NoSymbol }, 149 { VK_DEAD_MACRON, NoSymbol, XK_dead_macron, NoSymbol, NoSymbol, NoSymbol }, 150 { VK_DEAD_OGONEK, NoSymbol, XK_dead_ogonek, NoSymbol, NoSymbol, NoSymbol }, 151 { VK_DEAD_SEMIVOICED_SOUND, NoSymbol, XK_dead_semivoiced_sound, NoSymbol, NoSymbol, NoSymbol }, 152 { VK_DEAD_TILDE, NoSymbol, XK_dead_tilde, NoSymbol, NoSymbol, NoSymbol }, 153 { VK_DEAD_VOICED_SOUND, NoSymbol, XK_dead_voiced_sound, NoSymbol, NoSymbol, NoSymbol }, 154 { VK_ALPHANUMERIC, NoSymbol, XK_Eisu_Shift, NoSymbol, NoSymbol, NoSymbol }, 155 { VK_ALL_CANDIDATES, NoSymbol, XK_MultipleCandidate, NoSymbol, NoSymbol, NoSymbol }, 156 { VK_KANA, NoSymbol, XK_Kana_Shift, NoSymbol, NoSymbol, NoSymbol }, 157 { VK_JAPANESE_KATAKANA, NoSymbol, XK_Katakana, NoSymbol, NoSymbol, NoSymbol }, 158 { VK_JAPANESE_HIRAGANA, NoSymbol, XK_Hiragana, NoSymbol, NoSymbol, NoSymbol }, 159 { VK_COMPOSE, NoSymbol, XK_Multi_key, NoSymbol, NoSymbol, NoSymbol }, 160 }; 161 vkey_to_keysym(KeyEvent ev)162 public static int vkey_to_keysym(KeyEvent ev) 163 { 164 int keyCode = get_keycode_fallback_extended(ev); 165 166 // Start with keys that either don't generate a symbol, or 167 // generate the same symbol as some other key. 168 if (keyCode == KeyEvent.VK_PAUSE) 169 return (ev.isControlDown() ? XK_Break : XK_Pause); 170 else if (keyCode == KeyEvent.VK_PRINTSCREEN) 171 return (ev.isControlDown() ? XK_Sys_Req : XK_Print); 172 else 173 for(int i = 0; i < vkey_map.length; i++) 174 if (keyCode == vkey_map[i][0]) 175 return vkey_map[i][ev.getKeyLocation()+1]; 176 177 // Unknown special key? 178 if (KeyEvent.getKeyText(keyCode).isEmpty()) { 179 vlog.error("Unknown key code: 0x%04x", keyCode); 180 return NoSymbol; 181 } 182 183 // Pressing Ctrl wreaks havoc with the symbol lookup... 184 int ucs = (int)ev.getKeyChar(); 185 if (ev.isControlDown()) { 186 // For CTRL-<letter>, CTRL is sent separately, so just send <letter>. 187 if ((ucs >= 1 && ucs <= 26 && !ev.isShiftDown()) || 188 // CTRL-{, CTRL-|, CTRL-} also map to ASCII 96-127 189 (ucs >= 27 && ucs <= 29 && ev.isShiftDown())) 190 ucs += 96; 191 // For CTRL-SHIFT-<letter>, send capital <letter> to emulate behavior 192 // of Linux. For CTRL-@, send @. For CTRL-_, send _. For CTRL-^, 193 // send ^. 194 else if (ucs < 32) 195 ucs += 64; 196 // If it's still undefined, map the keyCode to ASCII symbol 197 else if (keyCode >= 0 && keyCode <= 127) 198 if (ucs == CHAR_UNDEFINED || ev.isAltDown()) 199 ucs = vk_to_ascii(keyCode, ev.isShiftDown()); 200 else if (VncViewer.os.startsWith("mac os x") && ev.isMetaDown()) 201 // Alt on OS X behaves more like AltGr on other systems, and to get 202 // sane behaviour we should translate things in that manner for the 203 // remote VNC server. However that means we lose the ability to use 204 // Alt as a shortcut modifier. Do what RealVNC does and hijack the 205 // left command key as an Alt replacement. 206 ucs = vk_to_ascii(keyCode, ev.isShiftDown()); 207 } 208 209 // Dead keys are represented by their spacing equivalent 210 // (or something similar depending on the layout) 211 if (Character.getType(ucs) == Character.COMBINING_SPACING_MARK) 212 return Keysym2ucs.ucs2keysym(Keysym2ucs.ucs2combining(ucs)); 213 214 if (Character.isDefined(ucs)) 215 return Keysym2ucs.ucs2keysym(ucs); 216 217 return NoSymbol; 218 } 219 get_keycode_fallback_extended(final KeyEvent ev)220 public static int get_keycode_fallback_extended(final KeyEvent ev) { 221 final int keyCode = ev.getKeyCode(); 222 return (keyCode == 0) ? ev.getExtendedKeyCode() : keyCode; 223 } 224 vk_to_ascii(int vk, boolean shift)225 private static int vk_to_ascii(int vk, boolean shift) { 226 char c = 0; 227 if (code_map_java_to_char.containsKey(vk)) 228 c = code_map_java_to_char.get(vk); 229 // 0x25 (%) and 0x3F (?) do not have VK_ constants 230 if (vk == VK_5) 231 c = shift ? '%' : c; 232 else if (vk == VK_SLASH) 233 c = shift ? '?' : c; 234 if (Character.isLetter(c)) 235 c = shift ? Character.toUpperCase(c) : Character.toLowerCase(c); 236 return (int)c; 237 } 238 239 static LogWriter vlog = new LogWriter("KeyMap"); 240 } 241