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