See issue #505tags/v1.8.90
/* 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 | * This is free software; you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
* along with this software; if not, write to the Free Software | * along with this software; if not, write to the Free Software | ||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | ||||
* USA. | * 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; | package com.tigervnc.rfb; | ||||
public class UnicodeToKeysym { | |||||
public class Keysym2ucs { | |||||
private static class codepair { | private static class codepair { | ||||
public codepair(int keysym_, int ucs_) { | public codepair(int keysym_, int ucs_) { | ||||
int ucs; | int ucs; | ||||
} | } | ||||
private static class combiningpair { | |||||
public combiningpair(int spacing_, int combining_) { | |||||
spacing = spacing_; | |||||
combining = combining_; | |||||
} | |||||
int spacing; | |||||
int combining; | |||||
} | |||||
public static codepair[] keysymtab = { | public static codepair[] keysymtab = { | ||||
new codepair(0x01a1, 0x0104), /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ | new codepair(0x01a1, 0x0104), /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ | ||||
new codepair(0x01a2, 0x02d8), /* breve ˘ BREVE */ | new codepair(0x01a2, 0x02d8), /* breve ˘ BREVE */ | ||||
new codepair(0xfe60, 0x0323), /* COMBINING DOT BELOW */ | 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) { | public static int ucs2keysym(int ucs) { | ||||
int cur = 0; | int cur = 0; | ||||
int max = keysymtab.length - 1; | int max = keysymtab.length - 1; | ||||
return ucs | 0x01000000; | return ucs | 0x01000000; | ||||
/* no matching Unicode value found */ | /* 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; | |||||
} | } |
/* 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"); | |||||
} |
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. | ||||
* Copyright 2009-2013 Pierre Ossman <ossman@cendio.se> for Cendio AB | * Copyright 2009-2013 Pierre Ossman <ossman@cendio.se> for Cendio AB | ||||
* Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved. | * 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 | * This is free software; you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
static final PixelFormat mediumColorPF = | static final PixelFormat mediumColorPF = | ||||
new PixelFormat(8, 8, false, true, 7, 7, 3, 5, 2, 0); | 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 | // The following methods are all called from the RFB thread | ||||
setShared(shared.getValue()); | setShared(shared.getValue()); | ||||
sock = socket; | sock = socket; | ||||
downKeySym = new HashMap<Integer, Integer>(); | |||||
upg = this; | upg = this; | ||||
} | } | ||||
} | } | ||||
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 | // writeClientCutText() is called from the clipboard dialog | ||||
public void writeClientCutText(String str, int len) { | public void writeClientCutText(String str, int len) { | ||||
if (state() != RFBSTATE_NORMAL || shuttingDown) | if (state() != RFBSTATE_NORMAL || shuttingDown) | ||||
writer().writeClientCutText(str, len); | 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 | // this is a special ActionListener passed in by the | ||||
// Java Plug-in software to control applet's close behavior | // Java Plug-in software to control applet's close behavior | ||||
public void setCloseListener(ActionListener cl) { | public void setCloseListener(ActionListener cl) { | ||||
// from when constructed). | // from when constructed). | ||||
// the following are only ever accessed by the GUI thread: | // the following are only ever accessed by the GUI thread: | ||||
int buttonMask; | |||||
private String serverHost; | private String serverHost; | ||||
private int serverPort; | private int serverPort; | ||||
private Socket sock; | private Socket sock; | ||||
private boolean supportsSyncFence; | private boolean supportsSyncFence; | ||||
private HashMap<Integer, Integer> downKeySym; | |||||
public ActionListener closeListener = null; | public ActionListener closeListener = null; | ||||
static LogWriter vlog = new LogWriter("CConn"); | static LogWriter vlog = new LogWriter("CConn"); |
scroll.getViewport().getView().requestFocusInWindow(); | scroll.getViewport().getView().requestFocusInWindow(); | ||||
} | } | ||||
public void windowLostFocus(WindowEvent e) { | public void windowLostFocus(WindowEvent e) { | ||||
cc.releaseDownKeys(); | |||||
viewport.releaseDownKeys(); | |||||
} | } | ||||
}); | }); | ||||
/* 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"); | |||||
} |
/* 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"); | |||||
} |
/* Copyright 2011 Martin Koegler <mkoegler@auto.tuwien.ac.at> | /* Copyright 2011 Martin Koegler <mkoegler@auto.tuwien.ac.at> | ||||
* Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB | * 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 | * This is free software; you can redistribute it and/or modify | ||||
* it under the terms of the GNU General Public License as published by | * it under the terms of the GNU General Public License as published by | ||||
import com.tigervnc.rfb.*; | import com.tigervnc.rfb.*; | ||||
import static java.awt.event.KeyEvent.*; | |||||
import static com.tigervnc.rfb.Keysymdef.*; | |||||
public class MenuKey | public class MenuKey | ||||
{ | { | ||||
static class MenuKeySymbol { | static class MenuKeySymbol { | ||||
public MenuKeySymbol(String name_, int keycode_, int keysym_) { | |||||
public MenuKeySymbol(String name_, int javacode_, | |||||
int keycode_, int keysym_) { | |||||
name = name_; | name = name_; | ||||
javacode = javacode_; | |||||
keycode = keycode_; | keycode = keycode_; | ||||
keysym = keysym_; | keysym = keysym_; | ||||
} | } | ||||
String name; | String name; | ||||
int javacode; | |||||
int keycode; | int keycode; | ||||
int keysym; | int keysym; | ||||
} | } | ||||
private static final MenuKeySymbol[] menuSymbols = { | 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() { | static int getMenuKeySymbolCount() { | ||||
return s; | 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() { | static int getMenuKeyCode() { | ||||
int menuKeyCode = KeyEvent.VK_F8; | |||||
int menuKeyCode = VK_F8; | |||||
@SuppressWarnings({"static"}) | @SuppressWarnings({"static"}) | ||||
String menuKeyStr = | String menuKeyStr = | ||||
} | } | ||||
static int getMenuKeySym() { | static int getMenuKeySym() { | ||||
int menuKeySym = Keysyms.F8; | |||||
int menuKeySym = XK_F8; | |||||
@SuppressWarnings({"static"}) | @SuppressWarnings({"static"}) | ||||
String menuKeyStr = | String menuKeyStr = |
} | } | ||||
private void handleAbout() { | private void handleAbout() { | ||||
VncViewer.showAbout(this); | |||||
VncViewer.about_vncviewer(this); | |||||
} | } | ||||
private void handleCancel() { | private void handleCancel() { |
package com.tigervnc.vncviewer; | package com.tigervnc.vncviewer; | ||||
import java.awt.*; | import java.awt.*; | ||||
import java.awt.Color; | |||||
import java.awt.color.ColorSpace; | |||||
import java.awt.event.*; | import java.awt.event.*; | ||||
import java.awt.geom.AffineTransform; | import java.awt.geom.AffineTransform; | ||||
import java.awt.image.*; | 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.nio.*; | ||||
import java.util.*; | |||||
import javax.swing.*; | import javax.swing.*; | ||||
import javax.imageio.*; | import javax.imageio.*; | ||||
import com.tigervnc.rfb.*; | import com.tigervnc.rfb.*; | ||||
import com.tigervnc.rfb.Cursor; | import com.tigervnc.rfb.Cursor; | ||||
import com.tigervnc.rfb.Exception; | |||||
import com.tigervnc.rfb.Point; | import com.tigervnc.rfb.Point; | ||||
import static java.awt.event.KeyEvent.*; | |||||
import static com.tigervnc.vncviewer.Parameters.*; | 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"); | 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_) | public Viewport(int w, int h, PixelFormat serverPF, CConn cc_) | ||||
{ | { | ||||
cc = cc_; | cc = cc_; | ||||
setScaledSize(cc.cp.width, cc.cp.height); | |||||
setScaledSize(w, h); | |||||
frameBuffer = createFramebuffer(serverPF, w, h); | frameBuffer = createFramebuffer(serverPF, w, h); | ||||
assert(frameBuffer != null); | assert(frameBuffer != null); | ||||
setBackground(Color.BLACK); | setBackground(Color.BLACK); | ||||
cc.setFramebuffer(frameBuffer); | cc.setFramebuffer(frameBuffer); | ||||
contextMenu = new JPopupMenu(); | |||||
OptionsDialog.addCallback("handleOptions", this); | 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() { | addFocusListener(new FocusAdapter() { | ||||
public void focusGained(FocusEvent e) { | public void focusGained(FocusEvent e) { | ||||
ClipboardDialog.clientCutText(); | ClipboardDialog.clientCutText(); | ||||
} | } | ||||
public void focusLost(FocusEvent e) { | public void focusLost(FocusEvent e) { | ||||
cc.releaseDownKeys(); | |||||
releaseDownKeys(); | |||||
} | } | ||||
}); | }); | ||||
setFocusTraversalKeysEnabled(false); | setFocusTraversalKeysEnabled(false); | ||||
setFocusable(true); | setFocusable(true); | ||||
setMenuKey(); | |||||
// Send a fake pointer event so that the server will stop rendering | // 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 | // 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 | // 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. | // 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) | // Most efficient format (from Viewport's point of view) | ||||
{ | { | ||||
int i; | int i; | ||||
if (cursor != null) | |||||
cursor.flush(); | |||||
for (i = 0; i < width*height; i++) | for (i = 0; i < width*height; i++) | ||||
if (data[i*4 + 3] != 0) break; | if (data[i*4 + 3] != 0) break; | ||||
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); | new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE); | ||||
cursor.setRGB(0, 0, width, height, buffer.array(), 0, width); | cursor.setRGB(0, 0, width, height, buffer.array(), 0, width); | ||||
cursorHotspot = hotspot; | cursorHotspot = hotspot; | ||||
} | } | ||||
} | } | ||||
int x = (int)Math.floor((float)cursorHotspot.x * scaleRatioX); | int x = (int)Math.floor((float)cursorHotspot.x * scaleRatioX); | ||||
int y = (int)Math.floor((float)cursorHotspot.y * scaleRatioY); | int y = (int)Math.floor((float)cursorHotspot.y * scaleRatioY); | ||||
java.awt.Cursor softCursor; | |||||
Dimension cs = tk.getBestCursorSize(cw, ch); | Dimension cs = tk.getBestCursorSize(cw, ch); | ||||
if (cs.width != cw && cs.height != ch) { | if (cs.width != cw && cs.height != ch) { | ||||
cw = Math.min(cw, cs.width); | cw = Math.min(cw, cs.width); | ||||
ch = Math.min(ch, cs.height); | ch = Math.min(ch, cs.height); | ||||
x = (int)Math.min(x, Math.max(cs.width - 1, 0)); | x = (int)Math.min(x, Math.max(cs.width - 1, 0)); | ||||
y = (int)Math.min(y, Math.max(cs.height - 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(); | 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) { | public void resize(int x, int y, int w, int h) { | ||||
setScaledSize(w, h); | 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) | private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h) | ||||
{ | { | ||||
PlatformPixelBuffer fb; | PlatformPixelBuffer fb; | ||||
g2.dispose(); | 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) | public void setScaledSize(int width, int height) | ||||
{ | { | ||||
assert(width != 0 && height != 0); | assert(width != 0 && height != 0); | ||||
setSize(new Dimension(scaledWidth, scaledHeight)); | 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() | public void handleOptions() | ||||
{ | { | ||||
setMenuKey(); | |||||
/* | /* | ||||
setScaledSize(cc.cp.width, cc.cp.height); | setScaledSize(cc.cp.width, cc.cp.height); | ||||
if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) { | if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) { | ||||
*/ | */ | ||||
} | } | ||||
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 | // access to cc by different threads is specified in CConn | ||||
private CConn cc; | private CConn cc; | ||||
// access to the following must be synchronized: | // 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(); | static Toolkit tk = Toolkit.getDefaultToolkit(); | ||||
public int scaledWidth = 0, scaledHeight = 0; | public int scaledWidth = 0, scaledHeight = 0; | ||||
float scaleRatioX, scaleRatioY; | float scaleRatioX, scaleRatioY; | ||||
BufferedImage cursor; | |||||
static BufferedImage cursor; | |||||
Point cursorHotspot = new Point(); | Point cursorHotspot = new Point(); | ||||
} | } |
} | } | ||||
public void windowDeactivated(WindowEvent e) { | public void windowDeactivated(WindowEvent e) { | ||||
if (cc != null) | if (cc != null) | ||||
cc.releaseDownKeys(); | |||||
cc.desktop.viewport.releaseDownKeys(); | |||||
} | } | ||||
}); | }); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
public static void showAbout(Container parent) { | |||||
public static void about_vncviewer(Container parent) { | |||||
String pkgDate = ""; | String pkgDate = ""; | ||||
String pkgTime = ""; | String pkgTime = ""; | ||||
try { | try { |