blob: 8f450c77aa57b39ec85e6cac430aa3152540ade7 [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
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000111 // True if we process keyboard and mouse events.
112 boolean inputEnabled;
113
114 //
115 // The constructors.
116 //
117
118 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
119 throws IOException {
120
121 viewer = v;
122 maxWidth = maxWidth_;
123 maxHeight = maxHeight_;
124
125 rfb = viewer.rfb;
126 scalingFactor = viewer.options.scalingFactor;
127
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000128 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
129 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
130
131 colors = new Color[256];
132 for (int i = 0; i < 256; i++)
133 colors[i] = new Color(cm8.getRGB(i));
134
enikeyc41ba1d2008-12-19 09:07:22 +0000135 //
136 // Create decoders
137 //
138
139 // Input stream for decoders
140 RfbInputStream rfbis = new RfbInputStream(rfb);
141
142 rawDecoder = new RawDecoder(memGraphics, rfbis);
143 rreDecoder = new RREDecoder(memGraphics, rfbis);
144 correDecoder = new CoRREDecoder(memGraphics, rfbis);
145 hextileDecoder = new HextileDecoder(memGraphics, rfbis);
146 tightDecoder = new TightDecoder(memGraphics, rfbis);
enikey4582bab2008-12-19 09:19:59 +0000147 zlibDecoder = new ZlibDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000148 zrleDecoder = new ZRLEDecoder(memGraphics, rfbis);
149
150 //
151 // Set data for decoders that needs extra parameters
152 //
153
154 hextileDecoder.setRepainableControl(this);
155 tightDecoder.setRepainableControl(this);
156
157 //
158 // Create array that contains our decoders
159 //
160
161 decoders = new RawDecoder[7];
162 decoders[0] = rawDecoder;
163 decoders[1] = rreDecoder;
164 decoders[2] = correDecoder;
165 decoders[3] = hextileDecoder;
166 decoders[4] = zlibDecoder;
167 decoders[5] = tightDecoder;
168 decoders[6] = zrleDecoder;
169
170 //
171 // Set session recorder for decoders
172 //
173
174 for (int i = 0; i < decoders.length; i++) {
175 decoders[i].setSessionRecorder(this);
176 }
enikey2f7f46e2008-12-19 09:32:35 +0000177
178 setPixelFormat();
179
180 resetSelection();
181
182 inputEnabled = false;
183 if (!viewer.options.viewOnly)
184 enableInput(true);
enikeyc41ba1d2008-12-19 09:07:22 +0000185
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000186 // Enable mouse and keyboard event listeners.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000187 addKeyListener(this);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000188 addMouseListener(this);
189 addMouseMotionListener(this);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000190 }
191
192 public VncCanvas(VncViewer v) throws IOException {
193 this(v, 0, 0);
194 }
195
196 //
197 // Callback methods to determine geometry of our Component.
198 //
199
200 public Dimension getPreferredSize() {
201 return new Dimension(scaledWidth, scaledHeight);
202 }
203
204 public Dimension getMinimumSize() {
205 return new Dimension(scaledWidth, scaledHeight);
206 }
207
208 public Dimension getMaximumSize() {
209 return new Dimension(scaledWidth, scaledHeight);
210 }
211
212 //
213 // All painting is performed here.
214 //
215
216 public void update(Graphics g) {
217 paint(g);
218 }
219
220 public void paint(Graphics g) {
221 synchronized(memImage) {
222 if (rfb.framebufferWidth == scaledWidth) {
223 g.drawImage(memImage, 0, 0, null);
224 } else {
225 paintScaledFrameBuffer(g);
226 }
227 }
228 if (showSoftCursor) {
229 int x0 = cursorX - hotX, y0 = cursorY - hotY;
230 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
231 if (r.intersects(g.getClipBounds())) {
232 g.drawImage(softCursor, x0, y0, null);
233 }
234 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000235 if (isInSelectionMode()) {
236 Rectangle r = getSelection(true);
237 if (r.width > 0 && r.height > 0) {
238 // Don't forget to correct the coordinates for the right and bottom
239 // borders, so that the borders are the part of the selection.
240 r.width -= 1;
241 r.height -= 1;
242 g.setXORMode(Color.yellow);
243 g.drawRect(r.x, r.y, r.width, r.height);
244 }
245 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000246 }
247
248 public void paintScaledFrameBuffer(Graphics g) {
249 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
250 }
251
252 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000253 // Start/stop receiving mouse events. Keyboard events are received
254 // even in view-only mode, because we want to map the 'r' key to the
255 // screen refreshing function.
256 //
257
258 public synchronized void enableInput(boolean enable) {
259 if (enable && !inputEnabled) {
260 inputEnabled = true;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000261 if (viewer.showControls) {
262 viewer.buttonPanel.enableRemoteAccessControls(true);
263 }
264 createSoftCursor(); // scaled cursor
265 } else if (!enable && inputEnabled) {
266 inputEnabled = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000267 if (viewer.showControls) {
268 viewer.buttonPanel.enableRemoteAccessControls(false);
269 }
270 createSoftCursor(); // non-scaled cursor
271 }
272 }
273
274 public void setPixelFormat() throws IOException {
275 if (viewer.options.eightBitColors) {
276 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
277 bytesPixel = 1;
278 } else {
279 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
280 bytesPixel = 4;
281 }
282 updateFramebufferSize();
283 }
284
enikey45cfaa52008-12-03 06:52:09 +0000285 void setScalingFactor(int sf) {
286 scalingFactor = sf;
287 updateFramebufferSize();
288 invalidate();
289 }
290
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000291 void updateFramebufferSize() {
292
293 // Useful shortcuts.
294 int fbWidth = rfb.framebufferWidth;
295 int fbHeight = rfb.framebufferHeight;
296
enikey87647e92008-12-04 08:42:34 +0000297 // FIXME: This part of code must be in VncViewer i think
enikey73683202008-12-03 09:17:25 +0000298 if (viewer.options.autoScale) {
enikey87647e92008-12-04 08:42:34 +0000299 if (viewer.inAnApplet) {
300 maxWidth = viewer.getWidth();
301 maxHeight = viewer.getHeight();
302 } else {
303 if (viewer.vncFrame != null) {
304 if (isFirstSizeAutoUpdate) {
305 isFirstSizeAutoUpdate = false;
306 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
307 maxWidth = (int)screenSize.getWidth() - 100;
enikeyc41ba1d2008-12-19 09:07:22 +0000308 maxHeight = (int)screenSize.getHeight() - 100;
enikey87647e92008-12-04 08:42:34 +0000309 viewer.vncFrame.setSize(maxWidth, maxHeight);
310 } else {
311 viewer.desktopScrollPane.doLayout();
312 maxWidth = viewer.desktopScrollPane.getWidth();
313 maxHeight = viewer.desktopScrollPane.getHeight();
314 }
315 } else {
316 maxWidth = fbWidth;
317 maxHeight = fbHeight;
318 }
enikey73683202008-12-03 09:17:25 +0000319 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000320 int f1 = maxWidth * 100 / fbWidth;
321 int f2 = maxHeight * 100 / fbHeight;
322 scalingFactor = Math.min(f1, f2);
323 if (scalingFactor > 100)
324 scalingFactor = 100;
325 System.out.println("Scaling desktop at " + scalingFactor + "%");
326 }
327
328 // Update scaled framebuffer geometry.
329 scaledWidth = (fbWidth * scalingFactor + 50) / 100;
330 scaledHeight = (fbHeight * scalingFactor + 50) / 100;
331
332 // Create new off-screen image either if it does not exist, or if
333 // its geometry should be changed. It's not necessary to replace
334 // existing image if only pixel format should be changed.
335 if (memImage == null) {
336 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
337 memGraphics = memImage.getGraphics();
338 } else if (memImage.getWidth(null) != fbWidth ||
339 memImage.getHeight(null) != fbHeight) {
340 synchronized(memImage) {
341 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
342 memGraphics = memImage.getGraphics();
343 }
344 }
345
enikey4582bab2008-12-19 09:19:59 +0000346 //
347 // Update decoders
348 //
349
350 //
351 // FIXME: Why decoders can be null here?
352 //
353
354 if (decoders != null) {
355 for (int i = 0; i < decoders.length; i++) {
356 //
357 // Set changes to every decoder that we can use
358 //
359
360 decoders[i].setBPP(bytesPixel);
361 decoders[i].setFrameBufferSize(fbWidth, fbHeight);
362 decoders[i].setGraphics(memGraphics);
363
364 //
365 // Update decoder
366 //
367
368 decoders[i].update();
369 }
370 }
371
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000372 // Images with raw pixels should be re-allocated on every change
373 // of geometry or pixel format.
374 if (bytesPixel == 1) {
375
376 pixels24 = null;
377 pixels8 = new byte[fbWidth * fbHeight];
378
379 pixelsSource =
380 new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
381
382 zrleTilePixels24 = null;
383 zrleTilePixels8 = new byte[64 * 64];
384
385 } else {
386
387 pixels8 = null;
388 pixels24 = new int[fbWidth * fbHeight];
389
390 pixelsSource =
391 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
392
393 zrleTilePixels8 = null;
394 zrleTilePixels24 = new int[64 * 64];
395
396 }
397 pixelsSource.setAnimated(true);
398 rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
399
enikey87647e92008-12-04 08:42:34 +0000400 // FIXME: This part of code must be in VncViewer i think
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000401 // Update the size of desktop containers.
402 if (viewer.inSeparateFrame) {
enikey87647e92008-12-04 08:42:34 +0000403 if (viewer.desktopScrollPane != null) {
404 if (!viewer.options.autoScale) {
405 resizeDesktopFrame();
406 } else {
407 setSize(scaledWidth, scaledHeight);
408 viewer.desktopScrollPane.setSize(maxWidth + 200,
409 maxHeight + 200);
410 }
411 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000412 } else {
413 setSize(scaledWidth, scaledHeight);
414 }
415 viewer.moveFocusToDesktop();
416 }
417
418 void resizeDesktopFrame() {
419 setSize(scaledWidth, scaledHeight);
420
421 // FIXME: Find a better way to determine correct size of a
422 // ScrollPane. -- const
423 Insets insets = viewer.desktopScrollPane.getInsets();
424 viewer.desktopScrollPane.setSize(scaledWidth +
425 2 * Math.min(insets.left, insets.right),
426 scaledHeight +
427 2 * Math.min(insets.top, insets.bottom));
428
429 viewer.vncFrame.pack();
430
431 // Try to limit the frame size to the screen size.
432
433 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
434 Dimension frameSize = viewer.vncFrame.getSize();
435 Dimension newSize = frameSize;
436
437 // Reduce Screen Size by 30 pixels in each direction;
438 // This is a (poor) attempt to account for
439 // 1) Menu bar on Macintosh (should really also account for
440 // Dock on OSX). Usually 22px on top of screen.
441 // 2) Taxkbar on Windows (usually about 28 px on bottom)
442 // 3) Other obstructions.
443
444 screenSize.height -= 30;
445 screenSize.width -= 30;
446
447 boolean needToResizeFrame = false;
448 if (frameSize.height > screenSize.height) {
449 newSize.height = screenSize.height;
450 needToResizeFrame = true;
451 }
452 if (frameSize.width > screenSize.width) {
453 newSize.width = screenSize.width;
454 needToResizeFrame = true;
455 }
456 if (needToResizeFrame) {
457 viewer.vncFrame.setSize(newSize);
458 }
459
460 viewer.desktopScrollPane.doLayout();
461 }
462
463 //
464 // processNormalProtocol() - executed by the rfbThread to deal with the
465 // RFB socket.
466 //
467
468 public void processNormalProtocol() throws Exception {
469
470 // Start/stop session recording if necessary.
471 viewer.checkRecordingStatus();
472
473 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
474 rfb.framebufferHeight, false);
475
476 if (viewer.options.continuousUpdates) {
477 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
478 rfb.framebufferHeight);
479 }
480
481 resetStats();
482 boolean statsRestarted = false;
483
484 //
485 // main dispatch loop
486 //
487
488 while (true) {
489
490 // Read message type from the server.
491 int msgType = rfb.readServerMessageType();
492
493 // Process the message depending on its type.
494 switch (msgType) {
495 case RfbProto.FramebufferUpdate:
496
497 if (statNumUpdates == viewer.debugStatsExcludeUpdates &&
498 !statsRestarted) {
499 resetStats();
500 statsRestarted = true;
501 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates &&
502 statsRestarted) {
503 viewer.disconnect();
504 }
505
506 rfb.readFramebufferUpdate();
507 statNumUpdates++;
508
509 boolean cursorPosReceived = false;
510
511 for (int i = 0; i < rfb.updateNRects; i++) {
512
513 rfb.readFramebufferUpdateRectHdr();
514 statNumTotalRects++;
515 int rx = rfb.updateRectX, ry = rfb.updateRectY;
516 int rw = rfb.updateRectW, rh = rfb.updateRectH;
517
518 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
519 break;
520
521 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
522 rfb.setFramebufferSize(rw, rh);
523 updateFramebufferSize();
524 break;
525 }
526
527 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
528 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
529 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
530 continue;
531 }
532
533 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
534 softCursorMove(rx, ry);
535 cursorPosReceived = true;
536 continue;
537 }
538
539 long numBytesReadBefore = rfb.getNumBytesRead();
540
541 rfb.startTiming();
542
543 switch (rfb.updateRectEncoding) {
544 case RfbProto.EncodingRaw:
545 statNumRectsRaw++;
546 handleRawRect(rx, ry, rw, rh);
547 break;
548 case RfbProto.EncodingCopyRect:
549 statNumRectsCopy++;
550 handleCopyRect(rx, ry, rw, rh);
551 break;
552 case RfbProto.EncodingRRE:
553 handleRRERect(rx, ry, rw, rh);
554 break;
555 case RfbProto.EncodingCoRRE:
556 handleCoRRERect(rx, ry, rw, rh);
557 break;
558 case RfbProto.EncodingHextile:
559 statNumRectsHextile++;
560 handleHextileRect(rx, ry, rw, rh);
561 break;
562 case RfbProto.EncodingZRLE:
563 statNumRectsZRLE++;
564 handleZRLERect(rx, ry, rw, rh);
565 break;
566 case RfbProto.EncodingZlib:
567 handleZlibRect(rx, ry, rw, rh);
568 break;
569 case RfbProto.EncodingTight:
enikey2f7f46e2008-12-19 09:32:35 +0000570 if (tightDecoder != null) {
enikey479b18c2008-12-19 09:39:40 +0000571 statNumRectsTightJPEG = tightDecoder.getNumJPEGRects();
enikey2f7f46e2008-12-19 09:32:35 +0000572 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000573 handleTightRect(rx, ry, rw, rh);
574 break;
575 default:
576 throw new Exception("Unknown RFB rectangle encoding " +
577 rfb.updateRectEncoding);
578 }
579
580 rfb.stopTiming();
581
582 statNumPixelRects++;
583 statNumBytesDecoded += rw * rh * bytesPixel;
584 statNumBytesEncoded +=
585 (int)(rfb.getNumBytesRead() - numBytesReadBefore);
586 }
587
588 boolean fullUpdateNeeded = false;
589
590 // Start/stop session recording if necessary. Request full
591 // update if a new session file was opened.
592 if (viewer.checkRecordingStatus())
593 fullUpdateNeeded = true;
594
595 // Defer framebuffer update request if necessary. But wake up
596 // immediately on keyboard or mouse event. Also, don't sleep
597 // if there is some data to receive, or if the last update
598 // included a PointerPos message.
599 if (viewer.deferUpdateRequests > 0 &&
600 rfb.available() == 0 && !cursorPosReceived) {
601 synchronized(rfb) {
602 try {
603 rfb.wait(viewer.deferUpdateRequests);
604 } catch (InterruptedException e) {
605 }
606 }
607 }
608
609 viewer.autoSelectEncodings();
610
611 // Before requesting framebuffer update, check if the pixel
612 // format should be changed.
613 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
614 // Pixel format should be changed.
615 if (!rfb.continuousUpdatesAreActive()) {
616 // Continuous updates are not used. In this case, we just
617 // set new pixel format and request full update.
618 setPixelFormat();
619 fullUpdateNeeded = true;
620 } else {
621 // Otherwise, disable continuous updates first. Pixel
622 // format will be set later when we are sure that there
623 // will be no unsolicited framebuffer updates.
624 rfb.tryDisableContinuousUpdates();
625 break; // skip the code below
626 }
627 }
628
629 // Enable/disable continuous updates to reflect the GUI setting.
630 boolean enable = viewer.options.continuousUpdates;
631 if (enable != rfb.continuousUpdatesAreActive()) {
632 if (enable) {
633 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
634 rfb.framebufferHeight);
635 } else {
636 rfb.tryDisableContinuousUpdates();
637 }
638 }
639
640 // Finally, request framebuffer update if needed.
641 if (fullUpdateNeeded) {
642 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
643 rfb.framebufferHeight, false);
644 } else if (!rfb.continuousUpdatesAreActive()) {
645 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
646 rfb.framebufferHeight, true);
647 }
648
649 break;
650
651 case RfbProto.SetColourMapEntries:
652 throw new Exception("Can't handle SetColourMapEntries message");
653
654 case RfbProto.Bell:
655 Toolkit.getDefaultToolkit().beep();
656 break;
657
658 case RfbProto.ServerCutText:
659 String s = rfb.readServerCutText();
660 viewer.clipboard.setCutText(s);
661 break;
662
663 case RfbProto.EndOfContinuousUpdates:
664 if (rfb.continuousUpdatesAreActive()) {
665 rfb.endOfContinuousUpdates();
666
667 // Change pixel format if such change was pending. Note that we
668 // could not change pixel format while continuous updates were
669 // in effect.
670 boolean incremental = true;
671 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
672 setPixelFormat();
673 incremental = false;
674 }
675 // From this point, we ask for updates explicitly.
676 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
677 rfb.framebufferHeight,
678 incremental);
679 }
680 break;
681
682 default:
683 throw new Exception("Unknown RFB message type " + msgType);
684 }
685 }
686 }
687
688
689 //
690 // Handle a raw rectangle. The second form with paint==false is used
691 // by the Hextile decoder for raw-encoded tiles.
692 //
693
694 void handleRawRect(int x, int y, int w, int h) throws IOException {
695 handleRawRect(x, y, w, h, true);
696 }
697
698 void handleRawRect(int x, int y, int w, int h, boolean paint)
699 throws IOException {
700
701 if (bytesPixel == 1) {
702 for (int dy = y; dy < y + h; dy++) {
703 rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
704 if (rfb.rec != null) {
705 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
706 }
707 }
708 } else {
709 byte[] buf = new byte[w * 4];
710 int i, offset;
711 for (int dy = y; dy < y + h; dy++) {
712 rfb.readFully(buf);
713 if (rfb.rec != null) {
714 rfb.rec.write(buf);
715 }
716 offset = dy * rfb.framebufferWidth + x;
717 for (i = 0; i < w; i++) {
718 pixels24[offset + i] =
719 (buf[i * 4 + 2] & 0xFF) << 16 |
720 (buf[i * 4 + 1] & 0xFF) << 8 |
721 (buf[i * 4] & 0xFF);
722 }
723 }
724 }
725
726 handleUpdatedPixels(x, y, w, h);
727 if (paint)
728 scheduleRepaint(x, y, w, h);
729 }
730
731 //
732 // Handle a CopyRect rectangle.
733 //
734
735 void handleCopyRect(int x, int y, int w, int h) throws IOException {
736
737 rfb.readCopyRect();
738 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
739 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
740
741 scheduleRepaint(x, y, w, h);
742 }
743
744 //
745 // Handle an RRE-encoded rectangle.
746 //
747
748 void handleRRERect(int x, int y, int w, int h) throws IOException {
749
750 int nSubrects = rfb.readU32();
751
752 byte[] bg_buf = new byte[bytesPixel];
753 rfb.readFully(bg_buf);
754 Color pixel;
755 if (bytesPixel == 1) {
756 pixel = colors[bg_buf[0] & 0xFF];
757 } else {
758 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
759 }
760 memGraphics.setColor(pixel);
761 memGraphics.fillRect(x, y, w, h);
762
763 byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
764 rfb.readFully(buf);
765 DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
766
767 if (rfb.rec != null) {
768 rfb.rec.writeIntBE(nSubrects);
769 rfb.rec.write(bg_buf);
770 rfb.rec.write(buf);
771 }
772
773 int sx, sy, sw, sh;
774
775 for (int j = 0; j < nSubrects; j++) {
776 if (bytesPixel == 1) {
777 pixel = colors[ds.readUnsignedByte()];
778 } else {
779 ds.skip(4);
780 pixel = new Color(buf[j*12+2] & 0xFF,
781 buf[j*12+1] & 0xFF,
782 buf[j*12] & 0xFF);
783 }
784 sx = x + ds.readUnsignedShort();
785 sy = y + ds.readUnsignedShort();
786 sw = ds.readUnsignedShort();
787 sh = ds.readUnsignedShort();
788
789 memGraphics.setColor(pixel);
790 memGraphics.fillRect(sx, sy, sw, sh);
791 }
792
793 scheduleRepaint(x, y, w, h);
794 }
795
796 //
797 // Handle a CoRRE-encoded rectangle.
798 //
799
800 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
801 int nSubrects = rfb.readU32();
802
803 byte[] bg_buf = new byte[bytesPixel];
804 rfb.readFully(bg_buf);
805 Color pixel;
806 if (bytesPixel == 1) {
807 pixel = colors[bg_buf[0] & 0xFF];
808 } else {
809 pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
810 }
811 memGraphics.setColor(pixel);
812 memGraphics.fillRect(x, y, w, h);
813
814 byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
815 rfb.readFully(buf);
816
817 if (rfb.rec != null) {
818 rfb.rec.writeIntBE(nSubrects);
819 rfb.rec.write(bg_buf);
820 rfb.rec.write(buf);
821 }
822
823 int sx, sy, sw, sh;
824 int i = 0;
825
826 for (int j = 0; j < nSubrects; j++) {
827 if (bytesPixel == 1) {
828 pixel = colors[buf[i++] & 0xFF];
829 } else {
830 pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
831 i += 4;
832 }
833 sx = x + (buf[i++] & 0xFF);
834 sy = y + (buf[i++] & 0xFF);
835 sw = buf[i++] & 0xFF;
836 sh = buf[i++] & 0xFF;
837
838 memGraphics.setColor(pixel);
839 memGraphics.fillRect(sx, sy, sw, sh);
840 }
841
842 scheduleRepaint(x, y, w, h);
843 }
844
845 //
846 // Handle a Hextile-encoded rectangle.
847 //
848
849 // These colors should be kept between handleHextileSubrect() calls.
850 private Color hextile_bg, hextile_fg;
851
852 void handleHextileRect(int x, int y, int w, int h) throws IOException {
853
854 hextile_bg = new Color(0);
855 hextile_fg = new Color(0);
856
857 for (int ty = y; ty < y + h; ty += 16) {
858 int th = 16;
859 if (y + h - ty < 16)
860 th = y + h - ty;
861
862 for (int tx = x; tx < x + w; tx += 16) {
863 int tw = 16;
864 if (x + w - tx < 16)
865 tw = x + w - tx;
866
867 handleHextileSubrect(tx, ty, tw, th);
868 }
869
870 // Finished with a row of tiles, now let's show it.
871 scheduleRepaint(x, y, w, h);
872 }
873 }
874
875 //
876 // Handle one tile in the Hextile-encoded data.
877 //
878
879 void handleHextileSubrect(int tx, int ty, int tw, int th)
880 throws IOException {
881
882 int subencoding = rfb.readU8();
883 if (rfb.rec != null) {
884 rfb.rec.writeByte(subencoding);
885 }
886
887 // Is it a raw-encoded sub-rectangle?
888 if ((subencoding & rfb.HextileRaw) != 0) {
889 handleRawRect(tx, ty, tw, th, false);
890 return;
891 }
892
893 // Read and draw the background if specified.
894 byte[] cbuf = new byte[bytesPixel];
895 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
896 rfb.readFully(cbuf);
897 if (bytesPixel == 1) {
898 hextile_bg = colors[cbuf[0] & 0xFF];
899 } else {
900 hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
901 }
902 if (rfb.rec != null) {
903 rfb.rec.write(cbuf);
904 }
905 }
906 memGraphics.setColor(hextile_bg);
907 memGraphics.fillRect(tx, ty, tw, th);
908
909 // Read the foreground color if specified.
910 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
911 rfb.readFully(cbuf);
912 if (bytesPixel == 1) {
913 hextile_fg = colors[cbuf[0] & 0xFF];
914 } else {
915 hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
916 }
917 if (rfb.rec != null) {
918 rfb.rec.write(cbuf);
919 }
920 }
921
922 // Done with this tile if there is no sub-rectangles.
923 if ((subencoding & rfb.HextileAnySubrects) == 0)
924 return;
925
926 int nSubrects = rfb.readU8();
927 int bufsize = nSubrects * 2;
928 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
929 bufsize += nSubrects * bytesPixel;
930 }
931 byte[] buf = new byte[bufsize];
932 rfb.readFully(buf);
933 if (rfb.rec != null) {
934 rfb.rec.writeByte(nSubrects);
935 rfb.rec.write(buf);
936 }
937
938 int b1, b2, sx, sy, sw, sh;
939 int i = 0;
940
941 if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
942
943 // Sub-rectangles are all of the same color.
944 memGraphics.setColor(hextile_fg);
945 for (int j = 0; j < nSubrects; j++) {
946 b1 = buf[i++] & 0xFF;
947 b2 = buf[i++] & 0xFF;
948 sx = tx + (b1 >> 4);
949 sy = ty + (b1 & 0xf);
950 sw = (b2 >> 4) + 1;
951 sh = (b2 & 0xf) + 1;
952 memGraphics.fillRect(sx, sy, sw, sh);
953 }
954 } else if (bytesPixel == 1) {
955
956 // BGR233 (8-bit color) version for colored sub-rectangles.
957 for (int j = 0; j < nSubrects; j++) {
958 hextile_fg = colors[buf[i++] & 0xFF];
959 b1 = buf[i++] & 0xFF;
960 b2 = buf[i++] & 0xFF;
961 sx = tx + (b1 >> 4);
962 sy = ty + (b1 & 0xf);
963 sw = (b2 >> 4) + 1;
964 sh = (b2 & 0xf) + 1;
965 memGraphics.setColor(hextile_fg);
966 memGraphics.fillRect(sx, sy, sw, sh);
967 }
968
969 } else {
970
971 // Full-color (24-bit) version for colored sub-rectangles.
972 for (int j = 0; j < nSubrects; j++) {
973 hextile_fg = new Color(buf[i+2] & 0xFF,
974 buf[i+1] & 0xFF,
975 buf[i] & 0xFF);
976 i += 4;
977 b1 = buf[i++] & 0xFF;
978 b2 = buf[i++] & 0xFF;
979 sx = tx + (b1 >> 4);
980 sy = ty + (b1 & 0xf);
981 sw = (b2 >> 4) + 1;
982 sh = (b2 & 0xf) + 1;
983 memGraphics.setColor(hextile_fg);
984 memGraphics.fillRect(sx, sy, sw, sh);
985 }
986
987 }
988 }
989
990 //
991 // Handle a ZRLE-encoded rectangle.
992 //
993 // FIXME: Currently, session recording is not fully supported for ZRLE.
994 //
995
996 void handleZRLERect(int x, int y, int w, int h) throws Exception {
997
998 if (zrleInStream == null)
999 zrleInStream = new ZlibInStream();
1000
1001 int nBytes = rfb.readU32();
1002 if (nBytes > 64 * 1024 * 1024)
1003 throw new Exception("ZRLE decoder: illegal compressed data size");
1004
1005 if (zrleBuf == null || zrleBufLen < nBytes) {
1006 zrleBufLen = nBytes + 4096;
1007 zrleBuf = new byte[zrleBufLen];
1008 }
1009
1010 // FIXME: Do not wait for all the data before decompression.
1011 rfb.readFully(zrleBuf, 0, nBytes);
1012
1013 if (rfb.rec != null) {
1014 if (rfb.recordFromBeginning) {
1015 rfb.rec.writeIntBE(nBytes);
1016 rfb.rec.write(zrleBuf, 0, nBytes);
1017 } else if (!zrleRecWarningShown) {
1018 System.out.println("Warning: ZRLE session can be recorded" +
1019 " only from the beginning");
1020 System.out.println("Warning: Recorded file may be corrupted");
1021 zrleRecWarningShown = true;
1022 }
1023 }
1024
1025 zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
1026
1027 for (int ty = y; ty < y+h; ty += 64) {
1028
1029 int th = Math.min(y+h-ty, 64);
1030
1031 for (int tx = x; tx < x+w; tx += 64) {
1032
1033 int tw = Math.min(x+w-tx, 64);
1034
1035 int mode = zrleInStream.readU8();
1036 boolean rle = (mode & 128) != 0;
1037 int palSize = mode & 127;
1038 int[] palette = new int[128];
1039
1040 readZrlePalette(palette, palSize);
1041
1042 if (palSize == 1) {
1043 int pix = palette[0];
1044 Color c = (bytesPixel == 1) ?
1045 colors[pix] : new Color(0xFF000000 | pix);
1046 memGraphics.setColor(c);
1047 memGraphics.fillRect(tx, ty, tw, th);
1048 continue;
1049 }
1050
1051 if (!rle) {
1052 if (palSize == 0) {
1053 readZrleRawPixels(tw, th);
1054 } else {
1055 readZrlePackedPixels(tw, th, palette, palSize);
1056 }
1057 } else {
1058 if (palSize == 0) {
1059 readZrlePlainRLEPixels(tw, th);
1060 } else {
1061 readZrlePackedRLEPixels(tw, th, palette);
1062 }
1063 }
1064 handleUpdatedZrleTile(tx, ty, tw, th);
1065 }
1066 }
1067
1068 zrleInStream.reset();
1069
1070 scheduleRepaint(x, y, w, h);
1071 }
1072
1073 int readPixel(InStream is) throws Exception {
1074 int pix;
1075 if (bytesPixel == 1) {
1076 pix = is.readU8();
1077 } else {
1078 int p1 = is.readU8();
1079 int p2 = is.readU8();
1080 int p3 = is.readU8();
1081 pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
1082 }
1083 return pix;
1084 }
1085
1086 void readPixels(InStream is, int[] dst, int count) throws Exception {
1087 int pix;
1088 if (bytesPixel == 1) {
1089 byte[] buf = new byte[count];
1090 is.readBytes(buf, 0, count);
1091 for (int i = 0; i < count; i++) {
1092 dst[i] = (int)buf[i] & 0xFF;
1093 }
1094 } else {
1095 byte[] buf = new byte[count * 3];
1096 is.readBytes(buf, 0, count * 3);
1097 for (int i = 0; i < count; i++) {
1098 dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
1099 (buf[i*3+1] & 0xFF) << 8 |
1100 (buf[i*3] & 0xFF));
1101 }
1102 }
1103 }
1104
1105 void readZrlePalette(int[] palette, int palSize) throws Exception {
1106 readPixels(zrleInStream, palette, palSize);
1107 }
1108
1109 void readZrleRawPixels(int tw, int th) throws Exception {
1110 if (bytesPixel == 1) {
1111 zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
1112 } else {
1113 readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
1114 }
1115 }
1116
1117 void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
1118 throws Exception {
1119
1120 int bppp = ((palSize > 16) ? 8 :
1121 ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
1122 int ptr = 0;
1123
1124 for (int i = 0; i < th; i++) {
1125 int eol = ptr + tw;
1126 int b = 0;
1127 int nbits = 0;
1128
1129 while (ptr < eol) {
1130 if (nbits == 0) {
1131 b = zrleInStream.readU8();
1132 nbits = 8;
1133 }
1134 nbits -= bppp;
1135 int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
1136 if (bytesPixel == 1) {
1137 zrleTilePixels8[ptr++] = (byte)palette[index];
1138 } else {
1139 zrleTilePixels24[ptr++] = palette[index];
1140 }
1141 }
1142 }
1143 }
1144
1145 void readZrlePlainRLEPixels(int tw, int th) throws Exception {
1146 int ptr = 0;
1147 int end = ptr + tw * th;
1148 while (ptr < end) {
1149 int pix = readPixel(zrleInStream);
1150 int len = 1;
1151 int b;
1152 do {
1153 b = zrleInStream.readU8();
1154 len += b;
1155 } while (b == 255);
1156
1157 if (!(len <= end - ptr))
1158 throw new Exception("ZRLE decoder: assertion failed" +
1159 " (len <= end-ptr)");
1160
1161 if (bytesPixel == 1) {
1162 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
1163 } else {
1164 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
1165 }
1166 }
1167 }
1168
1169 void readZrlePackedRLEPixels(int tw, int th, int[] palette)
1170 throws Exception {
1171
1172 int ptr = 0;
1173 int end = ptr + tw * th;
1174 while (ptr < end) {
1175 int index = zrleInStream.readU8();
1176 int len = 1;
1177 if ((index & 128) != 0) {
1178 int b;
1179 do {
1180 b = zrleInStream.readU8();
1181 len += b;
1182 } while (b == 255);
enikeyc41ba1d2008-12-19 09:07:22 +00001183
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001184 if (!(len <= end - ptr))
1185 throw new Exception("ZRLE decoder: assertion failed" +
1186 " (len <= end - ptr)");
1187 }
1188
1189 index &= 127;
1190 int pix = palette[index];
1191
1192 if (bytesPixel == 1) {
1193 while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
1194 } else {
1195 while (len-- > 0) zrleTilePixels24[ptr++] = pix;
1196 }
1197 }
1198 }
1199
1200 //
1201 // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
1202 //
1203
1204 void handleUpdatedZrleTile(int x, int y, int w, int h) {
1205 Object src, dst;
1206 if (bytesPixel == 1) {
1207 src = zrleTilePixels8; dst = pixels8;
1208 } else {
1209 src = zrleTilePixels24; dst = pixels24;
1210 }
1211 int offsetSrc = 0;
1212 int offsetDst = (y * rfb.framebufferWidth + x);
1213 for (int j = 0; j < h; j++) {
1214 System.arraycopy(src, offsetSrc, dst, offsetDst, w);
1215 offsetSrc += w;
1216 offsetDst += rfb.framebufferWidth;
1217 }
1218 handleUpdatedPixels(x, y, w, h);
1219 }
1220
1221 //
1222 // Handle a Zlib-encoded rectangle.
1223 //
1224
1225 void handleZlibRect(int x, int y, int w, int h) throws Exception {
1226
1227 int nBytes = rfb.readU32();
1228
1229 if (zlibBuf == null || zlibBufLen < nBytes) {
1230 zlibBufLen = nBytes * 2;
1231 zlibBuf = new byte[zlibBufLen];
1232 }
1233
1234 rfb.readFully(zlibBuf, 0, nBytes);
1235
1236 if (rfb.rec != null && rfb.recordFromBeginning) {
1237 rfb.rec.writeIntBE(nBytes);
1238 rfb.rec.write(zlibBuf, 0, nBytes);
1239 }
1240
1241 if (zlibInflater == null) {
1242 zlibInflater = new Inflater();
1243 }
1244 zlibInflater.setInput(zlibBuf, 0, nBytes);
1245
1246 if (bytesPixel == 1) {
1247 for (int dy = y; dy < y + h; dy++) {
1248 zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
1249 if (rfb.rec != null && !rfb.recordFromBeginning)
1250 rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1251 }
1252 } else {
1253 byte[] buf = new byte[w * 4];
1254 int i, offset;
1255 for (int dy = y; dy < y + h; dy++) {
1256 zlibInflater.inflate(buf);
1257 offset = dy * rfb.framebufferWidth + x;
1258 for (i = 0; i < w; i++) {
1259 pixels24[offset + i] =
1260 (buf[i * 4 + 2] & 0xFF) << 16 |
1261 (buf[i * 4 + 1] & 0xFF) << 8 |
1262 (buf[i * 4] & 0xFF);
1263 }
1264 if (rfb.rec != null && !rfb.recordFromBeginning)
1265 rfb.rec.write(buf);
1266 }
1267 }
1268
1269 handleUpdatedPixels(x, y, w, h);
1270 scheduleRepaint(x, y, w, h);
1271 }
1272
1273 //
1274 // Handle a Tight-encoded rectangle.
1275 //
1276
1277 void handleTightRect(int x, int y, int w, int h) throws Exception {
enikey2f7f46e2008-12-19 09:32:35 +00001278 tightDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001279 scheduleRepaint(x, y, w, h);
1280 }
1281
1282 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001283 // Display newly updated area of pixels.
1284 //
1285
1286 void handleUpdatedPixels(int x, int y, int w, int h) {
1287
1288 // Draw updated pixels of the off-screen image.
1289 pixelsSource.newPixels(x, y, w, h);
1290 memGraphics.setClip(x, y, w, h);
1291 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1292 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1293 }
1294
1295 //
1296 // Tell JVM to repaint specified desktop area.
1297 //
1298
enikey0dbc1532008-12-19 08:51:47 +00001299 public void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001300 // Request repaint, deferred if necessary.
1301 if (rfb.framebufferWidth == scaledWidth) {
1302 repaint(viewer.deferScreenUpdates, x, y, w, h);
1303 } else {
1304 int sx = x * scalingFactor / 100;
1305 int sy = y * scalingFactor / 100;
1306 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
1307 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
1308 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
1309 }
1310 }
1311
1312 //
1313 // Handle events.
1314 //
1315
1316 public void keyPressed(KeyEvent evt) {
1317 processLocalKeyEvent(evt);
1318 }
1319 public void keyReleased(KeyEvent evt) {
1320 processLocalKeyEvent(evt);
1321 }
1322 public void keyTyped(KeyEvent evt) {
1323 evt.consume();
1324 }
1325
1326 public void mousePressed(MouseEvent evt) {
1327 processLocalMouseEvent(evt, false);
1328 }
1329 public void mouseReleased(MouseEvent evt) {
1330 processLocalMouseEvent(evt, false);
1331 }
1332 public void mouseMoved(MouseEvent evt) {
1333 processLocalMouseEvent(evt, true);
1334 }
1335 public void mouseDragged(MouseEvent evt) {
1336 processLocalMouseEvent(evt, true);
1337 }
1338
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001339 //
1340 // Ignored events.
1341 //
1342
1343 public void mouseClicked(MouseEvent evt) {}
1344 public void mouseEntered(MouseEvent evt) {}
1345 public void mouseExited(MouseEvent evt) {}
1346
1347 //
1348 // Actual event processing.
1349 //
1350
1351 private void processLocalKeyEvent(KeyEvent evt) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001352 if (viewer.rfb != null && rfb.inNormalProtocol) {
1353 if (!inputEnabled) {
1354 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
1355 evt.getID() == KeyEvent.KEY_PRESSED ) {
1356 // Request screen update.
1357 try {
1358 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
1359 rfb.framebufferHeight, false);
1360 } catch (IOException e) {
1361 e.printStackTrace();
1362 }
1363 }
1364 } else {
1365 // Input enabled.
1366 synchronized(rfb) {
1367 try {
1368 rfb.writeKeyEvent(evt);
1369 } catch (Exception e) {
1370 e.printStackTrace();
1371 }
1372 rfb.notify();
1373 }
1374 }
1375 }
enikeyc41ba1d2008-12-19 09:07:22 +00001376 // Don't ever pass keyboard events to AWT for default processing.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001377 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
1378 evt.consume();
1379 }
1380
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001381 private void processLocalMouseEvent(MouseEvent evt, boolean moved) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001382 if (viewer.rfb != null && rfb.inNormalProtocol) {
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001383 if (!inSelectionMode) {
1384 if (inputEnabled) {
1385 sendMouseEvent(evt, moved);
1386 }
1387 } else {
1388 handleSelectionMouseEvent(evt);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001389 }
1390 }
1391 }
1392
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001393 private void sendMouseEvent(MouseEvent evt, boolean moved) {
1394 if (moved) {
1395 softCursorMove(evt.getX(), evt.getY());
1396 }
1397 if (rfb.framebufferWidth != scaledWidth) {
1398 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
1399 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
1400 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
1401 }
1402 synchronized(rfb) {
1403 try {
1404 rfb.writePointerEvent(evt);
1405 } catch (Exception e) {
1406 e.printStackTrace();
1407 }
1408 rfb.notify();
1409 }
1410 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001411
1412 //
1413 // Reset update statistics.
1414 //
1415
1416 void resetStats() {
1417 statStartTime = System.currentTimeMillis();
1418 statNumUpdates = 0;
1419 statNumTotalRects = 0;
1420 statNumPixelRects = 0;
1421 statNumRectsTight = 0;
1422 statNumRectsTightJPEG = 0;
1423 statNumRectsZRLE = 0;
1424 statNumRectsHextile = 0;
1425 statNumRectsRaw = 0;
1426 statNumRectsCopy = 0;
1427 statNumBytesEncoded = 0;
1428 statNumBytesDecoded = 0;
enikey2f7f46e2008-12-19 09:32:35 +00001429 if (tightDecoder != null)
1430 tightDecoder.setNumJPEGRects(0);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001431 }
1432
1433 //////////////////////////////////////////////////////////////////
1434 //
1435 // Handle cursor shape updates (XCursor and RichCursor encodings).
1436 //
1437
1438 boolean showSoftCursor = false;
1439
1440 MemoryImageSource softCursorSource;
1441 Image softCursor;
1442
1443 int cursorX = 0, cursorY = 0;
1444 int cursorWidth, cursorHeight;
1445 int origCursorWidth, origCursorHeight;
1446 int hotX, hotY;
1447 int origHotX, origHotY;
1448
1449 //
1450 // Handle cursor shape update (XCursor and RichCursor encodings).
1451 //
1452
1453 synchronized void
1454 handleCursorShapeUpdate(int encodingType,
1455 int xhot, int yhot, int width, int height)
1456 throws IOException {
1457
1458 softCursorFree();
1459
1460 if (width * height == 0)
1461 return;
1462
1463 // Ignore cursor shape data if requested by user.
1464 if (viewer.options.ignoreCursorUpdates) {
1465 int bytesPerRow = (width + 7) / 8;
1466 int bytesMaskData = bytesPerRow * height;
1467
1468 if (encodingType == rfb.EncodingXCursor) {
1469 rfb.skipBytes(6 + bytesMaskData * 2);
1470 } else {
1471 // rfb.EncodingRichCursor
1472 rfb.skipBytes(width * height + bytesMaskData);
1473 }
1474 return;
1475 }
1476
1477 // Decode cursor pixel data.
1478 softCursorSource = decodeCursorShape(encodingType, width, height);
1479
1480 // Set original (non-scaled) cursor dimensions.
1481 origCursorWidth = width;
1482 origCursorHeight = height;
1483 origHotX = xhot;
1484 origHotY = yhot;
1485
1486 // Create off-screen cursor image.
1487 createSoftCursor();
1488
1489 // Show the cursor.
1490 showSoftCursor = true;
1491 repaint(viewer.deferCursorUpdates,
1492 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1493 }
1494
1495 //
1496 // decodeCursorShape(). Decode cursor pixel data and return
1497 // corresponding MemoryImageSource instance.
1498 //
1499
1500 synchronized MemoryImageSource
1501 decodeCursorShape(int encodingType, int width, int height)
1502 throws IOException {
1503
1504 int bytesPerRow = (width + 7) / 8;
1505 int bytesMaskData = bytesPerRow * height;
1506
1507 int[] softCursorPixels = new int[width * height];
1508
1509 if (encodingType == rfb.EncodingXCursor) {
1510
1511 // Read foreground and background colors of the cursor.
1512 byte[] rgb = new byte[6];
1513 rfb.readFully(rgb);
1514 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
1515 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1516 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1517 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
1518
1519 // Read pixel and mask data.
1520 byte[] pixBuf = new byte[bytesMaskData];
1521 rfb.readFully(pixBuf);
1522 byte[] maskBuf = new byte[bytesMaskData];
1523 rfb.readFully(maskBuf);
1524
1525 // Decode pixel data into softCursorPixels[].
1526 byte pixByte, maskByte;
1527 int x, y, n, result;
1528 int i = 0;
1529 for (y = 0; y < height; y++) {
1530 for (x = 0; x < width / 8; x++) {
1531 pixByte = pixBuf[y * bytesPerRow + x];
1532 maskByte = maskBuf[y * bytesPerRow + x];
1533 for (n = 7; n >= 0; n--) {
1534 if ((maskByte >> n & 1) != 0) {
1535 result = colors[pixByte >> n & 1];
1536 } else {
1537 result = 0; // Transparent pixel
1538 }
1539 softCursorPixels[i++] = result;
1540 }
1541 }
1542 for (n = 7; n >= 8 - width % 8; n--) {
1543 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1544 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1545 } else {
1546 result = 0; // Transparent pixel
1547 }
1548 softCursorPixels[i++] = result;
1549 }
1550 }
1551
1552 } else {
1553 // encodingType == rfb.EncodingRichCursor
1554
1555 // Read pixel and mask data.
1556 byte[] pixBuf = new byte[width * height * bytesPixel];
1557 rfb.readFully(pixBuf);
1558 byte[] maskBuf = new byte[bytesMaskData];
1559 rfb.readFully(maskBuf);
1560
1561 // Decode pixel data into softCursorPixels[].
1562 byte pixByte, maskByte;
1563 int x, y, n, result;
1564 int i = 0;
1565 for (y = 0; y < height; y++) {
1566 for (x = 0; x < width / 8; x++) {
1567 maskByte = maskBuf[y * bytesPerRow + x];
1568 for (n = 7; n >= 0; n--) {
1569 if ((maskByte >> n & 1) != 0) {
1570 if (bytesPixel == 1) {
1571 result = cm8.getRGB(pixBuf[i]);
1572 } else {
1573 result = 0xFF000000 |
1574 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1575 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1576 (pixBuf[i * 4] & 0xFF);
1577 }
1578 } else {
1579 result = 0; // Transparent pixel
1580 }
1581 softCursorPixels[i++] = result;
1582 }
1583 }
1584 for (n = 7; n >= 8 - width % 8; n--) {
1585 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1586 if (bytesPixel == 1) {
1587 result = cm8.getRGB(pixBuf[i]);
1588 } else {
1589 result = 0xFF000000 |
1590 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1591 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1592 (pixBuf[i * 4] & 0xFF);
1593 }
1594 } else {
1595 result = 0; // Transparent pixel
1596 }
1597 softCursorPixels[i++] = result;
1598 }
1599 }
1600
1601 }
1602
1603 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1604 }
1605
1606 //
1607 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1608 // Uses softCursorSource as a source for new cursor image.
1609 //
1610
1611 synchronized void
1612 createSoftCursor() {
1613
1614 if (softCursorSource == null)
1615 return;
1616
1617 int scaleCursor = viewer.options.scaleCursor;
1618 if (scaleCursor == 0 || !inputEnabled)
1619 scaleCursor = 100;
1620
1621 // Save original cursor coordinates.
1622 int x = cursorX - hotX;
1623 int y = cursorY - hotY;
1624 int w = cursorWidth;
1625 int h = cursorHeight;
1626
1627 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1628 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1629 hotX = (origHotX * scaleCursor + 50) / 100;
1630 hotY = (origHotY * scaleCursor + 50) / 100;
1631 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1632
1633 if (scaleCursor != 100) {
1634 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1635 Image.SCALE_SMOOTH);
1636 }
1637
1638 if (showSoftCursor) {
1639 // Compute screen area to update.
1640 x = Math.min(x, cursorX - hotX);
1641 y = Math.min(y, cursorY - hotY);
1642 w = Math.max(w, cursorWidth);
1643 h = Math.max(h, cursorHeight);
1644
1645 repaint(viewer.deferCursorUpdates, x, y, w, h);
1646 }
1647 }
1648
1649 //
1650 // softCursorMove(). Moves soft cursor into a particular location.
1651 //
1652
1653 synchronized void softCursorMove(int x, int y) {
1654 int oldX = cursorX;
1655 int oldY = cursorY;
1656 cursorX = x;
1657 cursorY = y;
1658 if (showSoftCursor) {
1659 repaint(viewer.deferCursorUpdates,
1660 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1661 repaint(viewer.deferCursorUpdates,
1662 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1663 }
1664 }
1665
1666 //
1667 // softCursorFree(). Remove soft cursor, dispose resources.
1668 //
1669
1670 synchronized void softCursorFree() {
1671 if (showSoftCursor) {
1672 showSoftCursor = false;
1673 softCursor = null;
1674 softCursorSource = null;
1675
1676 repaint(viewer.deferCursorUpdates,
1677 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1678 }
1679 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001680
1681 //////////////////////////////////////////////////////////////////
1682 //
1683 // Support for selecting a rectangular video area.
1684 //
1685
1686 /** This flag is false in normal operation, and true in the selection mode. */
1687 private boolean inSelectionMode;
1688
1689 /** The point where the selection was started. */
1690 private Point selectionStart;
1691
1692 /** The second point of the selection. */
1693 private Point selectionEnd;
1694
1695 /**
1696 * We change cursor when enabling the selection mode. In this variable, we
1697 * save the original cursor so we can restore it on returning to the normal
1698 * mode.
1699 */
1700 private Cursor savedCursor;
1701
1702 /**
1703 * Initialize selection-related varibles.
1704 */
1705 private synchronized void resetSelection() {
1706 inSelectionMode = false;
1707 selectionStart = new Point(0, 0);
1708 selectionEnd = new Point(0, 0);
1709
1710 savedCursor = getCursor();
1711 }
1712
1713 /**
1714 * Check current state of the selection mode.
1715 * @return true in the selection mode, false otherwise.
1716 */
1717 public boolean isInSelectionMode() {
1718 return inSelectionMode;
1719 }
1720
1721 /**
1722 * Get current selection.
1723 * @param useScreenCoords use screen coordinates if true, or framebuffer
1724 * coordinates if false. This makes difference when scaling factor is not 100.
1725 * @return The selection as a {@link Rectangle}.
1726 */
1727 private synchronized Rectangle getSelection(boolean useScreenCoords) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001728 int x0 = selectionStart.x;
1729 int x1 = selectionEnd.x;
1730 int y0 = selectionStart.y;
1731 int y1 = selectionEnd.y;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001732 // Make x and y point to the upper left corner of the selection.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001733 if (x1 < x0) {
1734 int t = x0; x0 = x1; x1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001735 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001736 if (y1 < y0) {
1737 int t = y0; y0 = y1; y1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001738 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001739 // Include the borders in the selection (unless it's empty).
1740 if (x0 != x1 && y0 != y1) {
1741 x1 += 1;
1742 y1 += 1;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001743 }
1744 // Translate from screen coordinates to framebuffer coordinates.
1745 if (rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001746 x0 = (x0 * 100 + scalingFactor/2) / scalingFactor;
1747 y0 = (y0 * 100 + scalingFactor/2) / scalingFactor;
1748 x1 = (x1 * 100 + scalingFactor/2) / scalingFactor;
1749 y1 = (y1 * 100 + scalingFactor/2) / scalingFactor;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001750 }
Constantin Kaplinsky10da44d2008-09-03 03:12:18 +00001751 // Clip the selection to framebuffer.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001752 if (x0 < 0)
1753 x0 = 0;
1754 if (y0 < 0)
1755 y0 = 0;
1756 if (x1 > rfb.framebufferWidth)
1757 x1 = rfb.framebufferWidth;
1758 if (y1 > rfb.framebufferHeight)
1759 y1 = rfb.framebufferHeight;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001760 // Make width a multiple of 16.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001761 int widthBlocks = (x1 - x0 + 8) / 16;
1762 if (selectionStart.x <= selectionEnd.x) {
1763 x1 = x0 + widthBlocks * 16;
1764 if (x1 > rfb.framebufferWidth) {
1765 x1 -= 16;
1766 }
1767 } else {
1768 x0 = x1 - widthBlocks * 16;
1769 if (x0 < 0) {
1770 x0 += 16;
1771 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001772 }
1773 // Make height a multiple of 8.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001774 int heightBlocks = (y1 - y0 + 4) / 8;
1775 if (selectionStart.y <= selectionEnd.y) {
1776 y1 = y0 + heightBlocks * 8;
1777 if (y1 > rfb.framebufferHeight) {
1778 y1 -= 8;
1779 }
1780 } else {
1781 y0 = y1 - heightBlocks * 8;
1782 if (y0 < 0) {
1783 y0 += 8;
1784 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001785 }
1786 // Translate the selection back to screen coordinates if requested.
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001787 if (useScreenCoords && rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001788 x0 = (x0 * scalingFactor + 50) / 100;
1789 y0 = (y0 * scalingFactor + 50) / 100;
1790 x1 = (x1 * scalingFactor + 50) / 100;
1791 y1 = (y1 * scalingFactor + 50) / 100;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001792 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001793 // Construct and return the result.
1794 return new Rectangle(x0, y0, x1 - x0, y1 - y0);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001795 }
1796
1797 /**
1798 * Enable or disable the selection mode.
1799 * @param enable enables the selection mode if true, disables if fasle.
1800 */
1801 public synchronized void enableSelection(boolean enable) {
1802 if (enable && !inSelectionMode) {
1803 // Enter the selection mode.
1804 inSelectionMode = true;
1805 savedCursor = getCursor();
1806 setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
1807 repaint();
1808 } else if (!enable && inSelectionMode) {
1809 // Leave the selection mode.
1810 inSelectionMode = false;
1811 setCursor(savedCursor);
1812 repaint();
1813 }
1814 }
1815
1816 /**
1817 * Process mouse events in the selection mode.
enikeyc41ba1d2008-12-19 09:07:22 +00001818 *
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001819 * @param evt mouse event that was originally passed to
1820 * {@link MouseListener} or {@link MouseMotionListener}.
1821 */
1822 private synchronized void handleSelectionMouseEvent(MouseEvent evt) {
1823 int id = evt.getID();
1824 boolean button1 = (evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0;
1825
1826 if (id == MouseEvent.MOUSE_PRESSED && button1) {
1827 selectionStart = selectionEnd = evt.getPoint();
1828 repaint();
1829 }
1830 if (id == MouseEvent.MOUSE_DRAGGED && button1) {
1831 selectionEnd = evt.getPoint();
1832 repaint();
1833 }
1834 if (id == MouseEvent.MOUSE_RELEASED && button1) {
1835 try {
1836 rfb.trySendVideoSelection(getSelection(false));
1837 } catch (IOException e) {
1838 e.printStackTrace();
1839 }
1840 }
1841 }
1842
enikey418611f2008-12-19 04:37:09 +00001843 //
1844 // Override RecordInterface methods
1845 //
1846
1847 public boolean isRecordFromBeginning() {
1848 return rfb.recordFromBeginning;
1849 }
1850
1851 public boolean canWrite() {
1852 // We can record if rec is not null
1853 return rfb.rec != null;
1854 }
1855
1856 public void write(byte b[]) throws IOException {
1857 rfb.rec.write(b);
1858 }
1859
1860 public void write(byte b[], int off, int len) throws IOException {
1861 rfb.rec.write(b, off, len);
1862 }
1863
1864 public void writeByte(byte b) throws IOException {
1865 rfb.rec.writeByte(b);
1866 }
1867
1868 public void writeByte(int i) throws IOException {
1869 rfb.rec.writeByte(i);
1870 }
1871
1872 public void writeIntBE(int v) throws IOException {
1873 rfb.rec.writeIntBE(v);
1874 }
1875
1876 public void recordCompactLen(int len) throws IOException {
1877 rfb.recordCompactLen(len);
1878 }
1879
1880 public void recordCompressedData(byte[] data) throws IOException {
1881 rfb.recordCompressedData(data);
1882 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001883}