blob: 56d414803dc60e3a55088f2521b67ba6671cb1e3 [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;
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;
73
74 // Base decoder decoders array
75 RawDecoder []decoders = null;
76
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000077 // Update statistics.
78 long statStartTime; // time on first framebufferUpdateRequest
79 int statNumUpdates; // counter for FramebufferUpdate messages
80 int statNumTotalRects; // rectangles in FramebufferUpdate messages
81 int statNumPixelRects; // the same, but excluding pseudo-rectangles
82 int statNumRectsTight; // Tight-encoded rectangles (including JPEG)
83 int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles
84 int statNumRectsZRLE; // ZRLE-encoded rectangles
85 int statNumRectsHextile; // Hextile-encoded rectangles
86 int statNumRectsRaw; // Raw-encoded rectangles
87 int statNumRectsCopy; // CopyRect rectangles
88 int statNumBytesEncoded; // number of bytes in updates, as received
89 int statNumBytesDecoded; // number of bytes, as if Raw encoding was used
90
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000091 // True if we process keyboard and mouse events.
92 boolean inputEnabled;
93
enikeyc92c1d12008-12-19 09:58:31 +000094 // True if was no one auto resize of canvas
95 boolean isFirstSizeAutoUpdate = true;
96
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +000097 //
98 // The constructors.
99 //
100
101 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
102 throws IOException {
103
104 viewer = v;
105 maxWidth = maxWidth_;
106 maxHeight = maxHeight_;
107
108 rfb = viewer.rfb;
109 scalingFactor = viewer.options.scalingFactor;
110
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000111 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
112 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
113
enikeyc41ba1d2008-12-19 09:07:22 +0000114 //
115 // Create decoders
116 //
117
118 // Input stream for decoders
119 RfbInputStream rfbis = new RfbInputStream(rfb);
120
121 rawDecoder = new RawDecoder(memGraphics, rfbis);
122 rreDecoder = new RREDecoder(memGraphics, rfbis);
123 correDecoder = new CoRREDecoder(memGraphics, rfbis);
124 hextileDecoder = new HextileDecoder(memGraphics, rfbis);
125 tightDecoder = new TightDecoder(memGraphics, rfbis);
enikey4582bab2008-12-19 09:19:59 +0000126 zlibDecoder = new ZlibDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000127 zrleDecoder = new ZRLEDecoder(memGraphics, rfbis);
128
129 //
130 // Set data for decoders that needs extra parameters
131 //
132
133 hextileDecoder.setRepainableControl(this);
134 tightDecoder.setRepainableControl(this);
135
136 //
137 // Create array that contains our decoders
138 //
139
140 decoders = new RawDecoder[7];
141 decoders[0] = rawDecoder;
142 decoders[1] = rreDecoder;
143 decoders[2] = correDecoder;
144 decoders[3] = hextileDecoder;
145 decoders[4] = zlibDecoder;
146 decoders[5] = tightDecoder;
147 decoders[6] = zrleDecoder;
148
149 //
150 // Set session recorder for decoders
151 //
152
153 for (int i = 0; i < decoders.length; i++) {
154 decoders[i].setSessionRecorder(this);
155 }
enikeyc92c1d12008-12-19 09:58:31 +0000156
enikey2f7f46e2008-12-19 09:32:35 +0000157 setPixelFormat();
158
159 resetSelection();
160
161 inputEnabled = false;
162 if (!viewer.options.viewOnly)
163 enableInput(true);
enikeyc41ba1d2008-12-19 09:07:22 +0000164
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000165 // Enable mouse and keyboard event listeners.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000166 addKeyListener(this);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000167 addMouseListener(this);
168 addMouseMotionListener(this);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000169 }
170
171 public VncCanvas(VncViewer v) throws IOException {
172 this(v, 0, 0);
173 }
174
175 //
176 // Callback methods to determine geometry of our Component.
177 //
178
179 public Dimension getPreferredSize() {
180 return new Dimension(scaledWidth, scaledHeight);
181 }
182
183 public Dimension getMinimumSize() {
184 return new Dimension(scaledWidth, scaledHeight);
185 }
186
187 public Dimension getMaximumSize() {
188 return new Dimension(scaledWidth, scaledHeight);
189 }
190
191 //
192 // All painting is performed here.
193 //
194
195 public void update(Graphics g) {
196 paint(g);
197 }
198
199 public void paint(Graphics g) {
200 synchronized(memImage) {
201 if (rfb.framebufferWidth == scaledWidth) {
202 g.drawImage(memImage, 0, 0, null);
203 } else {
204 paintScaledFrameBuffer(g);
205 }
206 }
207 if (showSoftCursor) {
208 int x0 = cursorX - hotX, y0 = cursorY - hotY;
209 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
210 if (r.intersects(g.getClipBounds())) {
211 g.drawImage(softCursor, x0, y0, null);
212 }
213 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000214 if (isInSelectionMode()) {
215 Rectangle r = getSelection(true);
216 if (r.width > 0 && r.height > 0) {
217 // Don't forget to correct the coordinates for the right and bottom
218 // borders, so that the borders are the part of the selection.
219 r.width -= 1;
220 r.height -= 1;
221 g.setXORMode(Color.yellow);
222 g.drawRect(r.x, r.y, r.width, r.height);
223 }
224 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000225 }
226
227 public void paintScaledFrameBuffer(Graphics g) {
228 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
229 }
230
231 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000232 // Start/stop receiving mouse events. Keyboard events are received
233 // even in view-only mode, because we want to map the 'r' key to the
234 // screen refreshing function.
235 //
236
237 public synchronized void enableInput(boolean enable) {
238 if (enable && !inputEnabled) {
239 inputEnabled = true;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000240 if (viewer.showControls) {
241 viewer.buttonPanel.enableRemoteAccessControls(true);
242 }
243 createSoftCursor(); // scaled cursor
244 } else if (!enable && inputEnabled) {
245 inputEnabled = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000246 if (viewer.showControls) {
247 viewer.buttonPanel.enableRemoteAccessControls(false);
248 }
249 createSoftCursor(); // non-scaled cursor
250 }
251 }
252
253 public void setPixelFormat() throws IOException {
254 if (viewer.options.eightBitColors) {
255 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
256 bytesPixel = 1;
257 } else {
258 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
259 bytesPixel = 4;
260 }
261 updateFramebufferSize();
262 }
263
enikey45cfaa52008-12-03 06:52:09 +0000264 void setScalingFactor(int sf) {
265 scalingFactor = sf;
266 updateFramebufferSize();
267 invalidate();
268 }
269
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000270 void updateFramebufferSize() {
271
272 // Useful shortcuts.
273 int fbWidth = rfb.framebufferWidth;
274 int fbHeight = rfb.framebufferHeight;
275
enikey87647e92008-12-04 08:42:34 +0000276 // FIXME: This part of code must be in VncViewer i think
enikey73683202008-12-03 09:17:25 +0000277 if (viewer.options.autoScale) {
enikey87647e92008-12-04 08:42:34 +0000278 if (viewer.inAnApplet) {
279 maxWidth = viewer.getWidth();
280 maxHeight = viewer.getHeight();
281 } else {
282 if (viewer.vncFrame != null) {
283 if (isFirstSizeAutoUpdate) {
284 isFirstSizeAutoUpdate = false;
285 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
286 maxWidth = (int)screenSize.getWidth() - 100;
enikeyc41ba1d2008-12-19 09:07:22 +0000287 maxHeight = (int)screenSize.getHeight() - 100;
enikey87647e92008-12-04 08:42:34 +0000288 viewer.vncFrame.setSize(maxWidth, maxHeight);
289 } else {
290 viewer.desktopScrollPane.doLayout();
291 maxWidth = viewer.desktopScrollPane.getWidth();
292 maxHeight = viewer.desktopScrollPane.getHeight();
293 }
294 } else {
295 maxWidth = fbWidth;
296 maxHeight = fbHeight;
297 }
enikey73683202008-12-03 09:17:25 +0000298 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000299 int f1 = maxWidth * 100 / fbWidth;
300 int f2 = maxHeight * 100 / fbHeight;
301 scalingFactor = Math.min(f1, f2);
302 if (scalingFactor > 100)
303 scalingFactor = 100;
304 System.out.println("Scaling desktop at " + scalingFactor + "%");
305 }
306
307 // Update scaled framebuffer geometry.
308 scaledWidth = (fbWidth * scalingFactor + 50) / 100;
309 scaledHeight = (fbHeight * scalingFactor + 50) / 100;
310
311 // Create new off-screen image either if it does not exist, or if
312 // its geometry should be changed. It's not necessary to replace
313 // existing image if only pixel format should be changed.
314 if (memImage == null) {
315 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
316 memGraphics = memImage.getGraphics();
317 } else if (memImage.getWidth(null) != fbWidth ||
318 memImage.getHeight(null) != fbHeight) {
319 synchronized(memImage) {
320 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
321 memGraphics = memImage.getGraphics();
322 }
323 }
324
enikey4582bab2008-12-19 09:19:59 +0000325 //
326 // Update decoders
327 //
328
329 //
330 // FIXME: Why decoders can be null here?
331 //
332
333 if (decoders != null) {
334 for (int i = 0; i < decoders.length; i++) {
335 //
336 // Set changes to every decoder that we can use
337 //
338
339 decoders[i].setBPP(bytesPixel);
340 decoders[i].setFrameBufferSize(fbWidth, fbHeight);
341 decoders[i].setGraphics(memGraphics);
342
343 //
344 // Update decoder
345 //
346
347 decoders[i].update();
348 }
349 }
350
enikey87647e92008-12-04 08:42:34 +0000351 // FIXME: This part of code must be in VncViewer i think
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000352 // Update the size of desktop containers.
353 if (viewer.inSeparateFrame) {
enikey87647e92008-12-04 08:42:34 +0000354 if (viewer.desktopScrollPane != null) {
355 if (!viewer.options.autoScale) {
356 resizeDesktopFrame();
357 } else {
358 setSize(scaledWidth, scaledHeight);
359 viewer.desktopScrollPane.setSize(maxWidth + 200,
360 maxHeight + 200);
361 }
362 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000363 } else {
364 setSize(scaledWidth, scaledHeight);
365 }
366 viewer.moveFocusToDesktop();
367 }
368
369 void resizeDesktopFrame() {
370 setSize(scaledWidth, scaledHeight);
371
372 // FIXME: Find a better way to determine correct size of a
373 // ScrollPane. -- const
374 Insets insets = viewer.desktopScrollPane.getInsets();
375 viewer.desktopScrollPane.setSize(scaledWidth +
376 2 * Math.min(insets.left, insets.right),
377 scaledHeight +
378 2 * Math.min(insets.top, insets.bottom));
379
380 viewer.vncFrame.pack();
381
382 // Try to limit the frame size to the screen size.
383
384 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
385 Dimension frameSize = viewer.vncFrame.getSize();
386 Dimension newSize = frameSize;
387
388 // Reduce Screen Size by 30 pixels in each direction;
389 // This is a (poor) attempt to account for
390 // 1) Menu bar on Macintosh (should really also account for
391 // Dock on OSX). Usually 22px on top of screen.
392 // 2) Taxkbar on Windows (usually about 28 px on bottom)
393 // 3) Other obstructions.
394
395 screenSize.height -= 30;
396 screenSize.width -= 30;
397
398 boolean needToResizeFrame = false;
399 if (frameSize.height > screenSize.height) {
400 newSize.height = screenSize.height;
401 needToResizeFrame = true;
402 }
403 if (frameSize.width > screenSize.width) {
404 newSize.width = screenSize.width;
405 needToResizeFrame = true;
406 }
407 if (needToResizeFrame) {
408 viewer.vncFrame.setSize(newSize);
409 }
410
411 viewer.desktopScrollPane.doLayout();
412 }
413
414 //
415 // processNormalProtocol() - executed by the rfbThread to deal with the
416 // RFB socket.
417 //
418
419 public void processNormalProtocol() throws Exception {
420
421 // Start/stop session recording if necessary.
422 viewer.checkRecordingStatus();
423
424 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
425 rfb.framebufferHeight, false);
426
427 if (viewer.options.continuousUpdates) {
428 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
429 rfb.framebufferHeight);
430 }
431
432 resetStats();
433 boolean statsRestarted = false;
434
435 //
436 // main dispatch loop
437 //
438
439 while (true) {
440
441 // Read message type from the server.
442 int msgType = rfb.readServerMessageType();
443
444 // Process the message depending on its type.
445 switch (msgType) {
446 case RfbProto.FramebufferUpdate:
447
448 if (statNumUpdates == viewer.debugStatsExcludeUpdates &&
449 !statsRestarted) {
450 resetStats();
451 statsRestarted = true;
452 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates &&
453 statsRestarted) {
454 viewer.disconnect();
455 }
456
457 rfb.readFramebufferUpdate();
458 statNumUpdates++;
459
460 boolean cursorPosReceived = false;
461
462 for (int i = 0; i < rfb.updateNRects; i++) {
463
464 rfb.readFramebufferUpdateRectHdr();
465 statNumTotalRects++;
466 int rx = rfb.updateRectX, ry = rfb.updateRectY;
467 int rw = rfb.updateRectW, rh = rfb.updateRectH;
468
469 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
470 break;
471
472 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
473 rfb.setFramebufferSize(rw, rh);
474 updateFramebufferSize();
475 break;
476 }
477
478 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
479 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
480 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
481 continue;
482 }
483
484 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
485 softCursorMove(rx, ry);
486 cursorPosReceived = true;
487 continue;
488 }
489
490 long numBytesReadBefore = rfb.getNumBytesRead();
491
492 rfb.startTiming();
493
494 switch (rfb.updateRectEncoding) {
495 case RfbProto.EncodingRaw:
496 statNumRectsRaw++;
497 handleRawRect(rx, ry, rw, rh);
498 break;
499 case RfbProto.EncodingCopyRect:
500 statNumRectsCopy++;
501 handleCopyRect(rx, ry, rw, rh);
502 break;
503 case RfbProto.EncodingRRE:
504 handleRRERect(rx, ry, rw, rh);
505 break;
506 case RfbProto.EncodingCoRRE:
507 handleCoRRERect(rx, ry, rw, rh);
508 break;
509 case RfbProto.EncodingHextile:
510 statNumRectsHextile++;
511 handleHextileRect(rx, ry, rw, rh);
512 break;
513 case RfbProto.EncodingZRLE:
514 statNumRectsZRLE++;
515 handleZRLERect(rx, ry, rw, rh);
516 break;
517 case RfbProto.EncodingZlib:
518 handleZlibRect(rx, ry, rw, rh);
519 break;
520 case RfbProto.EncodingTight:
enikey2f7f46e2008-12-19 09:32:35 +0000521 if (tightDecoder != null) {
enikey479b18c2008-12-19 09:39:40 +0000522 statNumRectsTightJPEG = tightDecoder.getNumJPEGRects();
enikey2f7f46e2008-12-19 09:32:35 +0000523 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000524 handleTightRect(rx, ry, rw, rh);
525 break;
526 default:
527 throw new Exception("Unknown RFB rectangle encoding " +
528 rfb.updateRectEncoding);
529 }
530
531 rfb.stopTiming();
532
533 statNumPixelRects++;
534 statNumBytesDecoded += rw * rh * bytesPixel;
535 statNumBytesEncoded +=
536 (int)(rfb.getNumBytesRead() - numBytesReadBefore);
537 }
538
539 boolean fullUpdateNeeded = false;
540
541 // Start/stop session recording if necessary. Request full
542 // update if a new session file was opened.
543 if (viewer.checkRecordingStatus())
544 fullUpdateNeeded = true;
545
546 // Defer framebuffer update request if necessary. But wake up
547 // immediately on keyboard or mouse event. Also, don't sleep
548 // if there is some data to receive, or if the last update
549 // included a PointerPos message.
550 if (viewer.deferUpdateRequests > 0 &&
551 rfb.available() == 0 && !cursorPosReceived) {
552 synchronized(rfb) {
553 try {
554 rfb.wait(viewer.deferUpdateRequests);
555 } catch (InterruptedException e) {
556 }
557 }
558 }
559
560 viewer.autoSelectEncodings();
561
562 // Before requesting framebuffer update, check if the pixel
563 // format should be changed.
564 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
565 // Pixel format should be changed.
566 if (!rfb.continuousUpdatesAreActive()) {
567 // Continuous updates are not used. In this case, we just
568 // set new pixel format and request full update.
569 setPixelFormat();
570 fullUpdateNeeded = true;
571 } else {
572 // Otherwise, disable continuous updates first. Pixel
573 // format will be set later when we are sure that there
574 // will be no unsolicited framebuffer updates.
575 rfb.tryDisableContinuousUpdates();
576 break; // skip the code below
577 }
578 }
579
580 // Enable/disable continuous updates to reflect the GUI setting.
581 boolean enable = viewer.options.continuousUpdates;
582 if (enable != rfb.continuousUpdatesAreActive()) {
583 if (enable) {
584 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
585 rfb.framebufferHeight);
586 } else {
587 rfb.tryDisableContinuousUpdates();
588 }
589 }
590
591 // Finally, request framebuffer update if needed.
592 if (fullUpdateNeeded) {
593 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
594 rfb.framebufferHeight, false);
595 } else if (!rfb.continuousUpdatesAreActive()) {
596 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
597 rfb.framebufferHeight, true);
598 }
599
600 break;
601
602 case RfbProto.SetColourMapEntries:
603 throw new Exception("Can't handle SetColourMapEntries message");
604
605 case RfbProto.Bell:
606 Toolkit.getDefaultToolkit().beep();
607 break;
608
609 case RfbProto.ServerCutText:
610 String s = rfb.readServerCutText();
611 viewer.clipboard.setCutText(s);
612 break;
613
614 case RfbProto.EndOfContinuousUpdates:
615 if (rfb.continuousUpdatesAreActive()) {
616 rfb.endOfContinuousUpdates();
617
618 // Change pixel format if such change was pending. Note that we
619 // could not change pixel format while continuous updates were
620 // in effect.
621 boolean incremental = true;
622 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
623 setPixelFormat();
624 incremental = false;
625 }
626 // From this point, we ask for updates explicitly.
627 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
628 rfb.framebufferHeight,
629 incremental);
630 }
631 break;
632
633 default:
634 throw new Exception("Unknown RFB message type " + msgType);
635 }
636 }
637 }
638
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000639 //
640 // Handle a raw rectangle. The second form with paint==false is used
641 // by the Hextile decoder for raw-encoded tiles.
642 //
643
enikey6c72ce12008-12-19 09:47:14 +0000644 void handleRawRect(int x, int y, int w, int h) throws IOException, Exception {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000645 handleRawRect(x, y, w, h, true);
646 }
647
648 void handleRawRect(int x, int y, int w, int h, boolean paint)
enikey6c72ce12008-12-19 09:47:14 +0000649 throws IOException , Exception{
650 rawDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000651 if (paint)
652 scheduleRepaint(x, y, w, h);
653 }
654
655 //
656 // Handle a CopyRect rectangle.
657 //
658
659 void handleCopyRect(int x, int y, int w, int h) throws IOException {
660
661 rfb.readCopyRect();
662 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
663 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
664
665 scheduleRepaint(x, y, w, h);
666 }
667
668 //
669 // Handle an RRE-encoded rectangle.
670 //
671
672 void handleRRERect(int x, int y, int w, int h) throws IOException {
enikey6c72ce12008-12-19 09:47:14 +0000673 rreDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000674 scheduleRepaint(x, y, w, h);
675 }
676
677 //
678 // Handle a CoRRE-encoded rectangle.
679 //
680
681 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
enikey6c72ce12008-12-19 09:47:14 +0000682 correDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000683 scheduleRepaint(x, y, w, h);
684 }
685
686 //
687 // Handle a Hextile-encoded rectangle.
688 //
689
enikey6c72ce12008-12-19 09:47:14 +0000690 void handleHextileRect(int x, int y, int w, int h) throws IOException,
691 Exception {
692 hextileDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000693 }
694
695 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000696 // Handle a ZRLE-encoded rectangle.
697 //
698 // FIXME: Currently, session recording is not fully supported for ZRLE.
699 //
700
701 void handleZRLERect(int x, int y, int w, int h) throws Exception {
enikeyc92c1d12008-12-19 09:58:31 +0000702 zrleDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000703 scheduleRepaint(x, y, w, h);
704 }
705
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000706 //
707 // Handle a Zlib-encoded rectangle.
708 //
709
710 void handleZlibRect(int x, int y, int w, int h) throws Exception {
enikeyc92c1d12008-12-19 09:58:31 +0000711 zlibDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000712 scheduleRepaint(x, y, w, h);
713 }
714
715 //
716 // Handle a Tight-encoded rectangle.
717 //
718
719 void handleTightRect(int x, int y, int w, int h) throws Exception {
enikey2f7f46e2008-12-19 09:32:35 +0000720 tightDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000721 scheduleRepaint(x, y, w, h);
722 }
723
724 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000725 // Tell JVM to repaint specified desktop area.
726 //
727
enikey0dbc1532008-12-19 08:51:47 +0000728 public void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000729 // Request repaint, deferred if necessary.
730 if (rfb.framebufferWidth == scaledWidth) {
731 repaint(viewer.deferScreenUpdates, x, y, w, h);
732 } else {
733 int sx = x * scalingFactor / 100;
734 int sy = y * scalingFactor / 100;
735 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
736 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
737 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
738 }
739 }
740
741 //
742 // Handle events.
743 //
744
745 public void keyPressed(KeyEvent evt) {
746 processLocalKeyEvent(evt);
747 }
748 public void keyReleased(KeyEvent evt) {
749 processLocalKeyEvent(evt);
750 }
751 public void keyTyped(KeyEvent evt) {
752 evt.consume();
753 }
754
755 public void mousePressed(MouseEvent evt) {
756 processLocalMouseEvent(evt, false);
757 }
758 public void mouseReleased(MouseEvent evt) {
759 processLocalMouseEvent(evt, false);
760 }
761 public void mouseMoved(MouseEvent evt) {
762 processLocalMouseEvent(evt, true);
763 }
764 public void mouseDragged(MouseEvent evt) {
765 processLocalMouseEvent(evt, true);
766 }
767
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000768 //
769 // Ignored events.
770 //
771
772 public void mouseClicked(MouseEvent evt) {}
773 public void mouseEntered(MouseEvent evt) {}
774 public void mouseExited(MouseEvent evt) {}
775
776 //
777 // Actual event processing.
778 //
779
780 private void processLocalKeyEvent(KeyEvent evt) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000781 if (viewer.rfb != null && rfb.inNormalProtocol) {
782 if (!inputEnabled) {
783 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
784 evt.getID() == KeyEvent.KEY_PRESSED ) {
785 // Request screen update.
786 try {
787 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
788 rfb.framebufferHeight, false);
789 } catch (IOException e) {
790 e.printStackTrace();
791 }
792 }
793 } else {
794 // Input enabled.
795 synchronized(rfb) {
796 try {
797 rfb.writeKeyEvent(evt);
798 } catch (Exception e) {
799 e.printStackTrace();
800 }
801 rfb.notify();
802 }
803 }
804 }
enikeyc41ba1d2008-12-19 09:07:22 +0000805 // Don't ever pass keyboard events to AWT for default processing.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000806 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
807 evt.consume();
808 }
809
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000810 private void processLocalMouseEvent(MouseEvent evt, boolean moved) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000811 if (viewer.rfb != null && rfb.inNormalProtocol) {
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000812 if (!inSelectionMode) {
813 if (inputEnabled) {
814 sendMouseEvent(evt, moved);
815 }
816 } else {
817 handleSelectionMouseEvent(evt);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000818 }
819 }
820 }
821
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000822 private void sendMouseEvent(MouseEvent evt, boolean moved) {
823 if (moved) {
824 softCursorMove(evt.getX(), evt.getY());
825 }
826 if (rfb.framebufferWidth != scaledWidth) {
827 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
828 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
829 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
830 }
831 synchronized(rfb) {
832 try {
833 rfb.writePointerEvent(evt);
834 } catch (Exception e) {
835 e.printStackTrace();
836 }
837 rfb.notify();
838 }
839 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000840
841 //
842 // Reset update statistics.
843 //
844
845 void resetStats() {
846 statStartTime = System.currentTimeMillis();
847 statNumUpdates = 0;
848 statNumTotalRects = 0;
849 statNumPixelRects = 0;
850 statNumRectsTight = 0;
851 statNumRectsTightJPEG = 0;
852 statNumRectsZRLE = 0;
853 statNumRectsHextile = 0;
854 statNumRectsRaw = 0;
855 statNumRectsCopy = 0;
856 statNumBytesEncoded = 0;
857 statNumBytesDecoded = 0;
enikey2f7f46e2008-12-19 09:32:35 +0000858 if (tightDecoder != null)
859 tightDecoder.setNumJPEGRects(0);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000860 }
861
862 //////////////////////////////////////////////////////////////////
863 //
864 // Handle cursor shape updates (XCursor and RichCursor encodings).
865 //
866
867 boolean showSoftCursor = false;
868
869 MemoryImageSource softCursorSource;
870 Image softCursor;
871
872 int cursorX = 0, cursorY = 0;
873 int cursorWidth, cursorHeight;
874 int origCursorWidth, origCursorHeight;
875 int hotX, hotY;
876 int origHotX, origHotY;
877
878 //
879 // Handle cursor shape update (XCursor and RichCursor encodings).
880 //
881
882 synchronized void
883 handleCursorShapeUpdate(int encodingType,
884 int xhot, int yhot, int width, int height)
885 throws IOException {
886
887 softCursorFree();
888
889 if (width * height == 0)
890 return;
891
892 // Ignore cursor shape data if requested by user.
893 if (viewer.options.ignoreCursorUpdates) {
894 int bytesPerRow = (width + 7) / 8;
895 int bytesMaskData = bytesPerRow * height;
896
897 if (encodingType == rfb.EncodingXCursor) {
898 rfb.skipBytes(6 + bytesMaskData * 2);
899 } else {
900 // rfb.EncodingRichCursor
901 rfb.skipBytes(width * height + bytesMaskData);
902 }
903 return;
904 }
905
906 // Decode cursor pixel data.
907 softCursorSource = decodeCursorShape(encodingType, width, height);
908
909 // Set original (non-scaled) cursor dimensions.
910 origCursorWidth = width;
911 origCursorHeight = height;
912 origHotX = xhot;
913 origHotY = yhot;
914
915 // Create off-screen cursor image.
916 createSoftCursor();
917
918 // Show the cursor.
919 showSoftCursor = true;
920 repaint(viewer.deferCursorUpdates,
921 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
922 }
923
924 //
925 // decodeCursorShape(). Decode cursor pixel data and return
926 // corresponding MemoryImageSource instance.
927 //
928
929 synchronized MemoryImageSource
930 decodeCursorShape(int encodingType, int width, int height)
931 throws IOException {
932
933 int bytesPerRow = (width + 7) / 8;
934 int bytesMaskData = bytesPerRow * height;
935
936 int[] softCursorPixels = new int[width * height];
937
938 if (encodingType == rfb.EncodingXCursor) {
939
940 // Read foreground and background colors of the cursor.
941 byte[] rgb = new byte[6];
942 rfb.readFully(rgb);
943 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
944 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
945 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
946 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
947
948 // Read pixel and mask data.
949 byte[] pixBuf = new byte[bytesMaskData];
950 rfb.readFully(pixBuf);
951 byte[] maskBuf = new byte[bytesMaskData];
952 rfb.readFully(maskBuf);
953
954 // Decode pixel data into softCursorPixels[].
955 byte pixByte, maskByte;
956 int x, y, n, result;
957 int i = 0;
958 for (y = 0; y < height; y++) {
959 for (x = 0; x < width / 8; x++) {
960 pixByte = pixBuf[y * bytesPerRow + x];
961 maskByte = maskBuf[y * bytesPerRow + x];
962 for (n = 7; n >= 0; n--) {
963 if ((maskByte >> n & 1) != 0) {
964 result = colors[pixByte >> n & 1];
965 } else {
966 result = 0; // Transparent pixel
967 }
968 softCursorPixels[i++] = result;
969 }
970 }
971 for (n = 7; n >= 8 - width % 8; n--) {
972 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
973 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
974 } else {
975 result = 0; // Transparent pixel
976 }
977 softCursorPixels[i++] = result;
978 }
979 }
980
981 } else {
982 // encodingType == rfb.EncodingRichCursor
983
984 // Read pixel and mask data.
985 byte[] pixBuf = new byte[width * height * bytesPixel];
986 rfb.readFully(pixBuf);
987 byte[] maskBuf = new byte[bytesMaskData];
988 rfb.readFully(maskBuf);
989
990 // Decode pixel data into softCursorPixels[].
991 byte pixByte, maskByte;
992 int x, y, n, result;
993 int i = 0;
994 for (y = 0; y < height; y++) {
995 for (x = 0; x < width / 8; x++) {
996 maskByte = maskBuf[y * bytesPerRow + x];
997 for (n = 7; n >= 0; n--) {
998 if ((maskByte >> n & 1) != 0) {
999 if (bytesPixel == 1) {
1000 result = cm8.getRGB(pixBuf[i]);
1001 } else {
1002 result = 0xFF000000 |
1003 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1004 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1005 (pixBuf[i * 4] & 0xFF);
1006 }
1007 } else {
1008 result = 0; // Transparent pixel
1009 }
1010 softCursorPixels[i++] = result;
1011 }
1012 }
1013 for (n = 7; n >= 8 - width % 8; n--) {
1014 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1015 if (bytesPixel == 1) {
1016 result = cm8.getRGB(pixBuf[i]);
1017 } else {
1018 result = 0xFF000000 |
1019 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1020 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1021 (pixBuf[i * 4] & 0xFF);
1022 }
1023 } else {
1024 result = 0; // Transparent pixel
1025 }
1026 softCursorPixels[i++] = result;
1027 }
1028 }
1029
1030 }
1031
1032 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1033 }
1034
1035 //
1036 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1037 // Uses softCursorSource as a source for new cursor image.
1038 //
1039
1040 synchronized void
1041 createSoftCursor() {
1042
1043 if (softCursorSource == null)
1044 return;
1045
1046 int scaleCursor = viewer.options.scaleCursor;
1047 if (scaleCursor == 0 || !inputEnabled)
1048 scaleCursor = 100;
1049
1050 // Save original cursor coordinates.
1051 int x = cursorX - hotX;
1052 int y = cursorY - hotY;
1053 int w = cursorWidth;
1054 int h = cursorHeight;
1055
1056 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1057 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1058 hotX = (origHotX * scaleCursor + 50) / 100;
1059 hotY = (origHotY * scaleCursor + 50) / 100;
1060 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1061
1062 if (scaleCursor != 100) {
1063 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1064 Image.SCALE_SMOOTH);
1065 }
1066
1067 if (showSoftCursor) {
1068 // Compute screen area to update.
1069 x = Math.min(x, cursorX - hotX);
1070 y = Math.min(y, cursorY - hotY);
1071 w = Math.max(w, cursorWidth);
1072 h = Math.max(h, cursorHeight);
1073
1074 repaint(viewer.deferCursorUpdates, x, y, w, h);
1075 }
1076 }
1077
1078 //
1079 // softCursorMove(). Moves soft cursor into a particular location.
1080 //
1081
1082 synchronized void softCursorMove(int x, int y) {
1083 int oldX = cursorX;
1084 int oldY = cursorY;
1085 cursorX = x;
1086 cursorY = y;
1087 if (showSoftCursor) {
1088 repaint(viewer.deferCursorUpdates,
1089 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1090 repaint(viewer.deferCursorUpdates,
1091 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1092 }
1093 }
1094
1095 //
1096 // softCursorFree(). Remove soft cursor, dispose resources.
1097 //
1098
1099 synchronized void softCursorFree() {
1100 if (showSoftCursor) {
1101 showSoftCursor = false;
1102 softCursor = null;
1103 softCursorSource = null;
1104
1105 repaint(viewer.deferCursorUpdates,
1106 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1107 }
1108 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001109
1110 //////////////////////////////////////////////////////////////////
1111 //
1112 // Support for selecting a rectangular video area.
1113 //
1114
1115 /** This flag is false in normal operation, and true in the selection mode. */
1116 private boolean inSelectionMode;
1117
1118 /** The point where the selection was started. */
1119 private Point selectionStart;
1120
1121 /** The second point of the selection. */
1122 private Point selectionEnd;
1123
1124 /**
1125 * We change cursor when enabling the selection mode. In this variable, we
1126 * save the original cursor so we can restore it on returning to the normal
1127 * mode.
1128 */
1129 private Cursor savedCursor;
1130
1131 /**
1132 * Initialize selection-related varibles.
1133 */
1134 private synchronized void resetSelection() {
1135 inSelectionMode = false;
1136 selectionStart = new Point(0, 0);
1137 selectionEnd = new Point(0, 0);
1138
1139 savedCursor = getCursor();
1140 }
1141
1142 /**
1143 * Check current state of the selection mode.
1144 * @return true in the selection mode, false otherwise.
1145 */
1146 public boolean isInSelectionMode() {
1147 return inSelectionMode;
1148 }
1149
1150 /**
1151 * Get current selection.
1152 * @param useScreenCoords use screen coordinates if true, or framebuffer
1153 * coordinates if false. This makes difference when scaling factor is not 100.
1154 * @return The selection as a {@link Rectangle}.
1155 */
1156 private synchronized Rectangle getSelection(boolean useScreenCoords) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001157 int x0 = selectionStart.x;
1158 int x1 = selectionEnd.x;
1159 int y0 = selectionStart.y;
1160 int y1 = selectionEnd.y;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001161 // Make x and y point to the upper left corner of the selection.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001162 if (x1 < x0) {
1163 int t = x0; x0 = x1; x1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001164 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001165 if (y1 < y0) {
1166 int t = y0; y0 = y1; y1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001167 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001168 // Include the borders in the selection (unless it's empty).
1169 if (x0 != x1 && y0 != y1) {
1170 x1 += 1;
1171 y1 += 1;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001172 }
1173 // Translate from screen coordinates to framebuffer coordinates.
1174 if (rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001175 x0 = (x0 * 100 + scalingFactor/2) / scalingFactor;
1176 y0 = (y0 * 100 + scalingFactor/2) / scalingFactor;
1177 x1 = (x1 * 100 + scalingFactor/2) / scalingFactor;
1178 y1 = (y1 * 100 + scalingFactor/2) / scalingFactor;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001179 }
Constantin Kaplinsky10da44d2008-09-03 03:12:18 +00001180 // Clip the selection to framebuffer.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001181 if (x0 < 0)
1182 x0 = 0;
1183 if (y0 < 0)
1184 y0 = 0;
1185 if (x1 > rfb.framebufferWidth)
1186 x1 = rfb.framebufferWidth;
1187 if (y1 > rfb.framebufferHeight)
1188 y1 = rfb.framebufferHeight;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001189 // Make width a multiple of 16.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001190 int widthBlocks = (x1 - x0 + 8) / 16;
1191 if (selectionStart.x <= selectionEnd.x) {
1192 x1 = x0 + widthBlocks * 16;
1193 if (x1 > rfb.framebufferWidth) {
1194 x1 -= 16;
1195 }
1196 } else {
1197 x0 = x1 - widthBlocks * 16;
1198 if (x0 < 0) {
1199 x0 += 16;
1200 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001201 }
1202 // Make height a multiple of 8.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001203 int heightBlocks = (y1 - y0 + 4) / 8;
1204 if (selectionStart.y <= selectionEnd.y) {
1205 y1 = y0 + heightBlocks * 8;
1206 if (y1 > rfb.framebufferHeight) {
1207 y1 -= 8;
1208 }
1209 } else {
1210 y0 = y1 - heightBlocks * 8;
1211 if (y0 < 0) {
1212 y0 += 8;
1213 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001214 }
1215 // Translate the selection back to screen coordinates if requested.
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001216 if (useScreenCoords && rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001217 x0 = (x0 * scalingFactor + 50) / 100;
1218 y0 = (y0 * scalingFactor + 50) / 100;
1219 x1 = (x1 * scalingFactor + 50) / 100;
1220 y1 = (y1 * scalingFactor + 50) / 100;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001221 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001222 // Construct and return the result.
1223 return new Rectangle(x0, y0, x1 - x0, y1 - y0);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001224 }
1225
1226 /**
1227 * Enable or disable the selection mode.
1228 * @param enable enables the selection mode if true, disables if fasle.
1229 */
1230 public synchronized void enableSelection(boolean enable) {
1231 if (enable && !inSelectionMode) {
1232 // Enter the selection mode.
1233 inSelectionMode = true;
1234 savedCursor = getCursor();
1235 setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
1236 repaint();
1237 } else if (!enable && inSelectionMode) {
1238 // Leave the selection mode.
1239 inSelectionMode = false;
1240 setCursor(savedCursor);
1241 repaint();
1242 }
1243 }
1244
1245 /**
1246 * Process mouse events in the selection mode.
enikeyc41ba1d2008-12-19 09:07:22 +00001247 *
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001248 * @param evt mouse event that was originally passed to
1249 * {@link MouseListener} or {@link MouseMotionListener}.
1250 */
1251 private synchronized void handleSelectionMouseEvent(MouseEvent evt) {
1252 int id = evt.getID();
1253 boolean button1 = (evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0;
1254
1255 if (id == MouseEvent.MOUSE_PRESSED && button1) {
1256 selectionStart = selectionEnd = evt.getPoint();
1257 repaint();
1258 }
1259 if (id == MouseEvent.MOUSE_DRAGGED && button1) {
1260 selectionEnd = evt.getPoint();
1261 repaint();
1262 }
1263 if (id == MouseEvent.MOUSE_RELEASED && button1) {
1264 try {
1265 rfb.trySendVideoSelection(getSelection(false));
1266 } catch (IOException e) {
1267 e.printStackTrace();
1268 }
1269 }
1270 }
1271
enikey418611f2008-12-19 04:37:09 +00001272 //
1273 // Override RecordInterface methods
1274 //
1275
1276 public boolean isRecordFromBeginning() {
1277 return rfb.recordFromBeginning;
1278 }
1279
1280 public boolean canWrite() {
1281 // We can record if rec is not null
1282 return rfb.rec != null;
1283 }
1284
1285 public void write(byte b[]) throws IOException {
1286 rfb.rec.write(b);
1287 }
1288
1289 public void write(byte b[], int off, int len) throws IOException {
1290 rfb.rec.write(b, off, len);
1291 }
1292
1293 public void writeByte(byte b) throws IOException {
1294 rfb.rec.writeByte(b);
1295 }
1296
1297 public void writeByte(int i) throws IOException {
1298 rfb.rec.writeByte(i);
1299 }
1300
1301 public void writeIntBE(int v) throws IOException {
1302 rfb.rec.writeIntBE(v);
1303 }
1304
1305 public void recordCompactLen(int len) throws IOException {
1306 rfb.recordCompactLen(len);
1307 }
1308
1309 public void recordCompressedData(byte[] data) throws IOException {
1310 rfb.recordCompressedData(data);
1311 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001312}