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