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