blob: a0aade044876fe75c3dc05f8daf016ce66e07d26 [file] [log] [blame]
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001//
2// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved.
3// Copyright (C) 2001-2006 Constantin Kaplinsky. All Rights Reserved.
4// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
5// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20// USA.
21//
22
23//
24// RfbProto.java
25//
26
Adam Tkacf53e62a2009-03-13 13:20:26 +000027package com.tigervnc.vncviewer;
Constantin Kaplinsky90d8a502008-04-14 09:45:50 +000028
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000029import java.io.*;
30import java.awt.*;
31import java.awt.event.*;
32import java.net.Socket;
33import java.util.zip.*;
34
35class RfbProto {
36
37 final static String
38 versionMsg_3_3 = "RFB 003.003\n",
39 versionMsg_3_7 = "RFB 003.007\n",
40 versionMsg_3_8 = "RFB 003.008\n";
41
42 // Vendor signatures: standard VNC/RealVNC, TridiaVNC, and TightVNC
43 final static String
44 StandardVendor = "STDV",
45 TridiaVncVendor = "TRDV",
46 TightVncVendor = "TGHT";
47
48 // Security types
49 final static int
Adam Tkacea79b472010-11-11 11:47:11 +000050 SecTypeInvalid = 0,
51 SecTypeNone = 1,
52 SecTypeVncAuth = 2,
53 SecTypeTight = 16,
54 SecTypeVeNCrypt = 19,
55 SecTypePlain = 256,
56 SecTypeTLSNone = 257,
57 SecTypeTLSVnc = 258,
58 SecTypeTLSPlain = 259,
59 SecTypeX509None = 260,
60 SecTypeX509Vnc = 261,
61 SecTypeX509Plain = 262;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000062
63 // Supported tunneling types
64 final static int
65 NoTunneling = 0;
66 final static String
67 SigNoTunneling = "NOTUNNEL";
68
69 // Supported authentication types
70 final static int
71 AuthNone = 1,
72 AuthVNC = 2,
73 AuthUnixLogin = 129;
74 final static String
75 SigAuthNone = "NOAUTH__",
76 SigAuthVNC = "VNCAUTH_",
77 SigAuthUnixLogin = "ULGNAUTH";
78
79 // VNC authentication results
80 final static int
81 VncAuthOK = 0,
82 VncAuthFailed = 1,
83 VncAuthTooMany = 2;
84
85 // Standard server-to-client messages
86 final static int
87 FramebufferUpdate = 0,
88 SetColourMapEntries = 1,
89 Bell = 2,
90 ServerCutText = 3;
91
92 // Non-standard server-to-client messages
93 final static int
94 EndOfContinuousUpdates = 150;
95 final static String
96 SigEndOfContinuousUpdates = "CUS_EOCU";
97
98 // Standard client-to-server messages
99 final static int
100 SetPixelFormat = 0,
101 FixColourMapEntries = 1,
102 SetEncodings = 2,
103 FramebufferUpdateRequest = 3,
104 KeyboardEvent = 4,
105 PointerEvent = 5,
106 ClientCutText = 6;
107
108 // Non-standard client-to-server messages
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000109 final static int EnableContinuousUpdates = 150;
110 final static int VideoRectangleSelection = 151;
enikeyd7653562008-12-25 11:02:56 +0000111 final static int VideoFreeze = 152;
112 final static String SigVideoFreeze = "VD_FREEZ";
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000113 final static String SigEnableContinuousUpdates = "CUC_ENCU";
114 final static String SigVideoRectangleSelection = "VRECTSEL";
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000115
116 // Supported encodings and pseudo-encodings
117 final static int
118 EncodingRaw = 0,
119 EncodingCopyRect = 1,
120 EncodingRRE = 2,
121 EncodingCoRRE = 4,
122 EncodingHextile = 5,
123 EncodingZlib = 6,
124 EncodingTight = 7,
125 EncodingZRLE = 16,
126 EncodingCompressLevel0 = 0xFFFFFF00,
127 EncodingQualityLevel0 = 0xFFFFFFE0,
128 EncodingXCursor = 0xFFFFFF10,
129 EncodingRichCursor = 0xFFFFFF11,
130 EncodingPointerPos = 0xFFFFFF18,
131 EncodingLastRect = 0xFFFFFF20,
132 EncodingNewFBSize = 0xFFFFFF21;
133 final static String
134 SigEncodingRaw = "RAW_____",
135 SigEncodingCopyRect = "COPYRECT",
136 SigEncodingRRE = "RRE_____",
137 SigEncodingCoRRE = "CORRE___",
138 SigEncodingHextile = "HEXTILE_",
139 SigEncodingZlib = "ZLIB____",
140 SigEncodingTight = "TIGHT___",
141 SigEncodingZRLE = "ZRLE____",
142 SigEncodingCompressLevel0 = "COMPRLVL",
143 SigEncodingQualityLevel0 = "JPEGQLVL",
144 SigEncodingXCursor = "X11CURSR",
145 SigEncodingRichCursor = "RCHCURSR",
146 SigEncodingPointerPos = "POINTPOS",
147 SigEncodingLastRect = "LASTRECT",
148 SigEncodingNewFBSize = "NEWFBSIZ";
149
150 final static int MaxNormalEncoding = 255;
151
152 // Contstants used in the Hextile decoder
153 final static int
154 HextileRaw = 1,
155 HextileBackgroundSpecified = 2,
156 HextileForegroundSpecified = 4,
157 HextileAnySubrects = 8,
158 HextileSubrectsColoured = 16;
159
160 // Contstants used in the Tight decoder
161 final static int TightMinToCompress = 12;
162 final static int
163 TightExplicitFilter = 0x04,
164 TightFill = 0x08,
165 TightJpeg = 0x09,
166 TightMaxSubencoding = 0x09,
167 TightFilterCopy = 0x00,
168 TightFilterPalette = 0x01,
169 TightFilterGradient = 0x02;
170
171
172 String host;
173 int port;
174 Socket sock;
175 OutputStream os;
176 SessionRecorder rec;
177 boolean inNormalProtocol = false;
178 VncViewer viewer;
179
180 // Input stream is declared private to make sure it can be accessed
181 // only via RfbProto methods. We have to do this because we want to
182 // count how many bytes were read.
183 private DataInputStream is;
184 private long numBytesRead = 0;
185 public long getNumBytesRead() { return numBytesRead; }
186
187 // Java on UNIX does not call keyPressed() on some keys, for example
188 // swedish keys To prevent our workaround to produce duplicate
189 // keypresses on JVMs that actually works, keep track of if
enikey2f0294e2008-12-24 08:18:54 +0000190 // keyPressed() for a "broken" key was called or not.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000191 boolean brokenKeyPressed = false;
192
193 // This will be set to true on the first framebuffer update
194 // containing Zlib-, ZRLE- or Tight-encoded data.
195 boolean wereZlibUpdates = false;
196
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000197 // This fields are needed to show warnings about inefficiently saved
198 // sessions only once per each saved session file.
199 boolean zlibWarningShown;
200 boolean tightWarningShown;
201
202 // Before starting to record each saved session, we set this field
203 // to 0, and increment on each framebuffer update. We don't flush
enikey2f0294e2008-12-24 08:18:54 +0000204 // the SessionRecorder data into the file before the second update.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000205 // This allows us to write initial framebuffer update with zero
206 // timestamp, to let the player show initial desktop before
207 // playback.
208 int numUpdatesInSession;
209
210 // Measuring network throughput.
211 boolean timing;
212 long timeWaitedIn100us;
213 long timedKbits;
214
215 // Protocol version and TightVNC-specific protocol options.
216 int serverMajor, serverMinor;
217 int clientMajor, clientMinor;
218 boolean protocolTightVNC;
219 CapsContainer tunnelCaps, authCaps;
220 CapsContainer serverMsgCaps, clientMsgCaps;
221 CapsContainer encodingCaps;
222
223 // "Continuous updates" is a TightVNC-specific feature that allows
224 // receiving framebuffer updates continuously, without sending update
225 // requests. The variables below track the state of this feature.
226 // Initially, continuous updates are disabled. They can be enabled
227 // by calling tryEnableContinuousUpdates() method, and only if this
228 // feature is supported by the server. To disable continuous updates,
229 // tryDisableContinuousUpdates() should be called.
230 private boolean continuousUpdatesActive = false;
231 private boolean continuousUpdatesEnding = false;
232
233 // If true, informs that the RFB socket was closed.
234 private boolean closed;
235
236 //
237 // Constructor. Make TCP connection to RFB server.
238 //
239
240 RfbProto(String h, int p, VncViewer v) throws IOException {
241 viewer = v;
242 host = h;
243 port = p;
244
245 if (viewer.socketFactory == null) {
246 sock = new Socket(host, port);
enikey17ac1812008-12-19 11:22:13 +0000247 sock.setTcpNoDelay(true);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000248 } else {
249 try {
250 Class factoryClass = Class.forName(viewer.socketFactory);
251 SocketFactory factory = (SocketFactory)factoryClass.newInstance();
252 if (viewer.inAnApplet)
253 sock = factory.createSocket(host, port, viewer);
254 else
255 sock = factory.createSocket(host, port, viewer.mainArgs);
256 } catch(Exception e) {
257 e.printStackTrace();
258 throw new IOException(e.getMessage());
259 }
260 }
261 is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
262 16384));
263 os = sock.getOutputStream();
264
265 timing = false;
266 timeWaitedIn100us = 5;
267 timedKbits = 0;
268 }
269
270
271 synchronized void close() {
272 try {
273 sock.close();
274 closed = true;
275 System.out.println("RFB socket closed");
276 if (rec != null) {
277 rec.close();
278 rec = null;
279 }
280 } catch (Exception e) {
281 e.printStackTrace();
282 }
283 }
284
285 synchronized boolean closed() {
286 return closed;
287 }
288
289 //
290 // Read server's protocol version message
291 //
292
293 void readVersionMsg() throws Exception {
294
295 byte[] b = new byte[12];
296
297 readFully(b);
298
299 if ((b[0] != 'R') || (b[1] != 'F') || (b[2] != 'B') || (b[3] != ' ')
300 || (b[4] < '0') || (b[4] > '9') || (b[5] < '0') || (b[5] > '9')
301 || (b[6] < '0') || (b[6] > '9') || (b[7] != '.')
302 || (b[8] < '0') || (b[8] > '9') || (b[9] < '0') || (b[9] > '9')
303 || (b[10] < '0') || (b[10] > '9') || (b[11] != '\n'))
304 {
305 throw new Exception("Host " + host + " port " + port +
306 " is not an RFB server");
307 }
308
309 serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
310 serverMinor = (b[8] - '0') * 100 + (b[9] - '0') * 10 + (b[10] - '0');
311
312 if (serverMajor < 3) {
313 throw new Exception("RFB server does not support protocol version 3");
314 }
315 }
316
317
318 //
319 // Write our protocol version message
320 //
321
322 void writeVersionMsg() throws IOException {
323 clientMajor = 3;
324 if (serverMajor > 3 || serverMinor >= 8) {
325 clientMinor = 8;
326 os.write(versionMsg_3_8.getBytes());
327 } else if (serverMinor >= 7) {
328 clientMinor = 7;
329 os.write(versionMsg_3_7.getBytes());
330 } else {
331 clientMinor = 3;
332 os.write(versionMsg_3_3.getBytes());
333 }
334 protocolTightVNC = false;
335 initCapabilities();
336 }
337
338
339 //
340 // Negotiate the authentication scheme.
341 //
342
343 int negotiateSecurity() throws Exception {
344 return (clientMinor >= 7) ?
345 selectSecurityType() : readSecurityType();
346 }
347
348 //
349 // Read security type from the server (protocol version 3.3).
350 //
351
352 int readSecurityType() throws Exception {
353 int secType = readU32();
354
355 switch (secType) {
356 case SecTypeInvalid:
357 readConnFailedReason();
358 return SecTypeInvalid; // should never be executed
359 case SecTypeNone:
360 case SecTypeVncAuth:
361 return secType;
362 default:
363 throw new Exception("Unknown security type from RFB server: " + secType);
364 }
365 }
366
367 //
368 // Select security type from the server's list (protocol versions 3.7/3.8).
369 //
370
371 int selectSecurityType() throws Exception {
372 int secType = SecTypeInvalid;
373
374 // Read the list of secutiry types.
375 int nSecTypes = readU8();
376 if (nSecTypes == 0) {
377 readConnFailedReason();
378 return SecTypeInvalid; // should never be executed
379 }
380 byte[] secTypes = new byte[nSecTypes];
381 readFully(secTypes);
382
383 // Find out if the server supports TightVNC protocol extensions
384 for (int i = 0; i < nSecTypes; i++) {
385 if (secTypes[i] == SecTypeTight) {
386 protocolTightVNC = true;
387 os.write(SecTypeTight);
388 return SecTypeTight;
389 }
390 }
391
392 // Find first supported security type.
393 for (int i = 0; i < nSecTypes; i++) {
Adam Tkac053597c2010-11-11 13:21:55 +0000394 if (secTypes[i] == SecTypeNone || secTypes[i] == SecTypeVncAuth
395 || secTypes[i] == SecTypeVeNCrypt) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000396 secType = secTypes[i];
397 break;
398 }
399 }
400
401 if (secType == SecTypeInvalid) {
402 throw new Exception("Server did not offer supported security type");
403 } else {
404 os.write(secType);
405 }
406
407 return secType;
408 }
409
Adam Tkac053597c2010-11-11 13:21:55 +0000410 int authenticateVeNCrypt() throws Exception {
411 int majorVersion = readU8();
412 int minorVersion = readU8();
413 int Version = (majorVersion << 8) | minorVersion;
414 if (Version < 0x0002) {
415 os.write(0);
416 os.write(0);
417 throw new Exception("Server reported an unsupported VeNCrypt version");
418 }
419 os.write(0);
420 os.write(2);
421 if (readU8() != 0)
422 throw new Exception("Server reported it could not support the VeNCrypt version");
423 int nSecTypes = readU8();
424 int[] secTypes = new int[nSecTypes];
425 for(int i = 0; i < nSecTypes; i++)
426 secTypes[i] = readU32();
427
428 for(int i = 0; i < nSecTypes; i++)
429 switch(secTypes[i])
430 {
431 case SecTypeNone:
432 case SecTypeVncAuth:
Adam Tkac6a663cd2010-11-11 14:06:42 +0000433 case SecTypePlain:
Adam Tkac053597c2010-11-11 13:21:55 +0000434 writeInt(secTypes[i]);
435 return secTypes[i];
436 }
437
438 throw new Exception("No valid VeNCrypt sub-type");
439 }
440
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000441 //
442 // Perform "no authentication".
443 //
444
445 void authenticateNone() throws Exception {
446 if (clientMinor >= 8)
447 readSecurityResult("No authentication");
448 }
449
450 //
451 // Perform standard VNC Authentication.
452 //
453
454 void authenticateVNC(String pw) throws Exception {
455 byte[] challenge = new byte[16];
456 readFully(challenge);
457
458 if (pw.length() > 8)
459 pw = pw.substring(0, 8); // Truncate to 8 chars
460
461 // Truncate password on the first zero byte.
462 int firstZero = pw.indexOf(0);
463 if (firstZero != -1)
464 pw = pw.substring(0, firstZero);
465
466 byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
467 System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
468
469 DesCipher des = new DesCipher(key);
470
471 des.encrypt(challenge, 0, challenge, 0);
472 des.encrypt(challenge, 8, challenge, 8);
473
474 os.write(challenge);
475
476 readSecurityResult("VNC authentication");
477 }
478
Adam Tkac6a663cd2010-11-11 14:06:42 +0000479 void authenticatePlain(String User, String Password) throws Exception {
480 byte[] user=User.getBytes();
481 byte[] password=Password.getBytes();
482 writeInt(user.length);
483 writeInt(password.length);
484 os.write(user);
485 os.write(password);
486
487 readSecurityResult("Plain authentication");
488 }
489
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000490 //
491 // Read security result.
492 // Throws an exception on authentication failure.
493 //
494
495 void readSecurityResult(String authType) throws Exception {
496 int securityResult = readU32();
497
498 switch (securityResult) {
499 case VncAuthOK:
500 System.out.println(authType + ": success");
501 break;
502 case VncAuthFailed:
503 if (clientMinor >= 8)
504 readConnFailedReason();
505 throw new Exception(authType + ": failed");
506 case VncAuthTooMany:
507 throw new Exception(authType + ": failed, too many tries");
508 default:
509 throw new Exception(authType + ": unknown result " + securityResult);
510 }
511 }
512
513 //
514 // Read the string describing the reason for a connection failure,
515 // and throw an exception.
516 //
517
518 void readConnFailedReason() throws Exception {
519 int reasonLen = readU32();
520 byte[] reason = new byte[reasonLen];
521 readFully(reason);
522 throw new Exception(new String(reason));
523 }
524
525 //
526 // Initialize capability lists (TightVNC protocol extensions).
527 //
528
529 void initCapabilities() {
530 tunnelCaps = new CapsContainer();
531 authCaps = new CapsContainer();
532 serverMsgCaps = new CapsContainer();
533 clientMsgCaps = new CapsContainer();
534 encodingCaps = new CapsContainer();
535
536 // Supported authentication methods
537 authCaps.add(AuthNone, StandardVendor, SigAuthNone,
538 "No authentication");
539 authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
540 "Standard VNC password authentication");
541
542 // Supported non-standard server-to-client messages
543 serverMsgCaps.add(EndOfContinuousUpdates, TightVncVendor,
544 SigEndOfContinuousUpdates,
545 "End of continuous updates notification");
546
547 // Supported non-standard client-to-server messages
548 clientMsgCaps.add(EnableContinuousUpdates, TightVncVendor,
549 SigEnableContinuousUpdates,
550 "Enable/disable continuous updates");
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000551 clientMsgCaps.add(VideoRectangleSelection, TightVncVendor,
552 SigVideoRectangleSelection,
553 "Select a rectangle to be treated as video");
enikeyd7653562008-12-25 11:02:56 +0000554 clientMsgCaps.add(VideoFreeze, TightVncVendor,
555 SigVideoFreeze,
556 "Disable/enable video rectangle");
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000557
558 // Supported encoding types
559 encodingCaps.add(EncodingCopyRect, StandardVendor,
560 SigEncodingCopyRect, "Standard CopyRect encoding");
561 encodingCaps.add(EncodingRRE, StandardVendor,
562 SigEncodingRRE, "Standard RRE encoding");
563 encodingCaps.add(EncodingCoRRE, StandardVendor,
564 SigEncodingCoRRE, "Standard CoRRE encoding");
565 encodingCaps.add(EncodingHextile, StandardVendor,
566 SigEncodingHextile, "Standard Hextile encoding");
567 encodingCaps.add(EncodingZRLE, StandardVendor,
568 SigEncodingZRLE, "Standard ZRLE encoding");
569 encodingCaps.add(EncodingZlib, TridiaVncVendor,
570 SigEncodingZlib, "Zlib encoding");
571 encodingCaps.add(EncodingTight, TightVncVendor,
572 SigEncodingTight, "Tight encoding");
573
574 // Supported pseudo-encoding types
575 encodingCaps.add(EncodingCompressLevel0, TightVncVendor,
576 SigEncodingCompressLevel0, "Compression level");
577 encodingCaps.add(EncodingQualityLevel0, TightVncVendor,
578 SigEncodingQualityLevel0, "JPEG quality level");
579 encodingCaps.add(EncodingXCursor, TightVncVendor,
580 SigEncodingXCursor, "X-style cursor shape update");
581 encodingCaps.add(EncodingRichCursor, TightVncVendor,
582 SigEncodingRichCursor, "Rich-color cursor shape update");
583 encodingCaps.add(EncodingPointerPos, TightVncVendor,
584 SigEncodingPointerPos, "Pointer position update");
585 encodingCaps.add(EncodingLastRect, TightVncVendor,
586 SigEncodingLastRect, "LastRect protocol extension");
587 encodingCaps.add(EncodingNewFBSize, TightVncVendor,
588 SigEncodingNewFBSize, "Framebuffer size change");
589 }
590
591 //
592 // Setup tunneling (TightVNC protocol extensions)
593 //
594
595 void setupTunneling() throws IOException {
596 int nTunnelTypes = readU32();
597 if (nTunnelTypes != 0) {
598 readCapabilityList(tunnelCaps, nTunnelTypes);
599
600 // We don't support tunneling yet.
601 writeInt(NoTunneling);
602 }
603 }
604
605 //
606 // Negotiate authentication scheme (TightVNC protocol extensions)
607 //
608
609 int negotiateAuthenticationTight() throws Exception {
610 int nAuthTypes = readU32();
611 if (nAuthTypes == 0)
612 return AuthNone;
613
614 readCapabilityList(authCaps, nAuthTypes);
615 for (int i = 0; i < authCaps.numEnabled(); i++) {
616 int authType = authCaps.getByOrder(i);
617 if (authType == AuthNone || authType == AuthVNC) {
618 writeInt(authType);
619 return authType;
620 }
621 }
622 throw new Exception("No suitable authentication scheme found");
623 }
624
625 //
626 // Read a capability list (TightVNC protocol extensions)
627 //
628
629 void readCapabilityList(CapsContainer caps, int count) throws IOException {
630 int code;
631 byte[] vendor = new byte[4];
632 byte[] name = new byte[8];
633 for (int i = 0; i < count; i++) {
634 code = readU32();
635 readFully(vendor);
636 readFully(name);
637 caps.enable(new CapabilityInfo(code, vendor, name));
638 }
639 }
640
641 //
642 // Write a 32-bit integer into the output stream.
643 //
644
645 void writeInt(int value) throws IOException {
646 byte[] b = new byte[4];
647 b[0] = (byte) ((value >> 24) & 0xff);
648 b[1] = (byte) ((value >> 16) & 0xff);
649 b[2] = (byte) ((value >> 8) & 0xff);
650 b[3] = (byte) (value & 0xff);
651 os.write(b);
652 }
653
654 //
655 // Write the client initialisation message
656 //
657
658 void writeClientInit() throws IOException {
659 if (viewer.options.shareDesktop) {
660 os.write(1);
661 } else {
662 os.write(0);
663 }
664 viewer.options.disableShareDesktop();
665 }
666
667
668 //
669 // Read the server initialisation message
670 //
671
672 String desktopName;
673 int framebufferWidth, framebufferHeight;
674 int bitsPerPixel, depth;
675 boolean bigEndian, trueColour;
676 int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
677
678 void readServerInit() throws IOException {
679 framebufferWidth = readU16();
680 framebufferHeight = readU16();
681 bitsPerPixel = readU8();
682 depth = readU8();
683 bigEndian = (readU8() != 0);
684 trueColour = (readU8() != 0);
685 redMax = readU16();
686 greenMax = readU16();
687 blueMax = readU16();
688 redShift = readU8();
689 greenShift = readU8();
690 blueShift = readU8();
691 byte[] pad = new byte[3];
692 readFully(pad);
693 int nameLength = readU32();
694 byte[] name = new byte[nameLength];
695 readFully(name);
696 desktopName = new String(name);
697
698 // Read interaction capabilities (TightVNC protocol extensions)
699 if (protocolTightVNC) {
700 int nServerMessageTypes = readU16();
701 int nClientMessageTypes = readU16();
702 int nEncodingTypes = readU16();
703 readU16();
704 readCapabilityList(serverMsgCaps, nServerMessageTypes);
705 readCapabilityList(clientMsgCaps, nClientMessageTypes);
706 readCapabilityList(encodingCaps, nEncodingTypes);
707 }
708
709 if (!clientMsgCaps.isEnabled(EnableContinuousUpdates)) {
710 viewer.options.disableContUpdates();
711 }
712
713 inNormalProtocol = true;
714 }
715
716
717 //
718 // Create session file and write initial protocol messages into it.
719 //
720
721 void startSession(String fname) throws IOException {
722 rec = new SessionRecorder(fname);
723 rec.writeHeader();
724 rec.write(versionMsg_3_3.getBytes());
725 rec.writeIntBE(SecTypeNone);
726 rec.writeShortBE(framebufferWidth);
727 rec.writeShortBE(framebufferHeight);
728 byte[] fbsServerInitMsg = {
729 32, 24, 0, 1, 0,
730 (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF,
731 16, 8, 0, 0, 0, 0
732 };
733 rec.write(fbsServerInitMsg);
734 rec.writeIntBE(desktopName.length());
735 rec.write(desktopName.getBytes());
736 numUpdatesInSession = 0;
737
738 // FIXME: If there were e.g. ZRLE updates only, that should not
739 // affect recording of Zlib and Tight updates. So, actually
740 // we should maintain separate flags for Zlib, ZRLE and
741 // Tight, instead of one ``wereZlibUpdates'' variable.
742 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000743
744 zlibWarningShown = false;
745 tightWarningShown = false;
746 }
747
748 //
749 // Close session file.
750 //
751
752 void closeSession() throws IOException {
753 if (rec != null) {
754 rec.close();
755 rec = null;
756 }
757 }
758
759
760 //
761 // Set new framebuffer size
762 //
763
764 void setFramebufferSize(int width, int height) {
765 framebufferWidth = width;
766 framebufferHeight = height;
767 }
768
769
770 //
771 // Read the server message type
772 //
773
774 int readServerMessageType() throws IOException {
775 int msgType = readU8();
776
777 // If the session is being recorded:
778 if (rec != null) {
779 if (msgType == Bell) { // Save Bell messages in session files.
780 rec.writeByte(msgType);
781 if (numUpdatesInSession > 0)
782 rec.flush();
783 }
784 }
785
786 return msgType;
787 }
788
789
790 //
791 // Read a FramebufferUpdate message
792 //
793
794 int updateNRects;
795
796 void readFramebufferUpdate() throws IOException {
797 skipBytes(1);
798 updateNRects = readU16();
799
800 // If the session is being recorded:
801 if (rec != null) {
802 rec.writeByte(FramebufferUpdate);
803 rec.writeByte(0);
804 rec.writeShortBE(updateNRects);
805 }
806
807 numUpdatesInSession++;
808 }
809
enikey2f0294e2008-12-24 08:18:54 +0000810 //
811 // Returns true if encoding is not pseudo
812 //
813 // FIXME: Find better way to differ pseudo and real encodings
814 //
815
816 boolean isRealDecoderEncoding(int encoding) {
817 if ((encoding >= 1) && (encoding <= 16)) {
818 return true;
819 }
820 return false;
821 }
822
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000823 // Read a FramebufferUpdate rectangle header
824
825 int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
826
827 void readFramebufferUpdateRectHdr() throws Exception {
828 updateRectX = readU16();
829 updateRectY = readU16();
830 updateRectW = readU16();
831 updateRectH = readU16();
832 updateRectEncoding = readU32();
833
834 if (updateRectEncoding == EncodingZlib ||
835 updateRectEncoding == EncodingZRLE ||
836 updateRectEncoding == EncodingTight)
837 wereZlibUpdates = true;
838
839 // If the session is being recorded:
840 if (rec != null) {
841 if (numUpdatesInSession > 1)
842 rec.flush(); // Flush the output on each rectangle.
843 rec.writeShortBE(updateRectX);
844 rec.writeShortBE(updateRectY);
845 rec.writeShortBE(updateRectW);
846 rec.writeShortBE(updateRectH);
enikey2f0294e2008-12-24 08:18:54 +0000847
848 //
849 // If this is pseudo encoding or CopyRect that write encoding ID
850 // in this place. All real encoding ID will be written to record stream
851 // in decoder classes.
enikey2f0294e2008-12-24 08:18:54 +0000852
enikey7716b712008-12-25 11:45:52 +0000853 if (((!isRealDecoderEncoding(updateRectEncoding))) && (rec != null)) {
enikey2f0294e2008-12-24 08:18:54 +0000854 rec.writeIntBE(updateRectEncoding);
855 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000856 }
857
858 if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding)
859 return;
860
861 if (updateRectX + updateRectW > framebufferWidth ||
862 updateRectY + updateRectH > framebufferHeight) {
863 throw new Exception("Framebuffer update rectangle too large: " +
864 updateRectW + "x" + updateRectH + " at (" +
865 updateRectX + "," + updateRectY + ")");
866 }
867 }
868
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000869 //
870 // Read a ServerCutText message
871 //
872
873 String readServerCutText() throws IOException {
874 skipBytes(3);
875 int len = readU32();
876 byte[] text = new byte[len];
877 readFully(text);
878 return new String(text);
879 }
880
881
882 //
883 // Read an integer in compact representation (1..3 bytes).
884 // Such format is used as a part of the Tight encoding.
885 // Also, this method records data if session recording is active and
886 // the viewer's recordFromBeginning variable is set to true.
887 //
888
889 int readCompactLen() throws IOException {
890 int[] portion = new int[3];
891 portion[0] = readU8();
892 int byteCount = 1;
893 int len = portion[0] & 0x7F;
894 if ((portion[0] & 0x80) != 0) {
895 portion[1] = readU8();
896 byteCount++;
897 len |= (portion[1] & 0x7F) << 7;
898 if ((portion[1] & 0x80) != 0) {
899 portion[2] = readU8();
900 byteCount++;
901 len |= (portion[2] & 0xFF) << 14;
902 }
903 }
904
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000905 return len;
906 }
907
908
909 //
910 // Write a FramebufferUpdateRequest message
911 //
912
913 void writeFramebufferUpdateRequest(int x, int y, int w, int h,
914 boolean incremental)
915 throws IOException
916 {
917 byte[] b = new byte[10];
918
919 b[0] = (byte) FramebufferUpdateRequest;
920 b[1] = (byte) (incremental ? 1 : 0);
921 b[2] = (byte) ((x >> 8) & 0xff);
922 b[3] = (byte) (x & 0xff);
923 b[4] = (byte) ((y >> 8) & 0xff);
924 b[5] = (byte) (y & 0xff);
925 b[6] = (byte) ((w >> 8) & 0xff);
926 b[7] = (byte) (w & 0xff);
927 b[8] = (byte) ((h >> 8) & 0xff);
928 b[9] = (byte) (h & 0xff);
929
930 os.write(b);
931 }
932
933
934 //
935 // Write a SetPixelFormat message
936 //
937
938 void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian,
939 boolean trueColour,
940 int redMax, int greenMax, int blueMax,
941 int redShift, int greenShift, int blueShift)
942 throws IOException
943 {
944 byte[] b = new byte[20];
945
946 b[0] = (byte) SetPixelFormat;
947 b[4] = (byte) bitsPerPixel;
948 b[5] = (byte) depth;
949 b[6] = (byte) (bigEndian ? 1 : 0);
950 b[7] = (byte) (trueColour ? 1 : 0);
951 b[8] = (byte) ((redMax >> 8) & 0xff);
952 b[9] = (byte) (redMax & 0xff);
953 b[10] = (byte) ((greenMax >> 8) & 0xff);
954 b[11] = (byte) (greenMax & 0xff);
955 b[12] = (byte) ((blueMax >> 8) & 0xff);
956 b[13] = (byte) (blueMax & 0xff);
957 b[14] = (byte) redShift;
958 b[15] = (byte) greenShift;
959 b[16] = (byte) blueShift;
960
961 os.write(b);
962 }
963
964
965 //
966 // Write a FixColourMapEntries message. The values in the red, green and
967 // blue arrays are from 0 to 65535.
968 //
969
970 void writeFixColourMapEntries(int firstColour, int nColours,
971 int[] red, int[] green, int[] blue)
972 throws IOException
973 {
974 byte[] b = new byte[6 + nColours * 6];
975
976 b[0] = (byte) FixColourMapEntries;
977 b[2] = (byte) ((firstColour >> 8) & 0xff);
978 b[3] = (byte) (firstColour & 0xff);
979 b[4] = (byte) ((nColours >> 8) & 0xff);
980 b[5] = (byte) (nColours & 0xff);
981
982 for (int i = 0; i < nColours; i++) {
983 b[6 + i * 6] = (byte) ((red[i] >> 8) & 0xff);
984 b[6 + i * 6 + 1] = (byte) (red[i] & 0xff);
985 b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff);
986 b[6 + i * 6 + 3] = (byte) (green[i] & 0xff);
987 b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff);
988 b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff);
989 }
enikey2f0294e2008-12-24 08:18:54 +0000990
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000991 os.write(b);
992 }
993
994
995 //
996 // Write a SetEncodings message
997 //
998
999 void writeSetEncodings(int[] encs, int len) throws IOException {
1000 byte[] b = new byte[4 + 4 * len];
1001
1002 b[0] = (byte) SetEncodings;
1003 b[2] = (byte) ((len >> 8) & 0xff);
1004 b[3] = (byte) (len & 0xff);
1005
1006 for (int i = 0; i < len; i++) {
1007 b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff);
1008 b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff);
1009 b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff);
1010 b[7 + 4 * i] = (byte) (encs[i] & 0xff);
1011 }
1012
1013 os.write(b);
1014 }
1015
1016
1017 //
1018 // Write a ClientCutText message
1019 //
1020
1021 void writeClientCutText(String text) throws IOException {
1022 byte[] b = new byte[8 + text.length()];
1023
1024 b[0] = (byte) ClientCutText;
1025 b[4] = (byte) ((text.length() >> 24) & 0xff);
1026 b[5] = (byte) ((text.length() >> 16) & 0xff);
1027 b[6] = (byte) ((text.length() >> 8) & 0xff);
1028 b[7] = (byte) (text.length() & 0xff);
1029
1030 System.arraycopy(text.getBytes(), 0, b, 8, text.length());
1031
1032 os.write(b);
1033 }
1034
1035
1036 //
1037 // A buffer for putting pointer and keyboard events before being sent. This
enikey2f0294e2008-12-24 08:18:54 +00001038 // is to ensure that multiple RFB events generated from a single Java Event
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001039 // will all be sent in a single network packet. The maximum possible
1040 // length is 4 modifier down events, a single key event followed by 4
1041 // modifier up events i.e. 9 key events or 72 bytes.
1042 //
1043
1044 byte[] eventBuf = new byte[72];
1045 int eventBufLen;
1046
1047
1048 // Useful shortcuts for modifier masks.
1049
1050 final static int CTRL_MASK = InputEvent.CTRL_MASK;
1051 final static int SHIFT_MASK = InputEvent.SHIFT_MASK;
1052 final static int META_MASK = InputEvent.META_MASK;
1053 final static int ALT_MASK = InputEvent.ALT_MASK;
1054
1055
1056 //
1057 // Write a pointer event message. We may need to send modifier key events
1058 // around it to set the correct modifier state.
1059 //
1060
1061 int pointerMask = 0;
1062
1063 void writePointerEvent(MouseEvent evt) throws IOException {
1064 int modifiers = evt.getModifiers();
1065
1066 int mask2 = 2;
1067 int mask3 = 4;
1068 if (viewer.options.reverseMouseButtons2And3) {
1069 mask2 = 4;
1070 mask3 = 2;
1071 }
1072
1073 // Note: For some reason, AWT does not set BUTTON1_MASK on left
1074 // button presses. Here we think that it was the left button if
1075 // modifiers do not include BUTTON2_MASK or BUTTON3_MASK.
1076
1077 if (evt.getID() == MouseEvent.MOUSE_PRESSED) {
1078 if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
1079 pointerMask = mask2;
1080 modifiers &= ~ALT_MASK;
1081 } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
1082 pointerMask = mask3;
1083 modifiers &= ~META_MASK;
1084 } else {
1085 pointerMask = 1;
1086 }
1087 } else if (evt.getID() == MouseEvent.MOUSE_RELEASED) {
1088 pointerMask = 0;
1089 if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
1090 modifiers &= ~ALT_MASK;
1091 } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
1092 modifiers &= ~META_MASK;
1093 }
1094 }
1095
1096 eventBufLen = 0;
1097 writeModifierKeyEvents(modifiers);
1098
1099 int x = evt.getX();
1100 int y = evt.getY();
1101
1102 if (x < 0) x = 0;
1103 if (y < 0) y = 0;
1104
1105 eventBuf[eventBufLen++] = (byte) PointerEvent;
1106 eventBuf[eventBufLen++] = (byte) pointerMask;
1107 eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
1108 eventBuf[eventBufLen++] = (byte) (x & 0xff);
1109 eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
1110 eventBuf[eventBufLen++] = (byte) (y & 0xff);
1111
1112 //
1113 // Always release all modifiers after an "up" event
1114 //
1115
1116 if (pointerMask == 0) {
1117 writeModifierKeyEvents(0);
1118 }
1119
1120 os.write(eventBuf, 0, eventBufLen);
1121 }
1122
1123
1124 //
1125 // Write a key event message. We may need to send modifier key events
1126 // around it to set the correct modifier state. Also we need to translate
1127 // from the Java key values to the X keysym values used by the RFB protocol.
1128 //
1129
1130 void writeKeyEvent(KeyEvent evt) throws IOException {
1131
1132 int keyChar = evt.getKeyChar();
1133
1134 //
1135 // Ignore event if only modifiers were pressed.
1136 //
1137
1138 // Some JVMs return 0 instead of CHAR_UNDEFINED in getKeyChar().
1139 if (keyChar == 0)
1140 keyChar = KeyEvent.CHAR_UNDEFINED;
1141
1142 if (keyChar == KeyEvent.CHAR_UNDEFINED) {
1143 int code = evt.getKeyCode();
1144 if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT ||
1145 code == KeyEvent.VK_META || code == KeyEvent.VK_ALT)
1146 return;
1147 }
1148
1149 //
1150 // Key press or key release?
1151 //
1152
1153 boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
1154
1155 int key;
1156 if (evt.isActionKey()) {
1157
1158 //
1159 // An action key should be one of the following.
1160 // If not then just ignore the event.
1161 //
1162
1163 switch(evt.getKeyCode()) {
1164 case KeyEvent.VK_HOME: key = 0xff50; break;
1165 case KeyEvent.VK_LEFT: key = 0xff51; break;
1166 case KeyEvent.VK_UP: key = 0xff52; break;
1167 case KeyEvent.VK_RIGHT: key = 0xff53; break;
1168 case KeyEvent.VK_DOWN: key = 0xff54; break;
1169 case KeyEvent.VK_PAGE_UP: key = 0xff55; break;
1170 case KeyEvent.VK_PAGE_DOWN: key = 0xff56; break;
1171 case KeyEvent.VK_END: key = 0xff57; break;
1172 case KeyEvent.VK_INSERT: key = 0xff63; break;
1173 case KeyEvent.VK_F1: key = 0xffbe; break;
1174 case KeyEvent.VK_F2: key = 0xffbf; break;
1175 case KeyEvent.VK_F3: key = 0xffc0; break;
1176 case KeyEvent.VK_F4: key = 0xffc1; break;
1177 case KeyEvent.VK_F5: key = 0xffc2; break;
1178 case KeyEvent.VK_F6: key = 0xffc3; break;
1179 case KeyEvent.VK_F7: key = 0xffc4; break;
1180 case KeyEvent.VK_F8: key = 0xffc5; break;
1181 case KeyEvent.VK_F9: key = 0xffc6; break;
1182 case KeyEvent.VK_F10: key = 0xffc7; break;
1183 case KeyEvent.VK_F11: key = 0xffc8; break;
1184 case KeyEvent.VK_F12: key = 0xffc9; break;
1185 default:
1186 return;
1187 }
1188
1189 } else {
1190
1191 //
1192 // A "normal" key press. Ordinary ASCII characters go straight through.
1193 // For CTRL-<letter>, CTRL is sent separately so just send <letter>.
1194 // Backspace, tab, return, escape and delete have special keysyms.
1195 // Anything else we ignore.
1196 //
1197
1198 key = keyChar;
1199
1200 if (key < 0x20) {
1201 if (evt.isControlDown()) {
1202 key += 0x60;
1203 } else {
1204 switch(key) {
1205 case KeyEvent.VK_BACK_SPACE: key = 0xff08; break;
1206 case KeyEvent.VK_TAB: key = 0xff09; break;
1207 case KeyEvent.VK_ENTER: key = 0xff0d; break;
1208 case KeyEvent.VK_ESCAPE: key = 0xff1b; break;
1209 }
1210 }
1211 } else if (key == 0x7f) {
1212 // Delete
1213 key = 0xffff;
1214 } else if (key > 0xff) {
1215 // JDK1.1 on X incorrectly passes some keysyms straight through,
1216 // so we do too. JDK1.1.4 seems to have fixed this.
1217 // The keysyms passed are 0xff00 .. XK_BackSpace .. XK_Delete
1218 // Also, we pass through foreign currency keysyms (0x20a0..0x20af).
1219 if ((key < 0xff00 || key > 0xffff) &&
1220 !(key >= 0x20a0 && key <= 0x20af))
1221 return;
1222 }
1223 }
1224
1225 // Fake keyPresses for keys that only generates keyRelease events
1226 if ((key == 0xe5) || (key == 0xc5) || // XK_aring / XK_Aring
1227 (key == 0xe4) || (key == 0xc4) || // XK_adiaeresis / XK_Adiaeresis
1228 (key == 0xf6) || (key == 0xd6) || // XK_odiaeresis / XK_Odiaeresis
1229 (key == 0xa7) || (key == 0xbd) || // XK_section / XK_onehalf
1230 (key == 0xa3)) { // XK_sterling
1231 // Make sure we do not send keypress events twice on platforms
1232 // with correct JVMs (those that actually report KeyPress for all
enikey2f0294e2008-12-24 08:18:54 +00001233 // keys)
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001234 if (down)
1235 brokenKeyPressed = true;
1236
1237 if (!down && !brokenKeyPressed) {
1238 // We've got a release event for this key, but haven't received
enikey2f0294e2008-12-24 08:18:54 +00001239 // a press. Fake it.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001240 eventBufLen = 0;
1241 writeModifierKeyEvents(evt.getModifiers());
1242 writeKeyEvent(key, true);
1243 os.write(eventBuf, 0, eventBufLen);
1244 }
1245
1246 if (!down)
enikey2f0294e2008-12-24 08:18:54 +00001247 brokenKeyPressed = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001248 }
1249
1250 eventBufLen = 0;
1251 writeModifierKeyEvents(evt.getModifiers());
1252 writeKeyEvent(key, down);
1253
1254 // Always release all modifiers after an "up" event
1255 if (!down)
1256 writeModifierKeyEvents(0);
1257
1258 os.write(eventBuf, 0, eventBufLen);
1259 }
1260
1261
1262 //
1263 // Add a raw key event with the given X keysym to eventBuf.
1264 //
1265
1266 void writeKeyEvent(int keysym, boolean down) {
1267 eventBuf[eventBufLen++] = (byte) KeyboardEvent;
1268 eventBuf[eventBufLen++] = (byte) (down ? 1 : 0);
1269 eventBuf[eventBufLen++] = (byte) 0;
1270 eventBuf[eventBufLen++] = (byte) 0;
1271 eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff);
1272 eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff);
1273 eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff);
1274 eventBuf[eventBufLen++] = (byte) (keysym & 0xff);
1275 }
1276
1277
1278 //
1279 // Write key events to set the correct modifier state.
1280 //
1281
1282 int oldModifiers = 0;
1283
1284 void writeModifierKeyEvents(int newModifiers) {
1285 if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
1286 writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
1287
1288 if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK))
1289 writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0);
1290
1291 if ((newModifiers & META_MASK) != (oldModifiers & META_MASK))
1292 writeKeyEvent(0xffe7, (newModifiers & META_MASK) != 0);
1293
1294 if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK))
1295 writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0);
1296
1297 oldModifiers = newModifiers;
1298 }
1299
1300
1301 //
1302 // Enable continuous updates for the specified area of the screen (but
1303 // only if EnableContinuousUpdates message is supported by the server).
1304 //
1305
1306 void tryEnableContinuousUpdates(int x, int y, int w, int h)
1307 throws IOException
1308 {
1309 if (!clientMsgCaps.isEnabled(EnableContinuousUpdates)) {
1310 System.out.println("Continuous updates not supported by the server");
1311 return;
1312 }
1313
1314 if (continuousUpdatesActive) {
1315 System.out.println("Continuous updates already active");
1316 return;
1317 }
1318
1319 byte[] b = new byte[10];
1320
1321 b[0] = (byte) EnableContinuousUpdates;
1322 b[1] = (byte) 1; // enable
1323 b[2] = (byte) ((x >> 8) & 0xff);
1324 b[3] = (byte) (x & 0xff);
1325 b[4] = (byte) ((y >> 8) & 0xff);
1326 b[5] = (byte) (y & 0xff);
1327 b[6] = (byte) ((w >> 8) & 0xff);
1328 b[7] = (byte) (w & 0xff);
1329 b[8] = (byte) ((h >> 8) & 0xff);
1330 b[9] = (byte) (h & 0xff);
1331
1332 os.write(b);
1333
1334 continuousUpdatesActive = true;
1335 System.out.println("Continuous updates activated");
1336 }
1337
1338
1339 //
1340 // Disable continuous updates (only if EnableContinuousUpdates message
1341 // is supported by the server).
1342 //
1343
1344 void tryDisableContinuousUpdates() throws IOException
1345 {
1346 if (!clientMsgCaps.isEnabled(EnableContinuousUpdates)) {
1347 System.out.println("Continuous updates not supported by the server");
1348 return;
1349 }
1350
1351 if (!continuousUpdatesActive) {
1352 System.out.println("Continuous updates already disabled");
1353 return;
1354 }
1355
1356 if (continuousUpdatesEnding)
1357 return;
1358
1359 byte[] b = { (byte)EnableContinuousUpdates, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1360 os.write(b);
1361
1362 if (!serverMsgCaps.isEnabled(EndOfContinuousUpdates)) {
1363 // If the server did not advertise support for the
1364 // EndOfContinuousUpdates message (should not normally happen
1365 // when EnableContinuousUpdates is supported), then we clear
1366 // 'continuousUpdatesActive' variable immediately. Normally,
1367 // it will be reset on receiving EndOfContinuousUpdates message
1368 // from the server.
1369 continuousUpdatesActive = false;
1370 } else {
1371 // Indicate that we are waiting for EndOfContinuousUpdates.
1372 continuousUpdatesEnding = true;
1373 }
1374 }
1375
1376
1377 //
1378 // Process EndOfContinuousUpdates message received from the server.
1379 //
1380
1381 void endOfContinuousUpdates()
1382 {
1383 continuousUpdatesActive = false;
1384 continuousUpdatesEnding = false;
1385 }
1386
1387
1388 //
1389 // Check if continuous updates are in effect.
1390 //
1391
1392 boolean continuousUpdatesAreActive()
1393 {
1394 return continuousUpdatesActive;
1395 }
1396
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001397 /**
1398 * Send a rectangle selection to be treated as video by the server (but
1399 * only if VideoRectangleSelection message is supported by the server).
1400 * @param rect specifies coordinates and size of the rectangule.
1401 * @throws java.io.IOException
1402 */
1403 void trySendVideoSelection(Rectangle rect) throws IOException
1404 {
1405 if (!clientMsgCaps.isEnabled(VideoRectangleSelection)) {
1406 System.out.println("Video area selection is not supported by the server");
1407 return;
1408 }
1409
Constantin Kaplinsky4f652852008-09-05 02:41:15 +00001410 // Send zero coordinates if the rectangle is empty.
1411 if (rect.isEmpty()) {
1412 rect = new Rectangle();
1413 }
1414
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001415 int x = rect.x;
1416 int y = rect.y;
1417 int w = rect.width;
1418 int h = rect.height;
enikey2f0294e2008-12-24 08:18:54 +00001419
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001420 byte[] b = new byte[10];
1421
1422 b[0] = (byte) VideoRectangleSelection;
1423 b[1] = (byte) 0; // reserved
1424 b[2] = (byte) ((x >> 8) & 0xff);
1425 b[3] = (byte) (x & 0xff);
1426 b[4] = (byte) ((y >> 8) & 0xff);
1427 b[5] = (byte) (y & 0xff);
1428 b[6] = (byte) ((w >> 8) & 0xff);
1429 b[7] = (byte) (w & 0xff);
1430 b[8] = (byte) ((h >> 8) & 0xff);
1431 b[9] = (byte) (h & 0xff);
1432
1433 os.write(b);
1434
1435 System.out.println("Video rectangle selection message sent");
1436 }
1437
enikeyd7653562008-12-25 11:02:56 +00001438 void trySendVideoFreeze(boolean freeze) throws IOException
1439 {
1440 if (!clientMsgCaps.isEnabled(VideoFreeze)) {
1441 System.out.println("Video freeze is not supported by the server");
1442 return;
1443 }
1444
1445 byte[] b = new byte[2];
1446 byte fb = 0;
1447 if (freeze) {
1448 fb = 1;
1449 }
1450
1451 b[0] = (byte) VideoFreeze;
1452 b[1] = (byte) fb;
1453
1454 os.write(b);
1455
1456 System.out.println("Video freeze selection message sent");
1457 }
1458
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001459 public void startTiming() {
1460 timing = true;
1461
1462 // Carry over up to 1s worth of previous rate for smoothing.
1463
1464 if (timeWaitedIn100us > 10000) {
1465 timedKbits = timedKbits * 10000 / timeWaitedIn100us;
1466 timeWaitedIn100us = 10000;
1467 }
1468 }
1469
1470 public void stopTiming() {
enikey2f0294e2008-12-24 08:18:54 +00001471 timing = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001472 if (timeWaitedIn100us < timedKbits/2)
1473 timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
1474 }
1475
1476 public long kbitsPerSecond() {
1477 return timedKbits * 10000 / timeWaitedIn100us;
1478 }
1479
1480 public long timeWaited() {
1481 return timeWaitedIn100us;
1482 }
1483
1484 //
1485 // Methods for reading data via our DataInputStream member variable (is).
1486 //
1487 // In addition to reading data, the readFully() methods updates variables
1488 // used to estimate data throughput.
1489 //
1490
1491 public void readFully(byte b[]) throws IOException {
1492 readFully(b, 0, b.length);
1493 }
1494
1495 public void readFully(byte b[], int off, int len) throws IOException {
1496 long before = 0;
1497 if (timing)
1498 before = System.currentTimeMillis();
1499
1500 is.readFully(b, off, len);
1501
1502 if (timing) {
1503 long after = System.currentTimeMillis();
1504 long newTimeWaited = (after - before) * 10;
1505 int newKbits = len * 8 / 1000;
1506
1507 // limit rate to between 10kbit/s and 40Mbit/s
1508
1509 if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
1510 if (newTimeWaited < newKbits/4) newTimeWaited = newKbits/4;
1511
1512 timeWaitedIn100us += newTimeWaited;
1513 timedKbits += newKbits;
1514 }
1515
1516 numBytesRead += len;
1517 }
1518
1519 final int available() throws IOException {
1520 return is.available();
1521 }
1522
1523 // FIXME: DataInputStream::skipBytes() is not guaranteed to skip
1524 // exactly n bytes. Probably we don't want to use this method.
1525 final int skipBytes(int n) throws IOException {
1526 int r = is.skipBytes(n);
1527 numBytesRead += r;
1528 return r;
1529 }
1530
1531 final int readU8() throws IOException {
1532 int r = is.readUnsignedByte();
1533 numBytesRead++;
1534 return r;
1535 }
1536
1537 final int readU16() throws IOException {
1538 int r = is.readUnsignedShort();
1539 numBytesRead += 2;
1540 return r;
1541 }
1542
1543 final int readU32() throws IOException {
1544 int r = is.readInt();
1545 numBytesRead += 4;
1546 return r;
1547 }
1548}