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