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