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