blob: 4aa8d0ab6e5736198db31fa19182937aaff1c012 [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
enikeyc41ba1d2008-12-19 09:07:22 +0000147 //
148 // Create decoders
149 //
150
151 // Input stream for decoders
152 RfbInputStream rfbis = new RfbInputStream(rfb);
153
154 rawDecoder = new RawDecoder(memGraphics, rfbis);
155 rreDecoder = new RREDecoder(memGraphics, rfbis);
156 correDecoder = new CoRREDecoder(memGraphics, rfbis);
157 hextileDecoder = new HextileDecoder(memGraphics, rfbis);
158 tightDecoder = new TightDecoder(memGraphics, rfbis);
enikey4582bab2008-12-19 09:19:59 +0000159 zlibDecoder = new ZlibDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000160 zrleDecoder = new ZRLEDecoder(memGraphics, rfbis);
161
162 //
163 // Set data for decoders that needs extra parameters
164 //
165
166 hextileDecoder.setRepainableControl(this);
167 tightDecoder.setRepainableControl(this);
168
169 //
170 // Create array that contains our decoders
171 //
172
173 decoders = new RawDecoder[7];
174 decoders[0] = rawDecoder;
175 decoders[1] = rreDecoder;
176 decoders[2] = correDecoder;
177 decoders[3] = hextileDecoder;
178 decoders[4] = zlibDecoder;
179 decoders[5] = tightDecoder;
180 decoders[6] = zrleDecoder;
181
182 //
183 // Set session recorder for decoders
184 //
185
186 for (int i = 0; i < decoders.length; i++) {
187 decoders[i].setSessionRecorder(this);
188 }
enikey2f7f46e2008-12-19 09:32:35 +0000189
190 setPixelFormat();
191
192 resetSelection();
193
194 inputEnabled = false;
195 if (!viewer.options.viewOnly)
196 enableInput(true);
enikeyc41ba1d2008-12-19 09:07:22 +0000197
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:
enikey2f7f46e2008-12-19 09:32:35 +0000607 if (tightDecoder != null) {
608 statNumRectsTight = tightDecoder.getNumJPEGRects();
609 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000610 handleTightRect(rx, ry, rw, rh);
611 break;
612 default:
613 throw new Exception("Unknown RFB rectangle encoding " +
614 rfb.updateRectEncoding);
615 }
616
617 rfb.stopTiming();
618
619 statNumPixelRects++;
620 statNumBytesDecoded += rw * rh * bytesPixel;
621 statNumBytesEncoded +=
622 (int)(rfb.getNumBytesRead() - numBytesReadBefore);
623 }
624
625 boolean fullUpdateNeeded = false;
626
627 // Start/stop session recording if necessary. Request full
628 // update if a new session file was opened.
629 if (viewer.checkRecordingStatus())
630 fullUpdateNeeded = true;
631
632 // Defer framebuffer update request if necessary. But wake up
633 // immediately on keyboard or mouse event. Also, don't sleep
634 // if there is some data to receive, or if the last update
635 // included a PointerPos message.
636 if (viewer.deferUpdateRequests > 0 &&
637 rfb.available() == 0 && !cursorPosReceived) {
638 synchronized(rfb) {
639 try {
640 rfb.wait(viewer.deferUpdateRequests);
641 } catch (InterruptedException e) {
642 }
643 }
644 }
645
646 viewer.autoSelectEncodings();
647
648 // Before requesting framebuffer update, check if the pixel
649 // format should be changed.
650 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
651 // Pixel format should be changed.
652 if (!rfb.continuousUpdatesAreActive()) {
653 // Continuous updates are not used. In this case, we just
654 // set new pixel format and request full update.
655 setPixelFormat();
656 fullUpdateNeeded = true;
657 } else {
658 // Otherwise, disable continuous updates first. Pixel
659 // format will be set later when we are sure that there
660 // will be no unsolicited framebuffer updates.
661 rfb.tryDisableContinuousUpdates();
662 break; // skip the code below
663 }
664 }
665
666 // Enable/disable continuous updates to reflect the GUI setting.
667 boolean enable = viewer.options.continuousUpdates;
668 if (enable != rfb.continuousUpdatesAreActive()) {
669 if (enable) {
670 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
671 rfb.framebufferHeight);
672 } else {
673 rfb.tryDisableContinuousUpdates();
674 }
675 }
676
677 // Finally, request framebuffer update if needed.
678 if (fullUpdateNeeded) {
679 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
680 rfb.framebufferHeight, false);
681 } else if (!rfb.continuousUpdatesAreActive()) {
682 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
683 rfb.framebufferHeight, true);
684 }
685
686 break;
687
688 case RfbProto.SetColourMapEntries:
689 throw new Exception("Can't handle SetColourMapEntries message");
690
691 case RfbProto.Bell:
692 Toolkit.getDefaultToolkit().beep();
693 break;
694
695 case RfbProto.ServerCutText:
696 String s = rfb.readServerCutText();
697 viewer.clipboard.setCutText(s);
698 break;
699
700 case RfbProto.EndOfContinuousUpdates:
701 if (rfb.continuousUpdatesAreActive()) {
702 rfb.endOfContinuousUpdates();
703
704 // Change pixel format if such change was pending. Note that we
705 // could not change pixel format while continuous updates were
706 // in effect.
707 boolean incremental = true;
708 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
709 setPixelFormat();
710 incremental = false;
711 }
712 // From this point, we ask for updates explicitly.
713 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
714 rfb.framebufferHeight,
715 incremental);
716 }
717 break;
718
719 default:
720 throw new Exception("Unknown RFB message type " + msgType);
721 }
722 }
723 }
724
725
726 //
727 // Handle a raw rectangle. The second form with paint==false is used
728 // by the Hextile decoder for raw-encoded tiles.
729 //
730
731 void handleRawRect(int x, int y, int w, int h) throws IOException {
732 handleRawRect(x, y, w, h, true);
733 }
734
735 void handleRawRect(int x, int y, int w, int h, boolean paint)
736 throws IOException {
737
738 if (bytesPixel == 1) {
739 for (int dy = y; dy < y + h; dy++) {
740 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
741 if (rfb.rec != null) {
742 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
743 }
744 }
745 } else {
746 byte[] buf = new byte[w * 4];
747 int i, offset;
748 for (int dy = y; dy < y + h; dy++) {
749 rfb.readFully(buf);
750 if (rfb.rec != null) {
751 rfb.rec.write(buf);
752 }
753 offset = dy * rfb.framebufferWidth + x;
754 for (i = 0; i < w; i++) {
755 pixels24[offset + i] =
756 (buf[i * 4 + 2] & 0xFF) << 16 |
757 (buf[i * 4 + 1] & 0xFF) << 8 |
758 (buf[i * 4] & 0xFF);
759 }
760 }
761 }
762
763 handleUpdatedPixels(x, y, w, h);
764 if (paint)
765 scheduleRepaint(x, y, w, h);
766 }
767
768 //
769 // Handle a CopyRect rectangle.
770 //
771
772 void handleCopyRect(int x, int y, int w, int h) throws IOException {
773
774 rfb.readCopyRect();
775 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
776 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
777
778 scheduleRepaint(x, y, w, h);
779 }
780
781 //
782 // Handle an RRE-encoded rectangle.
783 //
784
785 void handleRRERect(int x, int y, int w, int h) throws IOException {
786
787 int nSubrects = rfb.readU32();
788
789 byte[] bg_buf = new byte[bytesPixel];
790 rfb.readFully(bg_buf);
791 Color pixel;
792 if (bytesPixel == 1) {
793 pixel = colors[bg_buf[0] & 0xFF];
794 } else {
795 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
796 }
797 memGraphics.setColor(pixel);
798 memGraphics.fillRect(x, y, w, h);
799
800 byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
801 rfb.readFully(buf);
802 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
803
804 if (rfb.rec != null) {
805 rfb.rec.writeIntBE(nSubrects);
806 rfb.rec.write(bg_buf);
807 rfb.rec.write(buf);
808 }
809
810 int sx, sy, sw, sh;
811
812 for (int j = 0; j < nSubrects; j++) {
813 if (bytesPixel == 1) {
814 pixel = colors[ds.readUnsignedByte()];
815 } else {
816 ds.skip(4);
817 pixel = new Color(buf[j*12+2] & 0xFF,
818 buf[j*12+1] & 0xFF,
819 buf[j*12] & 0xFF);
820 }
821 sx = x + ds.readUnsignedShort();
822 sy = y + ds.readUnsignedShort();
823 sw = ds.readUnsignedShort();
824 sh = ds.readUnsignedShort();
825
826 memGraphics.setColor(pixel);
827 memGraphics.fillRect(sx, sy, sw, sh);
828 }
829
830 scheduleRepaint(x, y, w, h);
831 }
832
833 //
834 // Handle a CoRRE-encoded rectangle.
835 //
836
837 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
838 int nSubrects = rfb.readU32();
839
840 byte[] bg_buf = new byte[bytesPixel];
841 rfb.readFully(bg_buf);
842 Color pixel;
843 if (bytesPixel == 1) {
844 pixel = colors[bg_buf[0] & 0xFF];
845 } else {
846 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
847 }
848 memGraphics.setColor(pixel);
849 memGraphics.fillRect(x, y, w, h);
850
851 byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
852 rfb.readFully(buf);
853
854 if (rfb.rec != null) {
855 rfb.rec.writeIntBE(nSubrects);
856 rfb.rec.write(bg_buf);
857 rfb.rec.write(buf);
858 }
859
860 int sx, sy, sw, sh;
861 int i = 0;
862
863 for (int j = 0; j < nSubrects; j++) {
864 if (bytesPixel == 1) {
865 pixel = colors[buf[i++] & 0xFF];
866 } else {
867 pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
868 i += 4;
869 }
870 sx = x + (buf[i++] & 0xFF);
871 sy = y + (buf[i++] & 0xFF);
872 sw = buf[i++] & 0xFF;
873 sh = buf[i++] & 0xFF;
874
875 memGraphics.setColor(pixel);
876 memGraphics.fillRect(sx, sy, sw, sh);
877 }
878
879 scheduleRepaint(x, y, w, h);
880 }
881
882 //
883 // Handle a Hextile-encoded rectangle.
884 //
885
886 // These colors should be kept between handleHextileSubrect() calls.
887 private Color hextile_bg, hextile_fg;
888
889 void handleHextileRect(int x, int y, int w, int h) throws IOException {
890
891 hextile_bg = new Color(0);
892 hextile_fg = new Color(0);
893
894 for (int ty = y; ty < y + h; ty += 16) {
895 int th = 16;
896 if (y + h - ty < 16)
897 th = y + h - ty;
898
899 for (int tx = x; tx < x + w; tx += 16) {
900 int tw = 16;
901 if (x + w - tx < 16)
902 tw = x + w - tx;
903
904 handleHextileSubrect(tx, ty, tw, th);
905 }
906
907 // Finished with a row of tiles, now let's show it.
908 scheduleRepaint(x, y, w, h);
909 }
910 }
911
912 //
913 // Handle one tile in the Hextile-encoded data.
914 //
915
916 void handleHextileSubrect(int tx, int ty, int tw, int th)
917 throws IOException {
918
919 int subencoding = rfb.readU8();
920 if (rfb.rec != null) {
921 rfb.rec.writeByte(subencoding);
922 }
923
924 // Is it a raw-encoded sub-rectangle?
925 if ((subencoding & rfb.HextileRaw) != 0) {
926 handleRawRect(tx, ty, tw, th, false);
927 return;
928 }
929
930 // Read and draw the background if specified.
931 byte[] cbuf = new byte[bytesPixel];
932 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
933 rfb.readFully(cbuf);
934 if (bytesPixel == 1) {
935 hextile_bg = colors[cbuf[0] & 0xFF];
936 } else {
937 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
938 }
939 if (rfb.rec != null) {
940 rfb.rec.write(cbuf);
941 }
942 }
943 memGraphics.setColor(hextile_bg);
944 memGraphics.fillRect(tx, ty, tw, th);
945
946 // Read the foreground color if specified.
947 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
948 rfb.readFully(cbuf);
949 if (bytesPixel == 1) {
950 hextile_fg = colors[cbuf[0] & 0xFF];
951 } else {
952 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
953 }
954 if (rfb.rec != null) {
955 rfb.rec.write(cbuf);
956 }
957 }
958
959 // Done with this tile if there is no sub-rectangles.
960 if ((subencoding & rfb.HextileAnySubrects) == 0)
961 return;
962
963 int nSubrects = rfb.readU8();
964 int bufsize = nSubrects * 2;
965 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
966 bufsize += nSubrects * bytesPixel;
967 }
968 byte[] buf = new byte[bufsize];
969 rfb.readFully(buf);
970 if (rfb.rec != null) {
971 rfb.rec.writeByte(nSubrects);
972 rfb.rec.write(buf);
973 }
974
975 int b1, b2, sx, sy, sw, sh;
976 int i = 0;
977
978 if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
979
980 // Sub-rectangles are all of the same color.
981 memGraphics.setColor(hextile_fg);
982 for (int j = 0; j < nSubrects; j++) {
983 b1 = buf[i++] & 0xFF;
984 b2 = buf[i++] & 0xFF;
985 sx = tx + (b1 >> 4);
986 sy = ty + (b1 & 0xf);
987 sw = (b2 >> 4) + 1;
988 sh = (b2 & 0xf) + 1;
989 memGraphics.fillRect(sx, sy, sw, sh);
990 }
991 } else if (bytesPixel == 1) {
992
993 // BGR233 (8-bit color) version for colored sub-rectangles.
994 for (int j = 0; j < nSubrects; j++) {
995 hextile_fg = colors[buf[i++] & 0xFF];
996 b1 = buf[i++] & 0xFF;
997 b2 = buf[i++] & 0xFF;
998 sx = tx + (b1 >> 4);
999 sy = ty + (b1 & 0xf);
1000 sw = (b2 >> 4) + 1;
1001 sh = (b2 & 0xf) + 1;
1002 memGraphics.setColor(hextile_fg);
1003 memGraphics.fillRect(sx, sy, sw, sh);
1004 }
1005
1006 } else {
1007
1008 // Full-color (24-bit) version for colored sub-rectangles.
1009 for (int j = 0; j < nSubrects; j++) {
1010 hextile_fg = new Color(buf[i+2] & 0xFF,
1011 buf[i+1] & 0xFF,
1012 buf[i] & 0xFF);
1013 i += 4;
1014 b1 = buf[i++] & 0xFF;
1015 b2 = buf[i++] & 0xFF;
1016 sx = tx + (b1 >> 4);
1017 sy = ty + (b1 & 0xf);
1018 sw = (b2 >> 4) + 1;
1019 sh = (b2 & 0xf) + 1;
1020 memGraphics.setColor(hextile_fg);
1021 memGraphics.fillRect(sx, sy, sw, sh);
1022 }
1023
1024 }
1025 }
1026
1027 //
1028 // Handle a ZRLE-encoded rectangle.
1029 //
1030 // FIXME: Currently, session recording is not fully supported for ZRLE.
1031 //
1032
1033 void handleZRLERect(int x, int y, int w, int h) throws Exception {
1034
1035 if (zrleInStream == null)
1036 zrleInStream = new ZlibInStream();
1037
1038 int nBytes = rfb.readU32();
1039 if (nBytes > 64 * 1024 * 1024)
1040 throw new Exception("ZRLE decoder: illegal compressed data size");
1041
1042 if (zrleBuf == null || zrleBufLen < nBytes) {
1043 zrleBufLen = nBytes + 4096;
1044 zrleBuf = new byte[zrleBufLen];
1045 }
1046
1047 // FIXME: Do not wait for all the data before decompression.
1048 rfb.readFully(zrleBuf, 0, nBytes);
1049
1050 if (rfb.rec != null) {
1051 if (rfb.recordFromBeginning) {
1052 rfb.rec.writeIntBE(nBytes);
1053 rfb.rec.write(zrleBuf, 0, nBytes);
1054 } else if (!zrleRecWarningShown) {
1055 System.out.println("Warning: ZRLE session can be recorded" +
1056 " only from the beginning");
1057 System.out.println("Warning: Recorded file may be corrupted");
1058 zrleRecWarningShown = true;
1059 }
1060 }
1061
1062 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
1063
1064 for (int ty = y; ty < y+h; ty += 64) {
1065
1066 int th = Math.min(y+h-ty, 64);
1067
1068 for (int tx = x; tx < x+w; tx += 64) {
1069
1070 int tw = Math.min(x+w-tx, 64);
1071
1072 int mode = zrleInStream.readU8();
1073 boolean rle = (mode & 128) != 0;
1074 int palSize = mode & 127;
1075 int[] palette = new int[128];
1076
1077 readZrlePalette(palette, palSize);
1078
1079 if (palSize == 1) {
1080 int pix = palette[0];
1081 Color c = (bytesPixel == 1) ?
1082 colors[pix] : new Color(0xFF000000 | pix);
1083 memGraphics.setColor(c);
1084 memGraphics.fillRect(tx, ty, tw, th);
1085 continue;
1086 }
1087
1088 if (!rle) {
1089 if (palSize == 0) {
1090 readZrleRawPixels(tw, th);
1091 } else {
1092 readZrlePackedPixels(tw, th, palette, palSize);
1093 }
1094 } else {
1095 if (palSize == 0) {
1096 readZrlePlainRLEPixels(tw, th);
1097 } else {
1098 readZrlePackedRLEPixels(tw, th, palette);
1099 }
1100 }
1101 handleUpdatedZrleTile(tx, ty, tw, th);
1102 }
1103 }
1104
1105 zrleInStream.reset();
1106
1107 scheduleRepaint(x, y, w, h);
1108 }
1109
1110 int readPixel(InStream is) throws Exception {
1111 int pix;
1112 if (bytesPixel == 1) {
1113 pix = is.readU8();
1114 } else {
1115 int p1 = is.readU8();
1116 int p2 = is.readU8();
1117 int p3 = is.readU8();
1118 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
1119 }
1120 return pix;
1121 }
1122
1123 void readPixels(InStream is, int[] dst, int count) throws Exception {
1124 int pix;
1125 if (bytesPixel == 1) {
1126 byte[] buf = new byte[count];
1127 is.readBytes(buf, 0, count);
1128 for (int i = 0; i < count; i++) {
1129 dst[i] = (int)buf[i] & 0xFF;
1130 }
1131 } else {
1132 byte[] buf = new byte[count * 3];
1133 is.readBytes(buf, 0, count * 3);
1134 for (int i = 0; i < count; i++) {
1135 dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
1136 (buf[i*3+1] & 0xFF) << 8 |
1137 (buf[i*3] & 0xFF));
1138 }
1139 }
1140 }
1141
1142 void readZrlePalette(int[] palette, int palSize) throws Exception {
1143 readPixels(zrleInStream, palette, palSize);
1144 }
1145
1146 void readZrleRawPixels(int tw, int th) throws Exception {
1147 if (bytesPixel == 1) {
1148 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
1149 } else {
1150 readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
1151 }
1152 }
1153
1154 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
1155 throws Exception {
1156
1157 int bppp = ((palSize > 16) ? 8 :
1158 ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
1159 int ptr = 0;
1160
1161 for (int i = 0; i < th; i++) {
1162 int eol = ptr + tw;
1163 int b = 0;
1164 int nbits = 0;
1165
1166 while (ptr < eol) {
1167 if (nbits == 0) {
1168 b = zrleInStream.readU8();
1169 nbits = 8;
1170 }
1171 nbits -= bppp;
1172 int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
1173 if (bytesPixel == 1) {
1174 zrleTilePixels8[ptr++] = (byte)palette[index];
1175 } else {
1176 zrleTilePixels24[ptr++] = palette[index];
1177 }
1178 }
1179 }
1180 }
1181
1182 void readZrlePlainRLEPixels(int tw, int th) throws Exception {
1183 int ptr = 0;
1184 int end = ptr + tw * th;
1185 while (ptr < end) {
1186 int pix = readPixel(zrleInStream);
1187 int len = 1;
1188 int b;
1189 do {
1190 b = zrleInStream.readU8();
1191 len += b;
1192 } while (b == 255);
1193
1194 if (!(len <= end - ptr))
1195 throw new Exception("ZRLE decoder: assertion failed" +
1196 " (len <= end-ptr)");
1197
1198 if (bytesPixel == 1) {
1199 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
1200 } else {
1201 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
1202 }
1203 }
1204 }
1205
1206 void readZrlePackedRLEPixels(int tw, int th, int[] palette)
1207 throws Exception {
1208
1209 int ptr = 0;
1210 int end = ptr + tw * th;
1211 while (ptr < end) {
1212 int index = zrleInStream.readU8();
1213 int len = 1;
1214 if ((index & 128) != 0) {
1215 int b;
1216 do {
1217 b = zrleInStream.readU8();
1218 len += b;
1219 } while (b == 255);
enikeyc41ba1d2008-12-19 09:07:22 +00001220
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001221 if (!(len <= end - ptr))
1222 throw new Exception("ZRLE decoder: assertion failed" +
1223 " (len <= end - ptr)");
1224 }
1225
1226 index &= 127;
1227 int pix = palette[index];
1228
1229 if (bytesPixel == 1) {
1230 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
1231 } else {
1232 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
1233 }
1234 }
1235 }
1236
1237 //
1238 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
1239 //
1240
1241 void handleUpdatedZrleTile(int x, int y, int w, int h) {
1242 Object src, dst;
1243 if (bytesPixel == 1) {
1244 src = zrleTilePixels8; dst = pixels8;
1245 } else {
1246 src = zrleTilePixels24; dst = pixels24;
1247 }
1248 int offsetSrc = 0;
1249 int offsetDst = (y * rfb.framebufferWidth + x);
1250 for (int j = 0; j < h; j++) {
1251 System.arraycopy(src, offsetSrc, dst, offsetDst, w);
1252 offsetSrc += w;
1253 offsetDst += rfb.framebufferWidth;
1254 }
1255 handleUpdatedPixels(x, y, w, h);
1256 }
1257
1258 //
1259 // Handle a Zlib-encoded rectangle.
1260 //
1261
1262 void handleZlibRect(int x, int y, int w, int h) throws Exception {
1263
1264 int nBytes = rfb.readU32();
1265
1266 if (zlibBuf == null || zlibBufLen < nBytes) {
1267 zlibBufLen = nBytes * 2;
1268 zlibBuf = new byte[zlibBufLen];
1269 }
1270
1271 rfb.readFully(zlibBuf, 0, nBytes);
1272
1273 if (rfb.rec != null && rfb.recordFromBeginning) {
1274 rfb.rec.writeIntBE(nBytes);
1275 rfb.rec.write(zlibBuf, 0, nBytes);
1276 }
1277
1278 if (zlibInflater == null) {
1279 zlibInflater = new Inflater();
1280 }
1281 zlibInflater.setInput(zlibBuf, 0, nBytes);
1282
1283 if (bytesPixel == 1) {
1284 for (int dy = y; dy < y + h; dy++) {
1285 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
1286 if (rfb.rec != null && !rfb.recordFromBeginning)
1287 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1288 }
1289 } else {
1290 byte[] buf = new byte[w * 4];
1291 int i, offset;
1292 for (int dy = y; dy < y + h; dy++) {
1293 zlibInflater.inflate(buf);
1294 offset = dy * rfb.framebufferWidth + x;
1295 for (i = 0; i < w; i++) {
1296 pixels24[offset + i] =
1297 (buf[i * 4 + 2] & 0xFF) << 16 |
1298 (buf[i * 4 + 1] & 0xFF) << 8 |
1299 (buf[i * 4] & 0xFF);
1300 }
1301 if (rfb.rec != null && !rfb.recordFromBeginning)
1302 rfb.rec.write(buf);
1303 }
1304 }
1305
1306 handleUpdatedPixels(x, y, w, h);
1307 scheduleRepaint(x, y, w, h);
1308 }
1309
1310 //
1311 // Handle a Tight-encoded rectangle.
1312 //
1313
1314 void handleTightRect(int x, int y, int w, int h) throws Exception {
enikey2f7f46e2008-12-19 09:32:35 +00001315 tightDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001316 scheduleRepaint(x, y, w, h);
1317 }
1318
1319 //
1320 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
1321 //
1322
1323 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
1324
1325 int dx, dy, n;
1326 int i = y * rfb.framebufferWidth + x;
1327 int rowBytes = (w + 7) / 8;
1328 byte b;
1329
1330 for (dy = 0; dy < h; dy++) {
1331 for (dx = 0; dx < w / 8; dx++) {
1332 b = src[dy*rowBytes+dx];
1333 for (n = 7; n >= 0; n--)
1334 pixels8[i++] = palette[b >> n & 1];
1335 }
1336 for (n = 7; n >= 8 - w % 8; n--) {
1337 pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1338 }
1339 i += (rfb.framebufferWidth - w);
1340 }
1341 }
1342
1343 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
1344
1345 int dx, dy, n;
1346 int i = y * rfb.framebufferWidth + x;
1347 int rowBytes = (w + 7) / 8;
1348 byte b;
1349
1350 for (dy = 0; dy < h; dy++) {
1351 for (dx = 0; dx < w / 8; dx++) {
1352 b = src[dy*rowBytes+dx];
1353 for (n = 7; n >= 0; n--)
1354 pixels24[i++] = palette[b >> n & 1];
1355 }
1356 for (n = 7; n >= 8 - w % 8; n--) {
1357 pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1358 }
1359 i += (rfb.framebufferWidth - w);
1360 }
1361 }
1362
1363 //
1364 // Decode data processed with the "Gradient" filter.
1365 //
1366
1367 void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
1368
1369 int dx, dy, c;
1370 byte[] prevRow = new byte[w * 3];
1371 byte[] thisRow = new byte[w * 3];
1372 byte[] pix = new byte[3];
1373 int[] est = new int[3];
1374
1375 int offset = y * rfb.framebufferWidth + x;
1376
1377 for (dy = 0; dy < h; dy++) {
1378
1379 /* First pixel in a row */
1380 for (c = 0; c < 3; c++) {
1381 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
1382 thisRow[c] = pix[c];
1383 }
1384 pixels24[offset++] =
1385 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1386
1387 /* Remaining pixels of a row */
1388 for (dx = 1; dx < w; dx++) {
1389 for (c = 0; c < 3; c++) {
1390 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
1391 (prevRow[(dx-1) * 3 + c] & 0xFF));
1392 if (est[c] > 0xFF) {
1393 est[c] = 0xFF;
1394 } else if (est[c] < 0x00) {
1395 est[c] = 0x00;
1396 }
1397 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
1398 thisRow[dx * 3 + c] = pix[c];
1399 }
1400 pixels24[offset++] =
1401 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1402 }
1403
1404 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
1405 offset += (rfb.framebufferWidth - w);
1406 }
1407 }
1408
1409 //
1410 // Display newly updated area of pixels.
1411 //
1412
1413 void handleUpdatedPixels(int x, int y, int w, int h) {
1414
1415 // Draw updated pixels of the off-screen image.
1416 pixelsSource.newPixels(x, y, w, h);
1417 memGraphics.setClip(x, y, w, h);
1418 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1419 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1420 }
1421
1422 //
1423 // Tell JVM to repaint specified desktop area.
1424 //
1425
enikey0dbc1532008-12-19 08:51:47 +00001426 public void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001427 // Request repaint, deferred if necessary.
1428 if (rfb.framebufferWidth == scaledWidth) {
1429 repaint(viewer.deferScreenUpdates, x, y, w, h);
1430 } else {
1431 int sx = x * scalingFactor / 100;
1432 int sy = y * scalingFactor / 100;
1433 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
1434 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
1435 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
1436 }
1437 }
1438
1439 //
1440 // Handle events.
1441 //
1442
1443 public void keyPressed(KeyEvent evt) {
1444 processLocalKeyEvent(evt);
1445 }
1446 public void keyReleased(KeyEvent evt) {
1447 processLocalKeyEvent(evt);
1448 }
1449 public void keyTyped(KeyEvent evt) {
1450 evt.consume();
1451 }
1452
1453 public void mousePressed(MouseEvent evt) {
1454 processLocalMouseEvent(evt, false);
1455 }
1456 public void mouseReleased(MouseEvent evt) {
1457 processLocalMouseEvent(evt, false);
1458 }
1459 public void mouseMoved(MouseEvent evt) {
1460 processLocalMouseEvent(evt, true);
1461 }
1462 public void mouseDragged(MouseEvent evt) {
1463 processLocalMouseEvent(evt, true);
1464 }
1465
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001466 //
1467 // Ignored events.
1468 //
1469
1470 public void mouseClicked(MouseEvent evt) {}
1471 public void mouseEntered(MouseEvent evt) {}
1472 public void mouseExited(MouseEvent evt) {}
1473
1474 //
1475 // Actual event processing.
1476 //
1477
1478 private void processLocalKeyEvent(KeyEvent evt) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001479 if (viewer.rfb != null && rfb.inNormalProtocol) {
1480 if (!inputEnabled) {
1481 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
1482 evt.getID() == KeyEvent.KEY_PRESSED ) {
1483 // Request screen update.
1484 try {
1485 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
1486 rfb.framebufferHeight, false);
1487 } catch (IOException e) {
1488 e.printStackTrace();
1489 }
1490 }
1491 } else {
1492 // Input enabled.
1493 synchronized(rfb) {
1494 try {
1495 rfb.writeKeyEvent(evt);
1496 } catch (Exception e) {
1497 e.printStackTrace();
1498 }
1499 rfb.notify();
1500 }
1501 }
1502 }
enikeyc41ba1d2008-12-19 09:07:22 +00001503 // Don't ever pass keyboard events to AWT for default processing.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001504 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
1505 evt.consume();
1506 }
1507
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001508 private void processLocalMouseEvent(MouseEvent evt, boolean moved) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001509 if (viewer.rfb != null && rfb.inNormalProtocol) {
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001510 if (!inSelectionMode) {
1511 if (inputEnabled) {
1512 sendMouseEvent(evt, moved);
1513 }
1514 } else {
1515 handleSelectionMouseEvent(evt);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001516 }
1517 }
1518 }
1519
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001520 private void sendMouseEvent(MouseEvent evt, boolean moved) {
1521 if (moved) {
1522 softCursorMove(evt.getX(), evt.getY());
1523 }
1524 if (rfb.framebufferWidth != scaledWidth) {
1525 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
1526 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
1527 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
1528 }
1529 synchronized(rfb) {
1530 try {
1531 rfb.writePointerEvent(evt);
1532 } catch (Exception e) {
1533 e.printStackTrace();
1534 }
1535 rfb.notify();
1536 }
1537 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001538
1539 //
1540 // Reset update statistics.
1541 //
1542
1543 void resetStats() {
1544 statStartTime = System.currentTimeMillis();
1545 statNumUpdates = 0;
1546 statNumTotalRects = 0;
1547 statNumPixelRects = 0;
1548 statNumRectsTight = 0;
1549 statNumRectsTightJPEG = 0;
1550 statNumRectsZRLE = 0;
1551 statNumRectsHextile = 0;
1552 statNumRectsRaw = 0;
1553 statNumRectsCopy = 0;
1554 statNumBytesEncoded = 0;
1555 statNumBytesDecoded = 0;
enikey2f7f46e2008-12-19 09:32:35 +00001556 if (tightDecoder != null)
1557 tightDecoder.setNumJPEGRects(0);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001558 }
1559
1560 //////////////////////////////////////////////////////////////////
1561 //
1562 // Handle cursor shape updates (XCursor and RichCursor encodings).
1563 //
1564
1565 boolean showSoftCursor = false;
1566
1567 MemoryImageSource softCursorSource;
1568 Image softCursor;
1569
1570 int cursorX = 0, cursorY = 0;
1571 int cursorWidth, cursorHeight;
1572 int origCursorWidth, origCursorHeight;
1573 int hotX, hotY;
1574 int origHotX, origHotY;
1575
1576 //
1577 // Handle cursor shape update (XCursor and RichCursor encodings).
1578 //
1579
1580 synchronized void
1581 handleCursorShapeUpdate(int encodingType,
1582 int xhot, int yhot, int width, int height)
1583 throws IOException {
1584
1585 softCursorFree();
1586
1587 if (width * height == 0)
1588 return;
1589
1590 // Ignore cursor shape data if requested by user.
1591 if (viewer.options.ignoreCursorUpdates) {
1592 int bytesPerRow = (width + 7) / 8;
1593 int bytesMaskData = bytesPerRow * height;
1594
1595 if (encodingType == rfb.EncodingXCursor) {
1596 rfb.skipBytes(6 + bytesMaskData * 2);
1597 } else {
1598 // rfb.EncodingRichCursor
1599 rfb.skipBytes(width * height + bytesMaskData);
1600 }
1601 return;
1602 }
1603
1604 // Decode cursor pixel data.
1605 softCursorSource = decodeCursorShape(encodingType, width, height);
1606
1607 // Set original (non-scaled) cursor dimensions.
1608 origCursorWidth = width;
1609 origCursorHeight = height;
1610 origHotX = xhot;
1611 origHotY = yhot;
1612
1613 // Create off-screen cursor image.
1614 createSoftCursor();
1615
1616 // Show the cursor.
1617 showSoftCursor = true;
1618 repaint(viewer.deferCursorUpdates,
1619 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1620 }
1621
1622 //
1623 // decodeCursorShape(). Decode cursor pixel data and return
1624 // corresponding MemoryImageSource instance.
1625 //
1626
1627 synchronized MemoryImageSource
1628 decodeCursorShape(int encodingType, int width, int height)
1629 throws IOException {
1630
1631 int bytesPerRow = (width + 7) / 8;
1632 int bytesMaskData = bytesPerRow * height;
1633
1634 int[] softCursorPixels = new int[width * height];
1635
1636 if (encodingType == rfb.EncodingXCursor) {
1637
1638 // Read foreground and background colors of the cursor.
1639 byte[] rgb = new byte[6];
1640 rfb.readFully(rgb);
1641 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
1642 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1643 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1644 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
1645
1646 // Read pixel and mask data.
1647 byte[] pixBuf = new byte[bytesMaskData];
1648 rfb.readFully(pixBuf);
1649 byte[] maskBuf = new byte[bytesMaskData];
1650 rfb.readFully(maskBuf);
1651
1652 // Decode pixel data into softCursorPixels[].
1653 byte pixByte, maskByte;
1654 int x, y, n, result;
1655 int i = 0;
1656 for (y = 0; y < height; y++) {
1657 for (x = 0; x < width / 8; x++) {
1658 pixByte = pixBuf[y * bytesPerRow + x];
1659 maskByte = maskBuf[y * bytesPerRow + x];
1660 for (n = 7; n >= 0; n--) {
1661 if ((maskByte >> n & 1) != 0) {
1662 result = colors[pixByte >> n & 1];
1663 } else {
1664 result = 0; // Transparent pixel
1665 }
1666 softCursorPixels[i++] = result;
1667 }
1668 }
1669 for (n = 7; n >= 8 - width % 8; n--) {
1670 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1671 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1672 } else {
1673 result = 0; // Transparent pixel
1674 }
1675 softCursorPixels[i++] = result;
1676 }
1677 }
1678
1679 } else {
1680 // encodingType == rfb.EncodingRichCursor
1681
1682 // Read pixel and mask data.
1683 byte[] pixBuf = new byte[width * height * bytesPixel];
1684 rfb.readFully(pixBuf);
1685 byte[] maskBuf = new byte[bytesMaskData];
1686 rfb.readFully(maskBuf);
1687
1688 // Decode pixel data into softCursorPixels[].
1689 byte pixByte, maskByte;
1690 int x, y, n, result;
1691 int i = 0;
1692 for (y = 0; y < height; y++) {
1693 for (x = 0; x < width / 8; x++) {
1694 maskByte = maskBuf[y * bytesPerRow + x];
1695 for (n = 7; n >= 0; n--) {
1696 if ((maskByte >> n & 1) != 0) {
1697 if (bytesPixel == 1) {
1698 result = cm8.getRGB(pixBuf[i]);
1699 } else {
1700 result = 0xFF000000 |
1701 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1702 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1703 (pixBuf[i * 4] & 0xFF);
1704 }
1705 } else {
1706 result = 0; // Transparent pixel
1707 }
1708 softCursorPixels[i++] = result;
1709 }
1710 }
1711 for (n = 7; n >= 8 - width % 8; n--) {
1712 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1713 if (bytesPixel == 1) {
1714 result = cm8.getRGB(pixBuf[i]);
1715 } else {
1716 result = 0xFF000000 |
1717 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1718 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1719 (pixBuf[i * 4] & 0xFF);
1720 }
1721 } else {
1722 result = 0; // Transparent pixel
1723 }
1724 softCursorPixels[i++] = result;
1725 }
1726 }
1727
1728 }
1729
1730 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1731 }
1732
1733 //
1734 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1735 // Uses softCursorSource as a source for new cursor image.
1736 //
1737
1738 synchronized void
1739 createSoftCursor() {
1740
1741 if (softCursorSource == null)
1742 return;
1743
1744 int scaleCursor = viewer.options.scaleCursor;
1745 if (scaleCursor == 0 || !inputEnabled)
1746 scaleCursor = 100;
1747
1748 // Save original cursor coordinates.
1749 int x = cursorX - hotX;
1750 int y = cursorY - hotY;
1751 int w = cursorWidth;
1752 int h = cursorHeight;
1753
1754 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1755 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1756 hotX = (origHotX * scaleCursor + 50) / 100;
1757 hotY = (origHotY * scaleCursor + 50) / 100;
1758 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1759
1760 if (scaleCursor != 100) {
1761 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1762 Image.SCALE_SMOOTH);
1763 }
1764
1765 if (showSoftCursor) {
1766 // Compute screen area to update.
1767 x = Math.min(x, cursorX - hotX);
1768 y = Math.min(y, cursorY - hotY);
1769 w = Math.max(w, cursorWidth);
1770 h = Math.max(h, cursorHeight);
1771
1772 repaint(viewer.deferCursorUpdates, x, y, w, h);
1773 }
1774 }
1775
1776 //
1777 // softCursorMove(). Moves soft cursor into a particular location.
1778 //
1779
1780 synchronized void softCursorMove(int x, int y) {
1781 int oldX = cursorX;
1782 int oldY = cursorY;
1783 cursorX = x;
1784 cursorY = y;
1785 if (showSoftCursor) {
1786 repaint(viewer.deferCursorUpdates,
1787 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1788 repaint(viewer.deferCursorUpdates,
1789 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1790 }
1791 }
1792
1793 //
1794 // softCursorFree(). Remove soft cursor, dispose resources.
1795 //
1796
1797 synchronized void softCursorFree() {
1798 if (showSoftCursor) {
1799 showSoftCursor = false;
1800 softCursor = null;
1801 softCursorSource = null;
1802
1803 repaint(viewer.deferCursorUpdates,
1804 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1805 }
1806 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001807
1808 //////////////////////////////////////////////////////////////////
1809 //
1810 // Support for selecting a rectangular video area.
1811 //
1812
1813 /** This flag is false in normal operation, and true in the selection mode. */
1814 private boolean inSelectionMode;
1815
1816 /** The point where the selection was started. */
1817 private Point selectionStart;
1818
1819 /** The second point of the selection. */
1820 private Point selectionEnd;
1821
1822 /**
1823 * We change cursor when enabling the selection mode. In this variable, we
1824 * save the original cursor so we can restore it on returning to the normal
1825 * mode.
1826 */
1827 private Cursor savedCursor;
1828
1829 /**
1830 * Initialize selection-related varibles.
1831 */
1832 private synchronized void resetSelection() {
1833 inSelectionMode = false;
1834 selectionStart = new Point(0, 0);
1835 selectionEnd = new Point(0, 0);
1836
1837 savedCursor = getCursor();
1838 }
1839
1840 /**
1841 * Check current state of the selection mode.
1842 * @return true in the selection mode, false otherwise.
1843 */
1844 public boolean isInSelectionMode() {
1845 return inSelectionMode;
1846 }
1847
1848 /**
1849 * Get current selection.
1850 * @param useScreenCoords use screen coordinates if true, or framebuffer
1851 * coordinates if false. This makes difference when scaling factor is not 100.
1852 * @return The selection as a {@link Rectangle}.
1853 */
1854 private synchronized Rectangle getSelection(boolean useScreenCoords) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001855 int x0 = selectionStart.x;
1856 int x1 = selectionEnd.x;
1857 int y0 = selectionStart.y;
1858 int y1 = selectionEnd.y;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001859 // Make x and y point to the upper left corner of the selection.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001860 if (x1 < x0) {
1861 int t = x0; x0 = x1; x1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001862 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001863 if (y1 < y0) {
1864 int t = y0; y0 = y1; y1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001865 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001866 // Include the borders in the selection (unless it's empty).
1867 if (x0 != x1 && y0 != y1) {
1868 x1 += 1;
1869 y1 += 1;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001870 }
1871 // Translate from screen coordinates to framebuffer coordinates.
1872 if (rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001873 x0 = (x0 * 100 + scalingFactor/2) / scalingFactor;
1874 y0 = (y0 * 100 + scalingFactor/2) / scalingFactor;
1875 x1 = (x1 * 100 + scalingFactor/2) / scalingFactor;
1876 y1 = (y1 * 100 + scalingFactor/2) / scalingFactor;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001877 }
Constantin Kaplinsky10da44d2008-09-03 03:12:18 +00001878 // Clip the selection to framebuffer.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001879 if (x0 < 0)
1880 x0 = 0;
1881 if (y0 < 0)
1882 y0 = 0;
1883 if (x1 > rfb.framebufferWidth)
1884 x1 = rfb.framebufferWidth;
1885 if (y1 > rfb.framebufferHeight)
1886 y1 = rfb.framebufferHeight;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001887 // Make width a multiple of 16.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001888 int widthBlocks = (x1 - x0 + 8) / 16;
1889 if (selectionStart.x <= selectionEnd.x) {
1890 x1 = x0 + widthBlocks * 16;
1891 if (x1 > rfb.framebufferWidth) {
1892 x1 -= 16;
1893 }
1894 } else {
1895 x0 = x1 - widthBlocks * 16;
1896 if (x0 < 0) {
1897 x0 += 16;
1898 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001899 }
1900 // Make height a multiple of 8.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001901 int heightBlocks = (y1 - y0 + 4) / 8;
1902 if (selectionStart.y <= selectionEnd.y) {
1903 y1 = y0 + heightBlocks * 8;
1904 if (y1 > rfb.framebufferHeight) {
1905 y1 -= 8;
1906 }
1907 } else {
1908 y0 = y1 - heightBlocks * 8;
1909 if (y0 < 0) {
1910 y0 += 8;
1911 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001912 }
1913 // Translate the selection back to screen coordinates if requested.
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001914 if (useScreenCoords && rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001915 x0 = (x0 * scalingFactor + 50) / 100;
1916 y0 = (y0 * scalingFactor + 50) / 100;
1917 x1 = (x1 * scalingFactor + 50) / 100;
1918 y1 = (y1 * scalingFactor + 50) / 100;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001919 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001920 // Construct and return the result.
1921 return new Rectangle(x0, y0, x1 - x0, y1 - y0);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001922 }
1923
1924 /**
1925 * Enable or disable the selection mode.
1926 * @param enable enables the selection mode if true, disables if fasle.
1927 */
1928 public synchronized void enableSelection(boolean enable) {
1929 if (enable && !inSelectionMode) {
1930 // Enter the selection mode.
1931 inSelectionMode = true;
1932 savedCursor = getCursor();
1933 setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
1934 repaint();
1935 } else if (!enable && inSelectionMode) {
1936 // Leave the selection mode.
1937 inSelectionMode = false;
1938 setCursor(savedCursor);
1939 repaint();
1940 }
1941 }
1942
1943 /**
1944 * Process mouse events in the selection mode.
enikeyc41ba1d2008-12-19 09:07:22 +00001945 *
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001946 * @param evt mouse event that was originally passed to
1947 * {@link MouseListener} or {@link MouseMotionListener}.
1948 */
1949 private synchronized void handleSelectionMouseEvent(MouseEvent evt) {
1950 int id = evt.getID();
1951 boolean button1 = (evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0;
1952
1953 if (id == MouseEvent.MOUSE_PRESSED && button1) {
1954 selectionStart = selectionEnd = evt.getPoint();
1955 repaint();
1956 }
1957 if (id == MouseEvent.MOUSE_DRAGGED && button1) {
1958 selectionEnd = evt.getPoint();
1959 repaint();
1960 }
1961 if (id == MouseEvent.MOUSE_RELEASED && button1) {
1962 try {
1963 rfb.trySendVideoSelection(getSelection(false));
1964 } catch (IOException e) {
1965 e.printStackTrace();
1966 }
1967 }
1968 }
1969
enikey418611f2008-12-19 04:37:09 +00001970 //
1971 // Override RecordInterface methods
1972 //
1973
1974 public boolean isRecordFromBeginning() {
1975 return rfb.recordFromBeginning;
1976 }
1977
1978 public boolean canWrite() {
1979 // We can record if rec is not null
1980 return rfb.rec != null;
1981 }
1982
1983 public void write(byte b[]) throws IOException {
1984 rfb.rec.write(b);
1985 }
1986
1987 public void write(byte b[], int off, int len) throws IOException {
1988 rfb.rec.write(b, off, len);
1989 }
1990
1991 public void writeByte(byte b) throws IOException {
1992 rfb.rec.writeByte(b);
1993 }
1994
1995 public void writeByte(int i) throws IOException {
1996 rfb.rec.writeByte(i);
1997 }
1998
1999 public void writeIntBE(int v) throws IOException {
2000 rfb.rec.writeIntBE(v);
2001 }
2002
2003 public void recordCompactLen(int len) throws IOException {
2004 rfb.recordCompactLen(len);
2005 }
2006
2007 public void recordCompressedData(byte[] data) throws IOException {
2008 rfb.recordCompressedData(data);
2009 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00002010}