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