| 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 | } | 
|---|