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