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