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