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