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