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