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