blob: ab3c1521fb14f01d01113953154aa145c3c656bc [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:
433 writeInt(secTypes[i]);
434 return secTypes[i];
435 }
436
437 throw new Exception("No valid VeNCrypt sub-type");
438 }
439
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000440 //
441 // Perform "no authentication".
442 //
443
444 void authenticateNone() throws Exception {
445 if (clientMinor >= 8)
446 readSecurityResult("No authentication");
447 }
448
449 //
450 // Perform standard VNC Authentication.
451 //
452
453 void authenticateVNC(String pw) throws Exception {
454 byte[] challenge = new byte[16];
455 readFully(challenge);
456
457 if (pw.length() > 8)
458 pw = pw.substring(0, 8); // Truncate to 8 chars
459
460 // Truncate password on the first zero byte.
461 int firstZero = pw.indexOf(0);
462 if (firstZero != -1)
463 pw = pw.substring(0, firstZero);
464
465 byte[] key = {0, 0, 0, 0, 0, 0, 0, 0};
466 System.arraycopy(pw.getBytes(), 0, key, 0, pw.length());
467
468 DesCipher des = new DesCipher(key);
469
470 des.encrypt(challenge, 0, challenge, 0);
471 des.encrypt(challenge, 8, challenge, 8);
472
473 os.write(challenge);
474
475 readSecurityResult("VNC authentication");
476 }
477
478 //
479 // Read security result.
480 // Throws an exception on authentication failure.
481 //
482
483 void readSecurityResult(String authType) throws Exception {
484 int securityResult = readU32();
485
486 switch (securityResult) {
487 case VncAuthOK:
488 System.out.println(authType + ": success");
489 break;
490 case VncAuthFailed:
491 if (clientMinor >= 8)
492 readConnFailedReason();
493 throw new Exception(authType + ": failed");
494 case VncAuthTooMany:
495 throw new Exception(authType + ": failed, too many tries");
496 default:
497 throw new Exception(authType + ": unknown result " + securityResult);
498 }
499 }
500
501 //
502 // Read the string describing the reason for a connection failure,
503 // and throw an exception.
504 //
505
506 void readConnFailedReason() throws Exception {
507 int reasonLen = readU32();
508 byte[] reason = new byte[reasonLen];
509 readFully(reason);
510 throw new Exception(new String(reason));
511 }
512
513 //
514 // Initialize capability lists (TightVNC protocol extensions).
515 //
516
517 void initCapabilities() {
518 tunnelCaps = new CapsContainer();
519 authCaps = new CapsContainer();
520 serverMsgCaps = new CapsContainer();
521 clientMsgCaps = new CapsContainer();
522 encodingCaps = new CapsContainer();
523
524 // Supported authentication methods
525 authCaps.add(AuthNone, StandardVendor, SigAuthNone,
526 "No authentication");
527 authCaps.add(AuthVNC, StandardVendor, SigAuthVNC,
528 "Standard VNC password authentication");
529
530 // Supported non-standard server-to-client messages
531 serverMsgCaps.add(EndOfContinuousUpdates, TightVncVendor,
532 SigEndOfContinuousUpdates,
533 "End of continuous updates notification");
534
535 // Supported non-standard client-to-server messages
536 clientMsgCaps.add(EnableContinuousUpdates, TightVncVendor,
537 SigEnableContinuousUpdates,
538 "Enable/disable continuous updates");
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000539 clientMsgCaps.add(VideoRectangleSelection, TightVncVendor,
540 SigVideoRectangleSelection,
541 "Select a rectangle to be treated as video");
enikeyd7653562008-12-25 11:02:56 +0000542 clientMsgCaps.add(VideoFreeze, TightVncVendor,
543 SigVideoFreeze,
544 "Disable/enable video rectangle");
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000545
546 // Supported encoding types
547 encodingCaps.add(EncodingCopyRect, StandardVendor,
548 SigEncodingCopyRect, "Standard CopyRect encoding");
549 encodingCaps.add(EncodingRRE, StandardVendor,
550 SigEncodingRRE, "Standard RRE encoding");
551 encodingCaps.add(EncodingCoRRE, StandardVendor,
552 SigEncodingCoRRE, "Standard CoRRE encoding");
553 encodingCaps.add(EncodingHextile, StandardVendor,
554 SigEncodingHextile, "Standard Hextile encoding");
555 encodingCaps.add(EncodingZRLE, StandardVendor,
556 SigEncodingZRLE, "Standard ZRLE encoding");
557 encodingCaps.add(EncodingZlib, TridiaVncVendor,
558 SigEncodingZlib, "Zlib encoding");
559 encodingCaps.add(EncodingTight, TightVncVendor,
560 SigEncodingTight, "Tight encoding");
561
562 // Supported pseudo-encoding types
563 encodingCaps.add(EncodingCompressLevel0, TightVncVendor,
564 SigEncodingCompressLevel0, "Compression level");
565 encodingCaps.add(EncodingQualityLevel0, TightVncVendor,
566 SigEncodingQualityLevel0, "JPEG quality level");
567 encodingCaps.add(EncodingXCursor, TightVncVendor,
568 SigEncodingXCursor, "X-style cursor shape update");
569 encodingCaps.add(EncodingRichCursor, TightVncVendor,
570 SigEncodingRichCursor, "Rich-color cursor shape update");
571 encodingCaps.add(EncodingPointerPos, TightVncVendor,
572 SigEncodingPointerPos, "Pointer position update");
573 encodingCaps.add(EncodingLastRect, TightVncVendor,
574 SigEncodingLastRect, "LastRect protocol extension");
575 encodingCaps.add(EncodingNewFBSize, TightVncVendor,
576 SigEncodingNewFBSize, "Framebuffer size change");
577 }
578
579 //
580 // Setup tunneling (TightVNC protocol extensions)
581 //
582
583 void setupTunneling() throws IOException {
584 int nTunnelTypes = readU32();
585 if (nTunnelTypes != 0) {
586 readCapabilityList(tunnelCaps, nTunnelTypes);
587
588 // We don't support tunneling yet.
589 writeInt(NoTunneling);
590 }
591 }
592
593 //
594 // Negotiate authentication scheme (TightVNC protocol extensions)
595 //
596
597 int negotiateAuthenticationTight() throws Exception {
598 int nAuthTypes = readU32();
599 if (nAuthTypes == 0)
600 return AuthNone;
601
602 readCapabilityList(authCaps, nAuthTypes);
603 for (int i = 0; i < authCaps.numEnabled(); i++) {
604 int authType = authCaps.getByOrder(i);
605 if (authType == AuthNone || authType == AuthVNC) {
606 writeInt(authType);
607 return authType;
608 }
609 }
610 throw new Exception("No suitable authentication scheme found");
611 }
612
613 //
614 // Read a capability list (TightVNC protocol extensions)
615 //
616
617 void readCapabilityList(CapsContainer caps, int count) throws IOException {
618 int code;
619 byte[] vendor = new byte[4];
620 byte[] name = new byte[8];
621 for (int i = 0; i < count; i++) {
622 code = readU32();
623 readFully(vendor);
624 readFully(name);
625 caps.enable(new CapabilityInfo(code, vendor, name));
626 }
627 }
628
629 //
630 // Write a 32-bit integer into the output stream.
631 //
632
633 void writeInt(int value) throws IOException {
634 byte[] b = new byte[4];
635 b[0] = (byte) ((value >> 24) & 0xff);
636 b[1] = (byte) ((value >> 16) & 0xff);
637 b[2] = (byte) ((value >> 8) & 0xff);
638 b[3] = (byte) (value & 0xff);
639 os.write(b);
640 }
641
642 //
643 // Write the client initialisation message
644 //
645
646 void writeClientInit() throws IOException {
647 if (viewer.options.shareDesktop) {
648 os.write(1);
649 } else {
650 os.write(0);
651 }
652 viewer.options.disableShareDesktop();
653 }
654
655
656 //
657 // Read the server initialisation message
658 //
659
660 String desktopName;
661 int framebufferWidth, framebufferHeight;
662 int bitsPerPixel, depth;
663 boolean bigEndian, trueColour;
664 int redMax, greenMax, blueMax, redShift, greenShift, blueShift;
665
666 void readServerInit() throws IOException {
667 framebufferWidth = readU16();
668 framebufferHeight = readU16();
669 bitsPerPixel = readU8();
670 depth = readU8();
671 bigEndian = (readU8() != 0);
672 trueColour = (readU8() != 0);
673 redMax = readU16();
674 greenMax = readU16();
675 blueMax = readU16();
676 redShift = readU8();
677 greenShift = readU8();
678 blueShift = readU8();
679 byte[] pad = new byte[3];
680 readFully(pad);
681 int nameLength = readU32();
682 byte[] name = new byte[nameLength];
683 readFully(name);
684 desktopName = new String(name);
685
686 // Read interaction capabilities (TightVNC protocol extensions)
687 if (protocolTightVNC) {
688 int nServerMessageTypes = readU16();
689 int nClientMessageTypes = readU16();
690 int nEncodingTypes = readU16();
691 readU16();
692 readCapabilityList(serverMsgCaps, nServerMessageTypes);
693 readCapabilityList(clientMsgCaps, nClientMessageTypes);
694 readCapabilityList(encodingCaps, nEncodingTypes);
695 }
696
697 if (!clientMsgCaps.isEnabled(EnableContinuousUpdates)) {
698 viewer.options.disableContUpdates();
699 }
700
701 inNormalProtocol = true;
702 }
703
704
705 //
706 // Create session file and write initial protocol messages into it.
707 //
708
709 void startSession(String fname) throws IOException {
710 rec = new SessionRecorder(fname);
711 rec.writeHeader();
712 rec.write(versionMsg_3_3.getBytes());
713 rec.writeIntBE(SecTypeNone);
714 rec.writeShortBE(framebufferWidth);
715 rec.writeShortBE(framebufferHeight);
716 byte[] fbsServerInitMsg = {
717 32, 24, 0, 1, 0,
718 (byte)0xFF, 0, (byte)0xFF, 0, (byte)0xFF,
719 16, 8, 0, 0, 0, 0
720 };
721 rec.write(fbsServerInitMsg);
722 rec.writeIntBE(desktopName.length());
723 rec.write(desktopName.getBytes());
724 numUpdatesInSession = 0;
725
726 // FIXME: If there were e.g. ZRLE updates only, that should not
727 // affect recording of Zlib and Tight updates. So, actually
728 // we should maintain separate flags for Zlib, ZRLE and
729 // Tight, instead of one ``wereZlibUpdates'' variable.
730 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000731
732 zlibWarningShown = false;
733 tightWarningShown = false;
734 }
735
736 //
737 // Close session file.
738 //
739
740 void closeSession() throws IOException {
741 if (rec != null) {
742 rec.close();
743 rec = null;
744 }
745 }
746
747
748 //
749 // Set new framebuffer size
750 //
751
752 void setFramebufferSize(int width, int height) {
753 framebufferWidth = width;
754 framebufferHeight = height;
755 }
756
757
758 //
759 // Read the server message type
760 //
761
762 int readServerMessageType() throws IOException {
763 int msgType = readU8();
764
765 // If the session is being recorded:
766 if (rec != null) {
767 if (msgType == Bell) { // Save Bell messages in session files.
768 rec.writeByte(msgType);
769 if (numUpdatesInSession > 0)
770 rec.flush();
771 }
772 }
773
774 return msgType;
775 }
776
777
778 //
779 // Read a FramebufferUpdate message
780 //
781
782 int updateNRects;
783
784 void readFramebufferUpdate() throws IOException {
785 skipBytes(1);
786 updateNRects = readU16();
787
788 // If the session is being recorded:
789 if (rec != null) {
790 rec.writeByte(FramebufferUpdate);
791 rec.writeByte(0);
792 rec.writeShortBE(updateNRects);
793 }
794
795 numUpdatesInSession++;
796 }
797
enikey2f0294e2008-12-24 08:18:54 +0000798 //
799 // Returns true if encoding is not pseudo
800 //
801 // FIXME: Find better way to differ pseudo and real encodings
802 //
803
804 boolean isRealDecoderEncoding(int encoding) {
805 if ((encoding >= 1) && (encoding <= 16)) {
806 return true;
807 }
808 return false;
809 }
810
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000811 // Read a FramebufferUpdate rectangle header
812
813 int updateRectX, updateRectY, updateRectW, updateRectH, updateRectEncoding;
814
815 void readFramebufferUpdateRectHdr() throws Exception {
816 updateRectX = readU16();
817 updateRectY = readU16();
818 updateRectW = readU16();
819 updateRectH = readU16();
820 updateRectEncoding = readU32();
821
822 if (updateRectEncoding == EncodingZlib ||
823 updateRectEncoding == EncodingZRLE ||
824 updateRectEncoding == EncodingTight)
825 wereZlibUpdates = true;
826
827 // If the session is being recorded:
828 if (rec != null) {
829 if (numUpdatesInSession > 1)
830 rec.flush(); // Flush the output on each rectangle.
831 rec.writeShortBE(updateRectX);
832 rec.writeShortBE(updateRectY);
833 rec.writeShortBE(updateRectW);
834 rec.writeShortBE(updateRectH);
enikey2f0294e2008-12-24 08:18:54 +0000835
836 //
837 // If this is pseudo encoding or CopyRect that write encoding ID
838 // in this place. All real encoding ID will be written to record stream
839 // in decoder classes.
enikey2f0294e2008-12-24 08:18:54 +0000840
enikey7716b712008-12-25 11:45:52 +0000841 if (((!isRealDecoderEncoding(updateRectEncoding))) && (rec != null)) {
enikey2f0294e2008-12-24 08:18:54 +0000842 rec.writeIntBE(updateRectEncoding);
843 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000844 }
845
846 if (updateRectEncoding < 0 || updateRectEncoding > MaxNormalEncoding)
847 return;
848
849 if (updateRectX + updateRectW > framebufferWidth ||
850 updateRectY + updateRectH > framebufferHeight) {
851 throw new Exception("Framebuffer update rectangle too large: " +
852 updateRectW + "x" + updateRectH + " at (" +
853 updateRectX + "," + updateRectY + ")");
854 }
855 }
856
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000857 //
858 // Read a ServerCutText message
859 //
860
861 String readServerCutText() throws IOException {
862 skipBytes(3);
863 int len = readU32();
864 byte[] text = new byte[len];
865 readFully(text);
866 return new String(text);
867 }
868
869
870 //
871 // Read an integer in compact representation (1..3 bytes).
872 // Such format is used as a part of the Tight encoding.
873 // Also, this method records data if session recording is active and
874 // the viewer's recordFromBeginning variable is set to true.
875 //
876
877 int readCompactLen() throws IOException {
878 int[] portion = new int[3];
879 portion[0] = readU8();
880 int byteCount = 1;
881 int len = portion[0] & 0x7F;
882 if ((portion[0] & 0x80) != 0) {
883 portion[1] = readU8();
884 byteCount++;
885 len |= (portion[1] & 0x7F) << 7;
886 if ((portion[1] & 0x80) != 0) {
887 portion[2] = readU8();
888 byteCount++;
889 len |= (portion[2] & 0xFF) << 14;
890 }
891 }
892
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000893 return len;
894 }
895
896
897 //
898 // Write a FramebufferUpdateRequest message
899 //
900
901 void writeFramebufferUpdateRequest(int x, int y, int w, int h,
902 boolean incremental)
903 throws IOException
904 {
905 byte[] b = new byte[10];
906
907 b[0] = (byte) FramebufferUpdateRequest;
908 b[1] = (byte) (incremental ? 1 : 0);
909 b[2] = (byte) ((x >> 8) & 0xff);
910 b[3] = (byte) (x & 0xff);
911 b[4] = (byte) ((y >> 8) & 0xff);
912 b[5] = (byte) (y & 0xff);
913 b[6] = (byte) ((w >> 8) & 0xff);
914 b[7] = (byte) (w & 0xff);
915 b[8] = (byte) ((h >> 8) & 0xff);
916 b[9] = (byte) (h & 0xff);
917
918 os.write(b);
919 }
920
921
922 //
923 // Write a SetPixelFormat message
924 //
925
926 void writeSetPixelFormat(int bitsPerPixel, int depth, boolean bigEndian,
927 boolean trueColour,
928 int redMax, int greenMax, int blueMax,
929 int redShift, int greenShift, int blueShift)
930 throws IOException
931 {
932 byte[] b = new byte[20];
933
934 b[0] = (byte) SetPixelFormat;
935 b[4] = (byte) bitsPerPixel;
936 b[5] = (byte) depth;
937 b[6] = (byte) (bigEndian ? 1 : 0);
938 b[7] = (byte) (trueColour ? 1 : 0);
939 b[8] = (byte) ((redMax >> 8) & 0xff);
940 b[9] = (byte) (redMax & 0xff);
941 b[10] = (byte) ((greenMax >> 8) & 0xff);
942 b[11] = (byte) (greenMax & 0xff);
943 b[12] = (byte) ((blueMax >> 8) & 0xff);
944 b[13] = (byte) (blueMax & 0xff);
945 b[14] = (byte) redShift;
946 b[15] = (byte) greenShift;
947 b[16] = (byte) blueShift;
948
949 os.write(b);
950 }
951
952
953 //
954 // Write a FixColourMapEntries message. The values in the red, green and
955 // blue arrays are from 0 to 65535.
956 //
957
958 void writeFixColourMapEntries(int firstColour, int nColours,
959 int[] red, int[] green, int[] blue)
960 throws IOException
961 {
962 byte[] b = new byte[6 + nColours * 6];
963
964 b[0] = (byte) FixColourMapEntries;
965 b[2] = (byte) ((firstColour >> 8) & 0xff);
966 b[3] = (byte) (firstColour & 0xff);
967 b[4] = (byte) ((nColours >> 8) & 0xff);
968 b[5] = (byte) (nColours & 0xff);
969
970 for (int i = 0; i < nColours; i++) {
971 b[6 + i * 6] = (byte) ((red[i] >> 8) & 0xff);
972 b[6 + i * 6 + 1] = (byte) (red[i] & 0xff);
973 b[6 + i * 6 + 2] = (byte) ((green[i] >> 8) & 0xff);
974 b[6 + i * 6 + 3] = (byte) (green[i] & 0xff);
975 b[6 + i * 6 + 4] = (byte) ((blue[i] >> 8) & 0xff);
976 b[6 + i * 6 + 5] = (byte) (blue[i] & 0xff);
977 }
enikey2f0294e2008-12-24 08:18:54 +0000978
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000979 os.write(b);
980 }
981
982
983 //
984 // Write a SetEncodings message
985 //
986
987 void writeSetEncodings(int[] encs, int len) throws IOException {
988 byte[] b = new byte[4 + 4 * len];
989
990 b[0] = (byte) SetEncodings;
991 b[2] = (byte) ((len >> 8) & 0xff);
992 b[3] = (byte) (len & 0xff);
993
994 for (int i = 0; i < len; i++) {
995 b[4 + 4 * i] = (byte) ((encs[i] >> 24) & 0xff);
996 b[5 + 4 * i] = (byte) ((encs[i] >> 16) & 0xff);
997 b[6 + 4 * i] = (byte) ((encs[i] >> 8) & 0xff);
998 b[7 + 4 * i] = (byte) (encs[i] & 0xff);
999 }
1000
1001 os.write(b);
1002 }
1003
1004
1005 //
1006 // Write a ClientCutText message
1007 //
1008
1009 void writeClientCutText(String text) throws IOException {
1010 byte[] b = new byte[8 + text.length()];
1011
1012 b[0] = (byte) ClientCutText;
1013 b[4] = (byte) ((text.length() >> 24) & 0xff);
1014 b[5] = (byte) ((text.length() >> 16) & 0xff);
1015 b[6] = (byte) ((text.length() >> 8) & 0xff);
1016 b[7] = (byte) (text.length() & 0xff);
1017
1018 System.arraycopy(text.getBytes(), 0, b, 8, text.length());
1019
1020 os.write(b);
1021 }
1022
1023
1024 //
1025 // A buffer for putting pointer and keyboard events before being sent. This
enikey2f0294e2008-12-24 08:18:54 +00001026 // is to ensure that multiple RFB events generated from a single Java Event
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001027 // will all be sent in a single network packet. The maximum possible
1028 // length is 4 modifier down events, a single key event followed by 4
1029 // modifier up events i.e. 9 key events or 72 bytes.
1030 //
1031
1032 byte[] eventBuf = new byte[72];
1033 int eventBufLen;
1034
1035
1036 // Useful shortcuts for modifier masks.
1037
1038 final static int CTRL_MASK = InputEvent.CTRL_MASK;
1039 final static int SHIFT_MASK = InputEvent.SHIFT_MASK;
1040 final static int META_MASK = InputEvent.META_MASK;
1041 final static int ALT_MASK = InputEvent.ALT_MASK;
1042
1043
1044 //
1045 // Write a pointer event message. We may need to send modifier key events
1046 // around it to set the correct modifier state.
1047 //
1048
1049 int pointerMask = 0;
1050
1051 void writePointerEvent(MouseEvent evt) throws IOException {
1052 int modifiers = evt.getModifiers();
1053
1054 int mask2 = 2;
1055 int mask3 = 4;
1056 if (viewer.options.reverseMouseButtons2And3) {
1057 mask2 = 4;
1058 mask3 = 2;
1059 }
1060
1061 // Note: For some reason, AWT does not set BUTTON1_MASK on left
1062 // button presses. Here we think that it was the left button if
1063 // modifiers do not include BUTTON2_MASK or BUTTON3_MASK.
1064
1065 if (evt.getID() == MouseEvent.MOUSE_PRESSED) {
1066 if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
1067 pointerMask = mask2;
1068 modifiers &= ~ALT_MASK;
1069 } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
1070 pointerMask = mask3;
1071 modifiers &= ~META_MASK;
1072 } else {
1073 pointerMask = 1;
1074 }
1075 } else if (evt.getID() == MouseEvent.MOUSE_RELEASED) {
1076 pointerMask = 0;
1077 if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
1078 modifiers &= ~ALT_MASK;
1079 } else if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
1080 modifiers &= ~META_MASK;
1081 }
1082 }
1083
1084 eventBufLen = 0;
1085 writeModifierKeyEvents(modifiers);
1086
1087 int x = evt.getX();
1088 int y = evt.getY();
1089
1090 if (x < 0) x = 0;
1091 if (y < 0) y = 0;
1092
1093 eventBuf[eventBufLen++] = (byte) PointerEvent;
1094 eventBuf[eventBufLen++] = (byte) pointerMask;
1095 eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
1096 eventBuf[eventBufLen++] = (byte) (x & 0xff);
1097 eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
1098 eventBuf[eventBufLen++] = (byte) (y & 0xff);
1099
1100 //
1101 // Always release all modifiers after an "up" event
1102 //
1103
1104 if (pointerMask == 0) {
1105 writeModifierKeyEvents(0);
1106 }
1107
1108 os.write(eventBuf, 0, eventBufLen);
1109 }
1110
1111
1112 //
1113 // Write a key event message. We may need to send modifier key events
1114 // around it to set the correct modifier state. Also we need to translate
1115 // from the Java key values to the X keysym values used by the RFB protocol.
1116 //
1117
1118 void writeKeyEvent(KeyEvent evt) throws IOException {
1119
1120 int keyChar = evt.getKeyChar();
1121
1122 //
1123 // Ignore event if only modifiers were pressed.
1124 //
1125
1126 // Some JVMs return 0 instead of CHAR_UNDEFINED in getKeyChar().
1127 if (keyChar == 0)
1128 keyChar = KeyEvent.CHAR_UNDEFINED;
1129
1130 if (keyChar == KeyEvent.CHAR_UNDEFINED) {
1131 int code = evt.getKeyCode();
1132 if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_SHIFT ||
1133 code == KeyEvent.VK_META || code == KeyEvent.VK_ALT)
1134 return;
1135 }
1136
1137 //
1138 // Key press or key release?
1139 //
1140
1141 boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
1142
1143 int key;
1144 if (evt.isActionKey()) {
1145
1146 //
1147 // An action key should be one of the following.
1148 // If not then just ignore the event.
1149 //
1150
1151 switch(evt.getKeyCode()) {
1152 case KeyEvent.VK_HOME: key = 0xff50; break;
1153 case KeyEvent.VK_LEFT: key = 0xff51; break;
1154 case KeyEvent.VK_UP: key = 0xff52; break;
1155 case KeyEvent.VK_RIGHT: key = 0xff53; break;
1156 case KeyEvent.VK_DOWN: key = 0xff54; break;
1157 case KeyEvent.VK_PAGE_UP: key = 0xff55; break;
1158 case KeyEvent.VK_PAGE_DOWN: key = 0xff56; break;
1159 case KeyEvent.VK_END: key = 0xff57; break;
1160 case KeyEvent.VK_INSERT: key = 0xff63; break;
1161 case KeyEvent.VK_F1: key = 0xffbe; break;
1162 case KeyEvent.VK_F2: key = 0xffbf; break;
1163 case KeyEvent.VK_F3: key = 0xffc0; break;
1164 case KeyEvent.VK_F4: key = 0xffc1; break;
1165 case KeyEvent.VK_F5: key = 0xffc2; break;
1166 case KeyEvent.VK_F6: key = 0xffc3; break;
1167 case KeyEvent.VK_F7: key = 0xffc4; break;
1168 case KeyEvent.VK_F8: key = 0xffc5; break;
1169 case KeyEvent.VK_F9: key = 0xffc6; break;
1170 case KeyEvent.VK_F10: key = 0xffc7; break;
1171 case KeyEvent.VK_F11: key = 0xffc8; break;
1172 case KeyEvent.VK_F12: key = 0xffc9; break;
1173 default:
1174 return;
1175 }
1176
1177 } else {
1178
1179 //
1180 // A "normal" key press. Ordinary ASCII characters go straight through.
1181 // For CTRL-<letter>, CTRL is sent separately so just send <letter>.
1182 // Backspace, tab, return, escape and delete have special keysyms.
1183 // Anything else we ignore.
1184 //
1185
1186 key = keyChar;
1187
1188 if (key < 0x20) {
1189 if (evt.isControlDown()) {
1190 key += 0x60;
1191 } else {
1192 switch(key) {
1193 case KeyEvent.VK_BACK_SPACE: key = 0xff08; break;
1194 case KeyEvent.VK_TAB: key = 0xff09; break;
1195 case KeyEvent.VK_ENTER: key = 0xff0d; break;
1196 case KeyEvent.VK_ESCAPE: key = 0xff1b; break;
1197 }
1198 }
1199 } else if (key == 0x7f) {
1200 // Delete
1201 key = 0xffff;
1202 } else if (key > 0xff) {
1203 // JDK1.1 on X incorrectly passes some keysyms straight through,
1204 // so we do too. JDK1.1.4 seems to have fixed this.
1205 // The keysyms passed are 0xff00 .. XK_BackSpace .. XK_Delete
1206 // Also, we pass through foreign currency keysyms (0x20a0..0x20af).
1207 if ((key < 0xff00 || key > 0xffff) &&
1208 !(key >= 0x20a0 && key <= 0x20af))
1209 return;
1210 }
1211 }
1212
1213 // Fake keyPresses for keys that only generates keyRelease events
1214 if ((key == 0xe5) || (key == 0xc5) || // XK_aring / XK_Aring
1215 (key == 0xe4) || (key == 0xc4) || // XK_adiaeresis / XK_Adiaeresis
1216 (key == 0xf6) || (key == 0xd6) || // XK_odiaeresis / XK_Odiaeresis
1217 (key == 0xa7) || (key == 0xbd) || // XK_section / XK_onehalf
1218 (key == 0xa3)) { // XK_sterling
1219 // Make sure we do not send keypress events twice on platforms
1220 // with correct JVMs (those that actually report KeyPress for all
enikey2f0294e2008-12-24 08:18:54 +00001221 // keys)
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001222 if (down)
1223 brokenKeyPressed = true;
1224
1225 if (!down && !brokenKeyPressed) {
1226 // We've got a release event for this key, but haven't received
enikey2f0294e2008-12-24 08:18:54 +00001227 // a press. Fake it.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001228 eventBufLen = 0;
1229 writeModifierKeyEvents(evt.getModifiers());
1230 writeKeyEvent(key, true);
1231 os.write(eventBuf, 0, eventBufLen);
1232 }
1233
1234 if (!down)
enikey2f0294e2008-12-24 08:18:54 +00001235 brokenKeyPressed = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001236 }
1237
1238 eventBufLen = 0;
1239 writeModifierKeyEvents(evt.getModifiers());
1240 writeKeyEvent(key, down);
1241
1242 // Always release all modifiers after an "up" event
1243 if (!down)
1244 writeModifierKeyEvents(0);
1245
1246 os.write(eventBuf, 0, eventBufLen);
1247 }
1248
1249
1250 //
1251 // Add a raw key event with the given X keysym to eventBuf.
1252 //
1253
1254 void writeKeyEvent(int keysym, boolean down) {
1255 eventBuf[eventBufLen++] = (byte) KeyboardEvent;
1256 eventBuf[eventBufLen++] = (byte) (down ? 1 : 0);
1257 eventBuf[eventBufLen++] = (byte) 0;
1258 eventBuf[eventBufLen++] = (byte) 0;
1259 eventBuf[eventBufLen++] = (byte) ((keysym >> 24) & 0xff);
1260 eventBuf[eventBufLen++] = (byte) ((keysym >> 16) & 0xff);
1261 eventBuf[eventBufLen++] = (byte) ((keysym >> 8) & 0xff);
1262 eventBuf[eventBufLen++] = (byte) (keysym & 0xff);
1263 }
1264
1265
1266 //
1267 // Write key events to set the correct modifier state.
1268 //
1269
1270 int oldModifiers = 0;
1271
1272 void writeModifierKeyEvents(int newModifiers) {
1273 if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
1274 writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
1275
1276 if ((newModifiers & SHIFT_MASK) != (oldModifiers & SHIFT_MASK))
1277 writeKeyEvent(0xffe1, (newModifiers & SHIFT_MASK) != 0);
1278
1279 if ((newModifiers & META_MASK) != (oldModifiers & META_MASK))
1280 writeKeyEvent(0xffe7, (newModifiers & META_MASK) != 0);
1281
1282 if ((newModifiers & ALT_MASK) != (oldModifiers & ALT_MASK))
1283 writeKeyEvent(0xffe9, (newModifiers & ALT_MASK) != 0);
1284
1285 oldModifiers = newModifiers;
1286 }
1287
1288
1289 //
1290 // Enable continuous updates for the specified area of the screen (but
1291 // only if EnableContinuousUpdates message is supported by the server).
1292 //
1293
1294 void tryEnableContinuousUpdates(int x, int y, int w, int h)
1295 throws IOException
1296 {
1297 if (!clientMsgCaps.isEnabled(EnableContinuousUpdates)) {
1298 System.out.println("Continuous updates not supported by the server");
1299 return;
1300 }
1301
1302 if (continuousUpdatesActive) {
1303 System.out.println("Continuous updates already active");
1304 return;
1305 }
1306
1307 byte[] b = new byte[10];
1308
1309 b[0] = (byte) EnableContinuousUpdates;
1310 b[1] = (byte) 1; // enable
1311 b[2] = (byte) ((x >> 8) & 0xff);
1312 b[3] = (byte) (x & 0xff);
1313 b[4] = (byte) ((y >> 8) & 0xff);
1314 b[5] = (byte) (y & 0xff);
1315 b[6] = (byte) ((w >> 8) & 0xff);
1316 b[7] = (byte) (w & 0xff);
1317 b[8] = (byte) ((h >> 8) & 0xff);
1318 b[9] = (byte) (h & 0xff);
1319
1320 os.write(b);
1321
1322 continuousUpdatesActive = true;
1323 System.out.println("Continuous updates activated");
1324 }
1325
1326
1327 //
1328 // Disable continuous updates (only if EnableContinuousUpdates message
1329 // is supported by the server).
1330 //
1331
1332 void tryDisableContinuousUpdates() throws IOException
1333 {
1334 if (!clientMsgCaps.isEnabled(EnableContinuousUpdates)) {
1335 System.out.println("Continuous updates not supported by the server");
1336 return;
1337 }
1338
1339 if (!continuousUpdatesActive) {
1340 System.out.println("Continuous updates already disabled");
1341 return;
1342 }
1343
1344 if (continuousUpdatesEnding)
1345 return;
1346
1347 byte[] b = { (byte)EnableContinuousUpdates, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1348 os.write(b);
1349
1350 if (!serverMsgCaps.isEnabled(EndOfContinuousUpdates)) {
1351 // If the server did not advertise support for the
1352 // EndOfContinuousUpdates message (should not normally happen
1353 // when EnableContinuousUpdates is supported), then we clear
1354 // 'continuousUpdatesActive' variable immediately. Normally,
1355 // it will be reset on receiving EndOfContinuousUpdates message
1356 // from the server.
1357 continuousUpdatesActive = false;
1358 } else {
1359 // Indicate that we are waiting for EndOfContinuousUpdates.
1360 continuousUpdatesEnding = true;
1361 }
1362 }
1363
1364
1365 //
1366 // Process EndOfContinuousUpdates message received from the server.
1367 //
1368
1369 void endOfContinuousUpdates()
1370 {
1371 continuousUpdatesActive = false;
1372 continuousUpdatesEnding = false;
1373 }
1374
1375
1376 //
1377 // Check if continuous updates are in effect.
1378 //
1379
1380 boolean continuousUpdatesAreActive()
1381 {
1382 return continuousUpdatesActive;
1383 }
1384
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001385 /**
1386 * Send a rectangle selection to be treated as video by the server (but
1387 * only if VideoRectangleSelection message is supported by the server).
1388 * @param rect specifies coordinates and size of the rectangule.
1389 * @throws java.io.IOException
1390 */
1391 void trySendVideoSelection(Rectangle rect) throws IOException
1392 {
1393 if (!clientMsgCaps.isEnabled(VideoRectangleSelection)) {
1394 System.out.println("Video area selection is not supported by the server");
1395 return;
1396 }
1397
Constantin Kaplinsky4f652852008-09-05 02:41:15 +00001398 // Send zero coordinates if the rectangle is empty.
1399 if (rect.isEmpty()) {
1400 rect = new Rectangle();
1401 }
1402
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001403 int x = rect.x;
1404 int y = rect.y;
1405 int w = rect.width;
1406 int h = rect.height;
enikey2f0294e2008-12-24 08:18:54 +00001407
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001408 byte[] b = new byte[10];
1409
1410 b[0] = (byte) VideoRectangleSelection;
1411 b[1] = (byte) 0; // reserved
1412 b[2] = (byte) ((x >> 8) & 0xff);
1413 b[3] = (byte) (x & 0xff);
1414 b[4] = (byte) ((y >> 8) & 0xff);
1415 b[5] = (byte) (y & 0xff);
1416 b[6] = (byte) ((w >> 8) & 0xff);
1417 b[7] = (byte) (w & 0xff);
1418 b[8] = (byte) ((h >> 8) & 0xff);
1419 b[9] = (byte) (h & 0xff);
1420
1421 os.write(b);
1422
1423 System.out.println("Video rectangle selection message sent");
1424 }
1425
enikeyd7653562008-12-25 11:02:56 +00001426 void trySendVideoFreeze(boolean freeze) throws IOException
1427 {
1428 if (!clientMsgCaps.isEnabled(VideoFreeze)) {
1429 System.out.println("Video freeze is not supported by the server");
1430 return;
1431 }
1432
1433 byte[] b = new byte[2];
1434 byte fb = 0;
1435 if (freeze) {
1436 fb = 1;
1437 }
1438
1439 b[0] = (byte) VideoFreeze;
1440 b[1] = (byte) fb;
1441
1442 os.write(b);
1443
1444 System.out.println("Video freeze selection message sent");
1445 }
1446
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001447 public void startTiming() {
1448 timing = true;
1449
1450 // Carry over up to 1s worth of previous rate for smoothing.
1451
1452 if (timeWaitedIn100us > 10000) {
1453 timedKbits = timedKbits * 10000 / timeWaitedIn100us;
1454 timeWaitedIn100us = 10000;
1455 }
1456 }
1457
1458 public void stopTiming() {
enikey2f0294e2008-12-24 08:18:54 +00001459 timing = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001460 if (timeWaitedIn100us < timedKbits/2)
1461 timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
1462 }
1463
1464 public long kbitsPerSecond() {
1465 return timedKbits * 10000 / timeWaitedIn100us;
1466 }
1467
1468 public long timeWaited() {
1469 return timeWaitedIn100us;
1470 }
1471
1472 //
1473 // Methods for reading data via our DataInputStream member variable (is).
1474 //
1475 // In addition to reading data, the readFully() methods updates variables
1476 // used to estimate data throughput.
1477 //
1478
1479 public void readFully(byte b[]) throws IOException {
1480 readFully(b, 0, b.length);
1481 }
1482
1483 public void readFully(byte b[], int off, int len) throws IOException {
1484 long before = 0;
1485 if (timing)
1486 before = System.currentTimeMillis();
1487
1488 is.readFully(b, off, len);
1489
1490 if (timing) {
1491 long after = System.currentTimeMillis();
1492 long newTimeWaited = (after - before) * 10;
1493 int newKbits = len * 8 / 1000;
1494
1495 // limit rate to between 10kbit/s and 40Mbit/s
1496
1497 if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
1498 if (newTimeWaited < newKbits/4) newTimeWaited = newKbits/4;
1499
1500 timeWaitedIn100us += newTimeWaited;
1501 timedKbits += newKbits;
1502 }
1503
1504 numBytesRead += len;
1505 }
1506
1507 final int available() throws IOException {
1508 return is.available();
1509 }
1510
1511 // FIXME: DataInputStream::skipBytes() is not guaranteed to skip
1512 // exactly n bytes. Probably we don't want to use this method.
1513 final int skipBytes(int n) throws IOException {
1514 int r = is.skipBytes(n);
1515 numBytesRead += r;
1516 return r;
1517 }
1518
1519 final int readU8() throws IOException {
1520 int r = is.readUnsignedByte();
1521 numBytesRead++;
1522 return r;
1523 }
1524
1525 final int readU16() throws IOException {
1526 int r = is.readUnsignedShort();
1527 numBytesRead += 2;
1528 return r;
1529 }
1530
1531 final int readU32() throws IOException {
1532 int r = is.readInt();
1533 numBytesRead += 4;
1534 return r;
1535 }
1536}