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