blob: b7764819c4e5831b95e0c389093f52d480e8deb8 [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
Adam Tkacf53e62a2009-03-13 13:20:26 +000024package com.tigervnc.vncviewer;
Constantin Kaplinsky90d8a502008-04-14 09:45:50 +000025
Adam Tkacf53e62a2009-03-13 13:20:26 +000026import com.tigervnc.decoder.CoRREDecoder;
27import com.tigervnc.decoder.CopyRectDecoder;
28import com.tigervnc.decoder.HextileDecoder;
29import com.tigervnc.decoder.RREDecoder;
30import com.tigervnc.decoder.RawDecoder;
31import com.tigervnc.decoder.TightDecoder;
32import com.tigervnc.decoder.ZRLEDecoder;
33import com.tigervnc.decoder.ZlibDecoder;
34import com.tigervnc.decoder.common.Repaintable;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000035import java.awt.*;
36import java.awt.event.*;
37import java.awt.image.*;
38import java.io.*;
39import java.lang.*;
40import java.util.zip.*;
41
42
43//
44// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
45//
46
47class VncCanvas extends Canvas
enikey866b88c2009-01-20 04:50:55 +000048 implements KeyListener, MouseListener, MouseMotionListener, Repaintable, Runnable {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000049
50 VncViewer viewer;
51 RfbProto rfb;
52 ColorModel cm8, cm24;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000053 int bytesPixel;
54
55 int maxWidth = 0, maxHeight = 0;
56 int scalingFactor;
57 int scaledWidth, scaledHeight;
58
59 Image memImage;
60 Graphics memGraphics;
61
enikeyc41ba1d2008-12-19 09:07:22 +000062 //
63 // Decoders
64 //
65
66 RawDecoder rawDecoder;
67 RREDecoder rreDecoder;
68 CoRREDecoder correDecoder;
69 ZlibDecoder zlibDecoder;
70 HextileDecoder hextileDecoder;
71 ZRLEDecoder zrleDecoder;
72 TightDecoder tightDecoder;
enikey5805ba62008-12-25 08:10:43 +000073 CopyRectDecoder copyRectDecoder;
enikeyc41ba1d2008-12-19 09:07:22 +000074
75 // Base decoder decoders array
76 RawDecoder []decoders = null;
77
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000078 // Update statistics.
79 long statStartTime; // time on first framebufferUpdateRequest
enikey191695b2008-12-24 09:09:31 +000080 long statNumUpdates; // counter for FramebufferUpdate messages
81 long statNumTotalRects; // rectangles in FramebufferUpdate messages
82 long statNumPixelRects; // the same, but excluding pseudo-rectangles
83 long statNumRectsTight; // Tight-encoded rectangles (including JPEG)
84 long statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles
85 long statNumRectsZRLE; // ZRLE-encoded rectangles
86 long statNumRectsHextile; // Hextile-encoded rectangles
87 long statNumRectsRaw; // Raw-encoded rectangles
88 long statNumRectsCopy; // CopyRect rectangles
89 long statNumBytesEncoded; // number of bytes in updates, as received
90 long statNumBytesDecoded; // number of bytes, as if Raw encoding was used
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000091
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000092 // True if we process keyboard and mouse events.
93 boolean inputEnabled;
94
enikeyc92c1d12008-12-19 09:58:31 +000095 // True if was no one auto resize of canvas
96 boolean isFirstSizeAutoUpdate = true;
97
enikey1abaff92009-01-19 11:30:27 +000098 // Members for limiting sending mouse events to server
99 long lastMouseEventSendTime = System.currentTimeMillis();
100 long mouseMaxFreq = 20;
101
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000102 //
103 // The constructors.
104 //
105
106 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
107 throws IOException {
108
109 viewer = v;
110 maxWidth = maxWidth_;
111 maxHeight = maxHeight_;
112
113 rfb = viewer.rfb;
114 scalingFactor = viewer.options.scalingFactor;
115
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000116 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
117 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
118
enikeyc41ba1d2008-12-19 09:07:22 +0000119 //
120 // Create decoders
121 //
122
123 // Input stream for decoders
124 RfbInputStream rfbis = new RfbInputStream(rfb);
enikey1622a3c2008-12-24 03:58:29 +0000125 // Create output stream for session recording
enikey54c1c7d2008-12-25 11:48:13 +0000126 RecordOutputStream ros = new RecordOutputStream(rfb);
enikeyc41ba1d2008-12-19 09:07:22 +0000127
128 rawDecoder = new RawDecoder(memGraphics, rfbis);
129 rreDecoder = new RREDecoder(memGraphics, rfbis);
130 correDecoder = new CoRREDecoder(memGraphics, rfbis);
131 hextileDecoder = new HextileDecoder(memGraphics, rfbis);
132 tightDecoder = new TightDecoder(memGraphics, rfbis);
enikey4582bab2008-12-19 09:19:59 +0000133 zlibDecoder = new ZlibDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000134 zrleDecoder = new ZRLEDecoder(memGraphics, rfbis);
enikey5805ba62008-12-25 08:10:43 +0000135 copyRectDecoder = new CopyRectDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000136
137 //
138 // Set data for decoders that needs extra parameters
139 //
140
141 hextileDecoder.setRepainableControl(this);
142 tightDecoder.setRepainableControl(this);
143
144 //
145 // Create array that contains our decoders
146 //
147
enikey5805ba62008-12-25 08:10:43 +0000148 decoders = new RawDecoder[8];
enikeyc41ba1d2008-12-19 09:07:22 +0000149 decoders[0] = rawDecoder;
150 decoders[1] = rreDecoder;
151 decoders[2] = correDecoder;
152 decoders[3] = hextileDecoder;
153 decoders[4] = zlibDecoder;
154 decoders[5] = tightDecoder;
155 decoders[6] = zrleDecoder;
enikey5805ba62008-12-25 08:10:43 +0000156 decoders[7] = copyRectDecoder;
enikeyc41ba1d2008-12-19 09:07:22 +0000157
158 //
159 // Set session recorder for decoders
160 //
161
162 for (int i = 0; i < decoders.length; i++) {
enikey1622a3c2008-12-24 03:58:29 +0000163 decoders[i].setDataOutputStream(ros);
enikeyc41ba1d2008-12-19 09:07:22 +0000164 }
enikeyc92c1d12008-12-19 09:58:31 +0000165
enikey2f7f46e2008-12-19 09:32:35 +0000166 setPixelFormat();
enikey2f7f46e2008-12-19 09:32:35 +0000167
168 inputEnabled = false;
169 if (!viewer.options.viewOnly)
170 enableInput(true);
enikeyc41ba1d2008-12-19 09:07:22 +0000171
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000172 // Enable mouse and keyboard event listeners.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000173 addKeyListener(this);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000174 addMouseListener(this);
175 addMouseMotionListener(this);
enikey866b88c2009-01-20 04:50:55 +0000176
177 // Create thread, that will send mouse movement events
178 // to VNC server.
179 Thread mouseThread = new Thread(this);
180 mouseThread.start();
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000181 }
182
183 public VncCanvas(VncViewer v) throws IOException {
184 this(v, 0, 0);
185 }
186
187 //
188 // Callback methods to determine geometry of our Component.
189 //
190
191 public Dimension getPreferredSize() {
192 return new Dimension(scaledWidth, scaledHeight);
193 }
194
195 public Dimension getMinimumSize() {
196 return new Dimension(scaledWidth, scaledHeight);
197 }
198
199 public Dimension getMaximumSize() {
200 return new Dimension(scaledWidth, scaledHeight);
201 }
202
203 //
204 // All painting is performed here.
205 //
206
207 public void update(Graphics g) {
208 paint(g);
209 }
210
211 public void paint(Graphics g) {
212 synchronized(memImage) {
213 if (rfb.framebufferWidth == scaledWidth) {
214 g.drawImage(memImage, 0, 0, null);
215 } else {
216 paintScaledFrameBuffer(g);
217 }
218 }
219 if (showSoftCursor) {
220 int x0 = cursorX - hotX, y0 = cursorY - hotY;
221 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
222 if (r.intersects(g.getClipBounds())) {
223 g.drawImage(softCursor, x0, y0, null);
224 }
225 }
226 }
227
228 public void paintScaledFrameBuffer(Graphics g) {
229 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
230 }
231
232 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000233 // Start/stop receiving mouse events. Keyboard events are received
234 // even in view-only mode, because we want to map the 'r' key to the
235 // screen refreshing function.
236 //
237
238 public synchronized void enableInput(boolean enable) {
239 if (enable && !inputEnabled) {
240 inputEnabled = true;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000241 if (viewer.showControls) {
242 viewer.buttonPanel.enableRemoteAccessControls(true);
243 }
244 createSoftCursor(); // scaled cursor
245 } else if (!enable && inputEnabled) {
246 inputEnabled = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000247 if (viewer.showControls) {
248 viewer.buttonPanel.enableRemoteAccessControls(false);
249 }
250 createSoftCursor(); // non-scaled cursor
251 }
252 }
253
254 public void setPixelFormat() throws IOException {
255 if (viewer.options.eightBitColors) {
256 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
257 bytesPixel = 1;
258 } else {
259 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
260 bytesPixel = 4;
261 }
262 updateFramebufferSize();
263 }
264
enikey45cfaa52008-12-03 06:52:09 +0000265 void setScalingFactor(int sf) {
266 scalingFactor = sf;
267 updateFramebufferSize();
268 invalidate();
269 }
270
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000271 void updateFramebufferSize() {
272
273 // Useful shortcuts.
274 int fbWidth = rfb.framebufferWidth;
275 int fbHeight = rfb.framebufferHeight;
276
enikey87647e92008-12-04 08:42:34 +0000277 // FIXME: This part of code must be in VncViewer i think
enikey73683202008-12-03 09:17:25 +0000278 if (viewer.options.autoScale) {
enikey87647e92008-12-04 08:42:34 +0000279 if (viewer.inAnApplet) {
280 maxWidth = viewer.getWidth();
281 maxHeight = viewer.getHeight();
282 } else {
283 if (viewer.vncFrame != null) {
284 if (isFirstSizeAutoUpdate) {
285 isFirstSizeAutoUpdate = false;
286 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
287 maxWidth = (int)screenSize.getWidth() - 100;
enikeyc41ba1d2008-12-19 09:07:22 +0000288 maxHeight = (int)screenSize.getHeight() - 100;
enikey87647e92008-12-04 08:42:34 +0000289 viewer.vncFrame.setSize(maxWidth, maxHeight);
290 } else {
291 viewer.desktopScrollPane.doLayout();
292 maxWidth = viewer.desktopScrollPane.getWidth();
293 maxHeight = viewer.desktopScrollPane.getHeight();
294 }
295 } else {
296 maxWidth = fbWidth;
297 maxHeight = fbHeight;
298 }
enikey73683202008-12-03 09:17:25 +0000299 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000300 int f1 = maxWidth * 100 / fbWidth;
301 int f2 = maxHeight * 100 / fbHeight;
302 scalingFactor = Math.min(f1, f2);
303 if (scalingFactor > 100)
304 scalingFactor = 100;
305 System.out.println("Scaling desktop at " + scalingFactor + "%");
306 }
307
308 // Update scaled framebuffer geometry.
309 scaledWidth = (fbWidth * scalingFactor + 50) / 100;
310 scaledHeight = (fbHeight * scalingFactor + 50) / 100;
311
312 // Create new off-screen image either if it does not exist, or if
313 // its geometry should be changed. It's not necessary to replace
314 // existing image if only pixel format should be changed.
315 if (memImage == null) {
316 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
317 memGraphics = memImage.getGraphics();
318 } else if (memImage.getWidth(null) != fbWidth ||
319 memImage.getHeight(null) != fbHeight) {
320 synchronized(memImage) {
321 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
322 memGraphics = memImage.getGraphics();
323 }
324 }
325
enikey4582bab2008-12-19 09:19:59 +0000326 //
327 // Update decoders
328 //
329
330 //
331 // FIXME: Why decoders can be null here?
332 //
333
334 if (decoders != null) {
335 for (int i = 0; i < decoders.length; i++) {
336 //
337 // Set changes to every decoder that we can use
338 //
339
340 decoders[i].setBPP(bytesPixel);
341 decoders[i].setFrameBufferSize(fbWidth, fbHeight);
342 decoders[i].setGraphics(memGraphics);
343
344 //
345 // Update decoder
346 //
347
348 decoders[i].update();
349 }
350 }
351
enikey87647e92008-12-04 08:42:34 +0000352 // FIXME: This part of code must be in VncViewer i think
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000353 // Update the size of desktop containers.
354 if (viewer.inSeparateFrame) {
enikey87647e92008-12-04 08:42:34 +0000355 if (viewer.desktopScrollPane != null) {
356 if (!viewer.options.autoScale) {
357 resizeDesktopFrame();
358 } else {
359 setSize(scaledWidth, scaledHeight);
360 viewer.desktopScrollPane.setSize(maxWidth + 200,
361 maxHeight + 200);
362 }
363 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000364 } else {
365 setSize(scaledWidth, scaledHeight);
366 }
367 viewer.moveFocusToDesktop();
368 }
369
370 void resizeDesktopFrame() {
371 setSize(scaledWidth, scaledHeight);
372
373 // FIXME: Find a better way to determine correct size of a
374 // ScrollPane. -- const
375 Insets insets = viewer.desktopScrollPane.getInsets();
376 viewer.desktopScrollPane.setSize(scaledWidth +
377 2 * Math.min(insets.left, insets.right),
378 scaledHeight +
379 2 * Math.min(insets.top, insets.bottom));
380
381 viewer.vncFrame.pack();
382
383 // Try to limit the frame size to the screen size.
384
385 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
386 Dimension frameSize = viewer.vncFrame.getSize();
387 Dimension newSize = frameSize;
388
389 // Reduce Screen Size by 30 pixels in each direction;
390 // This is a (poor) attempt to account for
391 // 1) Menu bar on Macintosh (should really also account for
392 // Dock on OSX). Usually 22px on top of screen.
393 // 2) Taxkbar on Windows (usually about 28 px on bottom)
394 // 3) Other obstructions.
395
396 screenSize.height -= 30;
397 screenSize.width -= 30;
398
399 boolean needToResizeFrame = false;
400 if (frameSize.height > screenSize.height) {
401 newSize.height = screenSize.height;
402 needToResizeFrame = true;
403 }
404 if (frameSize.width > screenSize.width) {
405 newSize.width = screenSize.width;
406 needToResizeFrame = true;
407 }
408 if (needToResizeFrame) {
409 viewer.vncFrame.setSize(newSize);
410 }
411
412 viewer.desktopScrollPane.doLayout();
413 }
414
415 //
416 // processNormalProtocol() - executed by the rfbThread to deal with the
417 // RFB socket.
418 //
419
420 public void processNormalProtocol() throws Exception {
421
422 // Start/stop session recording if necessary.
423 viewer.checkRecordingStatus();
424
425 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
426 rfb.framebufferHeight, false);
427
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000428 resetStats();
429 boolean statsRestarted = false;
430
431 //
432 // main dispatch loop
433 //
434
435 while (true) {
436
437 // Read message type from the server.
438 int msgType = rfb.readServerMessageType();
439
440 // Process the message depending on its type.
441 switch (msgType) {
442 case RfbProto.FramebufferUpdate:
443
444 if (statNumUpdates == viewer.debugStatsExcludeUpdates &&
445 !statsRestarted) {
446 resetStats();
447 statsRestarted = true;
448 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates &&
449 statsRestarted) {
450 viewer.disconnect();
451 }
452
453 rfb.readFramebufferUpdate();
454 statNumUpdates++;
455
456 boolean cursorPosReceived = false;
457
458 for (int i = 0; i < rfb.updateNRects; i++) {
459
460 rfb.readFramebufferUpdateRectHdr();
461 statNumTotalRects++;
462 int rx = rfb.updateRectX, ry = rfb.updateRectY;
463 int rw = rfb.updateRectW, rh = rfb.updateRectH;
464
465 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
466 break;
467
468 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
469 rfb.setFramebufferSize(rw, rh);
470 updateFramebufferSize();
471 break;
472 }
473
474 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
475 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
476 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
477 continue;
478 }
479
480 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
481 softCursorMove(rx, ry);
482 cursorPosReceived = true;
483 continue;
484 }
485
486 long numBytesReadBefore = rfb.getNumBytesRead();
487
488 rfb.startTiming();
489
490 switch (rfb.updateRectEncoding) {
491 case RfbProto.EncodingRaw:
492 statNumRectsRaw++;
493 handleRawRect(rx, ry, rw, rh);
494 break;
495 case RfbProto.EncodingCopyRect:
496 statNumRectsCopy++;
497 handleCopyRect(rx, ry, rw, rh);
498 break;
499 case RfbProto.EncodingRRE:
500 handleRRERect(rx, ry, rw, rh);
501 break;
502 case RfbProto.EncodingCoRRE:
503 handleCoRRERect(rx, ry, rw, rh);
504 break;
505 case RfbProto.EncodingHextile:
506 statNumRectsHextile++;
507 handleHextileRect(rx, ry, rw, rh);
508 break;
509 case RfbProto.EncodingZRLE:
510 statNumRectsZRLE++;
511 handleZRLERect(rx, ry, rw, rh);
512 break;
513 case RfbProto.EncodingZlib:
514 handleZlibRect(rx, ry, rw, rh);
515 break;
516 case RfbProto.EncodingTight:
enikey2f7f46e2008-12-19 09:32:35 +0000517 if (tightDecoder != null) {
enikey479b18c2008-12-19 09:39:40 +0000518 statNumRectsTightJPEG = tightDecoder.getNumJPEGRects();
enikey1214ab12008-12-24 09:01:19 +0000519 //statNumRectsTight = tightDecoder.getNumTightRects();
enikey2f7f46e2008-12-19 09:32:35 +0000520 }
enikey1214ab12008-12-24 09:01:19 +0000521 statNumRectsTight++;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000522 handleTightRect(rx, ry, rw, rh);
523 break;
524 default:
525 throw new Exception("Unknown RFB rectangle encoding " +
526 rfb.updateRectEncoding);
527 }
528
529 rfb.stopTiming();
530
531 statNumPixelRects++;
532 statNumBytesDecoded += rw * rh * bytesPixel;
533 statNumBytesEncoded +=
534 (int)(rfb.getNumBytesRead() - numBytesReadBefore);
535 }
536
537 boolean fullUpdateNeeded = false;
538
539 // Start/stop session recording if necessary. Request full
540 // update if a new session file was opened.
541 if (viewer.checkRecordingStatus())
542 fullUpdateNeeded = true;
543
544 // Defer framebuffer update request if necessary. But wake up
545 // immediately on keyboard or mouse event. Also, don't sleep
546 // if there is some data to receive, or if the last update
547 // included a PointerPos message.
548 if (viewer.deferUpdateRequests > 0 &&
549 rfb.available() == 0 && !cursorPosReceived) {
550 synchronized(rfb) {
551 try {
552 rfb.wait(viewer.deferUpdateRequests);
553 } catch (InterruptedException e) {
554 }
555 }
556 }
557
558 viewer.autoSelectEncodings();
559
560 // Before requesting framebuffer update, check if the pixel
561 // format should be changed.
562 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
563 // Pixel format should be changed.
Adam Tkac6a7b09f2010-11-18 17:19:45 +0000564 setPixelFormat();
565 fullUpdateNeeded = true;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000566 }
567
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000568 // Finally, request framebuffer update if needed.
569 if (fullUpdateNeeded) {
570 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
571 rfb.framebufferHeight, false);
Adam Tkac6a7b09f2010-11-18 17:19:45 +0000572 } else {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000573 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
574 rfb.framebufferHeight, true);
575 }
576
577 break;
578
579 case RfbProto.SetColourMapEntries:
580 throw new Exception("Can't handle SetColourMapEntries message");
581
582 case RfbProto.Bell:
583 Toolkit.getDefaultToolkit().beep();
584 break;
585
586 case RfbProto.ServerCutText:
587 String s = rfb.readServerCutText();
588 viewer.clipboard.setCutText(s);
589 break;
590
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000591 default:
592 throw new Exception("Unknown RFB message type " + msgType);
593 }
594 }
595 }
596
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000597 //
598 // Handle a raw rectangle. The second form with paint==false is used
599 // by the Hextile decoder for raw-encoded tiles.
600 //
601
enikey6c72ce12008-12-19 09:47:14 +0000602 void handleRawRect(int x, int y, int w, int h) throws IOException, Exception {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000603 handleRawRect(x, y, w, h, true);
604 }
605
606 void handleRawRect(int x, int y, int w, int h, boolean paint)
enikey6c72ce12008-12-19 09:47:14 +0000607 throws IOException , Exception{
608 rawDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000609 if (paint)
610 scheduleRepaint(x, y, w, h);
611 }
612
613 //
614 // Handle a CopyRect rectangle.
615 //
616
617 void handleCopyRect(int x, int y, int w, int h) throws IOException {
enikey5805ba62008-12-25 08:10:43 +0000618 copyRectDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000619 scheduleRepaint(x, y, w, h);
620 }
621
622 //
623 // Handle an RRE-encoded rectangle.
624 //
625
626 void handleRRERect(int x, int y, int w, int h) throws IOException {
enikey6c72ce12008-12-19 09:47:14 +0000627 rreDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000628 scheduleRepaint(x, y, w, h);
629 }
630
631 //
632 // Handle a CoRRE-encoded rectangle.
633 //
634
635 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
enikey6c72ce12008-12-19 09:47:14 +0000636 correDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000637 scheduleRepaint(x, y, w, h);
638 }
639
640 //
641 // Handle a Hextile-encoded rectangle.
642 //
643
enikey6c72ce12008-12-19 09:47:14 +0000644 void handleHextileRect(int x, int y, int w, int h) throws IOException,
645 Exception {
646 hextileDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000647 }
648
649 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000650 // Handle a ZRLE-encoded rectangle.
651 //
652 // FIXME: Currently, session recording is not fully supported for ZRLE.
653 //
654
655 void handleZRLERect(int x, int y, int w, int h) throws Exception {
enikeyc92c1d12008-12-19 09:58:31 +0000656 zrleDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000657 scheduleRepaint(x, y, w, h);
658 }
659
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000660 //
661 // Handle a Zlib-encoded rectangle.
662 //
663
664 void handleZlibRect(int x, int y, int w, int h) throws Exception {
enikeyc92c1d12008-12-19 09:58:31 +0000665 zlibDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000666 scheduleRepaint(x, y, w, h);
667 }
668
669 //
670 // Handle a Tight-encoded rectangle.
671 //
672
673 void handleTightRect(int x, int y, int w, int h) throws Exception {
enikey2f7f46e2008-12-19 09:32:35 +0000674 tightDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000675 scheduleRepaint(x, y, w, h);
676 }
677
678 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000679 // Tell JVM to repaint specified desktop area.
680 //
681
enikey0dbc1532008-12-19 08:51:47 +0000682 public void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000683 // Request repaint, deferred if necessary.
684 if (rfb.framebufferWidth == scaledWidth) {
685 repaint(viewer.deferScreenUpdates, x, y, w, h);
686 } else {
687 int sx = x * scalingFactor / 100;
688 int sy = y * scalingFactor / 100;
689 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
690 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
691 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
692 }
693 }
694
695 //
696 // Handle events.
697 //
698
699 public void keyPressed(KeyEvent evt) {
700 processLocalKeyEvent(evt);
701 }
702 public void keyReleased(KeyEvent evt) {
703 processLocalKeyEvent(evt);
704 }
705 public void keyTyped(KeyEvent evt) {
706 evt.consume();
707 }
708
709 public void mousePressed(MouseEvent evt) {
710 processLocalMouseEvent(evt, false);
711 }
712 public void mouseReleased(MouseEvent evt) {
713 processLocalMouseEvent(evt, false);
714 }
715 public void mouseMoved(MouseEvent evt) {
716 processLocalMouseEvent(evt, true);
717 }
718 public void mouseDragged(MouseEvent evt) {
719 processLocalMouseEvent(evt, true);
720 }
721
enikey866b88c2009-01-20 04:50:55 +0000722 private synchronized void trySendPointerEvent() {
723 if ((needToSendMouseEvent) && (mouseEvent!=null)) {
724 sendMouseEvent(mouseEvent, false);
725 needToSendMouseEvent = false;
726 lastMouseEventSendTime = System.currentTimeMillis();
727 }
728 }
729
730 public void run() {
731 while (true) {
732 // Send mouse movement if we have it
733 trySendPointerEvent();
734 // Sleep for some time
735 try {
736 Thread.sleep(1000 / mouseMaxFreq);
737 } catch (InterruptedException ex) {
738 }
739 }
740 }
741
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000742 //
743 // Ignored events.
744 //
745
746 public void mouseClicked(MouseEvent evt) {}
747 public void mouseEntered(MouseEvent evt) {}
748 public void mouseExited(MouseEvent evt) {}
749
750 //
751 // Actual event processing.
752 //
753
754 private void processLocalKeyEvent(KeyEvent evt) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000755 if (viewer.rfb != null && rfb.inNormalProtocol) {
756 if (!inputEnabled) {
757 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
758 evt.getID() == KeyEvent.KEY_PRESSED ) {
759 // Request screen update.
760 try {
761 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
762 rfb.framebufferHeight, false);
763 } catch (IOException e) {
764 e.printStackTrace();
765 }
766 }
767 } else {
768 // Input enabled.
769 synchronized(rfb) {
770 try {
771 rfb.writeKeyEvent(evt);
772 } catch (Exception e) {
773 e.printStackTrace();
774 }
775 rfb.notify();
776 }
777 }
778 }
enikeyc41ba1d2008-12-19 09:07:22 +0000779 // Don't ever pass keyboard events to AWT for default processing.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000780 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
781 evt.consume();
782 }
783
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000784 private void processLocalMouseEvent(MouseEvent evt, boolean moved) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000785 if (viewer.rfb != null && rfb.inNormalProtocol) {
Adam Tkac6a7b09f2010-11-18 17:19:45 +0000786 if (inputEnabled) {
787 // If mouse not moved, but it's click event then
788 // send it to server immideanlty.
789 // Else, it's mouse movement - we can send it in
790 // our thread later.
791 if (!moved) {
792 sendMouseEvent(evt, moved);
793 } else {
794 mouseEvent = evt;
795 needToSendMouseEvent = true;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000796 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000797 }
798 }
799 }
800
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000801 private void sendMouseEvent(MouseEvent evt, boolean moved) {
802 if (moved) {
803 softCursorMove(evt.getX(), evt.getY());
804 }
805 if (rfb.framebufferWidth != scaledWidth) {
806 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
807 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
808 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
809 }
810 synchronized(rfb) {
811 try {
812 rfb.writePointerEvent(evt);
813 } catch (Exception e) {
814 e.printStackTrace();
815 }
816 rfb.notify();
enikey1abaff92009-01-19 11:30:27 +0000817 lastMouseEventSendTime = System.currentTimeMillis();
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000818 }
819 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000820
821 //
822 // Reset update statistics.
823 //
824
825 void resetStats() {
826 statStartTime = System.currentTimeMillis();
827 statNumUpdates = 0;
828 statNumTotalRects = 0;
829 statNumPixelRects = 0;
830 statNumRectsTight = 0;
831 statNumRectsTightJPEG = 0;
832 statNumRectsZRLE = 0;
833 statNumRectsHextile = 0;
834 statNumRectsRaw = 0;
835 statNumRectsCopy = 0;
836 statNumBytesEncoded = 0;
837 statNumBytesDecoded = 0;
enikey1214ab12008-12-24 09:01:19 +0000838 if (tightDecoder != null) {
enikey2f7f46e2008-12-19 09:32:35 +0000839 tightDecoder.setNumJPEGRects(0);
enikey1214ab12008-12-24 09:01:19 +0000840 tightDecoder.setNumTightRects(0);
841 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000842 }
843
844 //////////////////////////////////////////////////////////////////
845 //
846 // Handle cursor shape updates (XCursor and RichCursor encodings).
847 //
848
849 boolean showSoftCursor = false;
850
851 MemoryImageSource softCursorSource;
852 Image softCursor;
enikey866b88c2009-01-20 04:50:55 +0000853 MouseEvent mouseEvent = null;
854 boolean needToSendMouseEvent = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000855 int cursorX = 0, cursorY = 0;
856 int cursorWidth, cursorHeight;
857 int origCursorWidth, origCursorHeight;
858 int hotX, hotY;
859 int origHotX, origHotY;
860
861 //
862 // Handle cursor shape update (XCursor and RichCursor encodings).
863 //
864
865 synchronized void
866 handleCursorShapeUpdate(int encodingType,
867 int xhot, int yhot, int width, int height)
868 throws IOException {
869
870 softCursorFree();
871
872 if (width * height == 0)
873 return;
874
875 // Ignore cursor shape data if requested by user.
876 if (viewer.options.ignoreCursorUpdates) {
877 int bytesPerRow = (width + 7) / 8;
878 int bytesMaskData = bytesPerRow * height;
879
880 if (encodingType == rfb.EncodingXCursor) {
881 rfb.skipBytes(6 + bytesMaskData * 2);
882 } else {
883 // rfb.EncodingRichCursor
884 rfb.skipBytes(width * height + bytesMaskData);
885 }
886 return;
887 }
888
889 // Decode cursor pixel data.
890 softCursorSource = decodeCursorShape(encodingType, width, height);
891
892 // Set original (non-scaled) cursor dimensions.
893 origCursorWidth = width;
894 origCursorHeight = height;
895 origHotX = xhot;
896 origHotY = yhot;
897
898 // Create off-screen cursor image.
899 createSoftCursor();
900
901 // Show the cursor.
902 showSoftCursor = true;
903 repaint(viewer.deferCursorUpdates,
904 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
905 }
906
907 //
908 // decodeCursorShape(). Decode cursor pixel data and return
909 // corresponding MemoryImageSource instance.
910 //
911
912 synchronized MemoryImageSource
913 decodeCursorShape(int encodingType, int width, int height)
914 throws IOException {
915
916 int bytesPerRow = (width + 7) / 8;
917 int bytesMaskData = bytesPerRow * height;
918
919 int[] softCursorPixels = new int[width * height];
920
921 if (encodingType == rfb.EncodingXCursor) {
922
923 // Read foreground and background colors of the cursor.
924 byte[] rgb = new byte[6];
925 rfb.readFully(rgb);
926 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
927 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
928 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
929 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
930
931 // Read pixel and mask data.
932 byte[] pixBuf = new byte[bytesMaskData];
933 rfb.readFully(pixBuf);
934 byte[] maskBuf = new byte[bytesMaskData];
935 rfb.readFully(maskBuf);
936
937 // Decode pixel data into softCursorPixels[].
938 byte pixByte, maskByte;
939 int x, y, n, result;
940 int i = 0;
941 for (y = 0; y < height; y++) {
942 for (x = 0; x < width / 8; x++) {
943 pixByte = pixBuf[y * bytesPerRow + x];
944 maskByte = maskBuf[y * bytesPerRow + x];
945 for (n = 7; n >= 0; n--) {
946 if ((maskByte >> n & 1) != 0) {
947 result = colors[pixByte >> n & 1];
948 } else {
949 result = 0; // Transparent pixel
950 }
951 softCursorPixels[i++] = result;
952 }
953 }
954 for (n = 7; n >= 8 - width % 8; n--) {
955 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
956 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
957 } else {
958 result = 0; // Transparent pixel
959 }
960 softCursorPixels[i++] = result;
961 }
962 }
963
964 } else {
965 // encodingType == rfb.EncodingRichCursor
966
967 // Read pixel and mask data.
968 byte[] pixBuf = new byte[width * height * bytesPixel];
969 rfb.readFully(pixBuf);
970 byte[] maskBuf = new byte[bytesMaskData];
971 rfb.readFully(maskBuf);
972
973 // Decode pixel data into softCursorPixels[].
Adam Tkac6a7b09f2010-11-18 17:19:45 +0000974 byte maskByte;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000975 int x, y, n, result;
976 int i = 0;
977 for (y = 0; y < height; y++) {
978 for (x = 0; x < width / 8; x++) {
979 maskByte = maskBuf[y * bytesPerRow + x];
980 for (n = 7; n >= 0; n--) {
981 if ((maskByte >> n & 1) != 0) {
982 if (bytesPixel == 1) {
983 result = cm8.getRGB(pixBuf[i]);
984 } else {
985 result = 0xFF000000 |
986 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
987 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
988 (pixBuf[i * 4] & 0xFF);
989 }
990 } else {
991 result = 0; // Transparent pixel
992 }
993 softCursorPixels[i++] = result;
994 }
995 }
996 for (n = 7; n >= 8 - width % 8; n--) {
997 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
998 if (bytesPixel == 1) {
999 result = cm8.getRGB(pixBuf[i]);
1000 } else {
1001 result = 0xFF000000 |
1002 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1003 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1004 (pixBuf[i * 4] & 0xFF);
1005 }
1006 } else {
1007 result = 0; // Transparent pixel
1008 }
1009 softCursorPixels[i++] = result;
1010 }
1011 }
1012
1013 }
1014
1015 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1016 }
1017
1018 //
1019 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1020 // Uses softCursorSource as a source for new cursor image.
1021 //
1022
1023 synchronized void
1024 createSoftCursor() {
1025
1026 if (softCursorSource == null)
1027 return;
1028
1029 int scaleCursor = viewer.options.scaleCursor;
1030 if (scaleCursor == 0 || !inputEnabled)
1031 scaleCursor = 100;
1032
1033 // Save original cursor coordinates.
1034 int x = cursorX - hotX;
1035 int y = cursorY - hotY;
1036 int w = cursorWidth;
1037 int h = cursorHeight;
1038
1039 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1040 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1041 hotX = (origHotX * scaleCursor + 50) / 100;
1042 hotY = (origHotY * scaleCursor + 50) / 100;
1043 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1044
1045 if (scaleCursor != 100) {
1046 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1047 Image.SCALE_SMOOTH);
1048 }
1049
1050 if (showSoftCursor) {
1051 // Compute screen area to update.
1052 x = Math.min(x, cursorX - hotX);
1053 y = Math.min(y, cursorY - hotY);
1054 w = Math.max(w, cursorWidth);
1055 h = Math.max(h, cursorHeight);
1056
1057 repaint(viewer.deferCursorUpdates, x, y, w, h);
1058 }
1059 }
1060
1061 //
1062 // softCursorMove(). Moves soft cursor into a particular location.
1063 //
1064
1065 synchronized void softCursorMove(int x, int y) {
1066 int oldX = cursorX;
1067 int oldY = cursorY;
1068 cursorX = x;
1069 cursorY = y;
1070 if (showSoftCursor) {
1071 repaint(viewer.deferCursorUpdates,
1072 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1073 repaint(viewer.deferCursorUpdates,
1074 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1075 }
1076 }
1077
1078 //
1079 // softCursorFree(). Remove soft cursor, dispose resources.
1080 //
1081
1082 synchronized void softCursorFree() {
1083 if (showSoftCursor) {
1084 showSoftCursor = false;
1085 softCursor = null;
1086 softCursorSource = null;
1087
1088 repaint(viewer.deferCursorUpdates,
1089 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1090 }
1091 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001092}