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