You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Viewport.java 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved.
  3. * Copyright (C) 2009 Paul Donohue. All Rights Reserved.
  4. * Copyright (C) 2010, 2012-2013 D. R. Commander. All Rights Reserved.
  5. * Copyright (C) 2011-2019 Brian P. Hinz
  6. *
  7. * This is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This software is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this software; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  20. * USA.
  21. */
  22. //
  23. // DesktopWindow is an AWT Canvas representing a VNC desktop.
  24. //
  25. // Methods on DesktopWindow are called from both the GUI thread and the thread
  26. // which processes incoming RFB messages ("the RFB thread"). This means we
  27. // need to be careful with synchronization here.
  28. //
  29. package com.tigervnc.vncviewer;
  30. import java.awt.*;
  31. import java.awt.event.*;
  32. import java.awt.geom.AffineTransform;
  33. import java.awt.image.*;
  34. import java.nio.*;
  35. import java.util.*;
  36. import javax.swing.*;
  37. import javax.imageio.*;
  38. import java.io.*;
  39. import com.tigervnc.rfb.*;
  40. import com.tigervnc.rfb.Cursor;
  41. import com.tigervnc.rfb.Exception;
  42. import com.tigervnc.rfb.Point;
  43. import static java.awt.event.KeyEvent.*;
  44. import static com.tigervnc.vncviewer.Parameters.*;
  45. import static com.tigervnc.rfb.Keysymdef.*;
  46. class Viewport extends JPanel implements ActionListener {
  47. static LogWriter vlog = new LogWriter("Viewport");
  48. enum ID { EXIT, FULLSCREEN, MINIMIZE, RESIZE, NEWVIEWER,
  49. CTRL, ALT, MENUKEY, CTRLALTDEL, CLIPBOARD,
  50. REFRESH, OPTIONS, INFO, ABOUT, DISMISS }
  51. enum MENU { INACTIVE, TOGGLE, VALUE, RADIO,
  52. INVISIBLE, SUBMENU_POINTER, SUBMENU, DIVIDER }
  53. public Viewport(int w, int h, PixelFormat serverPF, CConn cc_)
  54. {
  55. cc = cc_;
  56. setScaledSize(w, h);
  57. frameBuffer = createFramebuffer(serverPF, w, h);
  58. assert(frameBuffer != null);
  59. setBackground(Color.BLACK);
  60. cc.setFramebuffer(frameBuffer);
  61. contextMenu = new JPopupMenu();
  62. OptionsDialog.addCallback("handleOptions", this);
  63. addMouseListener(new MouseAdapter() {
  64. public void mouseClicked(MouseEvent e) { }
  65. public void mouseEntered(MouseEvent e) { handle(e); }
  66. public void mouseExited(MouseEvent e) { handle(e); }
  67. public void mouseReleased(MouseEvent e) { handle(e); }
  68. public void mousePressed(MouseEvent e) { handle(e); }
  69. });
  70. addMouseWheelListener(new MouseAdapter() {
  71. public void mouseWheelMoved(MouseWheelEvent e) { handle(e); }
  72. });
  73. addMouseMotionListener(new MouseMotionAdapter() {
  74. public void mouseDragged(MouseEvent e) { handle(e); }
  75. public void mouseMoved(MouseEvent e) { handle(e); }
  76. });
  77. addKeyListener(new KeyAdapter() {
  78. public void keyTyped(KeyEvent e) { }
  79. public void keyPressed(KeyEvent e) { handleSystemEvent(e); }
  80. public void keyReleased(KeyEvent e) { handleSystemEvent(e); }
  81. });
  82. addFocusListener(new FocusAdapter() {
  83. public void focusGained(FocusEvent e) {
  84. ClipboardDialog.clientCutText();
  85. }
  86. public void focusLost(FocusEvent e) {
  87. releaseDownKeys();
  88. }
  89. });
  90. // Override default key bindings from L&F
  91. getActionMap().put("null", new AbstractAction() {
  92. public void actionPerformed(ActionEvent e) { }
  93. });
  94. ArrayList<KeyStroke> keys = new ArrayList<KeyStroke>();
  95. keys.add(KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0, true));
  96. keys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true));
  97. for (int i=0; i<keys.size(); i++)
  98. getInputMap(JComponent.WHEN_FOCUSED).put(keys.get(i), "null");
  99. setFocusTraversalKeysEnabled(false);
  100. setFocusable(true);
  101. setMenuKey();
  102. // Send a fake pointer event so that the server will stop rendering
  103. // a server-side cursor. Ideally we'd like to send the actual pointer
  104. // position, but we can't really tell when the window manager is done
  105. // placing us so we don't have a good time for that.
  106. handlePointerEvent(new Point(w/2, h/2), 0);
  107. }
  108. // Most efficient format (from Viewport's point of view)
  109. public PixelFormat getPreferredPF()
  110. {
  111. return frameBuffer.getPF();
  112. }
  113. // Copy the areas of the framebuffer that have been changed (damaged)
  114. // to the displayed window.
  115. public void updateWindow() {
  116. Rect r = frameBuffer.getDamage();
  117. if (!r.is_empty()) {
  118. if (cc.server.width() != scaledWidth ||
  119. cc.server.height() != scaledHeight) {
  120. AffineTransform t = new AffineTransform();
  121. t.scale((double)scaleRatioX, (double)scaleRatioY);
  122. Rectangle s = new Rectangle(r.tl.x, r.tl.y, r.width(), r.height());
  123. s = t.createTransformedShape(s).getBounds();
  124. paintImmediately(s.x, s.y, s.width, s.height);
  125. } else {
  126. paintImmediately(r.tl.x, r.tl.y, r.width(), r.height());
  127. }
  128. }
  129. }
  130. static final int[] dotcursor_xpm = {
  131. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  132. 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
  133. 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
  134. 0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
  135. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  136. };
  137. static final int[] largecursor_xpm = {
  138. 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  139. 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  140. 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  141. 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  142. 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  143. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  144. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  145. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  146. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  147. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  148. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  149. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  150. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  151. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  152. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
  153. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
  154. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000,
  155. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000,
  156. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  157. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  158. 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  159. 0xffffffff, 0xffffffff, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  160. 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  161. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  162. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  163. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xff000000, 0xff000000, 0xff000000, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000,
  164. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  165. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  166. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  167. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  168. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
  169. 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
  170. };
  171. public void setCursor(int width, int height, Point hotspot,
  172. byte[] data)
  173. {
  174. int i;
  175. if (cursor != null)
  176. cursor.flush();
  177. for (i = 0; i < width*height; i++)
  178. if (data[i*4 + 3] != 0) break;
  179. if ((i == width*height) && dotWhenNoCursor.getValue()) {
  180. vlog.debug("cursor is empty - using dot");
  181. if (largeCursorInsteadOfDot.getValue()) {
  182. cursor = new BufferedImage(18, 32, BufferedImage.TYPE_INT_ARGB_PRE);
  183. cursor.setRGB(0, 0, 18, 32, largecursor_xpm, 0, 18);
  184. cursorHotspot.x = cursorHotspot.y = 3;
  185. } else {
  186. cursor = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB_PRE);
  187. cursor.setRGB(0, 0, 5, 5, dotcursor_xpm, 0, 5);
  188. cursorHotspot.x = cursorHotspot.y = 3;
  189. }
  190. } else {
  191. if ((width == 0) || (height == 0)) {
  192. cursor = new BufferedImage(tk.getBestCursorSize(0, 0).width,
  193. tk.getBestCursorSize(0, 0).height,
  194. BufferedImage.TYPE_INT_ARGB_PRE);
  195. cursorHotspot.x = cursorHotspot.y = 0;
  196. } else {
  197. IntBuffer buffer = IntBuffer.allocate(width*height);
  198. buffer.put(ByteBuffer.wrap(data).asIntBuffer());
  199. cursor =
  200. new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
  201. cursor.setRGB(0, 0, width, height, buffer.array(), 0, width);
  202. cursorHotspot = hotspot;
  203. }
  204. }
  205. int cw = (int) Math.floor((float) cursor.getWidth() * scaleRatioX);
  206. int ch = (int) Math.floor((float) cursor.getHeight() * scaleRatioY);
  207. int x = cursorHotspot.x;
  208. int y = cursorHotspot.y;
  209. Dimension cs = tk.getBestCursorSize(cw, ch);
  210. if (cs.width != cursor.getWidth() || cs.height != cursor.getHeight()) {
  211. cw = VncViewer.os.startsWith("windows") ? Math.min(cw, cs.width) : cs.width;
  212. ch = VncViewer.os.startsWith("windows") ? Math.min(ch, cs.height) : cs.height;
  213. BufferedImage tmp = new BufferedImage(cs.width, cs.height, BufferedImage.TYPE_INT_ARGB_PRE);
  214. Graphics2D g2 = tmp.createGraphics();
  215. g2.drawImage(cursor, 0, 0, cw, ch, 0, 0, width, height, null);
  216. g2.dispose();
  217. x = (int) Math.min(Math.floor((float) x * (float) cw / (float) width), cw - 1);
  218. y = (int) Math.min(Math.floor((float) y * (float) ch / (float) height), ch - 1);
  219. cursor = tmp;
  220. }
  221. setCursor(cursor, x, y);
  222. }
  223. private void setCursor(Image img, int x, int y)
  224. {
  225. java.awt.Point hotspot;
  226. java.awt.Cursor softCursor;
  227. String name = "rfb cursor";
  228. hotspot = new java.awt.Point(x, y);
  229. softCursor = tk.createCustomCursor(img, hotspot, name);
  230. setCursor(softCursor);
  231. }
  232. public void resize(int x, int y, int w, int h) {
  233. if ((w != frameBuffer.width()) || (h != frameBuffer.height())) {
  234. vlog.debug("Resizing framebuffer from "+frameBuffer.width()+"x"+
  235. frameBuffer.height()+" to "+w+"x"+h);
  236. frameBuffer = createFramebuffer(frameBuffer.getPF(), w, h);
  237. assert(frameBuffer != null);
  238. cc.setFramebuffer(frameBuffer);
  239. }
  240. setScaledSize(w, h);
  241. }
  242. public int handle(MouseEvent e)
  243. {
  244. int buttonMask, wheelMask;
  245. switch (e.getID()) {
  246. case MouseEvent.MOUSE_ENTERED:
  247. if (cursor != null)
  248. setCursor(cursor, cursorHotspot.x, cursorHotspot.y);
  249. return 1;
  250. case MouseEvent.MOUSE_EXITED:
  251. setCursor(java.awt.Cursor.getDefaultCursor());
  252. return 1;
  253. case MouseEvent.MOUSE_PRESSED:
  254. case MouseEvent.MOUSE_RELEASED:
  255. case MouseEvent.MOUSE_DRAGGED:
  256. case MouseEvent.MOUSE_MOVED:
  257. case MouseEvent.MOUSE_WHEEL:
  258. buttonMask = 0;
  259. if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
  260. buttonMask |= 1;
  261. if ((e.getModifiersEx() & MouseEvent.BUTTON2_DOWN_MASK) != 0)
  262. buttonMask |= 2;
  263. if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) != 0)
  264. buttonMask |= 4;
  265. if (e.getID() == MouseEvent.MOUSE_WHEEL) {
  266. wheelMask = 0;
  267. int clicks = ((MouseWheelEvent)e).getWheelRotation();
  268. if (clicks < 0)
  269. wheelMask |= e.isShiftDown() ? 32 : 8;
  270. else
  271. wheelMask |= e.isShiftDown() ? 64 : 16;
  272. Point pt = new Point(e.getX(), e.getY());
  273. for (int i = 0; i < Math.abs(clicks); i++) {
  274. handlePointerEvent(pt, buttonMask|wheelMask);
  275. handlePointerEvent(pt, buttonMask);
  276. }
  277. return 1;
  278. }
  279. handlePointerEvent(new Point(e.getX(), e.getY()), buttonMask);
  280. return 1;
  281. }
  282. return -1;
  283. }
  284. private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h)
  285. {
  286. PlatformPixelBuffer fb;
  287. fb = new JavaPixelBuffer(w, h);
  288. return fb;
  289. }
  290. //
  291. // Callback methods to determine geometry of our Component.
  292. //
  293. public Dimension getPreferredSize() {
  294. return new Dimension(scaledWidth, scaledHeight);
  295. }
  296. public Dimension getMinimumSize() {
  297. return new Dimension(scaledWidth, scaledHeight);
  298. }
  299. public Dimension getMaximumSize() {
  300. return new Dimension(scaledWidth, scaledHeight);
  301. }
  302. public void paintComponent(Graphics g) {
  303. Graphics2D g2 = (Graphics2D)g;
  304. synchronized(frameBuffer.getImage()) {
  305. if (cc.server.width() != scaledWidth ||
  306. cc.server.height() != scaledHeight) {
  307. g2.setRenderingHint(RenderingHints.KEY_RENDERING,
  308. RenderingHints.VALUE_RENDER_QUALITY);
  309. g2.drawImage(frameBuffer.getImage(), 0, 0,
  310. scaledWidth, scaledHeight, null);
  311. } else {
  312. g2.drawImage(frameBuffer.getImage(), 0, 0, null);
  313. }
  314. }
  315. g2.dispose();
  316. }
  317. public void setScaledSize(int width, int height)
  318. {
  319. assert(width != 0 && height != 0);
  320. String scaleString = scalingFactor.getValue();
  321. if (remoteResize.getValue()) {
  322. scaledWidth = width;
  323. scaledHeight = height;
  324. scaleRatioX = 1.00f;
  325. scaleRatioY = 1.00f;
  326. } else {
  327. if (scaleString.matches("^[0-9]+$")) {
  328. int scalingFactor = Integer.parseInt(scaleString);
  329. scaledWidth =
  330. (int)Math.floor((float)width * (float)scalingFactor/100.0);
  331. scaledHeight =
  332. (int)Math.floor((float)height * (float)scalingFactor/100.0);
  333. } else if (scaleString.equalsIgnoreCase("Auto")) {
  334. scaledWidth = width;
  335. scaledHeight = height;
  336. } else {
  337. float widthRatio = (float)width / (float)cc.server.width();
  338. float heightRatio = (float)height / (float)cc.server.height();
  339. float ratio = Math.min(widthRatio, heightRatio);
  340. scaledWidth = (int)Math.floor(cc.server.width() * ratio);
  341. scaledHeight = (int)Math.floor(cc.server.height() * ratio);
  342. }
  343. scaleRatioX = (float)scaledWidth / (float)cc.server.width();
  344. scaleRatioY = (float)scaledHeight / (float)cc.server.height();
  345. }
  346. if (scaledWidth != getWidth() || scaledHeight != getHeight())
  347. setSize(new Dimension(scaledWidth, scaledHeight));
  348. }
  349. private void handlePointerEvent(Point pos, int buttonMask)
  350. {
  351. if (!viewOnly.getValue()) {
  352. if (buttonMask != lastButtonMask || !pos.equals(lastPointerPos)) {
  353. try {
  354. if (cc.server.width() != scaledWidth ||
  355. cc.server.height() != scaledHeight) {
  356. int sx = (scaleRatioX == 1.00) ?
  357. pos.x : (int)Math.floor(pos.x / scaleRatioX);
  358. int sy = (scaleRatioY == 1.00) ?
  359. pos.y : (int)Math.floor(pos.y / scaleRatioY);
  360. pos = pos.translate(new Point(sx - pos.x, sy - pos.y));
  361. }
  362. cc.writer().writePointerEvent(pos, buttonMask);
  363. } catch (Exception e) {
  364. vlog.error("%s", e.getMessage());
  365. cc.close();
  366. }
  367. }
  368. lastPointerPos = pos;
  369. lastButtonMask = buttonMask;
  370. }
  371. }
  372. public void handleKeyPress(long keyCode, int keySym)
  373. {
  374. // Prevent recursion if the menu wants to send it's own
  375. // activation key.
  376. if ((menuKeySym != 0) && keySym == menuKeySym && !menuRecursion) {
  377. popupContextMenu();
  378. return;
  379. }
  380. if (viewOnly.getValue())
  381. return;
  382. if (keyCode == 0) {
  383. vlog.error("No key code specified on key press");
  384. return;
  385. }
  386. if (VncViewer.os.startsWith("mac os x")) {
  387. // Alt on OS X behaves more like AltGr on other systems, and to get
  388. // sane behaviour we should translate things in that manner for the
  389. // remote VNC server. However that means we lose the ability to use
  390. // Alt as a shortcut modifier. Do what RealVNC does and hijack the
  391. // left command key as an Alt replacement.
  392. switch (keySym) {
  393. case XK_Meta_L:
  394. keySym = XK_Alt_L;
  395. break;
  396. case XK_Meta_R:
  397. keySym = XK_Super_L;
  398. break;
  399. case XK_Alt_L:
  400. keySym = XK_Mode_switch;
  401. break;
  402. case XK_Alt_R:
  403. keySym = XK_ISO_Level3_Shift;
  404. break;
  405. }
  406. }
  407. if (VncViewer.os.startsWith("windows")) {
  408. // Ugly hack alert!
  409. //
  410. // Windows doesn't have a proper AltGr, but handles it using fake
  411. // Ctrl+Alt. Unfortunately X11 doesn't generally like the combination
  412. // Ctrl+Alt+AltGr, which we usually end up with when Xvnc tries to
  413. // get everything in the correct state. Cheat and temporarily release
  414. // Ctrl and Alt when we send some other symbol.
  415. if (downKeySym.containsValue(XK_Control_L) &&
  416. downKeySym.containsValue(XK_Alt_R)) {
  417. vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)");
  418. try {
  419. cc.writer().writeKeyEvent(XK_Control_L, false);
  420. cc.writer().writeKeyEvent(XK_Alt_R, false);
  421. } catch (Exception e) {
  422. vlog.error("%s", e.getMessage());
  423. cc.close();
  424. }
  425. }
  426. }
  427. // Because of the way keyboards work, we cannot expect to have the same
  428. // symbol on release as when pressed. This breaks the VNC protocol however,
  429. // so we need to keep track of what keysym a key _code_ generated on press
  430. // and send the same on release.
  431. downKeySym.put(keyCode, keySym);
  432. vlog.debug("Key pressed: 0x%016x => 0x%04x", keyCode, keySym);
  433. try {
  434. // Fake keycode?
  435. if (keyCode > 0xffffffffL)
  436. cc.writer().writeKeyEvent(keySym, true);
  437. else
  438. cc.writer().writeKeyEvent(keySym, true);
  439. } catch (Exception e) {
  440. vlog.error("%s", e.getMessage());
  441. cc.close();
  442. }
  443. if (VncViewer.os.startsWith("windows")) {
  444. // Ugly hack continued...
  445. if (downKeySym.containsValue(XK_Control_L) &&
  446. downKeySym.containsValue(XK_Alt_R)) {
  447. vlog.debug("Restoring AltGr state");
  448. try {
  449. cc.writer().writeKeyEvent(XK_Control_L, true);
  450. cc.writer().writeKeyEvent(XK_Alt_R, true);
  451. } catch (Exception e) {
  452. vlog.error("%s", e.getMessage());
  453. cc.close();
  454. }
  455. }
  456. }
  457. }
  458. public void handleKeyRelease(long keyCode)
  459. {
  460. Integer iter;
  461. if (viewOnly.getValue())
  462. return;
  463. iter = downKeySym.get(keyCode);
  464. if (iter == null) {
  465. // These occur somewhat frequently so let's not spam them unless
  466. // logging is turned up.
  467. vlog.debug("Unexpected release of key code %d", keyCode);
  468. return;
  469. }
  470. vlog.debug("Key released: 0x%016x => 0x%04x", keyCode, iter);
  471. try {
  472. if (keyCode > 0xffffffffL)
  473. cc.writer().writeKeyEvent(iter, false);
  474. else
  475. cc.writer().writeKeyEvent(iter, false);
  476. } catch (Exception e) {
  477. vlog.error("%s", e.getMessage());
  478. cc.close();
  479. }
  480. downKeySym.remove(keyCode);
  481. }
  482. private int handleSystemEvent(AWTEvent event)
  483. {
  484. if (event instanceof KeyEvent) {
  485. KeyEvent ev = (KeyEvent)event;
  486. if (KeyMap.get_keycode_fallback_extended(ev) == 0) {
  487. // Not much we can do with this...
  488. vlog.debug("Ignoring KeyEvent with unknown Java keycode");
  489. return 0;
  490. }
  491. if (ev.getID() == KeyEvent.KEY_PRESSED) {
  492. // Generate a fake keycode just for tracking if we can't figure
  493. // out the proper one. Java virtual key codes aren't unique
  494. // between left/right versions of keys, so we can't use them as
  495. // indexes to the downKeySym map.
  496. long keyCode = KeyMap.get_keycode_fallback_extended(ev) | ((long)ev.getKeyLocation()<<32);
  497. // Pressing Ctrl wreaks havoc with the symbol lookup, so turn
  498. // that off. But AltGr shows up as Ctrl_L+Alt_R in Windows, so
  499. // construct a new KeyEvent that uses a proper AltGraph for the
  500. // symbol lookup.
  501. int keySym;
  502. if (VncViewer.os.startsWith("windows") &&
  503. downKeySym.containsValue(XK_Control_L) &&
  504. downKeySym.containsValue(XK_Alt_R)) {
  505. int mask = ev.getModifiers();
  506. mask &= ~CTRL_MASK;
  507. mask &= ~ALT_MASK;
  508. mask |= ALT_GRAPH_MASK;
  509. AWTKeyStroke ks =
  510. AWTKeyStroke.getAWTKeyStroke(KeyMap.get_keycode_fallback_extended(ev), mask);
  511. // The mask manipulations above break key combinations involving AltGr
  512. // and a key with an accented letter on some keyboard layouts (i.e. IT).
  513. // So the code should first try the modified event, but if it returns no
  514. // symbol, the original event should be used.
  515. final KeyEvent winev = new KeyEvent((JComponent)ev.getSource(), ev.getID(),
  516. ev.getWhen(), mask, KeyMap.get_keycode_fallback_extended(ev),
  517. ks.getKeyChar(), ev.getKeyLocation());
  518. keySym = KeyMap.vkey_to_keysym(winev);
  519. if (keySym == KeyMap.NoSymbol)
  520. keySym = KeyMap.vkey_to_keysym(ev);
  521. else
  522. ev = winev;
  523. } else {
  524. keySym = KeyMap.vkey_to_keysym(ev);
  525. }
  526. if (keySym == KeyMap.NoSymbol)
  527. vlog.error("No symbol for virtual key 0x%016x", keyCode);
  528. if (VncViewer.os.startsWith("linux")) {
  529. switch (keySym) {
  530. // For the first few years, there wasn't a good consensus on what the
  531. // Windows keys should be mapped to for X11. So we need to help out a
  532. // bit and map all variants to the same key...
  533. case XK_Hyper_L:
  534. keySym = XK_Super_L;
  535. break;
  536. case XK_Hyper_R:
  537. keySym = XK_Super_R;
  538. break;
  539. // There has been several variants for Shift-Tab over the years.
  540. // RFB states that we should always send a normal tab.
  541. case XK_ISO_Left_Tab:
  542. keySym = XK_Tab;
  543. break;
  544. }
  545. }
  546. handleKeyPress(keyCode, keySym);
  547. if (VncViewer.os.startsWith("mac os x")) {
  548. // We don't get any release events for CapsLock, so we have to
  549. // send the release right away.
  550. if (keySym == XK_Caps_Lock)
  551. handleKeyRelease(keyCode);
  552. }
  553. return 1;
  554. } else if (ev.getID() == KeyEvent.KEY_RELEASED) {
  555. long keyCode = KeyMap.get_keycode_fallback_extended(ev) | ((long)ev.getKeyLocation()<<32);
  556. handleKeyRelease(keyCode);
  557. return 1;
  558. }
  559. }
  560. return 0;
  561. }
  562. private void initContextMenu()
  563. {
  564. contextMenu.setLightWeightPopupEnabled(false);
  565. contextMenu.removeAll();
  566. menu_add(contextMenu, "Exit viewer", KeyEvent.VK_X,
  567. this, ID.EXIT, EnumSet.of(MENU.DIVIDER));
  568. menu_add(contextMenu, "Full screen", KeyEvent.VK_F, this, ID.FULLSCREEN,
  569. window().fullscreen_active() ?
  570. EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE));
  571. menu_add(contextMenu, "Minimize", KeyEvent.VK_Z,
  572. this, ID.MINIMIZE, EnumSet.noneOf(MENU.class));
  573. menu_add(contextMenu, "Resize window to session", KeyEvent.VK_W,
  574. this, ID.RESIZE,
  575. window().fullscreen_active() ?
  576. EnumSet.of(MENU.INACTIVE, MENU.DIVIDER) : EnumSet.of(MENU.DIVIDER));
  577. menu_add(contextMenu, "Clipboard viewer...", KeyEvent.VK_UNDEFINED,
  578. this, ID.CLIPBOARD, EnumSet.of(MENU.DIVIDER));
  579. menu_add(contextMenu, "Ctrl", KeyEvent.VK_C,
  580. this, ID.CTRL,
  581. menuCtrlKey ? EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE));
  582. menu_add(contextMenu, "Alt", KeyEvent.VK_A,
  583. this, ID.ALT,
  584. menuAltKey ? EnumSet.of(MENU.TOGGLE, MENU.VALUE) : EnumSet.of(MENU.TOGGLE));
  585. if (menuKeySym != 0) {
  586. String sendMenuKey = String.format("Send %s", menuKey.getValueStr());
  587. menu_add(contextMenu, sendMenuKey, menuKeyJava,
  588. this, ID.MENUKEY, EnumSet.noneOf(MENU.class));
  589. }
  590. menu_add(contextMenu, "Send Ctrl-Alt-Del", KeyEvent.VK_D,
  591. this, ID.CTRLALTDEL, EnumSet.of(MENU.DIVIDER));
  592. menu_add(contextMenu, "Refresh screen", KeyEvent.VK_R,
  593. this, ID.REFRESH, EnumSet.of(MENU.DIVIDER));
  594. menu_add(contextMenu, "New connection...", KeyEvent.VK_N,
  595. this, ID.NEWVIEWER, EnumSet.of(MENU.DIVIDER));
  596. menu_add(contextMenu, "Options...", KeyEvent.VK_O,
  597. this, ID.OPTIONS, EnumSet.noneOf(MENU.class));
  598. menu_add(contextMenu, "Connection info...", KeyEvent.VK_I,
  599. this, ID.INFO, EnumSet.noneOf(MENU.class));
  600. menu_add(contextMenu, "About TigerVNC viewer...", KeyEvent.VK_T,
  601. this, ID.ABOUT, EnumSet.of(MENU.DIVIDER));
  602. menu_add(contextMenu, "Dismiss menu", KeyEvent.VK_M,
  603. this, ID.DISMISS, EnumSet.noneOf(MENU.class));
  604. }
  605. static void menu_add(JPopupMenu menu, String text,
  606. int shortcut, ActionListener cb,
  607. ID data, EnumSet<MENU> flags)
  608. {
  609. JMenuItem item;
  610. if (flags.contains(MENU.TOGGLE)) {
  611. item = new JCheckBoxMenuItem(text, flags.contains(MENU.VALUE));
  612. } else {
  613. if (shortcut != 0)
  614. item = new JMenuItem(text, shortcut);
  615. else
  616. item = new JMenuItem(text);
  617. }
  618. item.setActionCommand(data.toString());
  619. item.addActionListener(cb);
  620. item.setEnabled(!flags.contains(MENU.INACTIVE));
  621. menu.add(item);
  622. if (flags.contains(MENU.DIVIDER))
  623. menu.addSeparator();
  624. }
  625. void popupContextMenu()
  626. {
  627. // initialize context menu before display
  628. initContextMenu();
  629. contextMenu.setCursor(java.awt.Cursor.getDefaultCursor());
  630. contextMenu.show(this, lastPointerPos.x, lastPointerPos.y);
  631. }
  632. public void actionPerformed(ActionEvent ev)
  633. {
  634. switch(ID.valueOf(ev.getActionCommand())) {
  635. case EXIT:
  636. cc.close();
  637. break;
  638. case FULLSCREEN:
  639. if (window().fullscreen_active())
  640. window().fullscreen_off();
  641. else
  642. window().fullscreen_on();
  643. break;
  644. case MINIMIZE:
  645. if (window().fullscreen_active())
  646. window().fullscreen_off();
  647. window().setExtendedState(JFrame.ICONIFIED);
  648. break;
  649. case RESIZE:
  650. if (window().fullscreen_active())
  651. break;
  652. int dx = window().getInsets().left + window().getInsets().right;
  653. int dy = window().getInsets().top + window().getInsets().bottom;
  654. window().setSize(getWidth()+dx, getHeight()+dy);
  655. break;
  656. case CLIPBOARD:
  657. ClipboardDialog.showDialog(window());
  658. break;
  659. case CTRL:
  660. if (((JMenuItem)ev.getSource()).isSelected())
  661. handleKeyPress(0x1d, XK_Control_L);
  662. else
  663. handleKeyRelease(0x1d);
  664. menuCtrlKey = !menuCtrlKey;
  665. break;
  666. case ALT:
  667. if (((JMenuItem)ev.getSource()).isSelected())
  668. handleKeyPress(0x38, XK_Alt_L);
  669. else
  670. handleKeyRelease(0x38);
  671. menuAltKey = !menuAltKey;
  672. break;
  673. case MENUKEY:
  674. menuRecursion = true;
  675. handleKeyPress(menuKeyCode, menuKeySym);
  676. menuRecursion = false;
  677. handleKeyRelease(menuKeyCode);
  678. break;
  679. case CTRLALTDEL:
  680. handleKeyPress(0x1d, XK_Control_L);
  681. handleKeyPress(0x38, XK_Alt_L);
  682. handleKeyPress(0xd3, XK_Delete);
  683. handleKeyRelease(0xd3);
  684. handleKeyRelease(0x38);
  685. handleKeyRelease(0x1d);
  686. break;
  687. case REFRESH:
  688. cc.refreshFramebuffer();
  689. break;
  690. case NEWVIEWER:
  691. VncViewer.newViewer();
  692. break;
  693. case OPTIONS:
  694. OptionsDialog.showDialog(cc.desktop);
  695. break;
  696. case INFO:
  697. Window fullScreenWindow =
  698. DesktopWindow.getFullScreenWindow();
  699. if (fullScreenWindow != null)
  700. DesktopWindow.setFullScreenWindow(null);
  701. JOptionPane op = new JOptionPane(cc.connectionInfo(),
  702. JOptionPane.PLAIN_MESSAGE,
  703. JOptionPane.DEFAULT_OPTION);
  704. JDialog dlg = op.createDialog(window(), "VNC connection info");
  705. dlg.setIconImage(VncViewer.frameIcon);
  706. dlg.setAlwaysOnTop(true);
  707. dlg.setVisible(true);
  708. if (fullScreenWindow != null)
  709. DesktopWindow.setFullScreenWindow(fullScreenWindow);
  710. break;
  711. case ABOUT:
  712. VncViewer.about_vncviewer(cc.desktop);
  713. break;
  714. case DISMISS:
  715. break;
  716. }
  717. }
  718. private void setMenuKey()
  719. {
  720. menuKeyJava = MenuKey.getMenuKeyJavaCode();
  721. menuKeyCode = MenuKey.getMenuKeyCode();
  722. menuKeySym = MenuKey.getMenuKeySym();
  723. }
  724. public void handleOptions()
  725. {
  726. setMenuKey();
  727. /*
  728. setScaledSize(cc.server.width(), cc.server.height());
  729. if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) {
  730. // Re-layout the DesktopWindow when the scaled size changes.
  731. // Ideally we'd do this with a ComponentListener, but unfortunately
  732. // sometimes a spurious resize event is triggered on the viewport
  733. // when the DesktopWindow is manually resized via the drag handles.
  734. if (cc.desktop != null && cc.desktop.isVisible()) {
  735. JScrollPane scroll = (JScrollPane)((JViewport)getParent()).getParent();
  736. scroll.setViewportBorder(BorderFactory.createEmptyBorder(0,0,0,0));
  737. cc.desktop.pack();
  738. }
  739. */
  740. }
  741. public void releaseDownKeys() {
  742. while (!downKeySym.isEmpty())
  743. handleKeyRelease(downKeySym.keySet().iterator().next());
  744. }
  745. private DesktopWindow window() {
  746. return (DesktopWindow)getTopLevelAncestor();
  747. }
  748. private int x() { return getX(); }
  749. private int y() { return getY(); }
  750. private int w() { return getWidth(); }
  751. private int h() { return getHeight(); }
  752. // access to cc by different threads is specified in CConn
  753. private CConn cc;
  754. // access to the following must be synchronized:
  755. private PlatformPixelBuffer frameBuffer;
  756. Point lastPointerPos = new Point(0, 0);
  757. int lastButtonMask = 0;
  758. private class DownMap extends HashMap<Long, Integer> {
  759. public DownMap(int capacity) {
  760. super(capacity);
  761. }
  762. }
  763. DownMap downKeySym = new DownMap(256);
  764. int menuKeySym;
  765. int menuKeyCode, menuKeyJava;
  766. JPopupMenu contextMenu;
  767. boolean menuRecursion = false;
  768. boolean menuCtrlKey = false;
  769. boolean menuAltKey = false;
  770. static Toolkit tk = Toolkit.getDefaultToolkit();
  771. public int scaledWidth = 0, scaledHeight = 0;
  772. float scaleRatioX, scaleRatioY;
  773. static BufferedImage cursor;
  774. Point cursorHotspot = new Point();
  775. }