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