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