Mercurial > hg > Members > nobuyasu > tightVNCProxy
annotate src/VncCanvas.java @ 13:a5d73cafc8fe
add ProxyVncCanvas and VncProxyService
author | e085711 |
---|---|
date | Sun, 17 Apr 2011 01:50:24 +0900 |
parents | 2869ca1579ae |
children | 89e1c5f84407 |
rev | line source |
---|---|
0 | 1 // |
2 // Copyright (C) 2004 Horizon Wimba. All Rights Reserved. | |
3 // Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved. | |
4 // Copyright (C) 2001,2002 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 import java.awt.*; | |
25 import java.awt.event.*; | |
26 import java.awt.image.*; | |
27 import java.io.*; | |
28 import java.lang.*; | |
29 import java.nio.ByteBuffer; | |
30 import java.util.zip.*; | |
31 | |
11 | 32 import java.net.Socket; |
33 | |
0 | 34 // |
35 // VncCanvas is a subclass of Canvas which draws a VNC desktop on it. | |
36 // | |
37 | |
4 | 38 class VncCanvas extends Canvas implements KeyListener, MouseListener, |
39 MouseMotionListener { | |
0 | 40 |
4 | 41 VncViewer viewer; |
8 | 42 MyRfbProto rfb; |
4 | 43 ColorModel cm8, cm24; |
44 Color[] colors; | |
45 int bytesPixel; | |
0 | 46 |
4 | 47 int maxWidth = 0, maxHeight = 0; |
48 int scalingFactor; | |
49 int scaledWidth, scaledHeight; | |
0 | 50 |
4 | 51 Image memImage; |
52 Graphics memGraphics; | |
0 | 53 |
4 | 54 Image rawPixelsImage; |
55 MemoryImageSource pixelsSource; | |
56 byte[] pixels8; | |
57 int[] pixels24; | |
0 | 58 |
4 | 59 // Update statistics. |
60 long statStartTime; // time on first framebufferUpdateRequest | |
61 int statNumUpdates; // counter for FramebufferUpdate messages | |
62 int statNumTotalRects; // rectangles in FramebufferUpdate messages | |
63 int statNumPixelRects; // the same, but excluding pseudo-rectangles | |
64 int statNumRectsTight; // Tight-encoded rectangles (including JPEG) | |
65 int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles | |
66 int statNumRectsZRLE; // ZRLE-encoded rectangles | |
67 int statNumRectsHextile; // Hextile-encoded rectangles | |
68 int statNumRectsRaw; // Raw-encoded rectangles | |
69 int statNumRectsCopy; // CopyRect rectangles | |
70 int statNumBytesEncoded; // number of bytes in updates, as received | |
71 int statNumBytesDecoded; // number of bytes, as if Raw encoding was used | |
0 | 72 |
4 | 73 // ZRLE encoder's data. |
74 byte[] zrleBuf; | |
75 int zrleBufLen = 0; | |
76 byte[] zrleTilePixels8; | |
77 int[] zrleTilePixels24; | |
78 ZlibInStream zrleInStream; | |
79 boolean zrleRecWarningShown = false; | |
0 | 80 |
4 | 81 // Zlib encoder's data. |
82 byte[] zlibBuf; | |
83 int zlibBufLen = 0; | |
84 Inflater zlibInflater; | |
0 | 85 |
4 | 86 // Tight encoder's data. |
87 final static int tightZlibBufferSize = 512; | |
88 Inflater[] tightInflaters; | |
0 | 89 |
4 | 90 // Since JPEG images are loaded asynchronously, we have to remember |
91 // their position in the framebuffer. Also, this jpegRect object is | |
92 // used for synchronization between the rfbThread and a JVM's thread | |
93 // which decodes and loads JPEG images. | |
94 Rectangle jpegRect; | |
0 | 95 |
4 | 96 // True if we process keyboard and mouse events. |
97 boolean inputEnabled; | |
0 | 98 |
4 | 99 // |
100 // The constructors. | |
101 // | |
0 | 102 |
4 | 103 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_) |
104 throws IOException { | |
0 | 105 |
4 | 106 viewer = v; |
107 maxWidth = maxWidth_; | |
108 maxHeight = maxHeight_; | |
0 | 109 |
4 | 110 rfb = viewer.rfb; |
111 scalingFactor = viewer.options.scalingFactor; | |
112 | |
113 tightInflaters = new Inflater[4]; | |
0 | 114 |
4 | 115 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); |
116 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); | |
0 | 117 |
4 | 118 colors = new Color[256]; |
119 for (int i = 0; i < 256; i++) | |
120 colors[i] = new Color(cm8.getRGB(i)); | |
121 | |
122 setPixelFormat(); | |
0 | 123 |
4 | 124 inputEnabled = false; |
125 if (!viewer.options.viewOnly) | |
126 enableInput(true); | |
127 | |
128 // Keyboard listener is enabled even in view-only mode, to catch | |
129 // 'r' or 'R' key presses used to request screen update. | |
130 addKeyListener(this); | |
0 | 131 } |
4 | 132 |
133 public VncCanvas(VncViewer v) throws IOException { | |
134 this(v, 0, 0); | |
135 } | |
0 | 136 |
4 | 137 // |
138 // Callback methods to determine geometry of our Component. | |
139 // | |
140 | |
141 public Dimension getPreferredSize() { | |
142 return new Dimension(scaledWidth, scaledHeight); | |
143 } | |
0 | 144 |
4 | 145 public Dimension getMinimumSize() { |
146 return new Dimension(scaledWidth, scaledHeight); | |
147 } | |
148 | |
149 public Dimension getMaximumSize() { | |
150 return new Dimension(scaledWidth, scaledHeight); | |
151 } | |
0 | 152 |
4 | 153 // |
154 // All painting is performed here. | |
155 // | |
0 | 156 |
4 | 157 public void update(Graphics g) { |
158 paint(g); | |
159 } | |
0 | 160 |
4 | 161 public void paint(Graphics g) { |
162 synchronized (memImage) { | |
163 if (rfb.framebufferWidth == scaledWidth) { | |
164 g.drawImage(memImage, 0, 0, null); | |
165 } else { | |
166 paintScaledFrameBuffer(g); | |
167 } | |
168 } | |
169 if (showSoftCursor) { | |
170 int x0 = cursorX - hotX, y0 = cursorY - hotY; | |
171 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight); | |
172 if (r.intersects(g.getClipBounds())) { | |
173 g.drawImage(softCursor, x0, y0, null); | |
174 } | |
175 } | |
176 } | |
0 | 177 |
4 | 178 public void paintScaledFrameBuffer(Graphics g) { |
179 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null); | |
180 } | |
0 | 181 |
4 | 182 // |
183 // Override the ImageObserver interface method to handle drawing of | |
184 // JPEG-encoded data. | |
185 // | |
0 | 186 |
4 | 187 public boolean imageUpdate(Image img, int infoflags, int x, int y, |
188 int width, int height) { | |
189 if ((infoflags & (ALLBITS | ABORT)) == 0) { | |
190 return true; // We need more image data. | |
191 } else { | |
192 // If the whole image is available, draw it now. | |
193 if ((infoflags & ALLBITS) != 0) { | |
194 if (jpegRect != null) { | |
195 synchronized (jpegRect) { | |
196 memGraphics | |
197 .drawImage(img, jpegRect.x, jpegRect.y, null); | |
198 scheduleRepaint(jpegRect.x, jpegRect.y, jpegRect.width, | |
199 jpegRect.height); | |
200 jpegRect.notify(); | |
201 } | |
202 } | |
203 } | |
204 return false; // All image data was processed. | |
205 } | |
206 } | |
0 | 207 |
4 | 208 // |
209 // Start/stop receiving mouse events. Keyboard events are received | |
210 // even in view-only mode, because we want to map the 'r' key to the | |
211 // screen refreshing function. | |
212 // | |
0 | 213 |
4 | 214 public synchronized void enableInput(boolean enable) { |
215 if (enable && !inputEnabled) { | |
216 inputEnabled = true; | |
217 addMouseListener(this); | |
218 addMouseMotionListener(this); | |
219 if (viewer.showControls) { | |
220 viewer.buttonPanel.enableRemoteAccessControls(true); | |
221 } | |
222 createSoftCursor(); // scaled cursor | |
223 } else if (!enable && inputEnabled) { | |
224 inputEnabled = false; | |
225 removeMouseListener(this); | |
226 removeMouseMotionListener(this); | |
227 if (viewer.showControls) { | |
228 viewer.buttonPanel.enableRemoteAccessControls(false); | |
229 } | |
230 createSoftCursor(); // non-scaled cursor | |
231 } | |
232 } | |
0 | 233 |
4 | 234 public void setPixelFormat() throws IOException { |
235 if (viewer.options.eightBitColors) { | |
236 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6); | |
237 bytesPixel = 1; | |
238 } else { | |
239 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, | |
240 0); | |
241 bytesPixel = 4; | |
242 } | |
243 updateFramebufferSize(); | |
244 } | |
0 | 245 |
4 | 246 void updateFramebufferSize() { |
0 | 247 |
4 | 248 // Useful shortcuts. |
249 int fbWidth = rfb.framebufferWidth; | |
250 int fbHeight = rfb.framebufferHeight; | |
0 | 251 |
4 | 252 // Calculate scaling factor for auto scaling. |
253 if (maxWidth > 0 && maxHeight > 0) { | |
254 int f1 = maxWidth * 100 / fbWidth; | |
255 int f2 = maxHeight * 100 / fbHeight; | |
256 scalingFactor = Math.min(f1, f2); | |
257 if (scalingFactor > 100) | |
258 scalingFactor = 100; | |
259 System.out.println("Scaling desktop at " + scalingFactor + "%"); | |
260 } | |
0 | 261 |
4 | 262 // Update scaled framebuffer geometry. |
263 scaledWidth = (fbWidth * scalingFactor + 50) / 100; | |
264 scaledHeight = (fbHeight * scalingFactor + 50) / 100; | |
0 | 265 |
4 | 266 // Create new off-screen image either if it does not exist, or if |
267 // its geometry should be changed. It's not necessary to replace | |
268 // existing image if only pixel format should be changed. | |
269 if (memImage == null) { | |
270 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); | |
271 memGraphics = memImage.getGraphics(); | |
272 } else if (memImage.getWidth(null) != fbWidth | |
273 || memImage.getHeight(null) != fbHeight) { | |
274 synchronized (memImage) { | |
275 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight); | |
276 memGraphics = memImage.getGraphics(); | |
277 } | |
278 } | |
0 | 279 |
4 | 280 // Images with raw pixels should be re-allocated on every change |
281 // of geometry or pixel format. | |
282 if (bytesPixel == 1) { | |
0 | 283 |
4 | 284 pixels24 = null; |
285 pixels8 = new byte[fbWidth * fbHeight]; | |
286 | |
287 pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm8, | |
288 pixels8, 0, fbWidth); | |
289 | |
290 zrleTilePixels24 = null; | |
291 zrleTilePixels8 = new byte[64 * 64]; | |
0 | 292 |
4 | 293 } else { |
0 | 294 |
4 | 295 pixels8 = null; |
296 pixels24 = new int[fbWidth * fbHeight]; | |
0 | 297 |
4 | 298 pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24, |
299 pixels24, 0, fbWidth); | |
0 | 300 |
4 | 301 zrleTilePixels8 = null; |
302 zrleTilePixels24 = new int[64 * 64]; | |
0 | 303 |
304 } | |
4 | 305 pixelsSource.setAnimated(true); |
306 rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource); | |
0 | 307 |
4 | 308 // Update the size of desktop containers. |
309 if (viewer.inSeparateFrame) { | |
310 if (viewer.desktopScrollPane != null) | |
311 resizeDesktopFrame(); | |
312 } else { | |
313 setSize(scaledWidth, scaledHeight); | |
314 } | |
315 viewer.moveFocusToDesktop(); | |
316 } | |
0 | 317 |
4 | 318 void resizeDesktopFrame() { |
319 setSize(scaledWidth, scaledHeight); | |
0 | 320 |
4 | 321 // FIXME: Find a better way to determine correct size of a |
322 // ScrollPane. -- const | |
323 Insets insets = viewer.desktopScrollPane.getInsets(); | |
324 viewer.desktopScrollPane.setSize( | |
325 scaledWidth + 2 * Math.min(insets.left, insets.right), | |
326 scaledHeight + 2 * Math.min(insets.top, insets.bottom)); | |
0 | 327 |
4 | 328 viewer.vncFrame.pack(); |
0 | 329 |
4 | 330 // Try to limit the frame size to the screen size. |
0 | 331 |
4 | 332 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize(); |
333 Dimension frameSize = viewer.vncFrame.getSize(); | |
334 Dimension newSize = frameSize; | |
335 | |
336 // Reduce Screen Size by 30 pixels in each direction; | |
337 // This is a (poor) attempt to account for | |
338 // 1) Menu bar on Macintosh (should really also account for | |
339 // Dock on OSX). Usually 22px on top of screen. | |
340 // 2) Taxkbar on Windows (usually about 28 px on bottom) | |
341 // 3) Other obstructions. | |
342 | |
343 screenSize.height -= 30; | |
344 screenSize.width -= 30; | |
0 | 345 |
4 | 346 boolean needToResizeFrame = false; |
347 if (frameSize.height > screenSize.height) { | |
348 newSize.height = screenSize.height; | |
349 needToResizeFrame = true; | |
350 } | |
351 if (frameSize.width > screenSize.width) { | |
352 newSize.width = screenSize.width; | |
353 needToResizeFrame = true; | |
354 } | |
355 if (needToResizeFrame) { | |
356 viewer.vncFrame.setSize(newSize); | |
357 } | |
0 | 358 |
4 | 359 viewer.desktopScrollPane.doLayout(); |
0 | 360 } |
361 | |
4 | 362 // |
363 // processNormalProtocol() - executed by the rfbThread to deal with the | |
364 // RFB socket. | |
365 // | |
366 | |
367 public void processNormalProtocol() throws Exception { | |
368 | |
369 // Start/stop session recording if necessary. | |
370 viewer.checkRecordingStatus(); | |
371 | |
372 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth, | |
373 rfb.framebufferHeight, false); | |
374 | |
375 resetStats(); | |
376 boolean statsRestarted = false; | |
377 | |
378 // | |
379 // main dispatch loop | |
380 // | |
381 | |
382 long count = 0; | |
13 | 383 rfb.initServSock(5550); |
11 | 384 try { |
13 | 385 // rfb.setSoTimeout(1000); |
386 Socket newCli = rfb.accept(); | |
387 rfb.sendInitData(newCli); | |
388 rfb.addSock(newCli); | |
389 } catch (IOException e) { | |
11 | 390 } |
13 | 391 /* |
392 * Thread accept = new Thread(new acceptThread(rfb)); accept.start(); | |
393 */ | |
394 while (true) { | |
11 | 395 |
4 | 396 if (rfb.MYVNC) { |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
397 if (rfb.cliSize() > 0) { |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
398 System.out.println("\ncount=" + count); |
6 | 399 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
400 int nBytes = 0; |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
401 rfb.mark(20); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
402 int msgType = rfb.readU8(); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
403 System.out.println("msgType=" + msgType); |
6 | 404 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
405 rfb.skipBytes(11); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
406 int encoding = rfb.readU32(); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
407 System.out.println("encoding=" + encoding); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
408 nBytes = rfb.readU32(); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
409 System.out.println("nBytes=" + nBytes); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
410 rfb.reset(); |
4 | 411 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
412 int len = rfb.available(); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
413 System.out.println("rfb.available()=" + len); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
414 if (len > 0) { |
4 | 415 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
416 if (nBytes > 0 & encoding == 16) {// 0より大きい(データがある)ときデータを転送 |
4 | 417 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
418 rfb.mark(nBytes + 20); |
4 | 419 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
420 byte b[] = new byte[nBytes + 20]; |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
421 // byte b[] = new byte[18+rfb.rnBytes]; |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
422 System.out.println("b.length=" + b.length); |
4 | 423 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
424 rfb.readFully(b); |
4 | 425 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
426 // rfb.cliSock.getOutputStream().write(b, 0, |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
427 // b.length); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
428 rfb.sendData(b); |
0 | 429 |
9
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
430 try { |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
431 rfb.reset(); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
432 } catch (IOException e) { |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
433 System.out.println(e); |
c0ad3ecdf827
create method cliSize(). cliSize() is clisList.size()
e085711
parents:
8
diff
changeset
|
434 } |
4 | 435 } |
436 } | |
437 } | |
438 } | |
439 count++; | |
440 | |
441 // Read message type from the server. | |
442 int msgType = rfb.readServerMessageType(); | |
443 | |
444 // Process the message depending on its type. | |
445 switch (msgType) { | |
446 case RfbProto.FramebufferUpdate: | |
447 | |
448 if (statNumUpdates == viewer.debugStatsExcludeUpdates | |
449 && !statsRestarted) { | |
450 resetStats(); | |
451 statsRestarted = true; | |
452 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates | |
453 && statsRestarted) { | |
454 viewer.disconnect(); | |
455 } | |
456 | |
457 rfb.readFramebufferUpdate(); | |
458 statNumUpdates++; | |
459 | |
460 boolean cursorPosReceived = false; | |
461 | |
462 for (int i = 0; i < rfb.updateNRects; i++) { | |
463 | |
464 rfb.readFramebufferUpdateRectHdr(); | |
465 statNumTotalRects++; | |
466 int rx = rfb.updateRectX, ry = rfb.updateRectY; | |
467 int rw = rfb.updateRectW, rh = rfb.updateRectH; | |
468 | |
469 if (rfb.updateRectEncoding == rfb.EncodingLastRect) | |
470 break; | |
471 | |
472 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) { | |
473 rfb.setFramebufferSize(rw, rh); | |
474 updateFramebufferSize(); | |
475 break; | |
476 } | |
477 | |
478 if (rfb.updateRectEncoding == rfb.EncodingXCursor | |
479 || rfb.updateRectEncoding == rfb.EncodingRichCursor) { | |
480 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, | |
481 rw, rh); | |
482 continue; | |
483 } | |
484 | |
485 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) { | |
486 softCursorMove(rx, ry); | |
487 cursorPosReceived = true; | |
488 continue; | |
489 } | |
490 | |
491 long numBytesReadBefore = rfb.getNumBytesRead(); | |
492 | |
493 rfb.startTiming(); | |
0 | 494 |
4 | 495 switch (rfb.updateRectEncoding) { |
496 case RfbProto.EncodingRaw: | |
497 statNumRectsRaw++; | |
498 handleRawRect(rx, ry, rw, rh); | |
499 break; | |
500 case RfbProto.EncodingCopyRect: | |
501 statNumRectsCopy++; | |
502 handleCopyRect(rx, ry, rw, rh); | |
503 break; | |
504 case RfbProto.EncodingRRE: | |
505 handleRRERect(rx, ry, rw, rh); | |
506 break; | |
507 case RfbProto.EncodingCoRRE: | |
508 handleCoRRERect(rx, ry, rw, rh); | |
509 break; | |
510 case RfbProto.EncodingHextile: | |
511 statNumRectsHextile++; | |
512 handleHextileRect(rx, ry, rw, rh); | |
513 break; | |
514 case RfbProto.EncodingZRLE: | |
515 statNumRectsZRLE++; | |
516 handleZRLERect(rx, ry, rw, rh); | |
517 break; | |
518 case RfbProto.EncodingZlib: | |
519 handleZlibRect(rx, ry, rw, rh); | |
520 break; | |
521 case RfbProto.EncodingTight: | |
522 statNumRectsTight++; | |
523 handleTightRect(rx, ry, rw, rh); | |
524 break; | |
525 default: | |
526 throw new Exception("Unknown RFB rectangle encoding " | |
527 + rfb.updateRectEncoding); | |
528 } | |
529 | |
530 rfb.stopTiming(); | |
531 | |
532 statNumPixelRects++; | |
533 statNumBytesDecoded += rw * rh * bytesPixel; | |
534 statNumBytesEncoded += (int) (rfb.getNumBytesRead() - numBytesReadBefore); | |
535 } | |
536 | |
537 boolean fullUpdateNeeded = false; | |
538 | |
539 // Start/stop session recording if necessary. Request full | |
540 // update if a new session file was opened. | |
541 if (viewer.checkRecordingStatus()) | |
542 fullUpdateNeeded = true; | |
543 | |
544 // Defer framebuffer update request if necessary. But wake up | |
545 // immediately on keyboard or mouse event. Also, don't sleep | |
546 // if there is some data to receive, or if the last update | |
547 // included a PointerPos message. | |
548 if (viewer.deferUpdateRequests > 0 && rfb.available() == 0 | |
549 && !cursorPosReceived) { | |
550 synchronized (rfb) { | |
551 try { | |
552 rfb.wait(viewer.deferUpdateRequests); | |
553 } catch (InterruptedException e) { | |
554 } | |
555 } | |
556 } | |
557 | |
558 viewer.autoSelectEncodings(); | |
559 | |
560 // Before requesting framebuffer update, check if the pixel | |
561 // format should be changed. | |
562 if (viewer.options.eightBitColors != (bytesPixel == 1)) { | |
563 // Pixel format should be changed. | |
564 setPixelFormat(); | |
565 fullUpdateNeeded = true; | |
566 } | |
567 | |
568 // Request framebuffer update if needed. | |
569 int w = rfb.framebufferWidth; | |
570 int h = rfb.framebufferHeight; | |
571 rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded); | |
572 | |
573 break; | |
574 | |
575 case RfbProto.SetColourMapEntries: | |
576 throw new Exception("Can't handle SetColourMapEntries message"); | |
577 | |
578 case RfbProto.Bell: | |
579 Toolkit.getDefaultToolkit().beep(); | |
580 break; | |
581 | |
582 case RfbProto.ServerCutText: | |
583 String s = rfb.readServerCutText(); | |
584 viewer.clipboard.setCutText(s); | |
585 break; | |
586 | |
587 default: | |
588 throw new Exception("Unknown RFB message type " + msgType); | |
589 } | |
590 | |
591 } | |
592 } | |
593 | |
594 // | |
595 // Handle a raw rectangle. The second form with paint==false is used | |
596 // by the Hextile decoder for raw-encoded tiles. | |
597 // | |
598 | |
599 void handleRawRect(int x, int y, int w, int h) throws IOException { | |
600 handleRawRect(x, y, w, h, true); | |
601 } | |
602 | |
603 void handleRawRect(int x, int y, int w, int h, boolean paint) | |
604 throws IOException { | |
605 | |
606 if (bytesPixel == 1) { | |
607 for (int dy = y; dy < y + h; dy++) { | |
608 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); | |
609 if (rfb.rec != null) { | |
610 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); | |
611 } | |
612 } | |
613 } else { | |
614 byte[] buf = new byte[w * 4]; | |
615 int i, offset; | |
616 for (int dy = y; dy < y + h; dy++) { | |
617 rfb.readFully(buf); | |
618 if (rfb.rec != null) { | |
619 rfb.rec.write(buf); | |
620 } | |
621 offset = dy * rfb.framebufferWidth + x; | |
622 for (i = 0; i < w; i++) { | |
623 pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16 | |
624 | (buf[i * 4 + 1] & 0xFF) << 8 | |
625 | (buf[i * 4] & 0xFF); | |
626 } | |
627 } | |
628 } | |
629 | |
630 handleUpdatedPixels(x, y, w, h); | |
631 if (paint) | |
632 scheduleRepaint(x, y, w, h); | |
633 } | |
634 | |
635 // | |
636 // Handle a CopyRect rectangle. | |
637 // | |
638 | |
639 void handleCopyRect(int x, int y, int w, int h) throws IOException { | |
640 | |
641 rfb.readCopyRect(); | |
642 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h, x | |
643 - rfb.copyRectSrcX, y - rfb.copyRectSrcY); | |
644 | |
645 scheduleRepaint(x, y, w, h); | |
0 | 646 } |
647 | |
4 | 648 // |
649 // Handle an RRE-encoded rectangle. | |
650 // | |
651 | |
652 void handleRRERect(int x, int y, int w, int h) throws IOException { | |
653 | |
654 int nSubrects = rfb.readU32(); | |
655 | |
656 byte[] bg_buf = new byte[bytesPixel]; | |
657 rfb.readFully(bg_buf); | |
658 Color pixel; | |
659 if (bytesPixel == 1) { | |
660 pixel = colors[bg_buf[0] & 0xFF]; | |
661 } else { | |
662 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, | |
663 bg_buf[0] & 0xFF); | |
664 } | |
665 memGraphics.setColor(pixel); | |
666 memGraphics.fillRect(x, y, w, h); | |
667 | |
668 byte[] buf = new byte[nSubrects * (bytesPixel + 8)]; | |
669 rfb.readFully(buf); | |
670 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf)); | |
671 | |
672 if (rfb.rec != null) { | |
673 rfb.rec.writeIntBE(nSubrects); | |
674 rfb.rec.write(bg_buf); | |
675 rfb.rec.write(buf); | |
676 } | |
677 | |
678 int sx, sy, sw, sh; | |
679 | |
680 for (int j = 0; j < nSubrects; j++) { | |
681 if (bytesPixel == 1) { | |
682 pixel = colors[ds.readUnsignedByte()]; | |
683 } else { | |
684 ds.skip(4); | |
685 pixel = new Color(buf[j * 12 + 2] & 0xFF, | |
686 buf[j * 12 + 1] & 0xFF, buf[j * 12] & 0xFF); | |
687 } | |
688 sx = x + ds.readUnsignedShort(); | |
689 sy = y + ds.readUnsignedShort(); | |
690 sw = ds.readUnsignedShort(); | |
691 sh = ds.readUnsignedShort(); | |
692 | |
693 memGraphics.setColor(pixel); | |
694 memGraphics.fillRect(sx, sy, sw, sh); | |
695 } | |
696 | |
697 scheduleRepaint(x, y, w, h); | |
698 } | |
699 | |
700 // | |
701 // Handle a CoRRE-encoded rectangle. | |
702 // | |
703 | |
704 void handleCoRRERect(int x, int y, int w, int h) throws IOException { | |
705 int nSubrects = rfb.readU32(); | |
706 | |
707 byte[] bg_buf = new byte[bytesPixel]; | |
708 rfb.readFully(bg_buf); | |
709 Color pixel; | |
710 if (bytesPixel == 1) { | |
711 pixel = colors[bg_buf[0] & 0xFF]; | |
712 } else { | |
713 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, | |
714 bg_buf[0] & 0xFF); | |
715 } | |
716 memGraphics.setColor(pixel); | |
717 memGraphics.fillRect(x, y, w, h); | |
718 | |
719 byte[] buf = new byte[nSubrects * (bytesPixel + 4)]; | |
720 rfb.readFully(buf); | |
721 | |
722 if (rfb.rec != null) { | |
723 rfb.rec.writeIntBE(nSubrects); | |
724 rfb.rec.write(bg_buf); | |
725 rfb.rec.write(buf); | |
726 } | |
727 | |
728 int sx, sy, sw, sh; | |
729 int i = 0; | |
730 | |
731 for (int j = 0; j < nSubrects; j++) { | |
732 if (bytesPixel == 1) { | |
733 pixel = colors[buf[i++] & 0xFF]; | |
734 } else { | |
735 pixel = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF, | |
736 buf[i] & 0xFF); | |
737 i += 4; | |
738 } | |
739 sx = x + (buf[i++] & 0xFF); | |
740 sy = y + (buf[i++] & 0xFF); | |
741 sw = buf[i++] & 0xFF; | |
742 sh = buf[i++] & 0xFF; | |
743 | |
744 memGraphics.setColor(pixel); | |
745 memGraphics.fillRect(sx, sy, sw, sh); | |
746 } | |
747 | |
748 scheduleRepaint(x, y, w, h); | |
749 } | |
750 | |
751 // | |
752 // Handle a Hextile-encoded rectangle. | |
753 // | |
754 | |
755 // These colors should be kept between handleHextileSubrect() calls. | |
756 private Color hextile_bg, hextile_fg; | |
757 | |
758 void handleHextileRect(int x, int y, int w, int h) throws IOException { | |
759 | |
760 hextile_bg = new Color(0); | |
761 hextile_fg = new Color(0); | |
762 | |
763 for (int ty = y; ty < y + h; ty += 16) { | |
764 int th = 16; | |
765 if (y + h - ty < 16) | |
766 th = y + h - ty; | |
767 | |
768 for (int tx = x; tx < x + w; tx += 16) { | |
769 int tw = 16; | |
770 if (x + w - tx < 16) | |
771 tw = x + w - tx; | |
772 | |
773 handleHextileSubrect(tx, ty, tw, th); | |
774 } | |
775 | |
776 // Finished with a row of tiles, now let's show it. | |
777 scheduleRepaint(x, y, w, h); | |
778 } | |
779 } | |
780 | |
781 // | |
782 // Handle one tile in the Hextile-encoded data. | |
783 // | |
784 | |
785 void handleHextileSubrect(int tx, int ty, int tw, int th) | |
786 throws IOException { | |
787 | |
788 int subencoding = rfb.readU8(); | |
789 if (rfb.rec != null) { | |
790 rfb.rec.writeByte(subencoding); | |
791 } | |
792 | |
793 // Is it a raw-encoded sub-rectangle? | |
794 if ((subencoding & rfb.HextileRaw) != 0) { | |
795 handleRawRect(tx, ty, tw, th, false); | |
796 return; | |
797 } | |
0 | 798 |
4 | 799 // Read and draw the background if specified. |
800 byte[] cbuf = new byte[bytesPixel]; | |
801 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) { | |
802 rfb.readFully(cbuf); | |
803 if (bytesPixel == 1) { | |
804 hextile_bg = colors[cbuf[0] & 0xFF]; | |
805 } else { | |
806 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, | |
807 cbuf[0] & 0xFF); | |
808 } | |
809 if (rfb.rec != null) { | |
810 rfb.rec.write(cbuf); | |
811 } | |
812 } | |
813 memGraphics.setColor(hextile_bg); | |
814 memGraphics.fillRect(tx, ty, tw, th); | |
815 | |
816 // Read the foreground color if specified. | |
817 if ((subencoding & rfb.HextileForegroundSpecified) != 0) { | |
818 rfb.readFully(cbuf); | |
819 if (bytesPixel == 1) { | |
820 hextile_fg = colors[cbuf[0] & 0xFF]; | |
821 } else { | |
822 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, | |
823 cbuf[0] & 0xFF); | |
824 } | |
825 if (rfb.rec != null) { | |
826 rfb.rec.write(cbuf); | |
827 } | |
828 } | |
829 | |
830 // Done with this tile if there is no sub-rectangles. | |
831 if ((subencoding & rfb.HextileAnySubrects) == 0) | |
832 return; | |
833 | |
834 int nSubrects = rfb.readU8(); | |
835 int bufsize = nSubrects * 2; | |
836 if ((subencoding & rfb.HextileSubrectsColoured) != 0) { | |
837 bufsize += nSubrects * bytesPixel; | |
838 } | |
839 byte[] buf = new byte[bufsize]; | |
840 rfb.readFully(buf); | |
841 if (rfb.rec != null) { | |
842 rfb.rec.writeByte(nSubrects); | |
843 rfb.rec.write(buf); | |
844 } | |
845 | |
846 int b1, b2, sx, sy, sw, sh; | |
847 int i = 0; | |
848 | |
849 if ((subencoding & rfb.HextileSubrectsColoured) == 0) { | |
850 | |
851 // Sub-rectangles are all of the same color. | |
852 memGraphics.setColor(hextile_fg); | |
853 for (int j = 0; j < nSubrects; j++) { | |
854 b1 = buf[i++] & 0xFF; | |
855 b2 = buf[i++] & 0xFF; | |
856 sx = tx + (b1 >> 4); | |
857 sy = ty + (b1 & 0xf); | |
858 sw = (b2 >> 4) + 1; | |
859 sh = (b2 & 0xf) + 1; | |
860 memGraphics.fillRect(sx, sy, sw, sh); | |
861 } | |
862 } else if (bytesPixel == 1) { | |
863 | |
864 // BGR233 (8-bit color) version for colored sub-rectangles. | |
865 for (int j = 0; j < nSubrects; j++) { | |
866 hextile_fg = colors[buf[i++] & 0xFF]; | |
867 b1 = buf[i++] & 0xFF; | |
868 b2 = buf[i++] & 0xFF; | |
869 sx = tx + (b1 >> 4); | |
870 sy = ty + (b1 & 0xf); | |
871 sw = (b2 >> 4) + 1; | |
872 sh = (b2 & 0xf) + 1; | |
873 memGraphics.setColor(hextile_fg); | |
874 memGraphics.fillRect(sx, sy, sw, sh); | |
875 } | |
876 | |
877 } else { | |
878 | |
879 // Full-color (24-bit) version for colored sub-rectangles. | |
880 for (int j = 0; j < nSubrects; j++) { | |
881 hextile_fg = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF, | |
882 buf[i] & 0xFF); | |
883 i += 4; | |
884 b1 = buf[i++] & 0xFF; | |
885 b2 = buf[i++] & 0xFF; | |
886 sx = tx + (b1 >> 4); | |
887 sy = ty + (b1 & 0xf); | |
888 sw = (b2 >> 4) + 1; | |
889 sh = (b2 & 0xf) + 1; | |
890 memGraphics.setColor(hextile_fg); | |
891 memGraphics.fillRect(sx, sy, sw, sh); | |
892 } | |
893 | |
894 } | |
895 } | |
896 | |
897 // | |
898 // Handle a ZRLE-encoded rectangle. | |
899 // | |
900 // FIXME: Currently, session recording is not fully supported for ZRLE. | |
901 // | |
902 | |
903 void handleZRLERect(int x, int y, int w, int h) throws Exception { | |
904 | |
905 if (zrleInStream == null) | |
906 zrleInStream = new ZlibInStream(); | |
907 | |
908 int nBytes = rfb.readU32(); | |
909 if (nBytes > 64 * 1024 * 1024) | |
910 throw new Exception("ZRLE decoder: illegal compressed data size"); | |
911 | |
912 if (zrleBuf == null || zrleBufLen < nBytes) { | |
913 zrleBufLen = nBytes + 4096; | |
914 zrleBuf = new byte[zrleBufLen]; | |
915 } | |
916 | |
917 // FIXME: Do not wait for all the data before decompression. | |
918 rfb.readFully(zrleBuf, 0, nBytes); | |
919 | |
920 if (rfb.rec != null) { | |
921 if (rfb.recordFromBeginning) { | |
922 rfb.rec.writeIntBE(nBytes); | |
923 rfb.rec.write(zrleBuf, 0, nBytes); | |
924 } else if (!zrleRecWarningShown) { | |
925 System.out.println("Warning: ZRLE session can be recorded" | |
926 + " only from the beginning"); | |
927 System.out.println("Warning: Recorded file may be corrupted"); | |
928 zrleRecWarningShown = true; | |
929 } | |
930 | |
931 } | |
932 | |
933 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes); | |
934 | |
935 for (int ty = y; ty < y + h; ty += 64) { | |
936 | |
937 int th = Math.min(y + h - ty, 64); | |
938 | |
939 for (int tx = x; tx < x + w; tx += 64) { | |
940 | |
941 int tw = Math.min(x + w - tx, 64); | |
942 | |
943 int mode = zrleInStream.readU8(); | |
944 boolean rle = (mode & 128) != 0; | |
945 int palSize = mode & 127; | |
946 int[] palette = new int[128]; | |
947 | |
948 readZrlePalette(palette, palSize); | |
949 | |
950 if (palSize == 1) { | |
951 int pix = palette[0]; | |
952 Color c = (bytesPixel == 1) ? colors[pix] : new Color( | |
953 0xFF000000 | pix); | |
954 memGraphics.setColor(c); | |
955 memGraphics.fillRect(tx, ty, tw, th); | |
956 continue; | |
957 } | |
958 | |
959 if (!rle) { | |
960 if (palSize == 0) { | |
961 readZrleRawPixels(tw, th); | |
962 } else { | |
963 readZrlePackedPixels(tw, th, palette, palSize); | |
964 } | |
965 } else { | |
966 if (palSize == 0) { | |
967 readZrlePlainRLEPixels(tw, th); | |
968 } else { | |
969 readZrlePackedRLEPixels(tw, th, palette); | |
970 } | |
971 } | |
972 handleUpdatedZrleTile(tx, ty, tw, th); | |
973 } | |
974 } | |
975 | |
976 zrleInStream.reset(); | |
977 | |
978 scheduleRepaint(x, y, w, h); | |
0 | 979 } |
980 | |
4 | 981 int readPixel(InStream is) throws Exception { |
982 int pix; | |
0 | 983 |
4 | 984 if (bytesPixel == 1) { |
0 | 985 |
4 | 986 pix = is.readU8(); |
987 } else { | |
988 int p1 = is.readU8(); | |
989 int p2 = is.readU8(); | |
990 int p3 = is.readU8(); | |
991 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF); | |
992 } | |
993 return pix; | |
994 } | |
0 | 995 |
4 | 996 void readPixels(InStream is, int[] dst, int count) throws Exception { |
997 int pix; | |
998 if (bytesPixel == 1) { | |
999 byte[] buf = new byte[count]; | |
1000 is.readBytes(buf, 0, count); | |
1001 for (int i = 0; i < count; i++) { | |
1002 dst[i] = (int) buf[i] & 0xFF; | |
1003 } | |
1004 } else { | |
1005 byte[] buf = new byte[count * 3]; | |
1006 is.readBytes(buf, 0, count * 3); | |
1007 for (int i = 0; i < count; i++) { | |
1008 dst[i] = ((buf[i * 3 + 2] & 0xFF) << 16 | |
1009 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3] & 0xFF)); | |
1010 } | |
1011 } | |
0 | 1012 } |
4 | 1013 |
1014 void readZrlePalette(int[] palette, int palSize) throws Exception { | |
1015 readPixels(zrleInStream, palette, palSize); | |
0 | 1016 } |
4 | 1017 |
1018 void readZrleRawPixels(int tw, int th) throws Exception { | |
1019 if (bytesPixel == 1) { | |
1020 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th); | |
1021 } else { | |
1022 readPixels(zrleInStream, zrleTilePixels24, tw * th); // / | |
1023 } | |
0 | 1024 } |
1025 | |
4 | 1026 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) |
1027 throws Exception { | |
0 | 1028 |
4 | 1029 int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 |
1030 : ((palSize > 2) ? 2 : 1))); | |
1031 int ptr = 0; | |
0 | 1032 |
4 | 1033 for (int i = 0; i < th; i++) { |
1034 int eol = ptr + tw; | |
1035 int b = 0; | |
1036 int nbits = 0; | |
0 | 1037 |
4 | 1038 while (ptr < eol) { |
1039 if (nbits == 0) { | |
1040 b = zrleInStream.readU8(); | |
1041 nbits = 8; | |
1042 } | |
1043 nbits -= bppp; | |
1044 int index = (b >> nbits) & ((1 << bppp) - 1) & 127; | |
1045 if (bytesPixel == 1) { | |
1046 zrleTilePixels8[ptr++] = (byte) palette[index]; | |
1047 } else { | |
1048 zrleTilePixels24[ptr++] = palette[index]; | |
1049 } | |
1050 } | |
1051 } | |
1052 } | |
0 | 1053 |
4 | 1054 void readZrlePlainRLEPixels(int tw, int th) throws Exception { |
1055 int ptr = 0; | |
1056 int end = ptr + tw * th; | |
1057 while (ptr < end) { | |
1058 int pix = readPixel(zrleInStream); | |
1059 int len = 1; | |
1060 int b; | |
1061 do { | |
1062 b = zrleInStream.readU8(); | |
1063 len += b; | |
1064 } while (b == 255); | |
0 | 1065 |
4 | 1066 if (!(len <= end - ptr)) |
1067 throw new Exception("ZRLE decoder: assertion failed" | |
1068 + " (len <= end-ptr)"); | |
0 | 1069 |
4 | 1070 if (bytesPixel == 1) { |
1071 while (len-- > 0) | |
1072 zrleTilePixels8[ptr++] = (byte) pix; | |
1073 } else { | |
1074 while (len-- > 0) | |
1075 zrleTilePixels24[ptr++] = pix; | |
1076 } | |
1077 } | |
1078 } | |
0 | 1079 |
4 | 1080 void readZrlePackedRLEPixels(int tw, int th, int[] palette) |
1081 throws Exception { | |
0 | 1082 |
4 | 1083 int ptr = 0; |
1084 int end = ptr + tw * th; | |
1085 while (ptr < end) { | |
1086 int index = zrleInStream.readU8(); | |
1087 int len = 1; | |
1088 if ((index & 128) != 0) { | |
1089 int b; | |
1090 do { | |
1091 b = zrleInStream.readU8(); | |
1092 len += b; | |
1093 } while (b == 255); | |
0 | 1094 |
4 | 1095 if (!(len <= end - ptr)) |
1096 throw new Exception("ZRLE decoder: assertion failed" | |
1097 + " (len <= end - ptr)"); | |
1098 } | |
0 | 1099 |
4 | 1100 index &= 127; |
1101 int pix = palette[index]; | |
0 | 1102 |
4 | 1103 if (bytesPixel == 1) { |
1104 while (len-- > 0) | |
1105 zrleTilePixels8[ptr++] = (byte) pix; | |
1106 } else { | |
1107 while (len-- > 0) | |
1108 zrleTilePixels24[ptr++] = pix; | |
1109 } | |
1110 } | |
1111 } | |
0 | 1112 |
4 | 1113 // |
1114 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update. | |
1115 // | |
0 | 1116 |
4 | 1117 void handleUpdatedZrleTile(int x, int y, int w, int h) { |
1118 Object src, dst; | |
1119 if (bytesPixel == 1) { | |
1120 src = zrleTilePixels8; | |
1121 dst = pixels8; | |
1122 } else { | |
1123 src = zrleTilePixels24; | |
1124 dst = pixels24; | |
1125 } | |
1126 int offsetSrc = 0; | |
1127 int offsetDst = (y * rfb.framebufferWidth + x); | |
1128 for (int j = 0; j < h; j++) { | |
1129 System.arraycopy(src, offsetSrc, dst, offsetDst, w); | |
1130 offsetSrc += w; | |
1131 offsetDst += rfb.framebufferWidth; | |
1132 } | |
1133 handleUpdatedPixels(x, y, w, h); | |
1134 } | |
0 | 1135 |
4 | 1136 // |
1137 // Handle a Zlib-encoded rectangle. | |
1138 // | |
1139 | |
1140 void handleZlibRect(int x, int y, int w, int h) throws Exception { | |
1141 | |
1142 int nBytes = rfb.readU32(); | |
0 | 1143 |
4 | 1144 if (zlibBuf == null || zlibBufLen < nBytes) { |
1145 zlibBufLen = nBytes * 2; | |
1146 zlibBuf = new byte[zlibBufLen]; | |
1147 } | |
1148 | |
1149 rfb.readFully(zlibBuf, 0, nBytes); | |
1150 | |
1151 if (rfb.rec != null && rfb.recordFromBeginning) { | |
1152 rfb.rec.writeIntBE(nBytes); | |
1153 rfb.rec.write(zlibBuf, 0, nBytes); | |
1154 } | |
0 | 1155 |
4 | 1156 if (zlibInflater == null) { |
1157 zlibInflater = new Inflater(); | |
1158 } | |
1159 zlibInflater.setInput(zlibBuf, 0, nBytes); | |
0 | 1160 |
4 | 1161 if (bytesPixel == 1) { |
1162 for (int dy = y; dy < y + h; dy++) { | |
1163 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w); | |
1164 if (rfb.rec != null && !rfb.recordFromBeginning) | |
1165 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w); | |
1166 } | |
1167 } else { | |
1168 byte[] buf = new byte[w * 4]; | |
1169 int i, offset; | |
1170 for (int dy = y; dy < y + h; dy++) { | |
1171 zlibInflater.inflate(buf); | |
1172 offset = dy * rfb.framebufferWidth + x; | |
1173 for (i = 0; i < w; i++) { | |
1174 pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16 | |
1175 | (buf[i * 4 + 1] & 0xFF) << 8 | |
1176 | (buf[i * 4] & 0xFF); | |
1177 } | |
1178 if (rfb.rec != null && !rfb.recordFromBeginning) | |
1179 rfb.rec.write(buf); | |
1180 } | |
1181 } | |
0 | 1182 |
4 | 1183 handleUpdatedPixels(x, y, w, h); |
1184 scheduleRepaint(x, y, w, h); | |
1185 } | |
0 | 1186 |
4 | 1187 // |
1188 // Handle a Tight-encoded rectangle. | |
1189 // | |
0 | 1190 |
4 | 1191 void handleTightRect(int x, int y, int w, int h) throws Exception { |
0 | 1192 |
4 | 1193 int comp_ctl = rfb.readU8(); |
1194 if (rfb.rec != null) { | |
1195 if (rfb.recordFromBeginning || comp_ctl == (rfb.TightFill << 4) | |
1196 || comp_ctl == (rfb.TightJpeg << 4)) { | |
1197 // Send data exactly as received. | |
1198 rfb.rec.writeByte(comp_ctl); | |
1199 } else { | |
1200 // Tell the decoder to flush each of the four zlib streams. | |
1201 rfb.rec.writeByte(comp_ctl | 0x0F); | |
1202 } | |
1203 } | |
0 | 1204 |
4 | 1205 // Flush zlib streams if we are told by the server to do so. |
1206 for (int stream_id = 0; stream_id < 4; stream_id++) { | |
1207 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) { | |
1208 tightInflaters[stream_id] = null; | |
1209 } | |
1210 comp_ctl >>= 1; | |
1211 } | |
0 | 1212 |
4 | 1213 // Check correctness of subencoding value. |
1214 if (comp_ctl > rfb.TightMaxSubencoding) { | |
1215 throw new Exception("Incorrect tight subencoding: " + comp_ctl); | |
1216 } | |
0 | 1217 |
4 | 1218 // Handle solid-color rectangles. |
1219 if (comp_ctl == rfb.TightFill) { | |
0 | 1220 |
4 | 1221 if (bytesPixel == 1) { |
1222 int idx = rfb.readU8(); | |
1223 memGraphics.setColor(colors[idx]); | |
1224 if (rfb.rec != null) { | |
1225 rfb.rec.writeByte(idx); | |
1226 } | |
1227 } else { | |
1228 byte[] buf = new byte[3]; | |
1229 rfb.readFully(buf); | |
1230 if (rfb.rec != null) { | |
1231 rfb.rec.write(buf); | |
1232 } | |
1233 Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 | |
1234 | (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF)); | |
1235 memGraphics.setColor(bg); | |
1236 } | |
1237 memGraphics.fillRect(x, y, w, h); | |
1238 scheduleRepaint(x, y, w, h); | |
1239 return; | |
0 | 1240 |
4 | 1241 } |
0 | 1242 |
4 | 1243 if (comp_ctl == rfb.TightJpeg) { |
1244 | |
1245 statNumRectsTightJPEG++; | |
0 | 1246 |
4 | 1247 // Read JPEG data. |
1248 byte[] jpegData = new byte[rfb.readCompactLen()]; | |
1249 rfb.readFully(jpegData); | |
1250 if (rfb.rec != null) { | |
1251 if (!rfb.recordFromBeginning) { | |
1252 rfb.recordCompactLen(jpegData.length); | |
1253 } | |
1254 rfb.rec.write(jpegData); | |
1255 } | |
0 | 1256 |
4 | 1257 // Create an Image object from the JPEG data. |
1258 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData); | |
0 | 1259 |
4 | 1260 // Remember the rectangle where the image should be drawn. |
1261 jpegRect = new Rectangle(x, y, w, h); | |
0 | 1262 |
4 | 1263 // Let the imageUpdate() method do the actual drawing, here just |
1264 // wait until the image is fully loaded and drawn. | |
1265 synchronized (jpegRect) { | |
1266 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, | |
1267 this); | |
1268 try { | |
1269 // Wait no longer than three seconds. | |
1270 jpegRect.wait(3000); | |
1271 } catch (InterruptedException e) { | |
1272 throw new Exception("Interrupted while decoding JPEG image"); | |
1273 } | |
1274 } | |
0 | 1275 |
4 | 1276 // Done, jpegRect is not needed any more. |
1277 jpegRect = null; | |
1278 return; | |
0 | 1279 |
4 | 1280 } |
0 | 1281 |
4 | 1282 // Read filter id and parameters. |
1283 int numColors = 0, rowSize = w; | |
1284 byte[] palette8 = new byte[2]; | |
1285 int[] palette24 = new int[256]; | |
1286 boolean useGradient = false; | |
1287 if ((comp_ctl & rfb.TightExplicitFilter) != 0) { | |
1288 int filter_id = rfb.readU8(); | |
1289 if (rfb.rec != null) { | |
1290 rfb.rec.writeByte(filter_id); | |
1291 } | |
1292 if (filter_id == rfb.TightFilterPalette) { | |
1293 numColors = rfb.readU8() + 1; | |
1294 if (rfb.rec != null) { | |
1295 rfb.rec.writeByte(numColors - 1); | |
1296 } | |
1297 if (bytesPixel == 1) { | |
1298 if (numColors != 2) { | |
1299 throw new Exception("Incorrect tight palette size: " | |
1300 + numColors); | |
1301 } | |
1302 rfb.readFully(palette8); | |
1303 if (rfb.rec != null) { | |
1304 rfb.rec.write(palette8); | |
1305 } | |
1306 } else { | |
1307 byte[] buf = new byte[numColors * 3]; | |
1308 rfb.readFully(buf); | |
1309 if (rfb.rec != null) { | |
1310 rfb.rec.write(buf); | |
1311 } | |
1312 for (int i = 0; i < numColors; i++) { | |
1313 palette24[i] = ((buf[i * 3] & 0xFF) << 16 | |
1314 | (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3 + 2] & 0xFF)); | |
1315 } | |
1316 } | |
1317 if (numColors == 2) | |
1318 rowSize = (w + 7) / 8; | |
1319 } else if (filter_id == rfb.TightFilterGradient) { | |
1320 useGradient = true; | |
1321 } else if (filter_id != rfb.TightFilterCopy) { | |
1322 throw new Exception("Incorrect tight filter id: " + filter_id); | |
1323 } | |
1324 } | |
1325 if (numColors == 0 && bytesPixel == 4) | |
1326 rowSize *= 3; | |
0 | 1327 |
4 | 1328 // Read, optionally uncompress and decode data. |
1329 int dataSize = h * rowSize; | |
1330 if (dataSize < rfb.TightMinToCompress) { | |
1331 // Data size is small - not compressed with zlib. | |
1332 if (numColors != 0) { | |
1333 // Indexed colors. | |
1334 byte[] indexedData = new byte[dataSize]; | |
1335 rfb.readFully(indexedData); | |
1336 if (rfb.rec != null) { | |
1337 rfb.rec.write(indexedData); | |
1338 } | |
1339 if (numColors == 2) { | |
1340 // Two colors. | |
1341 if (bytesPixel == 1) { | |
1342 decodeMonoData(x, y, w, h, indexedData, palette8); | |
1343 } else { | |
1344 decodeMonoData(x, y, w, h, indexedData, palette24); | |
1345 } | |
1346 } else { | |
1347 // 3..255 colors (assuming bytesPixel == 4). | |
1348 int i = 0; | |
1349 for (int dy = y; dy < y + h; dy++) { | |
1350 for (int dx = x; dx < x + w; dx++) { | |
1351 pixels24[dy * rfb.framebufferWidth + dx] = palette24[indexedData[i++] & 0xFF]; | |
1352 } | |
1353 } | |
1354 } | |
1355 } else if (useGradient) { | |
1356 // "Gradient"-processed data | |
1357 byte[] buf = new byte[w * h * 3]; | |
1358 rfb.readFully(buf); | |
1359 if (rfb.rec != null) { | |
1360 rfb.rec.write(buf); | |
1361 } | |
1362 decodeGradientData(x, y, w, h, buf); | |
1363 } else { | |
1364 // Raw truecolor data. | |
1365 if (bytesPixel == 1) { | |
1366 for (int dy = y; dy < y + h; dy++) { | |
1367 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w); | |
1368 if (rfb.rec != null) { | |
1369 rfb.rec.write(pixels8, dy * rfb.framebufferWidth | |
1370 + x, w); | |
1371 } | |
1372 } | |
1373 } else { | |
1374 byte[] buf = new byte[w * 3]; | |
1375 int i, offset; | |
1376 for (int dy = y; dy < y + h; dy++) { | |
1377 rfb.readFully(buf); | |
1378 if (rfb.rec != null) { | |
1379 rfb.rec.write(buf); | |
1380 } | |
1381 offset = dy * rfb.framebufferWidth + x; | |
1382 for (i = 0; i < w; i++) { | |
1383 pixels24[offset + i] = (buf[i * 3] & 0xFF) << 16 | |
1384 | (buf[i * 3 + 1] & 0xFF) << 8 | |
1385 | (buf[i * 3 + 2] & 0xFF); | |
1386 } | |
1387 } | |
1388 } | |
1389 } | |
1390 } else { | |
1391 // Data was compressed with zlib. | |
1392 int zlibDataLen = rfb.readCompactLen(); | |
1393 byte[] zlibData = new byte[zlibDataLen]; | |
1394 rfb.readFully(zlibData); | |
1395 if (rfb.rec != null && rfb.recordFromBeginning) { | |
1396 rfb.rec.write(zlibData); | |
1397 } | |
1398 int stream_id = comp_ctl & 0x03; | |
1399 if (tightInflaters[stream_id] == null) { | |
1400 tightInflaters[stream_id] = new Inflater(); | |
1401 } | |
1402 Inflater myInflater = tightInflaters[stream_id]; | |
1403 myInflater.setInput(zlibData); | |
1404 byte[] buf = new byte[dataSize]; | |
1405 myInflater.inflate(buf); | |
1406 if (rfb.rec != null && !rfb.recordFromBeginning) { | |
1407 rfb.recordCompressedData(buf); | |
1408 } | |
0 | 1409 |
4 | 1410 if (numColors != 0) { |
1411 // Indexed colors. | |
1412 if (numColors == 2) { | |
1413 // Two colors. | |
1414 if (bytesPixel == 1) { | |
1415 decodeMonoData(x, y, w, h, buf, palette8); | |
1416 } else { | |
1417 decodeMonoData(x, y, w, h, buf, palette24); | |
1418 } | |
1419 } else { | |
1420 // More than two colors (assuming bytesPixel == 4). | |
1421 int i = 0; | |
1422 for (int dy = y; dy < y + h; dy++) { | |
1423 for (int dx = x; dx < x + w; dx++) { | |
1424 pixels24[dy * rfb.framebufferWidth + dx] = palette24[buf[i++] & 0xFF]; | |
1425 } | |
1426 } | |
1427 } | |
1428 } else if (useGradient) { | |
1429 // Compressed "Gradient"-filtered data (assuming bytesPixel == | |
1430 // 4). | |
1431 decodeGradientData(x, y, w, h, buf); | |
1432 } else { | |
1433 // Compressed truecolor data. | |
1434 if (bytesPixel == 1) { | |
1435 int destOffset = y * rfb.framebufferWidth + x; | |
1436 for (int dy = 0; dy < h; dy++) { | |
1437 System.arraycopy(buf, dy * w, pixels8, destOffset, w); | |
1438 destOffset += rfb.framebufferWidth; | |
1439 } | |
1440 } else { | |
1441 int srcOffset = 0; | |
1442 int destOffset, i; | |
1443 for (int dy = 0; dy < h; dy++) { | |
1444 myInflater.inflate(buf); | |
1445 destOffset = (y + dy) * rfb.framebufferWidth + x; | |
1446 for (i = 0; i < w; i++) { | |
1447 pixels24[destOffset + i] = (buf[srcOffset] & 0xFF) << 16 | |
1448 | (buf[srcOffset + 1] & 0xFF) << 8 | |
1449 | (buf[srcOffset + 2] & 0xFF); | |
1450 srcOffset += 3; | |
1451 } | |
1452 } | |
1453 } | |
1454 } | |
1455 } | |
0 | 1456 |
4 | 1457 handleUpdatedPixels(x, y, w, h); |
1458 scheduleRepaint(x, y, w, h); | |
0 | 1459 } |
1460 | |
4 | 1461 // |
1462 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions). | |
1463 // | |
0 | 1464 |
4 | 1465 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) { |
0 | 1466 |
4 | 1467 int dx, dy, n; |
1468 int i = y * rfb.framebufferWidth + x; | |
1469 int rowBytes = (w + 7) / 8; | |
1470 byte b; | |
0 | 1471 |
4 | 1472 for (dy = 0; dy < h; dy++) { |
1473 for (dx = 0; dx < w / 8; dx++) { | |
1474 b = src[dy * rowBytes + dx]; | |
1475 for (n = 7; n >= 0; n--) | |
1476 pixels8[i++] = palette[b >> n & 1]; | |
1477 } | |
1478 for (n = 7; n >= 8 - w % 8; n--) { | |
1479 pixels8[i++] = palette[src[dy * rowBytes + dx] >> n & 1]; | |
1480 } | |
1481 i += (rfb.framebufferWidth - w); | |
1482 } | |
0 | 1483 } |
1484 | |
4 | 1485 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) { |
1486 | |
1487 int dx, dy, n; | |
1488 int i = y * rfb.framebufferWidth + x; | |
1489 int rowBytes = (w + 7) / 8; | |
1490 byte b; | |
1491 | |
1492 for (dy = 0; dy < h; dy++) { | |
1493 for (dx = 0; dx < w / 8; dx++) { | |
1494 b = src[dy * rowBytes + dx]; | |
1495 for (n = 7; n >= 0; n--) | |
1496 pixels24[i++] = palette[b >> n & 1]; | |
1497 } | |
1498 for (n = 7; n >= 8 - w % 8; n--) { | |
1499 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1]; | |
1500 } | |
1501 i += (rfb.framebufferWidth - w); | |
1502 } | |
0 | 1503 } |
4 | 1504 |
1505 // | |
1506 // Decode data processed with the "Gradient" filter. | |
1507 // | |
1508 | |
1509 void decodeGradientData(int x, int y, int w, int h, byte[] buf) { | |
0 | 1510 |
4 | 1511 int dx, dy, c; |
1512 byte[] prevRow = new byte[w * 3]; | |
1513 byte[] thisRow = new byte[w * 3]; | |
1514 byte[] pix = new byte[3]; | |
1515 int[] est = new int[3]; | |
1516 | |
1517 int offset = y * rfb.framebufferWidth + x; | |
1518 | |
1519 for (dy = 0; dy < h; dy++) { | |
1520 | |
1521 /* First pixel in a row */ | |
1522 for (c = 0; c < 3; c++) { | |
1523 pix[c] = (byte) (prevRow[c] + buf[dy * w * 3 + c]); | |
1524 thisRow[c] = pix[c]; | |
1525 } | |
1526 pixels24[offset++] = (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | |
1527 | (pix[2] & 0xFF); | |
1528 | |
1529 /* Remaining pixels of a row */ | |
1530 for (dx = 1; dx < w; dx++) { | |
1531 for (c = 0; c < 3; c++) { | |
1532 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) - (prevRow[(dx - 1) | |
1533 * 3 + c] & 0xFF)); | |
1534 if (est[c] > 0xFF) { | |
1535 est[c] = 0xFF; | |
1536 } else if (est[c] < 0x00) { | |
1537 est[c] = 0x00; | |
1538 } | |
1539 pix[c] = (byte) (est[c] + buf[(dy * w + dx) * 3 + c]); | |
1540 thisRow[dx * 3 + c] = pix[c]; | |
1541 } | |
1542 pixels24[offset++] = (pix[0] & 0xFF) << 16 | |
1543 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF); | |
1544 } | |
1545 | |
1546 System.arraycopy(thisRow, 0, prevRow, 0, w * 3); | |
1547 offset += (rfb.framebufferWidth - w); | |
1548 } | |
0 | 1549 } |
4 | 1550 |
1551 // | |
1552 // Display newly updated area of pixels. | |
1553 // | |
1554 | |
1555 void handleUpdatedPixels(int x, int y, int w, int h) { | |
1556 | |
1557 // Draw updated pixels of the off-screen image. | |
1558 pixelsSource.newPixels(x, y, w, h); | |
1559 memGraphics.setClip(x, y, w, h); | |
1560 memGraphics.drawImage(rawPixelsImage, 0, 0, null); | |
1561 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight); | |
0 | 1562 } |
4 | 1563 |
1564 // | |
1565 // Tell JVM to repaint specified desktop area. | |
1566 // | |
0 | 1567 |
4 | 1568 void scheduleRepaint(int x, int y, int w, int h) { |
1569 // Request repaint, deferred if necessary. | |
1570 if (rfb.framebufferWidth == scaledWidth) { | |
1571 repaint(viewer.deferScreenUpdates, x, y, w, h); | |
1572 } else { | |
1573 int sx = x * scalingFactor / 100; | |
1574 int sy = y * scalingFactor / 100; | |
1575 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1; | |
1576 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1; | |
1577 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh); | |
1578 } | |
1579 } | |
1580 | |
1581 // | |
1582 // Handle events. | |
1583 // | |
1584 | |
1585 public void keyPressed(KeyEvent evt) { | |
1586 processLocalKeyEvent(evt); | |
0 | 1587 } |
4 | 1588 |
1589 public void keyReleased(KeyEvent evt) { | |
1590 processLocalKeyEvent(evt); | |
0 | 1591 } |
4 | 1592 |
1593 public void keyTyped(KeyEvent evt) { | |
1594 evt.consume(); | |
1595 } | |
0 | 1596 |
4 | 1597 public void mousePressed(MouseEvent evt) { |
1598 processLocalMouseEvent(evt, false); | |
1599 } | |
0 | 1600 |
4 | 1601 public void mouseReleased(MouseEvent evt) { |
1602 processLocalMouseEvent(evt, false); | |
1603 } | |
0 | 1604 |
4 | 1605 public void mouseMoved(MouseEvent evt) { |
1606 processLocalMouseEvent(evt, true); | |
1607 } | |
0 | 1608 |
4 | 1609 public void mouseDragged(MouseEvent evt) { |
1610 processLocalMouseEvent(evt, true); | |
1611 } | |
0 | 1612 |
4 | 1613 public void processLocalKeyEvent(KeyEvent evt) { |
1614 if (viewer.rfb != null && rfb.inNormalProtocol) { | |
1615 if (!inputEnabled) { | |
1616 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') | |
1617 && evt.getID() == KeyEvent.KEY_PRESSED) { | |
1618 // Request screen update. | |
1619 try { | |
1620 rfb.writeFramebufferUpdateRequest(0, 0, | |
1621 rfb.framebufferWidth, rfb.framebufferHeight, | |
1622 false); | |
1623 } catch (IOException e) { | |
1624 e.printStackTrace(); | |
1625 } | |
1626 } | |
1627 } else { | |
1628 // Input enabled. | |
1629 synchronized (rfb) { | |
1630 try { | |
1631 rfb.writeKeyEvent(evt); | |
1632 } catch (Exception e) { | |
1633 e.printStackTrace(); | |
1634 } | |
1635 rfb.notify(); | |
1636 } | |
1637 } | |
1638 } | |
1639 // Don't ever pass keyboard events to AWT for default processing. | |
1640 // Otherwise, pressing Tab would switch focus to ButtonPanel etc. | |
1641 evt.consume(); | |
1642 } | |
0 | 1643 |
4 | 1644 public void processLocalMouseEvent(MouseEvent evt, boolean moved) { |
1645 if (viewer.rfb != null && rfb.inNormalProtocol) { | |
1646 if (moved) { | |
1647 softCursorMove(evt.getX(), evt.getY()); | |
1648 } | |
1649 if (rfb.framebufferWidth != scaledWidth) { | |
1650 int sx = (evt.getX() * 100 + scalingFactor / 2) / scalingFactor; | |
1651 int sy = (evt.getY() * 100 + scalingFactor / 2) / scalingFactor; | |
1652 evt.translatePoint(sx - evt.getX(), sy - evt.getY()); | |
1653 } | |
1654 synchronized (rfb) { | |
1655 try { | |
1656 rfb.writePointerEvent(evt); | |
1657 } catch (Exception e) { | |
1658 e.printStackTrace(); | |
1659 } | |
1660 rfb.notify(); | |
1661 } | |
1662 } | |
0 | 1663 } |
1664 | |
4 | 1665 // |
1666 // Ignored events. | |
1667 // | |
0 | 1668 |
4 | 1669 public void mouseClicked(MouseEvent evt) { |
1670 } | |
1671 | |
1672 public void mouseEntered(MouseEvent evt) { | |
1673 } | |
0 | 1674 |
4 | 1675 public void mouseExited(MouseEvent evt) { |
1676 } | |
1677 | |
1678 // | |
1679 // Reset update statistics. | |
1680 // | |
0 | 1681 |
4 | 1682 void resetStats() { |
1683 statStartTime = System.currentTimeMillis(); | |
1684 statNumUpdates = 0; | |
1685 statNumTotalRects = 0; | |
1686 statNumPixelRects = 0; | |
1687 statNumRectsTight = 0; | |
1688 statNumRectsTightJPEG = 0; | |
1689 statNumRectsZRLE = 0; | |
1690 statNumRectsHextile = 0; | |
1691 statNumRectsRaw = 0; | |
1692 statNumRectsCopy = 0; | |
1693 statNumBytesEncoded = 0; | |
1694 statNumBytesDecoded = 0; | |
1695 } | |
0 | 1696 |
4 | 1697 // //////////////////////////////////////////////////////////////// |
1698 // | |
1699 // Handle cursor shape updates (XCursor and RichCursor encodings). | |
1700 // | |
1701 | |
1702 boolean showSoftCursor = false; | |
1703 | |
1704 MemoryImageSource softCursorSource; | |
1705 Image softCursor; | |
0 | 1706 |
4 | 1707 int cursorX = 0, cursorY = 0; |
1708 int cursorWidth, cursorHeight; | |
1709 int origCursorWidth, origCursorHeight; | |
1710 int hotX, hotY; | |
1711 int origHotX, origHotY; | |
1712 | |
1713 // | |
1714 // Handle cursor shape update (XCursor and RichCursor encodings). | |
1715 // | |
1716 | |
1717 synchronized void handleCursorShapeUpdate(int encodingType, int xhot, | |
1718 int yhot, int width, int height) throws IOException { | |
1719 | |
1720 softCursorFree(); | |
0 | 1721 |
4 | 1722 if (width * height == 0) |
1723 return; | |
1724 | |
1725 // Ignore cursor shape data if requested by user. | |
1726 if (viewer.options.ignoreCursorUpdates) { | |
1727 int bytesPerRow = (width + 7) / 8; | |
1728 int bytesMaskData = bytesPerRow * height; | |
0 | 1729 |
4 | 1730 if (encodingType == rfb.EncodingXCursor) { |
1731 rfb.skipBytes(6 + bytesMaskData * 2); | |
1732 } else { | |
1733 // rfb.EncodingRichCursor | |
1734 rfb.skipBytes(width * height * bytesPixel + bytesMaskData); | |
1735 } | |
1736 return; | |
1737 } | |
0 | 1738 |
4 | 1739 // Decode cursor pixel data. |
1740 softCursorSource = decodeCursorShape(encodingType, width, height); | |
0 | 1741 |
4 | 1742 // Set original (non-scaled) cursor dimensions. |
1743 origCursorWidth = width; | |
1744 origCursorHeight = height; | |
1745 origHotX = xhot; | |
1746 origHotY = yhot; | |
0 | 1747 |
4 | 1748 // Create off-screen cursor image. |
1749 createSoftCursor(); | |
0 | 1750 |
4 | 1751 // Show the cursor. |
1752 showSoftCursor = true; | |
1753 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1754 cursorWidth, cursorHeight); | |
1755 } | |
0 | 1756 |
4 | 1757 // |
1758 // decodeCursorShape(). Decode cursor pixel data and return | |
1759 // corresponding MemoryImageSource instance. | |
1760 // | |
1761 | |
1762 synchronized MemoryImageSource decodeCursorShape(int encodingType, | |
1763 int width, int height) throws IOException { | |
0 | 1764 |
4 | 1765 int bytesPerRow = (width + 7) / 8; |
1766 int bytesMaskData = bytesPerRow * height; | |
0 | 1767 |
4 | 1768 int[] softCursorPixels = new int[width * height]; |
1769 | |
1770 if (encodingType == rfb.EncodingXCursor) { | |
0 | 1771 |
4 | 1772 // Read foreground and background colors of the cursor. |
1773 byte[] rgb = new byte[6]; | |
1774 rfb.readFully(rgb); | |
1775 int[] colors = { | |
1776 (0xFF000000 | (rgb[3] & 0xFF) << 16 | (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)), | |
1777 (0xFF000000 | (rgb[0] & 0xFF) << 16 | (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) }; | |
0 | 1778 |
4 | 1779 // Read pixel and mask data. |
1780 byte[] pixBuf = new byte[bytesMaskData]; | |
1781 rfb.readFully(pixBuf); | |
1782 byte[] maskBuf = new byte[bytesMaskData]; | |
1783 rfb.readFully(maskBuf); | |
0 | 1784 |
4 | 1785 // Decode pixel data into softCursorPixels[]. |
1786 byte pixByte, maskByte; | |
1787 int x, y, n, result; | |
1788 int i = 0; | |
1789 for (y = 0; y < height; y++) { | |
1790 for (x = 0; x < width / 8; x++) { | |
1791 pixByte = pixBuf[y * bytesPerRow + x]; | |
1792 maskByte = maskBuf[y * bytesPerRow + x]; | |
1793 for (n = 7; n >= 0; n--) { | |
1794 if ((maskByte >> n & 1) != 0) { | |
1795 result = colors[pixByte >> n & 1]; | |
1796 } else { | |
1797 result = 0; // Transparent pixel | |
1798 } | |
1799 softCursorPixels[i++] = result; | |
1800 } | |
1801 } | |
1802 for (n = 7; n >= 8 - width % 8; n--) { | |
1803 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { | |
1804 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1]; | |
1805 } else { | |
1806 result = 0; // Transparent pixel | |
1807 } | |
1808 softCursorPixels[i++] = result; | |
1809 } | |
1810 } | |
0 | 1811 |
4 | 1812 } else { |
1813 // encodingType == rfb.EncodingRichCursor | |
0 | 1814 |
4 | 1815 // Read pixel and mask data. |
1816 byte[] pixBuf = new byte[width * height * bytesPixel]; | |
1817 rfb.readFully(pixBuf); | |
1818 byte[] maskBuf = new byte[bytesMaskData]; | |
1819 rfb.readFully(maskBuf); | |
0 | 1820 |
4 | 1821 // Decode pixel data into softCursorPixels[]. |
1822 byte pixByte, maskByte; | |
1823 int x, y, n, result; | |
1824 int i = 0; | |
1825 for (y = 0; y < height; y++) { | |
1826 for (x = 0; x < width / 8; x++) { | |
1827 maskByte = maskBuf[y * bytesPerRow + x]; | |
1828 for (n = 7; n >= 0; n--) { | |
1829 if ((maskByte >> n & 1) != 0) { | |
1830 if (bytesPixel == 1) { | |
1831 result = cm8.getRGB(pixBuf[i]); | |
1832 } else { | |
1833 result = 0xFF000000 | |
1834 | (pixBuf[i * 4 + 2] & 0xFF) << 16 | |
1835 | (pixBuf[i * 4 + 1] & 0xFF) << 8 | |
1836 | (pixBuf[i * 4] & 0xFF); | |
1837 } | |
1838 } else { | |
1839 result = 0; // Transparent pixel | |
1840 } | |
1841 softCursorPixels[i++] = result; | |
1842 } | |
1843 } | |
1844 for (n = 7; n >= 8 - width % 8; n--) { | |
1845 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) { | |
1846 if (bytesPixel == 1) { | |
1847 result = cm8.getRGB(pixBuf[i]); | |
1848 } else { | |
1849 result = 0xFF000000 | |
1850 | (pixBuf[i * 4 + 2] & 0xFF) << 16 | |
1851 | (pixBuf[i * 4 + 1] & 0xFF) << 8 | |
1852 | (pixBuf[i * 4] & 0xFF); | |
1853 } | |
1854 } else { | |
1855 result = 0; // Transparent pixel | |
1856 } | |
1857 softCursorPixels[i++] = result; | |
1858 } | |
1859 } | |
0 | 1860 |
4 | 1861 } |
0 | 1862 |
4 | 1863 return new MemoryImageSource(width, height, softCursorPixels, 0, width); |
0 | 1864 } |
4 | 1865 |
1866 // | |
1867 // createSoftCursor(). Assign softCursor new Image (scaled if necessary). | |
1868 // Uses softCursorSource as a source for new cursor image. | |
1869 // | |
0 | 1870 |
4 | 1871 synchronized void createSoftCursor() { |
0 | 1872 |
4 | 1873 if (softCursorSource == null) |
1874 return; | |
1875 | |
1876 int scaleCursor = viewer.options.scaleCursor; | |
1877 if (scaleCursor == 0 || !inputEnabled) | |
1878 scaleCursor = 100; | |
0 | 1879 |
4 | 1880 // Save original cursor coordinates. |
1881 int x = cursorX - hotX; | |
1882 int y = cursorY - hotY; | |
1883 int w = cursorWidth; | |
1884 int h = cursorHeight; | |
0 | 1885 |
4 | 1886 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100; |
1887 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100; | |
1888 hotX = (origHotX * scaleCursor + 50) / 100; | |
1889 hotY = (origHotY * scaleCursor + 50) / 100; | |
1890 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource); | |
0 | 1891 |
4 | 1892 if (scaleCursor != 100) { |
1893 softCursor = softCursor.getScaledInstance(cursorWidth, | |
1894 cursorHeight, Image.SCALE_SMOOTH); | |
1895 } | |
0 | 1896 |
4 | 1897 if (showSoftCursor) { |
1898 // Compute screen area to update. | |
1899 x = Math.min(x, cursorX - hotX); | |
1900 y = Math.min(y, cursorY - hotY); | |
1901 w = Math.max(w, cursorWidth); | |
1902 h = Math.max(h, cursorHeight); | |
0 | 1903 |
4 | 1904 repaint(viewer.deferCursorUpdates, x, y, w, h); |
1905 } | |
1906 } | |
0 | 1907 |
4 | 1908 // |
1909 // softCursorMove(). Moves soft cursor into a particular location. | |
1910 // | |
0 | 1911 |
4 | 1912 synchronized void softCursorMove(int x, int y) { |
1913 int oldX = cursorX; | |
1914 int oldY = cursorY; | |
1915 cursorX = x; | |
1916 cursorY = y; | |
1917 if (showSoftCursor) { | |
1918 repaint(viewer.deferCursorUpdates, oldX - hotX, oldY - hotY, | |
1919 cursorWidth, cursorHeight); | |
1920 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, | |
1921 cursorWidth, cursorHeight); | |
1922 } | |
1923 } | |
0 | 1924 |
4 | 1925 // |
1926 // softCursorFree(). Remove soft cursor, dispose resources. | |
1927 // | |
0 | 1928 |
4 | 1929 synchronized void softCursorFree() { |
1930 if (showSoftCursor) { | |
1931 showSoftCursor = false; | |
1932 softCursor = null; | |
1933 softCursorSource = null; | |
0 | 1934 |
4 | 1935 repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY, |
1936 cursorWidth, cursorHeight); | |
1937 } | |
1938 } | |
0 | 1939 } |