See issue #505tags/v1.8.90
@@ -1,6 +1,4 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright (C) 2013 Brian P. Hinz | |||
* Copyright (C) 2001 Markus G. Kuhn, University of Cambridge | |||
/* Copyright (C) 2017 Brian P. Hinz | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -16,14 +14,38 @@ | |||
* along with this software; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | |||
* USA. | |||
* | |||
* This module converts keysym values into the corresponding ISO 10646 | |||
* (UCS, Unicode) values. | |||
* | |||
* The array keysymtab[] contains pairs of X11 keysym values for graphical | |||
* characters and the corresponding Unicode value. The function | |||
* keysym2ucs() maps a keysym onto a Unicode value using a binary search, | |||
* therefore keysymtab[] must remain SORTED by keysym value. | |||
* | |||
* The keysym -> UTF-8 conversion will hopefully one day be provided | |||
* by Xlib via XmbLookupString() and should ideally not have to be | |||
* done in X applications. But we are not there yet. | |||
* | |||
* We allow to represent any UCS character in the range U-00000000 to | |||
* U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff. | |||
* This admittedly does not cover the entire 31-bit space of UCS, but | |||
* it does cover all of the characters up to U-10FFFF, which can be | |||
* represented by UTF-16, and more, and it is very unlikely that higher | |||
* UCS codes will ever be assigned by ISO. So to get Unicode character | |||
* U+ABCD you can directly use keysym 0x0100abcd. | |||
* | |||
* NOTE: The comments in the table below contain the actual character | |||
* encoded in UTF-8, so for viewing and editing best use an editor in | |||
* UTF-8 mode. | |||
* | |||
* Derived from keysym2ucs.c, originally authored by Markus G, Kuhn | |||
* | |||
*/ | |||
// | |||
// Derived from keysym2ucs.c, originally authored by Markus G, Kuhn | |||
// | |||
package com.tigervnc.rfb; | |||
public class UnicodeToKeysym { | |||
public class Keysym2ucs { | |||
private static class codepair { | |||
public codepair(int keysym_, int ucs_) { | |||
@@ -34,6 +56,15 @@ public class UnicodeToKeysym { | |||
int ucs; | |||
} | |||
private static class combiningpair { | |||
public combiningpair(int spacing_, int combining_) { | |||
spacing = spacing_; | |||
combining = combining_; | |||
} | |||
int spacing; | |||
int combining; | |||
} | |||
public static codepair[] keysymtab = { | |||
new codepair(0x01a1, 0x0104), /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ | |||
new codepair(0x01a2, 0x02d8), /* breve ˘ BREVE */ | |||
@@ -826,6 +857,63 @@ public class UnicodeToKeysym { | |||
new codepair(0xfe60, 0x0323), /* COMBINING DOT BELOW */ | |||
}; | |||
public static combiningpair[] combinetab = { | |||
new combiningpair(0x0060, 0x0300), /* GRAVE ACCENT ` COMBINING GRAVE ACCENT */ | |||
new combiningpair(0x00b4, 0x0301), /* ACUTE ACCENT ´ COMBINING ACUTE ACCENT */ | |||
new combiningpair(0x0027, 0x0301), /* APOSTROPHE ' COMBINING ACUTE ACCENT */ | |||
new combiningpair(0x0384, 0x0301), /* GREEK TONOS ΄ COMBINING ACUTE ACCENT */ | |||
new combiningpair(0x005e, 0x0302), /* CIRCUMFLEX ACCENT ^ COMBINING CIRCUMFLEX ACCENT */ | |||
new combiningpair(0x007e, 0x0303), /* TILDE ~ COMBINING TILDE */ | |||
new combiningpair(0x00af, 0x0304), /* MACRON ¯ COMBINING MACRON */ | |||
new combiningpair(0x02d8, 0x0306), /* BREVE ˘ COMBINING BREVE */ | |||
new combiningpair(0x02d9, 0x0307), /* DOT ABOVE ˙ COMBINING DOT ABOVE */ | |||
new combiningpair(0x00a8, 0x0308), /* DIAERESIS ¨ COMBINING DIAERESIS */ | |||
new combiningpair(0x0022, 0x0308), /* QUOTATION MARK " COMBINING DIAERESIS */ | |||
new combiningpair(0x02da, 0x030a), /* RING ABOVE ˚ COMBINING RING ABOVE */ | |||
new combiningpair(0x00b0, 0x030a), /* DEGREE SIGN ° COMBINING RING ABOVE */ | |||
new combiningpair(0x02dd, 0x030b), /* DOUBLE ACUTE ACCENT ˝ COMBINING DOUBLE ACUTE ACCENT */ | |||
new combiningpair(0x02c7, 0x030c), /* CARON ˇ COMBINING CARON */ | |||
new combiningpair(0x00b8, 0x0327), /* CEDILLA ¸ COMBINING CEDILLA */ | |||
new combiningpair(0x02db, 0x0328), /* OGONEK ¸ COMBINING OGONEK */ | |||
new combiningpair(0x037a, 0x0345), /* GREEK YPOGEGRAMMENI ͺ COMBINING GREEK YPOGEGRAMMENI */ | |||
new combiningpair(0x309b, 0x3099), /* KATAKANA-HIRAGANA VOICED SOUND MARK ゛COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK */ | |||
new combiningpair(0x309c, 0x309a), /* KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK ゜COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ | |||
new combiningpair(0x002e, 0x0323), /* FULL STOP . COMBINING DOT BELOW */ | |||
new combiningpair(0x0385, 0x0344), /* GREEK DIALYTIKA TONOS ΅ COMBINING GREEK DIALYTIKA TONOS */ | |||
}; | |||
public static int keysym2ucs(int keysym) | |||
{ | |||
int min = 0; | |||
int max = keysymtab.length - 1; | |||
int mid; | |||
/* first check for Latin-1 characters (1:1 mapping) */ | |||
if ((keysym >= 0x0020 && keysym <= 0x007e) || | |||
(keysym >= 0x00a0 && keysym <= 0x00ff)) | |||
return keysym; | |||
/* also check for directly encoded 24-bit UCS characters */ | |||
if ((keysym & 0xff000000) == 0x01000000) | |||
return keysym & 0x00ffffff; | |||
/* binary search in table */ | |||
while (max >= min) { | |||
mid = (min + max) / 2; | |||
if (keysymtab[mid].keysym < keysym) | |||
min = mid + 1; | |||
else if (keysymtab[mid].keysym > keysym) | |||
max = mid - 1; | |||
else { | |||
/* found it */ | |||
return keysymtab[mid].ucs; | |||
} | |||
} | |||
/* no matching Unicode value found */ | |||
return -1; | |||
} | |||
public static int ucs2keysym(int ucs) { | |||
int cur = 0; | |||
int max = keysymtab.length - 1; | |||
@@ -847,6 +935,24 @@ public class UnicodeToKeysym { | |||
return ucs | 0x01000000; | |||
/* no matching Unicode value found */ | |||
return 0xffffff; | |||
return NoSymbol; | |||
} | |||
public static int ucs2combining(int spacing) | |||
{ | |||
int cur = 0; | |||
int max = combinetab.length - 1; | |||
/* linear search in table */ | |||
while (cur <= max) { | |||
if (combinetab[cur].spacing == spacing) | |||
return combinetab[cur].combining; | |||
cur++; | |||
} | |||
/* no matching Unicode value found */ | |||
return -1; | |||
} | |||
private static final int NoSymbol = 0; | |||
} |
@@ -1,391 +0,0 @@ | |||
/* 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"); | |||
} |
@@ -1,7 +1,7 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright 2009-2013 Pierre Ossman <ossman@cendio.se> for Cendio AB | |||
* Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved. | |||
* Copyright (C) 2011-2015 Brian P. Hinz | |||
* Copyright (C) 2011-2017 Brian P. Hinz | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -77,10 +77,6 @@ public class CConn extends CConnection implements | |||
static final PixelFormat mediumColorPF = | |||
new PixelFormat(8, 8, false, true, 7, 7, 3, 5, 2, 0); | |||
static final int KEY_LOC_SHIFT_R = 0; | |||
static final int KEY_LOC_SHIFT_L = 16; | |||
static final int SUPER_MASK = 1<<15; | |||
//////////////////////////////////////////////////////////////////// | |||
// The following methods are all called from the RFB thread | |||
@@ -95,7 +91,6 @@ public class CConn extends CConnection implements | |||
setShared(shared.getValue()); | |||
sock = socket; | |||
downKeySym = new HashMap<Integer, Integer>(); | |||
upg = this; | |||
@@ -706,47 +701,6 @@ public class CConn extends CConnection implements | |||
} | |||
} | |||
void showInfo() { | |||
Window fullScreenWindow = DesktopWindow.getFullScreenWindow(); | |||
if (fullScreenWindow != null) | |||
DesktopWindow.setFullScreenWindow(null); | |||
String info = new String("Desktop name: %s%n"+ | |||
"Host: %s:%d%n"+ | |||
"Size: %dx%d%n"+ | |||
"Pixel format: %s%n"+ | |||
" (server default: %s)%n"+ | |||
"Requested encoding: %s%n"+ | |||
"Last used encoding: %s%n"+ | |||
"Line speed estimate: %d kbit/s%n"+ | |||
"Protocol version: %d.%d%n"+ | |||
"Security method: %s [%s]%n"); | |||
String msg = | |||
String.format(info, cp.name(), | |||
sock.getPeerName(), sock.getPeerPort(), | |||
cp.width, cp.height, | |||
cp.pf().print(), | |||
serverPF.print(), | |||
Encodings.encodingName(currentEncoding), | |||
Encodings.encodingName(lastServerEncoding), | |||
sock.inStream().kbitsPerSecond(), | |||
cp.majorVersion, cp.minorVersion, | |||
Security.secTypeName(csecurity.getType()), | |||
csecurity.description()); | |||
JOptionPane op = new JOptionPane(msg, JOptionPane.PLAIN_MESSAGE, | |||
JOptionPane.DEFAULT_OPTION); | |||
JDialog dlg = op.createDialog(desktop, "VNC connection info"); | |||
dlg.setIconImage(VncViewer.frameIcon); | |||
dlg.setAlwaysOnTop(true); | |||
dlg.setVisible(true); | |||
if (fullScreenWindow != null) | |||
DesktopWindow.setFullScreenWindow(fullScreenWindow); | |||
} | |||
public void refresh() { | |||
writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height), false); | |||
pendingUpdate = true; | |||
} | |||
// writeClientCutText() is called from the clipboard dialog | |||
public void writeClientCutText(String str, int len) { | |||
if (state() != RFBSTATE_NORMAL || shuttingDown) | |||
@@ -754,147 +708,6 @@ public class CConn extends CConnection implements | |||
writer().writeClientCutText(str, len); | |||
} | |||
public void writeKeyEvent(int keysym, boolean down) { | |||
if (state() != RFBSTATE_NORMAL || shuttingDown) | |||
return; | |||
writer().keyEvent(keysym, down); | |||
} | |||
public void writeKeyEvent(KeyEvent ev) { | |||
if (viewOnly.getValue() || shuttingDown) | |||
return; | |||
boolean down = (ev.getID() == KeyEvent.KEY_PRESSED); | |||
int keySym, keyCode = ev.getKeyCode(); | |||
// If neither the keyCode or keyChar are defined, then there's | |||
// really nothing that we can do with this. The fn key on OS-X | |||
// fires events like this when pressed but does not fire a | |||
// corresponding release event. | |||
if (keyCode == 0 && ev.getKeyChar() == KeyEvent.CHAR_UNDEFINED) | |||
return; | |||
if (!down) { | |||
Integer iter = downKeySym.get(keyCode); | |||
if (iter == null) { | |||
// Note that dead keys will raise this sort of error falsely | |||
// See https://bugs.openjdk.java.net/browse/JDK-6534883 | |||
vlog.debug("Unexpected key release of keyCode "+keyCode); | |||
String fmt = ev.paramString().replaceAll("%","%%"); | |||
vlog.debug(String.format(fmt.replaceAll(",","%n "))); | |||
return; | |||
} | |||
vlog.debug(String.format("Key released: 0x%04x => 0x%04x", | |||
keyCode, iter)); | |||
writeKeyEvent(iter, false); | |||
downKeySym.remove(keyCode); | |||
return; | |||
} | |||
keySym = Keysyms.translateKeyEvent(ev); | |||
if (keySym == Keysyms.VoidSymbol) | |||
return; | |||
boolean need_cheat = true; | |||
if (VncViewer.os.startsWith("windows")) { | |||
// Windows doesn't have a proper AltGr, but handles it using fake | |||
// Ctrl+Alt. Unfortunately X11 doesn't generally like the combination | |||
// Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to | |||
// get everything in the correct state. Cheat and temporarily release | |||
// Ctrl and Alt whenever we get a key with a symbol. | |||
if (KeyEvent.getKeyText(keyCode).isEmpty()) | |||
need_cheat = false; | |||
else if (!downKeySym.containsValue(Keysyms.Control_L) && | |||
!downKeySym.containsValue(Keysyms.Control_R)) | |||
need_cheat = false; | |||
else if (!downKeySym.containsValue(Keysyms.Alt_L) && | |||
!downKeySym.containsValue(Keysyms.Alt_R)) | |||
need_cheat = false; | |||
if (need_cheat) { | |||
vlog.info("Faking release of AltGr (Ctrl+Alt)"); | |||
if (downKeySym.containsValue(Keysyms.Control_L)) | |||
writeKeyEvent(Keysyms.Control_L, false); | |||
if (downKeySym.containsValue(Keysyms.Control_R)) | |||
writeKeyEvent(Keysyms.Control_R, false); | |||
if (downKeySym.containsValue(Keysyms.Alt_L)) | |||
writeKeyEvent(Keysyms.Alt_L, false); | |||
if (downKeySym.containsValue(Keysyms.Alt_R)) | |||
writeKeyEvent(Keysyms.Alt_R, false); | |||
} | |||
} | |||
vlog.debug(String.format("Key pressed: 0x%04x '%s' => 0x%04x", | |||
keyCode, Character.toString(ev.getKeyChar()), keySym)); | |||
downKeySym.put(keyCode, keySym); | |||
writeKeyEvent(keySym, down); | |||
if (VncViewer.os.startsWith("windows")) { | |||
if (need_cheat) { | |||
vlog.debug("Restoring AltGr state"); | |||
if (downKeySym.containsValue(Keysyms.Control_L)) | |||
writeKeyEvent(Keysyms.Control_L, true); | |||
if (downKeySym.containsValue(Keysyms.Control_R)) | |||
writeKeyEvent(Keysyms.Control_R, true); | |||
if (downKeySym.containsValue(Keysyms.Alt_L)) | |||
writeKeyEvent(Keysyms.Alt_L, true); | |||
if (downKeySym.containsValue(Keysyms.Alt_R)) | |||
writeKeyEvent(Keysyms.Alt_R, true); | |||
} | |||
} | |||
} | |||
public void writePointerEvent(MouseEvent ev) { | |||
if (state() != RFBSTATE_NORMAL || shuttingDown) | |||
return; | |||
switch (ev.getID()) { | |||
case MouseEvent.MOUSE_PRESSED: | |||
buttonMask = 1; | |||
if ((ev.getModifiers() & KeyEvent.ALT_MASK) != 0) buttonMask = 2; | |||
if ((ev.getModifiers() & KeyEvent.META_MASK) != 0) buttonMask = 4; | |||
break; | |||
case MouseEvent.MOUSE_RELEASED: | |||
buttonMask = 0; | |||
break; | |||
} | |||
writer().pointerEvent(new Point(ev.getX(), ev.getY()), buttonMask); | |||
} | |||
public void writeWheelEvent(MouseWheelEvent ev) { | |||
if (state() != RFBSTATE_NORMAL || shuttingDown) | |||
return; | |||
int x, y; | |||
int clicks = ev.getWheelRotation(); | |||
if (clicks < 0) { | |||
buttonMask = 8; | |||
} else { | |||
buttonMask = 16; | |||
} | |||
for (int i = 0; i < Math.abs(clicks); i++) { | |||
x = ev.getX(); | |||
y = ev.getY(); | |||
writer().pointerEvent(new Point(x, y), buttonMask); | |||
buttonMask = 0; | |||
writer().pointerEvent(new Point(x, y), buttonMask); | |||
} | |||
} | |||
synchronized void releaseDownKeys() { | |||
for (Map.Entry<Integer, Integer> entry : downKeySym.entrySet()) | |||
writeKeyEvent(entry.getValue(), false); | |||
downKeySym.clear(); | |||
} | |||
// this is a special ActionListener passed in by the | |||
// Java Plug-in software to control applet's close behavior | |||
public void setCloseListener(ActionListener cl) { | |||
@@ -930,8 +743,6 @@ public class CConn extends CConnection implements | |||
// from when constructed). | |||
// the following are only ever accessed by the GUI thread: | |||
int buttonMask; | |||
private String serverHost; | |||
private int serverPort; | |||
private Socket sock; | |||
@@ -957,7 +768,6 @@ public class CConn extends CConnection implements | |||
private boolean supportsSyncFence; | |||
private HashMap<Integer, Integer> downKeySym; | |||
public ActionListener closeListener = null; | |||
static LogWriter vlog = new LogWriter("CConn"); |
@@ -81,7 +81,7 @@ public class DesktopWindow extends JFrame | |||
scroll.getViewport().getView().requestFocusInWindow(); | |||
} | |||
public void windowLostFocus(WindowEvent e) { | |||
cc.releaseDownKeys(); | |||
viewport.releaseDownKeys(); | |||
} | |||
}); | |||
@@ -1,203 +0,0 @@ | |||
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | |||
* Copyright (C) 2011-2014 Brian P. Hinz | |||
* | |||
* 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. | |||
*/ | |||
package com.tigervnc.vncviewer; | |||
import java.awt.*; | |||
import java.awt.Cursor; | |||
import java.awt.event.*; | |||
import java.io.File; | |||
import javax.swing.filechooser.*; | |||
import javax.swing.JCheckBoxMenuItem; | |||
import javax.swing.JDialog; | |||
import javax.swing.JFrame; | |||
import javax.swing.JFileChooser; | |||
import javax.swing.JMenuItem; | |||
import javax.swing.JOptionPane; | |||
import javax.swing.JPopupMenu; | |||
import com.tigervnc.rfb.*; | |||
import static com.tigervnc.vncviewer.Parameters.*; | |||
public class F8Menu extends JPopupMenu implements ActionListener { | |||
public F8Menu(CConn cc) { | |||
super("VNC Menu"); | |||
setLightWeightPopupEnabled(false); | |||
String os = System.getProperty("os.name"); | |||
if (os.startsWith("Windows")) | |||
com.sun.java.swing.plaf.windows.WindowsLookAndFeel.setMnemonicHidden(false); | |||
this.cc = cc; | |||
restore = addMenuItem("Restore",KeyEvent.VK_R); | |||
restore.setEnabled(!embed.getValue()); | |||
move = addMenuItem("Move"); | |||
move.setEnabled(false); | |||
size = addMenuItem("Size"); | |||
size.setEnabled(false); | |||
minimize = addMenuItem("Minimize", KeyEvent.VK_N); | |||
minimize.setEnabled(!embed.getValue()); | |||
maximize = addMenuItem("Maximize", KeyEvent.VK_X); | |||
maximize.setEnabled(!embed.getValue()); | |||
addSeparator(); | |||
exit = addMenuItem("Close Viewer", KeyEvent.VK_C); | |||
addSeparator(); | |||
fullScreenCheckbox = new JCheckBoxMenuItem("Full Screen"); | |||
fullScreenCheckbox.setMnemonic(KeyEvent.VK_F); | |||
fullScreenCheckbox.setSelected(fullScreen.getValue()); | |||
fullScreenCheckbox.addActionListener(this); | |||
fullScreenCheckbox.setEnabled(!embed.getValue()); | |||
add(fullScreenCheckbox); | |||
addSeparator(); | |||
clipboard = addMenuItem("Clipboard..."); | |||
addSeparator(); | |||
int keyCode = MenuKey.getMenuKeyCode(); | |||
String keyText = KeyEvent.getKeyText(keyCode); | |||
f8 = addMenuItem("Send "+keyText, keyCode); | |||
ctrlAltDel = addMenuItem("Send Ctrl-Alt-Del"); | |||
addSeparator(); | |||
refresh = addMenuItem("Refresh Screen", KeyEvent.VK_H); | |||
addSeparator(); | |||
newConn = addMenuItem("New connection...", KeyEvent.VK_W); | |||
newConn.setEnabled(!embed.getValue()); | |||
options = addMenuItem("Options...", KeyEvent.VK_O); | |||
save = addMenuItem("Save connection info as...", KeyEvent.VK_S); | |||
info = addMenuItem("Connection info...", KeyEvent.VK_I); | |||
about = addMenuItem("About VncViewer...", KeyEvent.VK_A); | |||
addSeparator(); | |||
dismiss = addMenuItem("Dismiss menu"); | |||
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); | |||
} | |||
JMenuItem addMenuItem(String str, int mnemonic) { | |||
JMenuItem item = new JMenuItem(str, mnemonic); | |||
item.addActionListener(this); | |||
add(item); | |||
return item; | |||
} | |||
JMenuItem addMenuItem(String str) { | |||
JMenuItem item = new JMenuItem(str); | |||
item.addActionListener(this); | |||
add(item); | |||
return item; | |||
} | |||
boolean actionMatch(ActionEvent ev, JMenuItem item) { | |||
return ev.getActionCommand().equals(item.getActionCommand()); | |||
} | |||
public void actionPerformed(ActionEvent ev) { | |||
if (actionMatch(ev, exit)) { | |||
cc.close(); | |||
} else if (actionMatch(ev, fullScreenCheckbox)) { | |||
if (fullScreenCheckbox.isSelected()) | |||
cc.desktop.fullscreen_on(); | |||
else | |||
cc.desktop.fullscreen_off(); | |||
} else if (actionMatch(ev, restore)) { | |||
if (cc.desktop.fullscreen_active()) | |||
cc.desktop.fullscreen_off(); | |||
cc.desktop.setExtendedState(JFrame.NORMAL); | |||
} else if (actionMatch(ev, minimize)) { | |||
if (cc.desktop.fullscreen_active()) | |||
cc.desktop.fullscreen_off(); | |||
cc.desktop.setExtendedState(JFrame.ICONIFIED); | |||
} else if (actionMatch(ev, maximize)) { | |||
if (cc.desktop.fullscreen_active()) | |||
cc.desktop.fullscreen_off(); | |||
cc.desktop.setExtendedState(JFrame.MAXIMIZED_BOTH); | |||
} else if (actionMatch(ev, clipboard)) { | |||
ClipboardDialog.showDialog(cc.desktop); | |||
} else if (actionMatch(ev, f8)) { | |||
cc.writeKeyEvent(MenuKey.getMenuKeySym(), true); | |||
cc.writeKeyEvent(MenuKey.getMenuKeySym(), false); | |||
} else if (actionMatch(ev, ctrlAltDel)) { | |||
cc.writeKeyEvent(Keysyms.Control_L, true); | |||
cc.writeKeyEvent(Keysyms.Alt_L, true); | |||
cc.writeKeyEvent(Keysyms.Delete, true); | |||
cc.writeKeyEvent(Keysyms.Delete, false); | |||
cc.writeKeyEvent(Keysyms.Alt_L, false); | |||
cc.writeKeyEvent(Keysyms.Control_L, false); | |||
} else if (actionMatch(ev, refresh)) { | |||
cc.refresh(); | |||
} else if (actionMatch(ev, newConn)) { | |||
VncViewer.newViewer(); | |||
} else if (actionMatch(ev, options)) { | |||
OptionsDialog.showDialog(cc.desktop); | |||
} else if (actionMatch(ev, save)) { | |||
String title = "Save the TigerVNC configuration to file"; | |||
File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc")); | |||
if (!dflt.exists() || !dflt.isFile()) | |||
dflt = new File(FileUtils.getVncHomeDir()); | |||
FileNameExtensionFilter filter = | |||
new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc"); | |||
File f = Dialog.showChooser(title, dflt, this, filter); | |||
while (f != null && f.exists() && f.isFile()) { | |||
String msg = f.getAbsolutePath(); | |||
msg = msg.concat(" already exists. Do you want to overwrite?"); | |||
Object[] options = {"Overwrite", "No \u21B5"}; | |||
JOptionPane op = | |||
new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE, | |||
JOptionPane.OK_CANCEL_OPTION, null, options, options[1]); | |||
JDialog dlg = op.createDialog(this, "TigerVNC Viewer"); | |||
dlg.setIconImage(VncViewer.frameIcon); | |||
dlg.setAlwaysOnTop(true); | |||
dlg.setVisible(true); | |||
if (op.getValue() == options[0]) | |||
break; | |||
else | |||
f = Dialog.showChooser(title, f, this, filter); | |||
} | |||
if (f != null && (!f.exists() || f.canWrite())) | |||
saveViewerParameters(f.getAbsolutePath(), vncServerName.getValue()); | |||
} else if (actionMatch(ev, info)) { | |||
cc.showInfo(); | |||
} else if (actionMatch(ev, about)) { | |||
VncViewer.showAbout(cc.desktop); | |||
} else if (actionMatch(ev, dismiss)) { | |||
firePopupMenuCanceled(); | |||
} | |||
} | |||
public void show(Component invoker, int x, int y) { | |||
// lightweight components can't show in FullScreen Exclusive mode | |||
/* | |||
Window fsw = DesktopWindow.getFullScreenWindow(); | |||
GraphicsDevice gd = null; | |||
if (fsw != null) { | |||
gd = fsw.getGraphicsConfiguration().getDevice(); | |||
if (gd.isFullScreenSupported()) | |||
DesktopWindow.setFullScreenWindow(null); | |||
} | |||
*/ | |||
super.show(invoker, x, y); | |||
/* | |||
if (fsw != null && gd.isFullScreenSupported()) | |||
DesktopWindow.setFullScreenWindow(fsw); | |||
*/ | |||
} | |||
CConn cc; | |||
JMenuItem restore, move, size, minimize, maximize; | |||
JMenuItem exit, clipboard, ctrlAltDel, refresh; | |||
JMenuItem newConn, options, save, info, about, dismiss; | |||
static JMenuItem f8; | |||
JCheckBoxMenuItem fullScreenCheckbox; | |||
static LogWriter vlog = new LogWriter("F8Menu"); | |||
} |
@@ -0,0 +1,235 @@ | |||
/* Copyright (C) 2017 Brian P. Hinz | |||
* | |||
* 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. | |||
*/ | |||
package com.tigervnc.vncviewer; | |||
import java.awt.*; | |||
import java.awt.event.*; | |||
import java.util.*; | |||
import javax.swing.*; | |||
import com.tigervnc.rfb.*; | |||
import static java.awt.event.KeyEvent.*; | |||
import static com.tigervnc.rfb.Keysymdef.*; | |||
public class KeyMap { | |||
public final static int NoSymbol = 0; | |||
private static final HashMap<Integer, Character> code_map_java_to_char; | |||
static { | |||
// Certain KeyStrokes fail to produce a valid character (CTRL+ALT+... | |||
// on Windows and Mac almost never does). For these cases, the best | |||
// we can try to do is to map to the ASCII symbol from the keyCode. | |||
code_map_java_to_char = new HashMap<Integer, Character>(); | |||
for (int c=32; c<0x7f; c++) { | |||
int keyCode = KeyEvent.getExtendedKeyCodeForChar(c); | |||
if (keyCode != KeyEvent.VK_UNDEFINED) | |||
// Not all ASCII characters have VK_ constants, see vk_to_ascii() | |||
code_map_java_to_char.put(keyCode, (char)c); | |||
} | |||
} | |||
private static int[][] vkey_map = { | |||
/* KEYCODE LOCATION */ | |||
/* UNKNOWN STANDARD LEFT RIGHT NUMPAD */ | |||
{ VK_BACK_SPACE, NoSymbol, XK_BackSpace, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_TAB, NoSymbol, XK_Tab, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_CANCEL, NoSymbol, XK_Cancel, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_ENTER, NoSymbol, XK_Return, NoSymbol, NoSymbol, XK_KP_Enter }, | |||
{ VK_SHIFT, NoSymbol, XK_Shift_L, XK_Shift_L, XK_Shift_R, NoSymbol }, | |||
{ VK_CONTROL, NoSymbol, XK_Control_L, XK_Control_L, XK_Control_R, NoSymbol }, | |||
{ VK_ALT, NoSymbol, XK_Alt_L, XK_Alt_L, XK_Alt_R, NoSymbol }, | |||
/* VK_PAUSE left out on purpose because interpretation depends on state of CTRL. See further down. */ | |||
{ VK_CAPS_LOCK, NoSymbol, XK_Caps_Lock, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_ESCAPE, NoSymbol, XK_Escape, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_END, NoSymbol, XK_End, NoSymbol, NoSymbol, XK_KP_End }, | |||
{ VK_HOME, NoSymbol, XK_Home, NoSymbol, NoSymbol, XK_KP_Home }, | |||
{ VK_LEFT, NoSymbol, XK_Left, NoSymbol, NoSymbol, XK_KP_Left }, | |||
{ VK_UP, NoSymbol, XK_Up, NoSymbol, NoSymbol, XK_KP_Up }, | |||
{ VK_RIGHT, NoSymbol, XK_Right, NoSymbol, NoSymbol, XK_KP_Right }, | |||
{ VK_DOWN, NoSymbol, XK_Down, NoSymbol, NoSymbol, XK_KP_Down }, | |||
/* VK_PRINTSCREEN left out on purpose because interpretation depends on state of CTRL. See further down. */ | |||
{ VK_PAGE_UP, NoSymbol, XK_Page_Up, NoSymbol, NoSymbol, XK_KP_Page_Up }, | |||
{ VK_PAGE_DOWN, NoSymbol, XK_Page_Down, NoSymbol, NoSymbol, XK_KP_Page_Down }, | |||
{ VK_BEGIN, NoSymbol, XK_Begin, NoSymbol, NoSymbol, XK_KP_Begin }, | |||
{ VK_KP_LEFT, NoSymbol, XK_KP_Left, NoSymbol, NoSymbol, XK_KP_Left }, | |||
{ VK_KP_UP, NoSymbol, XK_KP_Up, NoSymbol, NoSymbol, XK_KP_Up }, | |||
{ VK_KP_RIGHT, NoSymbol, XK_KP_Right, NoSymbol, NoSymbol, XK_KP_Right }, | |||
{ VK_KP_DOWN, NoSymbol, XK_KP_Down, NoSymbol, NoSymbol, XK_KP_Down }, | |||
{ VK_INSERT, NoSymbol, XK_Insert, NoSymbol, NoSymbol, XK_KP_Insert }, | |||
{ VK_DELETE, NoSymbol, XK_Delete, NoSymbol, NoSymbol, XK_KP_Delete }, | |||
{ VK_WINDOWS, NoSymbol, NoSymbol, XK_Super_L, XK_Super_R, NoSymbol }, | |||
{ VK_CONTEXT_MENU, NoSymbol, XK_Menu, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_NUMPAD0, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_0 }, | |||
{ VK_NUMPAD1, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_1 }, | |||
{ VK_NUMPAD2, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_2 }, | |||
{ VK_NUMPAD3, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_3 }, | |||
{ VK_NUMPAD4, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_4 }, | |||
{ VK_NUMPAD5, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_5 }, | |||
{ VK_NUMPAD6, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_6 }, | |||
{ VK_NUMPAD7, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_7 }, | |||
{ VK_NUMPAD8, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_8 }, | |||
{ VK_NUMPAD9, NoSymbol, NoSymbol, NoSymbol, NoSymbol, XK_KP_9 }, | |||
{ VK_MULTIPLY, NoSymbol, XK_KP_Multiply, NoSymbol, NoSymbol, XK_KP_Multiply }, | |||
{ VK_ADD, NoSymbol, XK_KP_Add, NoSymbol, NoSymbol, XK_KP_Add }, | |||
{ VK_SUBTRACT, NoSymbol, XK_KP_Subtract, NoSymbol, NoSymbol, XK_KP_Subtract }, | |||
{ VK_DIVIDE, NoSymbol, XK_KP_Divide, NoSymbol, NoSymbol, XK_KP_Divide }, | |||
{ VK_SEPARATER, NoSymbol, XK_KP_Separator, NoSymbol, NoSymbol, XK_KP_Separator }, | |||
{ VK_DECIMAL, NoSymbol, XK_KP_Decimal, NoSymbol, NoSymbol, XK_KP_Decimal }, | |||
{ VK_F1, NoSymbol, XK_F1, XK_L1, XK_R1, NoSymbol }, | |||
{ VK_F2, NoSymbol, XK_F2, XK_L2, XK_R2, NoSymbol }, | |||
{ VK_F3, NoSymbol, XK_F3, XK_L3, XK_R3, NoSymbol }, | |||
{ VK_F4, NoSymbol, XK_F4, XK_L4, XK_R4, NoSymbol }, | |||
{ VK_F5, NoSymbol, XK_F5, XK_L5, XK_R5, NoSymbol }, | |||
{ VK_F6, NoSymbol, XK_F6, XK_L6, XK_R6, NoSymbol }, | |||
{ VK_F7, NoSymbol, XK_F7, XK_L7, XK_R7, NoSymbol }, | |||
{ VK_F8, NoSymbol, XK_F8, XK_L8, XK_R8, NoSymbol }, | |||
{ VK_F9, NoSymbol, XK_F9, XK_L9, XK_R9, NoSymbol }, | |||
{ VK_F10, NoSymbol, XK_F10, XK_L10, XK_R10, NoSymbol }, | |||
{ VK_F11, NoSymbol, XK_F11, NoSymbol, XK_R11, NoSymbol }, | |||
{ VK_F12, NoSymbol, XK_F12, NoSymbol, XK_R12, NoSymbol }, | |||
{ VK_F13, NoSymbol, XK_F13, NoSymbol, XK_R13, NoSymbol }, | |||
{ VK_F14, NoSymbol, XK_F14, NoSymbol, XK_R14, NoSymbol }, | |||
{ VK_F15, NoSymbol, XK_F15, NoSymbol, XK_R15, NoSymbol }, | |||
{ VK_F16, NoSymbol, XK_F16, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F17, NoSymbol, XK_F17, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F18, NoSymbol, XK_F18, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F19, NoSymbol, XK_F19, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F20, NoSymbol, XK_F20, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F21, NoSymbol, XK_F21, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F22, NoSymbol, XK_F22, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F23, NoSymbol, XK_F23, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_F24, NoSymbol, XK_F24, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_NUM_LOCK, NoSymbol, XK_Num_Lock, NoSymbol, NoSymbol, XK_Num_Lock }, | |||
{ VK_SCROLL_LOCK, NoSymbol, XK_Scroll_Lock, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_ALT_GRAPH, NoSymbol, XK_ISO_Level3_Shift, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_META, NoSymbol, NoSymbol, XK_Meta_L, XK_Meta_R, NoSymbol }, | |||
{ VK_MODECHANGE, NoSymbol, XK_Mode_switch, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_CLEAR, NoSymbol, XK_Clear, NoSymbol, NoSymbol, XK_KP_Begin }, | |||
{ VK_AGAIN, NoSymbol, XK_Redo, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_UNDO, NoSymbol, XK_Undo, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_FIND, NoSymbol, XK_Find, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_STOP, NoSymbol, XK_Cancel, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_HELP, NoSymbol, XK_Help, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_KANJI, NoSymbol, XK_Kanji, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_KATAKANA, NoSymbol, XK_Katakana, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_HIRAGANA, NoSymbol, XK_Hiragana, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_PREVIOUS_CANDIDATE, NoSymbol, XK_PreviousCandidate, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_CODE_INPUT, NoSymbol, XK_Codeinput, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_JAPANESE_ROMAN, NoSymbol, XK_Romaji, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_KANA_LOCK, NoSymbol, XK_Kana_Lock, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_ABOVEDOT, NoSymbol, XK_dead_abovedot, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_ABOVERING, NoSymbol, XK_dead_abovering, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_ACUTE, NoSymbol, XK_dead_acute, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_BREVE, NoSymbol, XK_dead_breve, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_CARON, NoSymbol, XK_dead_caron, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_CEDILLA, NoSymbol, XK_dead_cedilla, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_CIRCUMFLEX, NoSymbol, XK_dead_circumflex, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_DIAERESIS, NoSymbol, XK_dead_diaeresis, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_DOUBLEACUTE, NoSymbol, XK_dead_doubleacute, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_GRAVE, NoSymbol, XK_dead_grave, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_IOTA, NoSymbol, XK_dead_iota, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_MACRON, NoSymbol, XK_dead_macron, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_OGONEK, NoSymbol, XK_dead_ogonek, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_SEMIVOICED_SOUND, NoSymbol, XK_dead_semivoiced_sound, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_TILDE, NoSymbol, XK_dead_tilde, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_DEAD_VOICED_SOUND, NoSymbol, XK_dead_voiced_sound, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_ALPHANUMERIC, NoSymbol, XK_Eisu_Shift, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_ALL_CANDIDATES, NoSymbol, XK_MultipleCandidate, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_KANA, NoSymbol, XK_Kana_Shift, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_JAPANESE_KATAKANA, NoSymbol, XK_Katakana, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_JAPANESE_HIRAGANA, NoSymbol, XK_Hiragana, NoSymbol, NoSymbol, NoSymbol }, | |||
{ VK_COMPOSE, NoSymbol, XK_Multi_key, NoSymbol, NoSymbol, NoSymbol }, | |||
}; | |||
public static int vkey_to_keysym(KeyEvent ev) | |||
{ | |||
int keyCode = ev.getKeyCode(); | |||
// Start with keys that either don't generate a symbol, or | |||
// generate the same symbol as some other key. | |||
if (keyCode == KeyEvent.VK_PAUSE) | |||
return (ev.isControlDown() ? XK_Break : XK_Pause); | |||
else if (keyCode == KeyEvent.VK_PRINTSCREEN) | |||
return (ev.isControlDown() ? XK_Sys_Req : XK_Print); | |||
else | |||
for(int i = 0; i < vkey_map.length; i++) | |||
if (keyCode == vkey_map[i][0]) | |||
return vkey_map[i][ev.getKeyLocation()+1]; | |||
// Unknown special key? | |||
if (KeyEvent.getKeyText(keyCode).isEmpty()) { | |||
vlog.error("Unknown key code: 0x%04x", keyCode); | |||
return NoSymbol; | |||
} | |||
// Pressing Ctrl wreaks havoc with the symbol lookup... | |||
int ucs = (int)ev.getKeyChar(); | |||
if (ev.isControlDown()) { | |||
// For CTRL-<letter>, CTRL is sent separately, so just send <letter>. | |||
if ((ucs >= 1 && ucs <= 26 && !ev.isShiftDown()) || | |||
// CTRL-{, CTRL-|, CTRL-} also map to ASCII 96-127 | |||
(ucs >= 27 && ucs <= 29 && ev.isShiftDown())) | |||
ucs += 96; | |||
// For CTRL-SHIFT-<letter>, send capital <letter> to emulate behavior | |||
// of Linux. For CTRL-@, send @. For CTRL-_, send _. For CTRL-^, | |||
// send ^. | |||
else if (ucs < 32) | |||
ucs += 64; | |||
// If it's still undefined, map the keyCode to ASCII symbol | |||
else if (keyCode >= 0 && keyCode <= 127) | |||
if (ucs == CHAR_UNDEFINED || ev.isAltDown()) | |||
ucs = vk_to_ascii(keyCode, ev.isShiftDown()); | |||
else if (VncViewer.os.startsWith("mac os x") && ev.isMetaDown()) | |||
// 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. | |||
ucs = vk_to_ascii(keyCode, ev.isShiftDown()); | |||
} | |||
// Dead keys are represented by their spacing equivalent | |||
// (or something similar depending on the layout) | |||
if (Character.getType(ucs) == Character.COMBINING_SPACING_MARK) | |||
return Keysym2ucs.ucs2keysym(Keysym2ucs.ucs2combining(ucs)); | |||
if (Character.isDefined(ucs)) | |||
return Keysym2ucs.ucs2keysym(ucs); | |||
return NoSymbol; | |||
} | |||
private static int vk_to_ascii(int vk, boolean shift) { | |||
char c = 0; | |||
if (code_map_java_to_char.containsKey(vk)) | |||
c = code_map_java_to_char.get(vk); | |||
// 0x25 (%) and 0x3F (?) do not have VK_ constants | |||
if (vk == VK_5) | |||
c = shift ? '%' : c; | |||
else if (vk == VK_SLASH) | |||
c = shift ? '?' : c; | |||
if (Character.isLetter(c)) | |||
c = shift ? Character.toUpperCase(c) : Character.toLowerCase(c); | |||
return (int)c; | |||
} | |||
static LogWriter vlog = new LogWriter("KeyMap"); | |||
} |
@@ -1,6 +1,6 @@ | |||
/* Copyright 2011 Martin Koegler <mkoegler@auto.tuwien.ac.at> | |||
* Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB | |||
* Copyright 2012 Brian P. Hinz | |||
* Copyright 2012-2017 Brian P. Hinz | |||
* | |||
* This is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
@@ -24,42 +24,47 @@ import java.awt.event.KeyEvent; | |||
import com.tigervnc.rfb.*; | |||
import static java.awt.event.KeyEvent.*; | |||
import static com.tigervnc.rfb.Keysymdef.*; | |||
public class MenuKey | |||
{ | |||
static class MenuKeySymbol { | |||
public MenuKeySymbol(String name_, int keycode_, int keysym_) { | |||
public MenuKeySymbol(String name_, int javacode_, | |||
int keycode_, int keysym_) { | |||
name = name_; | |||
javacode = javacode_; | |||
keycode = keycode_; | |||
keysym = keysym_; | |||
} | |||
String name; | |||
int javacode; | |||
int keycode; | |||
int keysym; | |||
} | |||
private static final MenuKeySymbol[] menuSymbols = { | |||
new MenuKeySymbol("F1", KeyEvent.VK_F1, Keysyms.F1), | |||
new MenuKeySymbol("F2", KeyEvent.VK_F2, Keysyms.F2), | |||
new MenuKeySymbol("F3", KeyEvent.VK_F3, Keysyms.F3), | |||
new MenuKeySymbol("F4", KeyEvent.VK_F4, Keysyms.F4), | |||
new MenuKeySymbol("F5", KeyEvent.VK_F5, Keysyms.F5), | |||
new MenuKeySymbol("F6", KeyEvent.VK_F6, Keysyms.F6), | |||
new MenuKeySymbol("F7", KeyEvent.VK_F7, Keysyms.F7), | |||
new MenuKeySymbol("F8", KeyEvent.VK_F8, Keysyms.F8), | |||
new MenuKeySymbol("F9", KeyEvent.VK_F9, Keysyms.F9), | |||
new MenuKeySymbol("F10", KeyEvent.VK_F10, Keysyms.F10), | |||
new MenuKeySymbol("F11", KeyEvent.VK_F11, Keysyms.F11), | |||
new MenuKeySymbol("F12", KeyEvent.VK_F12, Keysyms.F12), | |||
new MenuKeySymbol("Pause", KeyEvent.VK_PAUSE, Keysyms.Pause), | |||
new MenuKeySymbol("Print", KeyEvent.VK_PRINTSCREEN, Keysyms.Print), | |||
new MenuKeySymbol("Scroll_Lock", KeyEvent.VK_SCROLL_LOCK, | |||
Keysyms.Scroll_Lock), | |||
new MenuKeySymbol("Escape", KeyEvent.VK_ESCAPE, Keysyms.Escape), | |||
new MenuKeySymbol("Insert", KeyEvent.VK_INSERT, Keysyms.Insert), | |||
new MenuKeySymbol("Delete", KeyEvent.VK_DELETE, Keysyms.Delete), | |||
new MenuKeySymbol("Home", KeyEvent.VK_HOME, Keysyms.Home), | |||
new MenuKeySymbol("Page_Up", KeyEvent.VK_PAGE_UP, Keysyms.Page_Up), | |||
new MenuKeySymbol("Page_Down", KeyEvent.VK_PAGE_DOWN, Keysyms.Page_Down) | |||
new MenuKeySymbol("F1", VK_F1, 0x3b, XK_F1), | |||
new MenuKeySymbol("F2", VK_F2, 0x3c, XK_F2), | |||
new MenuKeySymbol("F3", VK_F3, 0x3d, XK_F3), | |||
new MenuKeySymbol("F4", VK_F4, 0x3e, XK_F4), | |||
new MenuKeySymbol("F5", VK_F5, 0x3f, XK_F5), | |||
new MenuKeySymbol("F6", VK_F6, 0x40, XK_F6), | |||
new MenuKeySymbol("F7", VK_F7, 0x41, XK_F7), | |||
new MenuKeySymbol("F8", VK_F8, 0x42, XK_F8), | |||
new MenuKeySymbol("F9", VK_F9, 0x43, XK_F9), | |||
new MenuKeySymbol("F10", VK_F10, 0x44, XK_F10), | |||
new MenuKeySymbol("F11", VK_F11, 0x57, XK_F11), | |||
new MenuKeySymbol("F12", VK_F12, 0x58, XK_F12), | |||
new MenuKeySymbol("Pause", VK_PAUSE, 0xc6, XK_Pause), | |||
new MenuKeySymbol("Scroll_Lock", VK_SCROLL_LOCK, | |||
0x46, XK_Scroll_Lock), | |||
new MenuKeySymbol("Escape", VK_ESCAPE, 0x01, XK_Escape), | |||
new MenuKeySymbol("Insert", VK_INSERT, 0xd2, XK_Insert), | |||
new MenuKeySymbol("Delete", VK_DELETE, 0xd3, XK_Delete), | |||
new MenuKeySymbol("Home", VK_HOME, 0xc7, XK_Home), | |||
new MenuKeySymbol("Page_Up", VK_PAGE_UP, 0xc9, XK_Page_Up), | |||
new MenuKeySymbol("Page_Down", VK_PAGE_DOWN, 0xd1, XK_Page_Down) | |||
}; | |||
static int getMenuKeySymbolCount() { | |||
@@ -87,8 +92,21 @@ public class MenuKey | |||
return s; | |||
} | |||
static int getMenuKeyJavaCode() { | |||
int menuKeyCode = VK_F8; | |||
@SuppressWarnings({"static"}) | |||
String menuKeyStr = | |||
Configuration.global().getParam("menuKey").getValueStr(); | |||
for(int i = 0; i < getMenuKeySymbolCount(); i++) | |||
if (menuSymbols[i].name.equals(menuKeyStr)) | |||
menuKeyCode = menuSymbols[i].javacode; | |||
return menuKeyCode; | |||
} | |||
static int getMenuKeyCode() { | |||
int menuKeyCode = KeyEvent.VK_F8; | |||
int menuKeyCode = VK_F8; | |||
@SuppressWarnings({"static"}) | |||
String menuKeyStr = | |||
@@ -101,7 +119,7 @@ public class MenuKey | |||
} | |||
static int getMenuKeySym() { | |||
int menuKeySym = Keysyms.F8; | |||
int menuKeySym = XK_F8; | |||
@SuppressWarnings({"static"}) | |||
String menuKeyStr = |
@@ -269,7 +269,7 @@ class ServerDialog extends Dialog implements Runnable { | |||
} | |||
private void handleAbout() { | |||
VncViewer.showAbout(this); | |||
VncViewer.about_vncviewer(this); | |||
} | |||
private void handleCancel() { |
@@ -31,16 +31,11 @@ | |||
package com.tigervnc.vncviewer; | |||
import java.awt.*; | |||
import java.awt.Color; | |||
import java.awt.color.ColorSpace; | |||
import java.awt.event.*; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.image.*; | |||
import java.awt.datatransfer.DataFlavor; | |||
import java.awt.datatransfer.Transferable; | |||
import java.awt.datatransfer.Clipboard; | |||
import java.io.BufferedReader; | |||
import java.nio.*; | |||
import java.util.*; | |||
import javax.swing.*; | |||
import javax.imageio.*; | |||
@@ -48,46 +43,76 @@ import java.io.*; | |||
import com.tigervnc.rfb.*; | |||
import com.tigervnc.rfb.Cursor; | |||
import com.tigervnc.rfb.Exception; | |||
import com.tigervnc.rfb.Point; | |||
import static java.awt.event.KeyEvent.*; | |||
import static com.tigervnc.vncviewer.Parameters.*; | |||
import static com.tigervnc.rfb.Keysymdef.*; | |||
class Viewport extends JPanel implements MouseListener, | |||
MouseMotionListener, MouseWheelListener, KeyListener { | |||
class Viewport extends JPanel implements ActionListener { | |||
static LogWriter vlog = new LogWriter("Viewport"); | |||
enum ID { EXIT, FULLSCREEN, MINIMIZE, RESIZE, NEWVIEWER, | |||
CTRL, ALT, MENUKEY, CTRLALTDEL, CLIPBOARD, | |||
REFRESH, OPTIONS, INFO, ABOUT, DISMISS } | |||
enum MENU { INACTIVE, TOGGLE, VALUE, RADIO, | |||
INVISIBLE, SUBMENU_POINTER, SUBMENU, DIVIDER } | |||
public Viewport(int w, int h, PixelFormat serverPF, CConn cc_) | |||
{ | |||
cc = cc_; | |||
setScaledSize(cc.cp.width, cc.cp.height); | |||
setScaledSize(w, h); | |||
frameBuffer = createFramebuffer(serverPF, w, h); | |||
assert(frameBuffer != null); | |||
setBackground(Color.BLACK); | |||
cc.setFramebuffer(frameBuffer); | |||
contextMenu = new JPopupMenu(); | |||
OptionsDialog.addCallback("handleOptions", this); | |||
addMouseListener(this); | |||
addMouseWheelListener(this); | |||
addMouseMotionListener(this); | |||
addKeyListener(this); | |||
addMouseListener(new MouseAdapter() { | |||
public void mouseClicked(MouseEvent e) { } | |||
public void mouseEntered(MouseEvent e) { handle(e); } | |||
public void mouseExited(MouseEvent e) { handle(e); } | |||
public void mouseReleased(MouseEvent e) { handle(e); } | |||
public void mousePressed(MouseEvent e) { handle(e); } | |||
}); | |||
addMouseWheelListener(new MouseAdapter() { | |||
public void mouseWheelMoved(MouseWheelEvent e) { handle(e); } | |||
}); | |||
addMouseMotionListener(new MouseMotionAdapter() { | |||
public void mouseDragged(MouseEvent e) { handle(e); } | |||
public void mouseMoved(MouseEvent e) { handle(e); } | |||
}); | |||
addKeyListener(new KeyAdapter() { | |||
public void keyTyped(KeyEvent e) { } | |||
public void keyPressed(KeyEvent e) { handleSystemEvent(e); } | |||
public void keyReleased(KeyEvent e) { handleSystemEvent(e); } | |||
}); | |||
addFocusListener(new FocusAdapter() { | |||
public void focusGained(FocusEvent e) { | |||
ClipboardDialog.clientCutText(); | |||
} | |||
public void focusLost(FocusEvent e) { | |||
cc.releaseDownKeys(); | |||
releaseDownKeys(); | |||
} | |||
}); | |||
setFocusTraversalKeysEnabled(false); | |||
setFocusable(true); | |||
setMenuKey(); | |||
// Send a fake pointer event so that the server will stop rendering | |||
// a server-side cursor. Ideally we'd like to send the actual pointer | |||
// position, but we can't really tell when the window manager is done | |||
// placing us so we don't have a good time for that. | |||
cc.writer().pointerEvent(new Point(w/2, h/2), 0); | |||
handlePointerEvent(new Point(w/2, h/2), 0); | |||
} | |||
// Most efficient format (from Viewport's point of view) | |||
@@ -127,6 +152,9 @@ class Viewport extends JPanel implements MouseListener, | |||
{ | |||
int i; | |||
if (cursor != null) | |||
cursor.flush(); | |||
for (i = 0; i < width*height; i++) | |||
if (data[i*4 + 3] != 0) break; | |||
@@ -148,7 +176,6 @@ class Viewport extends JPanel implements MouseListener, | |||
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); | |||
cursor.setRGB(0, 0, width, height, buffer.array(), 0, width); | |||
cursorHotspot = hotspot; | |||
} | |||
} | |||
@@ -158,35 +185,32 @@ class Viewport extends JPanel implements MouseListener, | |||
int x = (int)Math.floor((float)cursorHotspot.x * scaleRatioX); | |||
int y = (int)Math.floor((float)cursorHotspot.y * scaleRatioY); | |||
java.awt.Cursor softCursor; | |||
Dimension cs = tk.getBestCursorSize(cw, ch); | |||
if (cs.width != cw && cs.height != ch) { | |||
cw = Math.min(cw, cs.width); | |||
ch = Math.min(ch, cs.height); | |||
x = (int)Math.min(x, Math.max(cs.width - 1, 0)); | |||
y = (int)Math.min(y, Math.max(cs.height - 1, 0)); | |||
BufferedImage scaledImage = | |||
new BufferedImage(cs.width, cs.height, BufferedImage.TYPE_INT_ARGB); | |||
Graphics2D g2 = scaledImage.createGraphics(); | |||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, | |||
RenderingHints.VALUE_RENDER_QUALITY); | |||
g2.drawImage(cursor, | |||
0, 0, cw, ch, | |||
0, 0, cursor.getWidth(), cursor.getHeight(), null); | |||
BufferedImage tmp = | |||
new BufferedImage(cs.width, cs.height, BufferedImage.TYPE_INT_ARGB_PRE); | |||
Graphics2D g2 = tmp.createGraphics(); | |||
g2.drawImage(cursor, 0, 0, cw, ch, 0, 0, width, height, null); | |||
g2.dispose(); | |||
java.awt.Point hs = new java.awt.Point(x, y); | |||
softCursor = tk.createCustomCursor(scaledImage, hs, "softCursor"); | |||
scaledImage.flush(); | |||
} else { | |||
java.awt.Point hs = new java.awt.Point(x, y); | |||
softCursor = tk.createCustomCursor(cursor, hs, "softCursor"); | |||
cursor = tmp; | |||
} | |||
cursor.flush(); | |||
setCursor(cursor, x, y); | |||
} | |||
setCursor(softCursor); | |||
private void setCursor(Image img, int x, int y) | |||
{ | |||
java.awt.Point hotspot; | |||
java.awt.Cursor softCursor; | |||
String name = "rfb cursor"; | |||
hotspot = new java.awt.Point(x, y); | |||
softCursor = tk.createCustomCursor(img, hotspot, name); | |||
setCursor(softCursor); | |||
} | |||
public void resize(int x, int y, int w, int h) { | |||
@@ -200,6 +224,54 @@ class Viewport extends JPanel implements MouseListener, | |||
setScaledSize(w, h); | |||
} | |||
public int handle(MouseEvent e) | |||
{ | |||
int buttonMask, wheelMask; | |||
switch (e.getID()) { | |||
case MouseEvent.MOUSE_ENTERED: | |||
if (cursor != null) | |||
setCursor(cursor, cursorHotspot.x, cursorHotspot.y); | |||
if (embed.getValue()) | |||
requestFocus(); | |||
return 1; | |||
case MouseEvent.MOUSE_EXITED: | |||
setCursor(java.awt.Cursor.getDefaultCursor()); | |||
return 1; | |||
case MouseEvent.MOUSE_PRESSED: | |||
case MouseEvent.MOUSE_RELEASED: | |||
case MouseEvent.MOUSE_DRAGGED: | |||
case MouseEvent.MOUSE_MOVED: | |||
case MouseEvent.MOUSE_WHEEL: | |||
buttonMask = 0; | |||
if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0) | |||
buttonMask |= 1; | |||
if ((e.getModifiersEx() & MouseEvent.BUTTON2_DOWN_MASK) != 0) | |||
buttonMask |= 2; | |||
if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0) | |||
buttonMask |= 4; | |||
if (e.getID() == MouseEvent.MOUSE_WHEEL) { | |||
wheelMask = 0; | |||
int clicks = ((MouseWheelEvent)e).getWheelRotation(); | |||
if (clicks < 0) | |||
wheelMask |= e.isShiftDown() ? 32 : 8; | |||
else | |||
wheelMask |= e.isShiftDown() ? 64 : 16; | |||
Point pt = new Point(e.getX(), e.getY()); | |||
for (int i = 0; i < Math.abs(clicks); i++) { | |||
handlePointerEvent(pt, buttonMask|wheelMask); | |||
handlePointerEvent(pt, buttonMask); | |||
} | |||
return 1; | |||
} | |||
handlePointerEvent(new Point(e.getX(), e.getY()), buttonMask); | |||
return 1; | |||
} | |||
return -1; | |||
} | |||
private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h) | |||
{ | |||
PlatformPixelBuffer fb; | |||
@@ -241,116 +313,6 @@ class Viewport extends JPanel implements MouseListener, | |||
g2.dispose(); | |||
} | |||
// Mouse-Motion callback function | |||
private void mouseMotionCB(MouseEvent e) { | |||
if (!viewOnly.getValue() && | |||
e.getX() >= 0 && e.getX() <= scaledWidth && | |||
e.getY() >= 0 && e.getY() <= scaledHeight) | |||
cc.writePointerEvent(translateMouseEvent(e)); | |||
} | |||
public void mouseDragged(MouseEvent e) { mouseMotionCB(e); } | |||
public void mouseMoved(MouseEvent e) { mouseMotionCB(e); } | |||
// Mouse callback function | |||
private void mouseCB(MouseEvent e) { | |||
if (!viewOnly.getValue()) | |||
if ((e.getID() == MouseEvent.MOUSE_RELEASED) || | |||
(e.getX() >= 0 && e.getX() <= scaledWidth && | |||
e.getY() >= 0 && e.getY() <= scaledHeight)) | |||
cc.writePointerEvent(translateMouseEvent(e)); | |||
} | |||
public void mouseReleased(MouseEvent e) { mouseCB(e); } | |||
public void mousePressed(MouseEvent e) { mouseCB(e); } | |||
public void mouseClicked(MouseEvent e) {} | |||
public void mouseEntered(MouseEvent e) { | |||
if (embed.getValue()) | |||
requestFocus(); | |||
} | |||
public void mouseExited(MouseEvent e) {} | |||
// MouseWheel callback function | |||
private void mouseWheelCB(MouseWheelEvent e) { | |||
if (!viewOnly.getValue()) | |||
cc.writeWheelEvent(e); | |||
} | |||
public void mouseWheelMoved(MouseWheelEvent e) { | |||
mouseWheelCB(e); | |||
} | |||
private static final Integer keyEventLock = 0; | |||
// Handle the key-typed event. | |||
public void keyTyped(KeyEvent e) { } | |||
// Handle the key-released event. | |||
public void keyReleased(KeyEvent e) { | |||
synchronized(keyEventLock) { | |||
cc.writeKeyEvent(e); | |||
} | |||
} | |||
// Handle the key-pressed event. | |||
public void keyPressed(KeyEvent e) | |||
{ | |||
if (e.getKeyCode() == MenuKey.getMenuKeyCode()) { | |||
java.awt.Point pt = e.getComponent().getMousePosition(); | |||
if (pt != null) { | |||
F8Menu menu = new F8Menu(cc); | |||
menu.show(e.getComponent(), (int)pt.getX(), (int)pt.getY()); | |||
} | |||
return; | |||
} | |||
int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK; | |||
if ((e.getModifiers() & ctrlAltShiftMask) == ctrlAltShiftMask) { | |||
switch (e.getKeyCode()) { | |||
case KeyEvent.VK_A: | |||
VncViewer.showAbout(this); | |||
return; | |||
case KeyEvent.VK_F: | |||
if (cc.desktop.fullscreen_active()) | |||
cc.desktop.fullscreen_on(); | |||
else | |||
cc.desktop.fullscreen_off(); | |||
return; | |||
case KeyEvent.VK_H: | |||
cc.refresh(); | |||
return; | |||
case KeyEvent.VK_I: | |||
cc.showInfo(); | |||
return; | |||
case KeyEvent.VK_O: | |||
OptionsDialog.showDialog(this); | |||
return; | |||
case KeyEvent.VK_W: | |||
VncViewer.newViewer(); | |||
return; | |||
case KeyEvent.VK_LEFT: | |||
case KeyEvent.VK_RIGHT: | |||
case KeyEvent.VK_UP: | |||
case KeyEvent.VK_DOWN: | |||
return; | |||
} | |||
} | |||
if ((e.getModifiers() & Event.META_MASK) == Event.META_MASK) { | |||
switch (e.getKeyCode()) { | |||
case KeyEvent.VK_COMMA: | |||
case KeyEvent.VK_N: | |||
case KeyEvent.VK_W: | |||
case KeyEvent.VK_I: | |||
case KeyEvent.VK_R: | |||
case KeyEvent.VK_L: | |||
case KeyEvent.VK_F: | |||
case KeyEvent.VK_Z: | |||
case KeyEvent.VK_T: | |||
return; | |||
} | |||
} | |||
synchronized(keyEventLock) { | |||
cc.writeKeyEvent(e); | |||
} | |||
} | |||
public void setScaledSize(int width, int height) | |||
{ | |||
assert(width != 0 && height != 0); | |||
@@ -384,21 +346,417 @@ class Viewport extends JPanel implements MouseListener, | |||
setSize(new Dimension(scaledWidth, scaledHeight)); | |||
} | |||
private MouseEvent translateMouseEvent(MouseEvent e) | |||
private void handlePointerEvent(Point pos, int buttonMask) | |||
{ | |||
if (cc.cp.width != scaledWidth || | |||
cc.cp.height != scaledHeight) { | |||
int sx = (scaleRatioX == 1.00) ? | |||
e.getX() : (int)Math.floor(e.getX() / scaleRatioX); | |||
int sy = (scaleRatioY == 1.00) ? | |||
e.getY() : (int)Math.floor(e.getY() / scaleRatioY); | |||
e.translatePoint(sx - e.getX(), sy - e.getY()); | |||
if (!viewOnly.getValue()) { | |||
if (buttonMask != lastButtonMask || !pos.equals(lastPointerPos)) { | |||
try { | |||
if (cc.cp.width != scaledWidth || | |||
cc.cp.height != scaledHeight) { | |||
int sx = (scaleRatioX == 1.00) ? | |||
pos.x : (int)Math.floor(pos.x / scaleRatioX); | |||
int sy = (scaleRatioY == 1.00) ? | |||
pos.y : (int)Math.floor(pos.y / scaleRatioY); | |||
pos = pos.translate(new Point(sx - pos.x, sy - pos.y)); | |||
} | |||
cc.writer().pointerEvent(pos, buttonMask); | |||
} catch (Exception e) { | |||
vlog.error("%s", e.getMessage()); | |||
cc.close(); | |||
} | |||
} | |||
lastPointerPos = pos; | |||
lastButtonMask = buttonMask; | |||
} | |||
return e; | |||
} | |||
public void handleKeyPress(int keyCode, int keySym) | |||
{ | |||
// Prevent recursion if the menu wants to send it's own | |||
// activation key. | |||
if ((menuKeySym != 0) && keySym == menuKeySym && !menuRecursion) { | |||
popupContextMenu(); | |||
return; | |||
} | |||
if (viewOnly.getValue()) | |||
return; | |||
if (keyCode == 0) { | |||
vlog.error("No key code specified on key press"); | |||
return; | |||
} | |||
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 (keySym) { | |||
case XK_Meta_L: | |||
keySym = XK_Alt_L; | |||
break; | |||
case XK_Meta_R: | |||
keySym = XK_Super_L; | |||
break; | |||
case XK_Alt_L: | |||
keySym = XK_Mode_switch; | |||
break; | |||
case XK_Alt_R: | |||
keySym = XK_ISO_Level3_Shift; | |||
break; | |||
} | |||
} | |||
if (VncViewer.os.startsWith("windows")) { | |||
// Ugly hack alert! | |||
// | |||
// Windows doesn't have a proper AltGr, but handles it using fake | |||
// Ctrl+Alt. Unfortunately X11 doesn't generally like the combination | |||
// Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to | |||
// get everything in the correct state. Cheat and temporarily release | |||
// Ctrl and Alt when we send some other symbol. | |||
if (downKeySym.containsValue(XK_Control_L) && | |||
downKeySym.containsValue(XK_Alt_R)) { | |||
vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)"); | |||
try { | |||
cc.writer().keyEvent(XK_Control_L, false); | |||
cc.writer().keyEvent(XK_Alt_R, false); | |||
} catch (Exception e) { | |||
vlog.error("%s", e.getMessage()); | |||
cc.close(); | |||
} | |||
} | |||
} | |||
// Because of the way keyboards work, we cannot expect to have the same | |||
// symbol on release as when pressed. This breaks the VNC protocol however, | |||
// so we need to keep track of what keysym a key _code_ generated on press | |||
// and send the same on release. | |||
downKeySym.put(keyCode, keySym); | |||
vlog.debug("Key pressed: 0x%04x => 0x%04x", keyCode, keySym); | |||
try { | |||
// Fake keycode? | |||
if (keyCode > 0xffff) | |||
cc.writer().keyEvent(keySym, true); | |||
else | |||
cc.writer().keyEvent(keySym, true); | |||
} catch (Exception e) { | |||
vlog.error("%s", e.getMessage()); | |||
cc.close(); | |||
} | |||
if (VncViewer.os.startsWith("windows")) { | |||
// Ugly hack continued... | |||
if (downKeySym.containsValue(XK_Control_L) && | |||
downKeySym.containsValue(XK_Alt_R)) { | |||
vlog.debug("Restoring AltGr state"); | |||
try { | |||
cc.writer().keyEvent(XK_Control_L, true); | |||
cc.writer().keyEvent(XK_Alt_R, true); | |||
} catch (Exception e) { | |||
vlog.error("%s", e.getMessage()); | |||
cc.close(); | |||
} | |||
} | |||
} | |||
} | |||
public void handleKeyRelease(int keyCode) | |||
{ | |||
Integer iter; | |||
if (viewOnly.getValue()) | |||
return; | |||
iter = downKeySym.get(keyCode); | |||
if (iter == null) { | |||
// These occur somewhat frequently so let's not spam them unless | |||
// logging is turned up. | |||
vlog.debug("Unexpected release of key code %d", keyCode); | |||
return; | |||
} | |||
vlog.debug("Key released: 0x%04x => 0x%04x", keyCode, iter); | |||
try { | |||
if (keyCode > 0xffff) | |||
cc.writer().keyEvent(iter, false); | |||
else | |||
cc.writer().keyEvent(iter, false); | |||
} catch (Exception e) { | |||
vlog.error("%s", e.getMessage()); | |||
cc.close(); | |||
} | |||
downKeySym.remove(keyCode); | |||
} | |||
private int handleSystemEvent(AWTEvent event) | |||
{ | |||
if (event instanceof KeyEvent) { | |||
KeyEvent ev = (KeyEvent)event; | |||
if (ev.getKeyCode() == 0) { | |||
// Not much we can do with this... | |||
vlog.debug("Ignoring KeyEvent with unknown Java keycode"); | |||
return 0; | |||
} | |||
if (ev.getID() == KeyEvent.KEY_PRESSED) { | |||
// Generate a fake keycode just for tracking if we can't figure | |||
// out the proper one. Java virtual key codes aren't unique | |||
// between left/right versions of keys, so we can't use them as | |||
// indexes to the downKeySym map. There should not be any key | |||
// codes > 0xFFFF so we can use the high nibble to make a unique | |||
// pseudo-key code. | |||
int keyCode = ev.getKeyCode() | ev.getKeyLocation()<<16; | |||
// Pressing Ctrl wreaks havoc with the symbol lookup, so turn | |||
// that off. But AltGr shows up as Ctrl_L+Alt_R in Windows, so | |||
// construct a new KeyEvent that uses a proper AltGraph for the | |||
// symbol lookup. | |||
if (VncViewer.os.startsWith("windows")) { | |||
if (downKeySym.containsValue(XK_Control_L) && | |||
downKeySym.containsValue(XK_Alt_R)) { | |||
int mask = ev.getModifiers(); | |||
mask &= ~CTRL_MASK; | |||
mask &= ~ALT_MASK; | |||
mask |= ALT_GRAPH_MASK; | |||
AWTKeyStroke ks = | |||
AWTKeyStroke.getAWTKeyStroke(ev.getKeyCode(), mask); | |||
ev = new KeyEvent((JComponent)ev.getSource(), ev.getID(), | |||
ev.getWhen(), mask, ev.getKeyCode(), | |||
ks.getKeyChar(), ev.getKeyLocation()); | |||
} | |||
} | |||
int keySym = KeyMap.vkey_to_keysym(ev); | |||
if (keySym == KeyMap.NoSymbol) | |||
vlog.error("No symbol for virtual key 0x%02x", keyCode); | |||
if (VncViewer.os.startsWith("linux")) { | |||
switch (keySym) { | |||
// For the first few years, there wasn't a good consensus on what the | |||
// Windows keys should be mapped to for X11. So we need to help out a | |||
// bit and map all variants to the same key... | |||
case XK_Hyper_L: | |||
keySym = XK_Super_L; | |||
break; | |||
case XK_Hyper_R: | |||
keySym = XK_Super_R; | |||
break; | |||
// There has been several variants for Shift-Tab over the years. | |||
// RFB states that we should always send a normal tab. | |||
case XK_ISO_Left_Tab: | |||
keySym = XK_Tab; | |||
break; | |||
} | |||
} | |||
handleKeyPress(keyCode, keySym); | |||
if (VncViewer.os.startsWith("mac os x")) { | |||
// We don't get any release events for CapsLock, so we have to | |||
// send the release right away. | |||
if (keySym == XK_Caps_Lock) | |||
handleKeyRelease(keyCode); | |||
} | |||
return 1; | |||
} else if (ev.getID() == KeyEvent.KEY_RELEASED) { | |||
int keyCode = keyCode = ev.getKeyCode() | ev.getKeyLocation()<<16; | |||
handleKeyRelease(keyCode); | |||
return 1; | |||
} | |||
} | |||
return 0; | |||
} | |||
private void initContextMenu() | |||
{ | |||
contextMenu.setLightWeightPopupEnabled(false); | |||
contextMenu.removeAll(); | |||
menu_add(contextMenu, "Exit viewer", KeyEvent.VK_X, | |||
this, ID.EXIT, EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "Full screen", KeyEvent.VK_F, this, ID.FULLSCREEN, | |||
window().fullscreen_active() ? | |||
EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE)); | |||
menu_add(contextMenu, "Minimize", KeyEvent.VK_Z, | |||
this, ID.MINIMIZE, EnumSet.noneOf(MENU.class)); | |||
menu_add(contextMenu, "Resize window to session", KeyEvent.VK_W, | |||
this, ID.RESIZE, | |||
window().fullscreen_active() ? | |||
EnumSet.of(MENU.INACTIVE, MENU.DIVIDER) : EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "Clipboard viewer...", KeyEvent.VK_UNDEFINED, | |||
this, ID.CLIPBOARD, EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "Ctrl", KeyEvent.VK_C, | |||
this, ID.CTRL, | |||
menuCtrlKey ? EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE)); | |||
menu_add(contextMenu, "Alt", KeyEvent.VK_A, | |||
this, ID.ALT, | |||
menuAltKey ? EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE)); | |||
menu_add(contextMenu, "Send Ctrl-Alt-Del", KeyEvent.VK_D, | |||
this, ID.CTRLALTDEL, EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "Refresh screen", KeyEvent.VK_R, | |||
this, ID.REFRESH, EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "New connection...", KeyEvent.VK_N, | |||
this, ID.NEWVIEWER, | |||
embed.getValue() ? EnumSet.of(MENU.INACTIVE, MENU.DIVIDER) : EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "Options...", KeyEvent.VK_O, | |||
this, ID.OPTIONS, EnumSet.noneOf(MENU.class)); | |||
menu_add(contextMenu, "Connection info...", KeyEvent.VK_I, | |||
this, ID.INFO, EnumSet.noneOf(MENU.class)); | |||
menu_add(contextMenu, "About TigerVNC viewer...", KeyEvent.VK_T, | |||
this, ID.ABOUT, EnumSet.of(MENU.DIVIDER)); | |||
menu_add(contextMenu, "Dismiss menu", KeyEvent.VK_M, | |||
this, ID.DISMISS, EnumSet.noneOf(MENU.class)); | |||
} | |||
static void menu_add(JPopupMenu menu, String text, | |||
int shortcut, ActionListener cb, | |||
ID data, EnumSet<MENU> flags) | |||
{ | |||
JMenuItem item; | |||
if (flags.contains(MENU.TOGGLE)) { | |||
item = new JCheckBoxMenuItem(text, flags.contains(MENU.VALUE)); | |||
} else { | |||
if (shortcut != 0) | |||
item = new JMenuItem(text, shortcut); | |||
else | |||
item = new JMenuItem(text); | |||
} | |||
item.setActionCommand(data.toString()); | |||
item.addActionListener(cb); | |||
item.setEnabled(!flags.contains(MENU.INACTIVE)); | |||
menu.add(item); | |||
if (flags.contains(MENU.DIVIDER)) | |||
menu.addSeparator(); | |||
} | |||
void popupContextMenu() | |||
{ | |||
// initialize context menu before display | |||
initContextMenu(); | |||
contextMenu.setCursor(java.awt.Cursor.getDefaultCursor()); | |||
contextMenu.show(this, lastPointerPos.x, lastPointerPos.y); | |||
} | |||
public void actionPerformed(ActionEvent ev) | |||
{ | |||
switch(ID.valueOf(ev.getActionCommand())) { | |||
case EXIT: | |||
cc.close(); | |||
break; | |||
case FULLSCREEN: | |||
if (window().fullscreen_active()) | |||
window().fullscreen_off(); | |||
else | |||
window().fullscreen_on(); | |||
break; | |||
case MINIMIZE: | |||
if (window().fullscreen_active()) | |||
window().fullscreen_off(); | |||
window().setExtendedState(JFrame.ICONIFIED); | |||
break; | |||
case RESIZE: | |||
if (window().fullscreen_active()) | |||
break; | |||
int dx = window().getInsets().left + window().getInsets().right; | |||
int dy = window().getInsets().top + window().getInsets().bottom; | |||
window().setSize(getWidth()+dx, getHeight()+dy); | |||
break; | |||
case CLIPBOARD: | |||
ClipboardDialog.showDialog(window()); | |||
break; | |||
case CTRL: | |||
if (((JMenuItem)ev.getSource()).isSelected()) | |||
handleKeyPress(0x1d, XK_Control_L); | |||
else | |||
handleKeyRelease(0x1d); | |||
menuCtrlKey = !menuCtrlKey; | |||
break; | |||
case ALT: | |||
if (((JMenuItem)ev.getSource()).isSelected()) | |||
handleKeyPress(0x38, XK_Alt_L); | |||
else | |||
handleKeyRelease(0x38); | |||
menuAltKey = !menuAltKey; | |||
break; | |||
case MENUKEY: | |||
menuRecursion = true; | |||
handleKeyPress(menuKeyCode, menuKeySym); | |||
menuRecursion = false; | |||
handleKeyRelease(menuKeyCode); | |||
break; | |||
case CTRLALTDEL: | |||
handleKeyPress(0x1d, XK_Control_L); | |||
handleKeyPress(0x38, XK_Alt_L); | |||
handleKeyPress(0xd3, XK_Delete); | |||
handleKeyRelease(0xd3); | |||
handleKeyRelease(0x38); | |||
handleKeyRelease(0x1d); | |||
break; | |||
case REFRESH: | |||
cc.refreshFramebuffer(); | |||
break; | |||
case NEWVIEWER: | |||
VncViewer.newViewer(); | |||
break; | |||
case OPTIONS: | |||
OptionsDialog.showDialog(cc.desktop); | |||
break; | |||
case INFO: | |||
Window fullScreenWindow = | |||
DesktopWindow.getFullScreenWindow(); | |||
if (fullScreenWindow != null) | |||
DesktopWindow.setFullScreenWindow(null); | |||
JOptionPane op = new JOptionPane(cc.connectionInfo(), | |||
JOptionPane.PLAIN_MESSAGE, | |||
JOptionPane.DEFAULT_OPTION); | |||
JDialog dlg = op.createDialog(window(), "VNC connection info"); | |||
dlg.setIconImage(VncViewer.frameIcon); | |||
dlg.setAlwaysOnTop(true); | |||
dlg.setVisible(true); | |||
if (fullScreenWindow != null) | |||
DesktopWindow.setFullScreenWindow(fullScreenWindow); | |||
break; | |||
case ABOUT: | |||
VncViewer.about_vncviewer(cc.desktop); | |||
break; | |||
case DISMISS: | |||
break; | |||
} | |||
} | |||
private void setMenuKey() | |||
{ | |||
menuKeyJava = MenuKey.getMenuKeyJavaCode(); | |||
menuKeyCode = MenuKey.getMenuKeyCode(); | |||
menuKeySym = MenuKey.getMenuKeySym(); | |||
} | |||
public void handleOptions() | |||
{ | |||
setMenuKey(); | |||
/* | |||
setScaledSize(cc.cp.width, cc.cp.height); | |||
if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) { | |||
@@ -414,18 +772,48 @@ class Viewport extends JPanel implements MouseListener, | |||
*/ | |||
} | |||
public void releaseDownKeys() { | |||
while (!downKeySym.isEmpty()) | |||
handleKeyRelease(downKeySym.keySet().iterator().next()); | |||
} | |||
private DesktopWindow window() { | |||
return (DesktopWindow)getTopLevelAncestor(); | |||
} | |||
private int x() { return getX(); } | |||
private int y() { return getY(); } | |||
private int w() { return getWidth(); } | |||
private int h() { return getHeight(); } | |||
// access to cc by different threads is specified in CConn | |||
private CConn cc; | |||
// access to the following must be synchronized: | |||
public PlatformPixelBuffer frameBuffer; | |||
private PlatformPixelBuffer frameBuffer; | |||
Point lastPointerPos = new Point(0, 0); | |||
int lastButtonMask = 0; | |||
private class DownMap extends HashMap<Integer, Integer> { | |||
public DownMap(int capacity) { | |||
super(capacity); | |||
} | |||
} | |||
DownMap downKeySym = new DownMap(256); | |||
int menuKeySym; | |||
int menuKeyCode, menuKeyJava; | |||
JPopupMenu contextMenu; | |||
boolean menuRecursion = false; | |||
boolean menuCtrlKey = false; | |||
boolean menuAltKey = false; | |||
static Toolkit tk = Toolkit.getDefaultToolkit(); | |||
public int scaledWidth = 0, scaledHeight = 0; | |||
float scaleRatioX, scaleRatioY; | |||
BufferedImage cursor; | |||
static BufferedImage cursor; | |||
Point cursorHotspot = new Point(); | |||
} |
@@ -394,7 +394,7 @@ public class VncViewer extends javax.swing.JApplet | |||
} | |||
public void windowDeactivated(WindowEvent e) { | |||
if (cc != null) | |||
cc.releaseDownKeys(); | |||
cc.desktop.viewport.releaseDownKeys(); | |||
} | |||
}); | |||
} | |||
@@ -412,7 +412,7 @@ public class VncViewer extends javax.swing.JApplet | |||
} | |||
} | |||
public static void showAbout(Container parent) { | |||
public static void about_vncviewer(Container parent) { | |||
String pkgDate = ""; | |||
String pkgTime = ""; | |||
try { |