Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

CConn.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
  2. * Copyright 2009-2013 Pierre Ossman <ossman@cendio.se> for Cendio AB
  3. * Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved.
  4. * Copyright (C) 2011-2019 Brian P. Hinz
  5. *
  6. * This is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This software is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this software; if not, write to the Free Software
  18. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
  19. * USA.
  20. */
  21. //
  22. // CConn
  23. //
  24. // Methods on CConn are called from both the GUI thread and the thread which
  25. // processes incoming RFB messages ("the RFB thread"). This means we need to
  26. // be careful with synchronization here.
  27. //
  28. // Any access to writer() must not only be synchronized, but we must also make
  29. // sure that the connection is in RFBSTATE_NORMAL. We are guaranteed this for
  30. // any code called after serverInit() has been called. Since the DesktopWindow
  31. // isn't created until then, any methods called only from DesktopWindow can
  32. // assume that we are in RFBSTATE_NORMAL.
  33. package com.tigervnc.vncviewer;
  34. import java.awt.*;
  35. import java.awt.datatransfer.StringSelection;
  36. import java.awt.event.*;
  37. import java.awt.Toolkit;
  38. import java.io.IOException;
  39. import java.io.InputStream;
  40. import java.io.File;
  41. import java.io.FileInputStream;
  42. import java.io.FileNotFoundException;
  43. import java.util.jar.Attributes;
  44. import java.util.jar.Manifest;
  45. import javax.swing.*;
  46. import javax.swing.ImageIcon;
  47. import java.net.InetSocketAddress;
  48. import java.net.SocketException;
  49. import java.util.*;
  50. import java.util.prefs.*;
  51. import com.tigervnc.rdr.*;
  52. import com.tigervnc.rfb.*;
  53. import com.tigervnc.rfb.Point;
  54. import com.tigervnc.rfb.Exception;
  55. import com.tigervnc.network.Socket;
  56. import com.tigervnc.network.TcpSocket;
  57. import static com.tigervnc.vncviewer.Parameters.*;
  58. public class CConn extends CConnection implements
  59. FdInStreamBlockCallback, ActionListener {
  60. // 8 colours (1 bit per component)
  61. static final PixelFormat verylowColorPF =
  62. new PixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0);
  63. // 64 colours (2 bits per component)
  64. static final PixelFormat lowColorPF =
  65. new PixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0);
  66. // 256 colours (2-3 bits per component)
  67. static final PixelFormat mediumColorPF =
  68. new PixelFormat(8, 8, false, true, 7, 7, 3, 5, 2, 0);
  69. ////////////////////////////////////////////////////////////////////
  70. // The following methods are all called from the RFB thread
  71. public CConn(String vncServerName, Socket socket)
  72. {
  73. serverHost = null; serverPort = 0; desktop = null;
  74. updateCount = 0; pixelCount = 0;
  75. lastServerEncoding = -1;
  76. setShared(shared.getValue());
  77. sock = socket;
  78. server.supportsLocalCursor = true;
  79. server.supportsDesktopResize = true;
  80. server.supportsClientRedirect = true;
  81. if (customCompressLevel.getValue())
  82. setCompressLevel(compressLevel.getValue());
  83. if (!noJpeg.getValue())
  84. setQualityLevel(qualityLevel.getValue());
  85. if (sock == null) {
  86. setServerName(Hostname.getHost(vncServerName));
  87. setServerPort(Hostname.getPort(vncServerName));
  88. try {
  89. if (tunnel.getValue() || !via.getValue().isEmpty()) {
  90. int localPort = TcpSocket.findFreeTcpPort();
  91. if (localPort == 0)
  92. throw new Exception("Could not obtain free TCP port");
  93. String gatewayHost = Tunnel.getSshHost();
  94. if (gatewayHost.isEmpty())
  95. gatewayHost = getServerName();
  96. Tunnel.createTunnel(gatewayHost, getServerName(),
  97. getServerPort(), localPort);
  98. sock = new TcpSocket("localhost", localPort);
  99. vlog.info("connected to localhost port "+localPort);
  100. } else {
  101. sock = new TcpSocket(getServerName(), getServerPort());
  102. vlog.info("connected to host "+getServerName()+" port "+getServerPort());
  103. }
  104. } catch (java.lang.Exception e) {
  105. throw new Exception(e.getMessage());
  106. }
  107. } else {
  108. String name = sock.getPeerEndpoint();
  109. if (listenMode.getValue())
  110. vlog.info("Accepted connection from " + name);
  111. else
  112. vlog.info("connected to host "+Hostname.getHost(name)+" port "+Hostname.getPort(name));
  113. }
  114. // See callback below
  115. sock.inStream().setBlockCallback(this);
  116. setStreams(sock.inStream(), sock.outStream());
  117. initialiseProtocol();
  118. OptionsDialog.addCallback("handleOptions", this);
  119. }
  120. public String connectionInfo() {
  121. String info = new String("Desktop name: %s%n"+
  122. "Host: %s:%d%n"+
  123. "Size: %dx%d%n"+
  124. "Pixel format: %s%n"+
  125. " (server default: %s)%n"+
  126. "Requested encoding: %s%n"+
  127. "Last used encoding: %s%n"+
  128. "Line speed estimate: %d kbit/s%n"+
  129. "Protocol version: %d.%d%n"+
  130. "Security method: %s [%s]%n");
  131. String infoText =
  132. String.format(info, server.name(),
  133. sock.getPeerName(), sock.getPeerPort(),
  134. server.width(), server.height(),
  135. server.pf().print(),
  136. serverPF.print(),
  137. Encodings.encodingName(getPreferredEncoding()),
  138. Encodings.encodingName(lastServerEncoding),
  139. sock.inStream().kbitsPerSecond(),
  140. server.majorVersion, server.minorVersion,
  141. Security.secTypeName(csecurity.getType()),
  142. csecurity.description());
  143. return infoText;
  144. }
  145. public int getUpdateCount()
  146. {
  147. return updateCount;
  148. }
  149. public int getPixelCount()
  150. {
  151. return pixelCount;
  152. }
  153. // The RFB core is not properly asynchronous, so it calls this callback
  154. // whenever it needs to block to wait for more data. Since FLTK is
  155. // monitoring the socket, we just make sure FLTK gets to run.
  156. public void blockCallback() {
  157. try {
  158. synchronized(this) {
  159. wait(1);
  160. }
  161. } catch (java.lang.InterruptedException e) {
  162. throw new Exception(e.getMessage());
  163. }
  164. }
  165. ////////////////////// CConnection callback methods //////////////////////
  166. // serverInit() is called when the serverInit message has been received. At
  167. // this point we create the desktop window and display it. We also tell the
  168. // server the pixel format and encodings to use and request the first update.
  169. public void initDone()
  170. {
  171. // If using AutoSelect with old servers, start in FullColor
  172. // mode. See comment in autoSelectFormatAndEncoding.
  173. if (server.beforeVersion(3, 8) && autoSelect.getValue())
  174. fullColor.setParam(true);
  175. serverPF = server.pf();
  176. desktop = new DesktopWindow(server.width(), server.height(),
  177. server.name(), serverPF, this);
  178. fullColorPF = desktop.getPreferredPF();
  179. // Force a switch to the format and encoding we'd like
  180. updatePixelFormat();
  181. int encNum = Encodings.encodingNum(preferredEncoding.getValue());
  182. if (encNum != -1)
  183. setPreferredEncoding(encNum);
  184. }
  185. // setDesktopSize() is called when the desktop size changes (including when
  186. // it is set initially).
  187. public void setDesktopSize(int w, int h)
  188. {
  189. super.setDesktopSize(w, h);
  190. resizeFramebuffer();
  191. }
  192. // setExtendedDesktopSize() is a more advanced version of setDesktopSize()
  193. public void setExtendedDesktopSize(int reason, int result,
  194. int w, int h, ScreenSet layout)
  195. {
  196. super.setExtendedDesktopSize(reason, result, w, h, layout);
  197. if ((reason == screenTypes.reasonClient) &&
  198. (result != screenTypes.resultSuccess)) {
  199. vlog.error("SetDesktopSize failed: " + result);
  200. return;
  201. }
  202. resizeFramebuffer();
  203. }
  204. // clientRedirect() migrates the client to another host/port
  205. public void clientRedirect(int port, String host, String x509subject)
  206. {
  207. try {
  208. sock.close();
  209. sock = new TcpSocket(host, port);
  210. vlog.info("Redirected to "+host+":"+port);
  211. setServerName(host);
  212. setServerPort(port);
  213. sock.inStream().setBlockCallback(this);
  214. setStreams(sock.inStream(), sock.outStream());
  215. if (desktop != null)
  216. desktop.dispose();
  217. initialiseProtocol();
  218. } catch (java.lang.Exception e) {
  219. throw new Exception(e.getMessage());
  220. }
  221. }
  222. // setName() is called when the desktop name changes
  223. public void setName(String name)
  224. {
  225. super.setName(name);
  226. if (desktop != null)
  227. desktop.setName(name);
  228. }
  229. // framebufferUpdateStart() is called at the beginning of an update.
  230. // Here we try to send out a new framebuffer update request so that the
  231. // next update can be sent out in parallel with us decoding the current
  232. // one.
  233. public void framebufferUpdateStart()
  234. {
  235. super.framebufferUpdateStart();
  236. }
  237. // framebufferUpdateEnd() is called at the end of an update.
  238. // For each rectangle, the FdInStream will have timed the speed
  239. // of the connection, allowing us to select format and encoding
  240. // appropriately, and then request another incremental update.
  241. public void framebufferUpdateEnd()
  242. {
  243. super.framebufferUpdateEnd();
  244. updateCount++;
  245. desktop.updateWindow();
  246. // Compute new settings based on updated bandwidth values
  247. if (autoSelect.getValue())
  248. autoSelectFormatAndEncoding();
  249. }
  250. // The rest of the callbacks are fairly self-explanatory...
  251. public void setColourMapEntries(int firstColor, int nColors, int[] rgbs)
  252. {
  253. vlog.error("Invalid SetColourMapEntries from server!");
  254. }
  255. public void bell()
  256. {
  257. if (acceptBell.getValue())
  258. desktop.getToolkit().beep();
  259. }
  260. public void serverCutText(String str, int len)
  261. {
  262. StringSelection buffer;
  263. if (!acceptClipboard.getValue())
  264. return;
  265. ClipboardDialog.serverCutText(str);
  266. }
  267. public void dataRect(Rect r, int encoding)
  268. {
  269. sock.inStream().startTiming();
  270. if (encoding != Encodings.encodingCopyRect)
  271. lastServerEncoding = encoding;
  272. super.dataRect(r, encoding);
  273. sock.inStream().stopTiming();
  274. pixelCount += r.area();
  275. }
  276. public void setCursor(int width, int height, Point hotspot,
  277. byte[] data)
  278. {
  279. desktop.setCursor(width, height, hotspot, data);
  280. }
  281. public void fence(int flags, int len, byte[] data)
  282. {
  283. // can't call super.super.fence(flags, len, data);
  284. server.supportsFence = true;
  285. if ((flags & fenceTypes.fenceFlagRequest) != 0) {
  286. // We handle everything synchronously so we trivially honor these modes
  287. flags = flags & (fenceTypes.fenceFlagBlockBefore | fenceTypes.fenceFlagBlockAfter);
  288. writer().writeFence(flags, len, data);
  289. return;
  290. }
  291. }
  292. ////////////////////// Internal methods //////////////////////
  293. public void resizeFramebuffer()
  294. {
  295. if (desktop == null)
  296. return;
  297. desktop.resizeFramebuffer(server.width(), server.height());
  298. }
  299. // autoSelectFormatAndEncoding() chooses the format and encoding appropriate
  300. // to the connection speed:
  301. //
  302. // First we wait for at least one second of bandwidth measurement.
  303. //
  304. // Above 16Mbps (i.e. LAN), we choose the second highest JPEG quality,
  305. // which should be perceptually lossless.
  306. //
  307. // If the bandwidth is below that, we choose a more lossy JPEG quality.
  308. //
  309. // If the bandwidth drops below 256 Kbps, we switch to palette mode.
  310. //
  311. // Note: The system here is fairly arbitrary and should be replaced
  312. // with something more intelligent at the server end.
  313. //
  314. private void autoSelectFormatAndEncoding()
  315. {
  316. long kbitsPerSecond = sock.inStream().kbitsPerSecond();
  317. long timeWaited = sock.inStream().timeWaited();
  318. boolean newFullColour = fullColor.getValue();
  319. int newQualityLevel = qualityLevel.getValue();
  320. // Always use Tight
  321. setPreferredEncoding(Encodings.encodingTight);
  322. // Check that we have a decent bandwidth measurement
  323. if ((kbitsPerSecond == 0) || (timeWaited < 100))
  324. return;
  325. // Select appropriate quality level
  326. if (!noJpeg.getValue()) {
  327. if (kbitsPerSecond > 16000)
  328. newQualityLevel = 8;
  329. else
  330. newQualityLevel = 6;
  331. if (newQualityLevel != qualityLevel.getValue()) {
  332. vlog.info("Throughput "+kbitsPerSecond+
  333. " kbit/s - changing to quality "+newQualityLevel);
  334. qualityLevel.setParam(newQualityLevel);
  335. setQualityLevel(newQualityLevel);
  336. }
  337. }
  338. if (server.beforeVersion(3, 8)) {
  339. // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with
  340. // cursors "asynchronously". If this happens in the middle of a
  341. // pixel format change, the server will encode the cursor with
  342. // the old format, but the client will try to decode it
  343. // according to the new format. This will lead to a
  344. // crash. Therefore, we do not allow automatic format change for
  345. // old servers.
  346. return;
  347. }
  348. // Select best color level
  349. newFullColour = (kbitsPerSecond > 256);
  350. if (newFullColour != fullColor.getValue()) {
  351. if (newFullColour)
  352. vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now enabled");
  353. else
  354. vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now disabled");
  355. fullColor.setParam(newFullColour);
  356. updatePixelFormat();
  357. }
  358. }
  359. // updatePixelFormat() requests an update from the server, having set the
  360. // format and encoding appropriately.
  361. private void updatePixelFormat()
  362. {
  363. PixelFormat pf;
  364. if (fullColor.getValue()) {
  365. pf = fullColorPF;
  366. } else {
  367. if (lowColorLevel.getValue() == 0) {
  368. pf = verylowColorPF;
  369. } else if (lowColorLevel.getValue() == 1) {
  370. pf = lowColorPF;
  371. } else {
  372. pf = mediumColorPF;
  373. }
  374. }
  375. String str = pf.print();
  376. vlog.info("Using pixel format " + str);
  377. setPF(pf);
  378. }
  379. public void handleOptions()
  380. {
  381. // Checking all the details of the current set of encodings is just
  382. // a pain. Assume something has changed, as resending the encoding
  383. // list is cheap. Avoid overriding what the auto logic has selected
  384. // though.
  385. if (!autoSelect.getValue()) {
  386. int encNum = Encodings.encodingNum(preferredEncoding.getValue());
  387. if (encNum != -1)
  388. this.setPreferredEncoding(encNum);
  389. }
  390. if (customCompressLevel.getValue())
  391. this.setCompressLevel(compressLevel.getValue());
  392. else
  393. this.setCompressLevel(-1);
  394. if (!noJpeg.getValue() && !autoSelect.getValue())
  395. this.setQualityLevel(qualityLevel.getValue());
  396. else
  397. this.setQualityLevel(-1);
  398. this.updatePixelFormat();
  399. }
  400. ////////////////////////////////////////////////////////////////////
  401. // The following methods are all called from the GUI thread
  402. // close() shuts down the socket, thus waking up the RFB thread.
  403. public void close() {
  404. if (closeListener != null) {
  405. JFrame f =
  406. (JFrame)SwingUtilities.getAncestorOfClass(JFrame.class, desktop);
  407. if (f != null)
  408. f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
  409. }
  410. shuttingDown = true;
  411. try {
  412. if (sock != null)
  413. sock.shutdown();
  414. } catch (java.lang.Exception e) {
  415. throw new Exception(e.getMessage());
  416. }
  417. }
  418. // writeClientCutText() is called from the clipboard dialog
  419. public void writeClientCutText(String str, int len) {
  420. if ((state() != stateEnum.RFBSTATE_NORMAL) || shuttingDown)
  421. return;
  422. writer().writeClientCutText(str, len);
  423. }
  424. public void actionPerformed(ActionEvent e) {}
  425. public Socket getSocket() {
  426. return sock;
  427. }
  428. ////////////////////////////////////////////////////////////////////
  429. // The following methods are called from both RFB and GUI threads
  430. // the following never change so need no synchronization:
  431. // access to desktop by different threads is specified in DesktopWindow
  432. // the following need no synchronization:
  433. // shuttingDown is set by the GUI thread and only ever tested by the RFB
  434. // thread after the window has been destroyed.
  435. boolean shuttingDown = false;
  436. // reading and writing int and boolean is atomic in java, so no
  437. // synchronization of the following flags is needed:
  438. // All menu, options, about and info stuff is done in the GUI thread (apart
  439. // from when constructed).
  440. // the following are only ever accessed by the GUI thread:
  441. private String serverHost;
  442. private int serverPort;
  443. private Socket sock;
  444. protected DesktopWindow desktop;
  445. private int updateCount;
  446. private int pixelCount;
  447. private PixelFormat serverPF;
  448. private PixelFormat fullColorPF;
  449. private int lastServerEncoding;
  450. public ActionListener closeListener = null;
  451. static LogWriter vlog = new LogWriter("CConn");
  452. }