blob: 05d887281337cbde115c449008da929e581797a1 [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
Adam Tkacf53e62a2009-03-13 13:20:26 +000024package com.tigervnc.vncviewer;
Constantin Kaplinsky90d8a502008-04-14 09:45:50 +000025
Adam Tkacf53e62a2009-03-13 13:20:26 +000026import com.tigervnc.decoder.CoRREDecoder;
27import com.tigervnc.decoder.CopyRectDecoder;
28import com.tigervnc.decoder.HextileDecoder;
29import com.tigervnc.decoder.RREDecoder;
30import com.tigervnc.decoder.RawDecoder;
31import com.tigervnc.decoder.TightDecoder;
32import com.tigervnc.decoder.ZRLEDecoder;
33import com.tigervnc.decoder.ZlibDecoder;
34import com.tigervnc.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
enikey866b88c2009-01-20 04:50:55 +000048 implements KeyListener, MouseListener, MouseMotionListener, Repaintable, Runnable {
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
enikey1abaff92009-01-19 11:30:27 +000098 // Members for limiting sending mouse events to server
99 long lastMouseEventSendTime = System.currentTimeMillis();
100 long mouseMaxFreq = 20;
101
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000102 //
103 // The constructors.
104 //
105
106 public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
107 throws IOException {
108
109 viewer = v;
110 maxWidth = maxWidth_;
111 maxHeight = maxHeight_;
112
113 rfb = viewer.rfb;
114 scalingFactor = viewer.options.scalingFactor;
115
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000116 cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
117 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
118
enikeyc41ba1d2008-12-19 09:07:22 +0000119 //
120 // Create decoders
121 //
122
123 // Input stream for decoders
124 RfbInputStream rfbis = new RfbInputStream(rfb);
enikey1622a3c2008-12-24 03:58:29 +0000125 // Create output stream for session recording
enikey54c1c7d2008-12-25 11:48:13 +0000126 RecordOutputStream ros = new RecordOutputStream(rfb);
enikeyc41ba1d2008-12-19 09:07:22 +0000127
128 rawDecoder = new RawDecoder(memGraphics, rfbis);
129 rreDecoder = new RREDecoder(memGraphics, rfbis);
130 correDecoder = new CoRREDecoder(memGraphics, rfbis);
131 hextileDecoder = new HextileDecoder(memGraphics, rfbis);
132 tightDecoder = new TightDecoder(memGraphics, rfbis);
enikey4582bab2008-12-19 09:19:59 +0000133 zlibDecoder = new ZlibDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000134 zrleDecoder = new ZRLEDecoder(memGraphics, rfbis);
enikey5805ba62008-12-25 08:10:43 +0000135 copyRectDecoder = new CopyRectDecoder(memGraphics, rfbis);
enikeyc41ba1d2008-12-19 09:07:22 +0000136
137 //
138 // Set data for decoders that needs extra parameters
139 //
140
141 hextileDecoder.setRepainableControl(this);
142 tightDecoder.setRepainableControl(this);
143
144 //
145 // Create array that contains our decoders
146 //
147
enikey5805ba62008-12-25 08:10:43 +0000148 decoders = new RawDecoder[8];
enikeyc41ba1d2008-12-19 09:07:22 +0000149 decoders[0] = rawDecoder;
150 decoders[1] = rreDecoder;
151 decoders[2] = correDecoder;
152 decoders[3] = hextileDecoder;
153 decoders[4] = zlibDecoder;
154 decoders[5] = tightDecoder;
155 decoders[6] = zrleDecoder;
enikey5805ba62008-12-25 08:10:43 +0000156 decoders[7] = copyRectDecoder;
enikeyc41ba1d2008-12-19 09:07:22 +0000157
158 //
159 // Set session recorder for decoders
160 //
161
162 for (int i = 0; i < decoders.length; i++) {
enikey1622a3c2008-12-24 03:58:29 +0000163 decoders[i].setDataOutputStream(ros);
enikeyc41ba1d2008-12-19 09:07:22 +0000164 }
enikeyc92c1d12008-12-19 09:58:31 +0000165
enikey2f7f46e2008-12-19 09:32:35 +0000166 setPixelFormat();
enikey2f7f46e2008-12-19 09:32:35 +0000167 resetSelection();
168
169 inputEnabled = false;
170 if (!viewer.options.viewOnly)
171 enableInput(true);
enikeyc41ba1d2008-12-19 09:07:22 +0000172
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000173 // Enable mouse and keyboard event listeners.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000174 addKeyListener(this);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000175 addMouseListener(this);
176 addMouseMotionListener(this);
enikey866b88c2009-01-20 04:50:55 +0000177
178 // Create thread, that will send mouse movement events
179 // to VNC server.
180 Thread mouseThread = new Thread(this);
181 mouseThread.start();
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000182 }
183
184 public VncCanvas(VncViewer v) throws IOException {
185 this(v, 0, 0);
186 }
187
188 //
189 // Callback methods to determine geometry of our Component.
190 //
191
192 public Dimension getPreferredSize() {
193 return new Dimension(scaledWidth, scaledHeight);
194 }
195
196 public Dimension getMinimumSize() {
197 return new Dimension(scaledWidth, scaledHeight);
198 }
199
200 public Dimension getMaximumSize() {
201 return new Dimension(scaledWidth, scaledHeight);
202 }
203
204 //
205 // All painting is performed here.
206 //
207
208 public void update(Graphics g) {
209 paint(g);
210 }
211
212 public void paint(Graphics g) {
213 synchronized(memImage) {
214 if (rfb.framebufferWidth == scaledWidth) {
215 g.drawImage(memImage, 0, 0, null);
216 } else {
217 paintScaledFrameBuffer(g);
218 }
219 }
220 if (showSoftCursor) {
221 int x0 = cursorX - hotX, y0 = cursorY - hotY;
222 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
223 if (r.intersects(g.getClipBounds())) {
224 g.drawImage(softCursor, x0, y0, null);
225 }
226 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000227 if (isInSelectionMode()) {
228 Rectangle r = getSelection(true);
229 if (r.width > 0 && r.height > 0) {
230 // Don't forget to correct the coordinates for the right and bottom
231 // borders, so that the borders are the part of the selection.
232 r.width -= 1;
233 r.height -= 1;
234 g.setXORMode(Color.yellow);
235 g.drawRect(r.x, r.y, r.width, r.height);
236 }
237 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000238 }
239
240 public void paintScaledFrameBuffer(Graphics g) {
241 g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
242 }
243
244 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000245 // Start/stop receiving mouse events. Keyboard events are received
246 // even in view-only mode, because we want to map the 'r' key to the
247 // screen refreshing function.
248 //
249
250 public synchronized void enableInput(boolean enable) {
251 if (enable && !inputEnabled) {
252 inputEnabled = true;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000253 if (viewer.showControls) {
254 viewer.buttonPanel.enableRemoteAccessControls(true);
255 }
256 createSoftCursor(); // scaled cursor
257 } else if (!enable && inputEnabled) {
258 inputEnabled = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000259 if (viewer.showControls) {
260 viewer.buttonPanel.enableRemoteAccessControls(false);
261 }
262 createSoftCursor(); // non-scaled cursor
263 }
264 }
265
266 public void setPixelFormat() throws IOException {
267 if (viewer.options.eightBitColors) {
268 rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
269 bytesPixel = 1;
270 } else {
271 rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
272 bytesPixel = 4;
273 }
274 updateFramebufferSize();
275 }
276
enikey45cfaa52008-12-03 06:52:09 +0000277 void setScalingFactor(int sf) {
278 scalingFactor = sf;
279 updateFramebufferSize();
280 invalidate();
281 }
282
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000283 void updateFramebufferSize() {
284
285 // Useful shortcuts.
286 int fbWidth = rfb.framebufferWidth;
287 int fbHeight = rfb.framebufferHeight;
288
enikey87647e92008-12-04 08:42:34 +0000289 // FIXME: This part of code must be in VncViewer i think
enikey73683202008-12-03 09:17:25 +0000290 if (viewer.options.autoScale) {
enikey87647e92008-12-04 08:42:34 +0000291 if (viewer.inAnApplet) {
292 maxWidth = viewer.getWidth();
293 maxHeight = viewer.getHeight();
294 } else {
295 if (viewer.vncFrame != null) {
296 if (isFirstSizeAutoUpdate) {
297 isFirstSizeAutoUpdate = false;
298 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
299 maxWidth = (int)screenSize.getWidth() - 100;
enikeyc41ba1d2008-12-19 09:07:22 +0000300 maxHeight = (int)screenSize.getHeight() - 100;
enikey87647e92008-12-04 08:42:34 +0000301 viewer.vncFrame.setSize(maxWidth, maxHeight);
302 } else {
303 viewer.desktopScrollPane.doLayout();
304 maxWidth = viewer.desktopScrollPane.getWidth();
305 maxHeight = viewer.desktopScrollPane.getHeight();
306 }
307 } else {
308 maxWidth = fbWidth;
309 maxHeight = fbHeight;
310 }
enikey73683202008-12-03 09:17:25 +0000311 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000312 int f1 = maxWidth * 100 / fbWidth;
313 int f2 = maxHeight * 100 / fbHeight;
314 scalingFactor = Math.min(f1, f2);
315 if (scalingFactor > 100)
316 scalingFactor = 100;
317 System.out.println("Scaling desktop at " + scalingFactor + "%");
318 }
319
320 // Update scaled framebuffer geometry.
321 scaledWidth = (fbWidth * scalingFactor + 50) / 100;
322 scaledHeight = (fbHeight * scalingFactor + 50) / 100;
323
324 // Create new off-screen image either if it does not exist, or if
325 // its geometry should be changed. It's not necessary to replace
326 // existing image if only pixel format should be changed.
327 if (memImage == null) {
328 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
329 memGraphics = memImage.getGraphics();
330 } else if (memImage.getWidth(null) != fbWidth ||
331 memImage.getHeight(null) != fbHeight) {
332 synchronized(memImage) {
333 memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
334 memGraphics = memImage.getGraphics();
335 }
336 }
337
enikey4582bab2008-12-19 09:19:59 +0000338 //
339 // Update decoders
340 //
341
342 //
343 // FIXME: Why decoders can be null here?
344 //
345
346 if (decoders != null) {
347 for (int i = 0; i < decoders.length; i++) {
348 //
349 // Set changes to every decoder that we can use
350 //
351
352 decoders[i].setBPP(bytesPixel);
353 decoders[i].setFrameBufferSize(fbWidth, fbHeight);
354 decoders[i].setGraphics(memGraphics);
355
356 //
357 // Update decoder
358 //
359
360 decoders[i].update();
361 }
362 }
363
enikey87647e92008-12-04 08:42:34 +0000364 // FIXME: This part of code must be in VncViewer i think
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000365 // Update the size of desktop containers.
366 if (viewer.inSeparateFrame) {
enikey87647e92008-12-04 08:42:34 +0000367 if (viewer.desktopScrollPane != null) {
368 if (!viewer.options.autoScale) {
369 resizeDesktopFrame();
370 } else {
371 setSize(scaledWidth, scaledHeight);
372 viewer.desktopScrollPane.setSize(maxWidth + 200,
373 maxHeight + 200);
374 }
375 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000376 } else {
377 setSize(scaledWidth, scaledHeight);
378 }
379 viewer.moveFocusToDesktop();
380 }
381
382 void resizeDesktopFrame() {
383 setSize(scaledWidth, scaledHeight);
384
385 // FIXME: Find a better way to determine correct size of a
386 // ScrollPane. -- const
387 Insets insets = viewer.desktopScrollPane.getInsets();
388 viewer.desktopScrollPane.setSize(scaledWidth +
389 2 * Math.min(insets.left, insets.right),
390 scaledHeight +
391 2 * Math.min(insets.top, insets.bottom));
392
393 viewer.vncFrame.pack();
394
395 // Try to limit the frame size to the screen size.
396
397 Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
398 Dimension frameSize = viewer.vncFrame.getSize();
399 Dimension newSize = frameSize;
400
401 // Reduce Screen Size by 30 pixels in each direction;
402 // This is a (poor) attempt to account for
403 // 1) Menu bar on Macintosh (should really also account for
404 // Dock on OSX). Usually 22px on top of screen.
405 // 2) Taxkbar on Windows (usually about 28 px on bottom)
406 // 3) Other obstructions.
407
408 screenSize.height -= 30;
409 screenSize.width -= 30;
410
411 boolean needToResizeFrame = false;
412 if (frameSize.height > screenSize.height) {
413 newSize.height = screenSize.height;
414 needToResizeFrame = true;
415 }
416 if (frameSize.width > screenSize.width) {
417 newSize.width = screenSize.width;
418 needToResizeFrame = true;
419 }
420 if (needToResizeFrame) {
421 viewer.vncFrame.setSize(newSize);
422 }
423
424 viewer.desktopScrollPane.doLayout();
425 }
426
427 //
428 // processNormalProtocol() - executed by the rfbThread to deal with the
429 // RFB socket.
430 //
431
432 public void processNormalProtocol() throws Exception {
433
434 // Start/stop session recording if necessary.
435 viewer.checkRecordingStatus();
436
437 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
438 rfb.framebufferHeight, false);
439
440 if (viewer.options.continuousUpdates) {
441 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
442 rfb.framebufferHeight);
443 }
444
445 resetStats();
446 boolean statsRestarted = false;
447
448 //
449 // main dispatch loop
450 //
451
452 while (true) {
453
454 // Read message type from the server.
455 int msgType = rfb.readServerMessageType();
456
457 // Process the message depending on its type.
458 switch (msgType) {
459 case RfbProto.FramebufferUpdate:
460
461 if (statNumUpdates == viewer.debugStatsExcludeUpdates &&
462 !statsRestarted) {
463 resetStats();
464 statsRestarted = true;
465 } else if (statNumUpdates == viewer.debugStatsMeasureUpdates &&
466 statsRestarted) {
467 viewer.disconnect();
468 }
469
470 rfb.readFramebufferUpdate();
471 statNumUpdates++;
472
473 boolean cursorPosReceived = false;
474
475 for (int i = 0; i < rfb.updateNRects; i++) {
476
477 rfb.readFramebufferUpdateRectHdr();
478 statNumTotalRects++;
479 int rx = rfb.updateRectX, ry = rfb.updateRectY;
480 int rw = rfb.updateRectW, rh = rfb.updateRectH;
481
482 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
483 break;
484
485 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
486 rfb.setFramebufferSize(rw, rh);
487 updateFramebufferSize();
488 break;
489 }
490
491 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
492 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
493 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
494 continue;
495 }
496
497 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
498 softCursorMove(rx, ry);
499 cursorPosReceived = true;
500 continue;
501 }
502
503 long numBytesReadBefore = rfb.getNumBytesRead();
504
505 rfb.startTiming();
506
507 switch (rfb.updateRectEncoding) {
508 case RfbProto.EncodingRaw:
509 statNumRectsRaw++;
510 handleRawRect(rx, ry, rw, rh);
511 break;
512 case RfbProto.EncodingCopyRect:
513 statNumRectsCopy++;
514 handleCopyRect(rx, ry, rw, rh);
515 break;
516 case RfbProto.EncodingRRE:
517 handleRRERect(rx, ry, rw, rh);
518 break;
519 case RfbProto.EncodingCoRRE:
520 handleCoRRERect(rx, ry, rw, rh);
521 break;
522 case RfbProto.EncodingHextile:
523 statNumRectsHextile++;
524 handleHextileRect(rx, ry, rw, rh);
525 break;
526 case RfbProto.EncodingZRLE:
527 statNumRectsZRLE++;
528 handleZRLERect(rx, ry, rw, rh);
529 break;
530 case RfbProto.EncodingZlib:
531 handleZlibRect(rx, ry, rw, rh);
532 break;
533 case RfbProto.EncodingTight:
enikey2f7f46e2008-12-19 09:32:35 +0000534 if (tightDecoder != null) {
enikey479b18c2008-12-19 09:39:40 +0000535 statNumRectsTightJPEG = tightDecoder.getNumJPEGRects();
enikey1214ab12008-12-24 09:01:19 +0000536 //statNumRectsTight = tightDecoder.getNumTightRects();
enikey2f7f46e2008-12-19 09:32:35 +0000537 }
enikey1214ab12008-12-24 09:01:19 +0000538 statNumRectsTight++;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000539 handleTightRect(rx, ry, rw, rh);
540 break;
541 default:
542 throw new Exception("Unknown RFB rectangle encoding " +
543 rfb.updateRectEncoding);
544 }
545
546 rfb.stopTiming();
547
548 statNumPixelRects++;
549 statNumBytesDecoded += rw * rh * bytesPixel;
550 statNumBytesEncoded +=
551 (int)(rfb.getNumBytesRead() - numBytesReadBefore);
552 }
553
554 boolean fullUpdateNeeded = false;
555
556 // Start/stop session recording if necessary. Request full
557 // update if a new session file was opened.
558 if (viewer.checkRecordingStatus())
559 fullUpdateNeeded = true;
560
561 // Defer framebuffer update request if necessary. But wake up
562 // immediately on keyboard or mouse event. Also, don't sleep
563 // if there is some data to receive, or if the last update
564 // included a PointerPos message.
565 if (viewer.deferUpdateRequests > 0 &&
566 rfb.available() == 0 && !cursorPosReceived) {
567 synchronized(rfb) {
568 try {
569 rfb.wait(viewer.deferUpdateRequests);
570 } catch (InterruptedException e) {
571 }
572 }
573 }
574
575 viewer.autoSelectEncodings();
576
577 // Before requesting framebuffer update, check if the pixel
578 // format should be changed.
579 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
580 // Pixel format should be changed.
581 if (!rfb.continuousUpdatesAreActive()) {
582 // Continuous updates are not used. In this case, we just
583 // set new pixel format and request full update.
584 setPixelFormat();
585 fullUpdateNeeded = true;
586 } else {
587 // Otherwise, disable continuous updates first. Pixel
588 // format will be set later when we are sure that there
589 // will be no unsolicited framebuffer updates.
590 rfb.tryDisableContinuousUpdates();
591 break; // skip the code below
592 }
593 }
594
595 // Enable/disable continuous updates to reflect the GUI setting.
596 boolean enable = viewer.options.continuousUpdates;
597 if (enable != rfb.continuousUpdatesAreActive()) {
598 if (enable) {
599 rfb.tryEnableContinuousUpdates(0, 0, rfb.framebufferWidth,
600 rfb.framebufferHeight);
601 } else {
602 rfb.tryDisableContinuousUpdates();
603 }
604 }
605
606 // Finally, request framebuffer update if needed.
607 if (fullUpdateNeeded) {
608 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
609 rfb.framebufferHeight, false);
610 } else if (!rfb.continuousUpdatesAreActive()) {
611 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
612 rfb.framebufferHeight, true);
613 }
614
615 break;
616
617 case RfbProto.SetColourMapEntries:
618 throw new Exception("Can't handle SetColourMapEntries message");
619
620 case RfbProto.Bell:
621 Toolkit.getDefaultToolkit().beep();
622 break;
623
624 case RfbProto.ServerCutText:
625 String s = rfb.readServerCutText();
626 viewer.clipboard.setCutText(s);
627 break;
628
629 case RfbProto.EndOfContinuousUpdates:
630 if (rfb.continuousUpdatesAreActive()) {
631 rfb.endOfContinuousUpdates();
632
633 // Change pixel format if such change was pending. Note that we
634 // could not change pixel format while continuous updates were
635 // in effect.
636 boolean incremental = true;
637 if (viewer.options.eightBitColors != (bytesPixel == 1)) {
638 setPixelFormat();
639 incremental = false;
640 }
641 // From this point, we ask for updates explicitly.
642 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
643 rfb.framebufferHeight,
644 incremental);
645 }
646 break;
647
648 default:
649 throw new Exception("Unknown RFB message type " + msgType);
650 }
651 }
652 }
653
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000654 //
655 // Handle a raw rectangle. The second form with paint==false is used
656 // by the Hextile decoder for raw-encoded tiles.
657 //
658
enikey6c72ce12008-12-19 09:47:14 +0000659 void handleRawRect(int x, int y, int w, int h) throws IOException, Exception {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000660 handleRawRect(x, y, w, h, true);
661 }
662
663 void handleRawRect(int x, int y, int w, int h, boolean paint)
enikey6c72ce12008-12-19 09:47:14 +0000664 throws IOException , Exception{
665 rawDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000666 if (paint)
667 scheduleRepaint(x, y, w, h);
668 }
669
670 //
671 // Handle a CopyRect rectangle.
672 //
673
674 void handleCopyRect(int x, int y, int w, int h) throws IOException {
enikey5805ba62008-12-25 08:10:43 +0000675 copyRectDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000676 scheduleRepaint(x, y, w, h);
677 }
678
679 //
680 // Handle an RRE-encoded rectangle.
681 //
682
683 void handleRRERect(int x, int y, int w, int h) throws IOException {
enikey6c72ce12008-12-19 09:47:14 +0000684 rreDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000685 scheduleRepaint(x, y, w, h);
686 }
687
688 //
689 // Handle a CoRRE-encoded rectangle.
690 //
691
692 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
enikey6c72ce12008-12-19 09:47:14 +0000693 correDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000694 scheduleRepaint(x, y, w, h);
695 }
696
697 //
698 // Handle a Hextile-encoded rectangle.
699 //
700
enikey6c72ce12008-12-19 09:47:14 +0000701 void handleHextileRect(int x, int y, int w, int h) throws IOException,
702 Exception {
703 hextileDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000704 }
705
706 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000707 // Handle a ZRLE-encoded rectangle.
708 //
709 // FIXME: Currently, session recording is not fully supported for ZRLE.
710 //
711
712 void handleZRLERect(int x, int y, int w, int h) throws Exception {
enikeyc92c1d12008-12-19 09:58:31 +0000713 zrleDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000714 scheduleRepaint(x, y, w, h);
715 }
716
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000717 //
718 // Handle a Zlib-encoded rectangle.
719 //
720
721 void handleZlibRect(int x, int y, int w, int h) throws Exception {
enikeyc92c1d12008-12-19 09:58:31 +0000722 zlibDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000723 scheduleRepaint(x, y, w, h);
724 }
725
726 //
727 // Handle a Tight-encoded rectangle.
728 //
729
730 void handleTightRect(int x, int y, int w, int h) throws Exception {
enikey2f7f46e2008-12-19 09:32:35 +0000731 tightDecoder.handleRect(x, y, w, h);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000732 scheduleRepaint(x, y, w, h);
733 }
734
735 //
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000736 // Tell JVM to repaint specified desktop area.
737 //
738
enikey0dbc1532008-12-19 08:51:47 +0000739 public void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000740 // Request repaint, deferred if necessary.
741 if (rfb.framebufferWidth == scaledWidth) {
742 repaint(viewer.deferScreenUpdates, x, y, w, h);
743 } else {
744 int sx = x * scalingFactor / 100;
745 int sy = y * scalingFactor / 100;
746 int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
747 int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
748 repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
749 }
750 }
751
752 //
753 // Handle events.
754 //
755
756 public void keyPressed(KeyEvent evt) {
757 processLocalKeyEvent(evt);
758 }
759 public void keyReleased(KeyEvent evt) {
760 processLocalKeyEvent(evt);
761 }
762 public void keyTyped(KeyEvent evt) {
763 evt.consume();
764 }
765
766 public void mousePressed(MouseEvent evt) {
767 processLocalMouseEvent(evt, false);
768 }
769 public void mouseReleased(MouseEvent evt) {
770 processLocalMouseEvent(evt, false);
771 }
772 public void mouseMoved(MouseEvent evt) {
773 processLocalMouseEvent(evt, true);
774 }
775 public void mouseDragged(MouseEvent evt) {
776 processLocalMouseEvent(evt, true);
777 }
778
enikey866b88c2009-01-20 04:50:55 +0000779 private synchronized void trySendPointerEvent() {
780 if ((needToSendMouseEvent) && (mouseEvent!=null)) {
781 sendMouseEvent(mouseEvent, false);
782 needToSendMouseEvent = false;
783 lastMouseEventSendTime = System.currentTimeMillis();
784 }
785 }
786
787 public void run() {
788 while (true) {
789 // Send mouse movement if we have it
790 trySendPointerEvent();
791 // Sleep for some time
792 try {
793 Thread.sleep(1000 / mouseMaxFreq);
794 } catch (InterruptedException ex) {
795 }
796 }
797 }
798
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000799 //
800 // Ignored events.
801 //
802
803 public void mouseClicked(MouseEvent evt) {}
804 public void mouseEntered(MouseEvent evt) {}
805 public void mouseExited(MouseEvent evt) {}
806
807 //
808 // Actual event processing.
809 //
810
811 private void processLocalKeyEvent(KeyEvent evt) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000812 if (viewer.rfb != null && rfb.inNormalProtocol) {
813 if (!inputEnabled) {
814 if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
815 evt.getID() == KeyEvent.KEY_PRESSED ) {
816 // Request screen update.
817 try {
818 rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
819 rfb.framebufferHeight, false);
820 } catch (IOException e) {
821 e.printStackTrace();
822 }
823 }
824 } else {
825 // Input enabled.
826 synchronized(rfb) {
827 try {
828 rfb.writeKeyEvent(evt);
829 } catch (Exception e) {
830 e.printStackTrace();
831 }
832 rfb.notify();
833 }
834 }
835 }
enikeyc41ba1d2008-12-19 09:07:22 +0000836 // Don't ever pass keyboard events to AWT for default processing.
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000837 // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
838 evt.consume();
839 }
840
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000841 private void processLocalMouseEvent(MouseEvent evt, boolean moved) {
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000842 if (viewer.rfb != null && rfb.inNormalProtocol) {
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000843 if (!inSelectionMode) {
844 if (inputEnabled) {
enikey866b88c2009-01-20 04:50:55 +0000845 // If mouse not moved, but it's click event then
846 // send it to server immideanlty.
847 // Else, it's mouse movement - we can send it in
848 // our thread later.
849 if (!moved) {
enikey1abaff92009-01-19 11:30:27 +0000850 sendMouseEvent(evt, moved);
enikey866b88c2009-01-20 04:50:55 +0000851 } else {
852 mouseEvent = evt;
853 needToSendMouseEvent = true;
enikey1abaff92009-01-19 11:30:27 +0000854 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000855 }
856 } else {
857 handleSelectionMouseEvent(evt);
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000858 }
859 }
860 }
861
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000862 private void sendMouseEvent(MouseEvent evt, boolean moved) {
863 if (moved) {
864 softCursorMove(evt.getX(), evt.getY());
865 }
866 if (rfb.framebufferWidth != scaledWidth) {
867 int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
868 int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
869 evt.translatePoint(sx - evt.getX(), sy - evt.getY());
870 }
871 synchronized(rfb) {
872 try {
873 rfb.writePointerEvent(evt);
874 } catch (Exception e) {
875 e.printStackTrace();
876 }
877 rfb.notify();
enikey1abaff92009-01-19 11:30:27 +0000878 lastMouseEventSendTime = System.currentTimeMillis();
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +0000879 }
880 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000881
882 //
883 // Reset update statistics.
884 //
885
886 void resetStats() {
887 statStartTime = System.currentTimeMillis();
888 statNumUpdates = 0;
889 statNumTotalRects = 0;
890 statNumPixelRects = 0;
891 statNumRectsTight = 0;
892 statNumRectsTightJPEG = 0;
893 statNumRectsZRLE = 0;
894 statNumRectsHextile = 0;
895 statNumRectsRaw = 0;
896 statNumRectsCopy = 0;
897 statNumBytesEncoded = 0;
898 statNumBytesDecoded = 0;
enikey1214ab12008-12-24 09:01:19 +0000899 if (tightDecoder != null) {
enikey2f7f46e2008-12-19 09:32:35 +0000900 tightDecoder.setNumJPEGRects(0);
enikey1214ab12008-12-24 09:01:19 +0000901 tightDecoder.setNumTightRects(0);
902 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000903 }
904
905 //////////////////////////////////////////////////////////////////
906 //
907 // Handle cursor shape updates (XCursor and RichCursor encodings).
908 //
909
910 boolean showSoftCursor = false;
911
912 MemoryImageSource softCursorSource;
913 Image softCursor;
enikey866b88c2009-01-20 04:50:55 +0000914 MouseEvent mouseEvent = null;
915 boolean needToSendMouseEvent = false;
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +0000916 int cursorX = 0, cursorY = 0;
917 int cursorWidth, cursorHeight;
918 int origCursorWidth, origCursorHeight;
919 int hotX, hotY;
920 int origHotX, origHotY;
921
922 //
923 // Handle cursor shape update (XCursor and RichCursor encodings).
924 //
925
926 synchronized void
927 handleCursorShapeUpdate(int encodingType,
928 int xhot, int yhot, int width, int height)
929 throws IOException {
930
931 softCursorFree();
932
933 if (width * height == 0)
934 return;
935
936 // Ignore cursor shape data if requested by user.
937 if (viewer.options.ignoreCursorUpdates) {
938 int bytesPerRow = (width + 7) / 8;
939 int bytesMaskData = bytesPerRow * height;
940
941 if (encodingType == rfb.EncodingXCursor) {
942 rfb.skipBytes(6 + bytesMaskData * 2);
943 } else {
944 // rfb.EncodingRichCursor
945 rfb.skipBytes(width * height + bytesMaskData);
946 }
947 return;
948 }
949
950 // Decode cursor pixel data.
951 softCursorSource = decodeCursorShape(encodingType, width, height);
952
953 // Set original (non-scaled) cursor dimensions.
954 origCursorWidth = width;
955 origCursorHeight = height;
956 origHotX = xhot;
957 origHotY = yhot;
958
959 // Create off-screen cursor image.
960 createSoftCursor();
961
962 // Show the cursor.
963 showSoftCursor = true;
964 repaint(viewer.deferCursorUpdates,
965 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
966 }
967
968 //
969 // decodeCursorShape(). Decode cursor pixel data and return
970 // corresponding MemoryImageSource instance.
971 //
972
973 synchronized MemoryImageSource
974 decodeCursorShape(int encodingType, int width, int height)
975 throws IOException {
976
977 int bytesPerRow = (width + 7) / 8;
978 int bytesMaskData = bytesPerRow * height;
979
980 int[] softCursorPixels = new int[width * height];
981
982 if (encodingType == rfb.EncodingXCursor) {
983
984 // Read foreground and background colors of the cursor.
985 byte[] rgb = new byte[6];
986 rfb.readFully(rgb);
987 int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
988 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
989 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
990 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
991
992 // Read pixel and mask data.
993 byte[] pixBuf = new byte[bytesMaskData];
994 rfb.readFully(pixBuf);
995 byte[] maskBuf = new byte[bytesMaskData];
996 rfb.readFully(maskBuf);
997
998 // Decode pixel data into softCursorPixels[].
999 byte pixByte, maskByte;
1000 int x, y, n, result;
1001 int i = 0;
1002 for (y = 0; y < height; y++) {
1003 for (x = 0; x < width / 8; x++) {
1004 pixByte = pixBuf[y * bytesPerRow + x];
1005 maskByte = maskBuf[y * bytesPerRow + x];
1006 for (n = 7; n >= 0; n--) {
1007 if ((maskByte >> n & 1) != 0) {
1008 result = colors[pixByte >> n & 1];
1009 } else {
1010 result = 0; // Transparent pixel
1011 }
1012 softCursorPixels[i++] = result;
1013 }
1014 }
1015 for (n = 7; n >= 8 - width % 8; n--) {
1016 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1017 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1018 } else {
1019 result = 0; // Transparent pixel
1020 }
1021 softCursorPixels[i++] = result;
1022 }
1023 }
1024
1025 } else {
1026 // encodingType == rfb.EncodingRichCursor
1027
1028 // Read pixel and mask data.
1029 byte[] pixBuf = new byte[width * height * bytesPixel];
1030 rfb.readFully(pixBuf);
1031 byte[] maskBuf = new byte[bytesMaskData];
1032 rfb.readFully(maskBuf);
1033
1034 // Decode pixel data into softCursorPixels[].
1035 byte pixByte, maskByte;
1036 int x, y, n, result;
1037 int i = 0;
1038 for (y = 0; y < height; y++) {
1039 for (x = 0; x < width / 8; x++) {
1040 maskByte = maskBuf[y * bytesPerRow + x];
1041 for (n = 7; n >= 0; n--) {
1042 if ((maskByte >> n & 1) != 0) {
1043 if (bytesPixel == 1) {
1044 result = cm8.getRGB(pixBuf[i]);
1045 } else {
1046 result = 0xFF000000 |
1047 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1048 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1049 (pixBuf[i * 4] & 0xFF);
1050 }
1051 } else {
1052 result = 0; // Transparent pixel
1053 }
1054 softCursorPixels[i++] = result;
1055 }
1056 }
1057 for (n = 7; n >= 8 - width % 8; n--) {
1058 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1059 if (bytesPixel == 1) {
1060 result = cm8.getRGB(pixBuf[i]);
1061 } else {
1062 result = 0xFF000000 |
1063 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1064 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1065 (pixBuf[i * 4] & 0xFF);
1066 }
1067 } else {
1068 result = 0; // Transparent pixel
1069 }
1070 softCursorPixels[i++] = result;
1071 }
1072 }
1073
1074 }
1075
1076 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1077 }
1078
1079 //
1080 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1081 // Uses softCursorSource as a source for new cursor image.
1082 //
1083
1084 synchronized void
1085 createSoftCursor() {
1086
1087 if (softCursorSource == null)
1088 return;
1089
1090 int scaleCursor = viewer.options.scaleCursor;
1091 if (scaleCursor == 0 || !inputEnabled)
1092 scaleCursor = 100;
1093
1094 // Save original cursor coordinates.
1095 int x = cursorX - hotX;
1096 int y = cursorY - hotY;
1097 int w = cursorWidth;
1098 int h = cursorHeight;
1099
1100 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1101 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1102 hotX = (origHotX * scaleCursor + 50) / 100;
1103 hotY = (origHotY * scaleCursor + 50) / 100;
1104 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1105
1106 if (scaleCursor != 100) {
1107 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1108 Image.SCALE_SMOOTH);
1109 }
1110
1111 if (showSoftCursor) {
1112 // Compute screen area to update.
1113 x = Math.min(x, cursorX - hotX);
1114 y = Math.min(y, cursorY - hotY);
1115 w = Math.max(w, cursorWidth);
1116 h = Math.max(h, cursorHeight);
1117
1118 repaint(viewer.deferCursorUpdates, x, y, w, h);
1119 }
1120 }
1121
1122 //
1123 // softCursorMove(). Moves soft cursor into a particular location.
1124 //
1125
1126 synchronized void softCursorMove(int x, int y) {
1127 int oldX = cursorX;
1128 int oldY = cursorY;
1129 cursorX = x;
1130 cursorY = y;
1131 if (showSoftCursor) {
1132 repaint(viewer.deferCursorUpdates,
1133 oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1134 repaint(viewer.deferCursorUpdates,
1135 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1136 }
1137 }
1138
1139 //
1140 // softCursorFree(). Remove soft cursor, dispose resources.
1141 //
1142
1143 synchronized void softCursorFree() {
1144 if (showSoftCursor) {
1145 showSoftCursor = false;
1146 softCursor = null;
1147 softCursorSource = null;
1148
1149 repaint(viewer.deferCursorUpdates,
1150 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1151 }
1152 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001153
1154 //////////////////////////////////////////////////////////////////
1155 //
1156 // Support for selecting a rectangular video area.
1157 //
1158
1159 /** This flag is false in normal operation, and true in the selection mode. */
1160 private boolean inSelectionMode;
1161
1162 /** The point where the selection was started. */
1163 private Point selectionStart;
1164
1165 /** The second point of the selection. */
1166 private Point selectionEnd;
1167
1168 /**
1169 * We change cursor when enabling the selection mode. In this variable, we
1170 * save the original cursor so we can restore it on returning to the normal
1171 * mode.
1172 */
1173 private Cursor savedCursor;
1174
1175 /**
1176 * Initialize selection-related varibles.
1177 */
1178 private synchronized void resetSelection() {
1179 inSelectionMode = false;
1180 selectionStart = new Point(0, 0);
1181 selectionEnd = new Point(0, 0);
1182
1183 savedCursor = getCursor();
1184 }
1185
1186 /**
1187 * Check current state of the selection mode.
1188 * @return true in the selection mode, false otherwise.
1189 */
1190 public boolean isInSelectionMode() {
1191 return inSelectionMode;
1192 }
1193
1194 /**
1195 * Get current selection.
1196 * @param useScreenCoords use screen coordinates if true, or framebuffer
1197 * coordinates if false. This makes difference when scaling factor is not 100.
1198 * @return The selection as a {@link Rectangle}.
1199 */
1200 private synchronized Rectangle getSelection(boolean useScreenCoords) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001201 int x0 = selectionStart.x;
1202 int x1 = selectionEnd.x;
1203 int y0 = selectionStart.y;
1204 int y1 = selectionEnd.y;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001205 // Make x and y point to the upper left corner of the selection.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001206 if (x1 < x0) {
1207 int t = x0; x0 = x1; x1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001208 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001209 if (y1 < y0) {
1210 int t = y0; y0 = y1; y1 = t;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001211 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001212 // Include the borders in the selection (unless it's empty).
1213 if (x0 != x1 && y0 != y1) {
1214 x1 += 1;
1215 y1 += 1;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001216 }
1217 // Translate from screen coordinates to framebuffer coordinates.
1218 if (rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001219 x0 = (x0 * 100 + scalingFactor/2) / scalingFactor;
1220 y0 = (y0 * 100 + scalingFactor/2) / scalingFactor;
1221 x1 = (x1 * 100 + scalingFactor/2) / scalingFactor;
1222 y1 = (y1 * 100 + scalingFactor/2) / scalingFactor;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001223 }
Constantin Kaplinsky10da44d2008-09-03 03:12:18 +00001224 // Clip the selection to framebuffer.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001225 if (x0 < 0)
1226 x0 = 0;
1227 if (y0 < 0)
1228 y0 = 0;
1229 if (x1 > rfb.framebufferWidth)
1230 x1 = rfb.framebufferWidth;
1231 if (y1 > rfb.framebufferHeight)
1232 y1 = rfb.framebufferHeight;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001233 // Make width a multiple of 16.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001234 int widthBlocks = (x1 - x0 + 8) / 16;
1235 if (selectionStart.x <= selectionEnd.x) {
1236 x1 = x0 + widthBlocks * 16;
1237 if (x1 > rfb.framebufferWidth) {
1238 x1 -= 16;
1239 }
1240 } else {
1241 x0 = x1 - widthBlocks * 16;
1242 if (x0 < 0) {
1243 x0 += 16;
1244 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001245 }
1246 // Make height a multiple of 8.
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001247 int heightBlocks = (y1 - y0 + 4) / 8;
1248 if (selectionStart.y <= selectionEnd.y) {
1249 y1 = y0 + heightBlocks * 8;
1250 if (y1 > rfb.framebufferHeight) {
1251 y1 -= 8;
1252 }
1253 } else {
1254 y0 = y1 - heightBlocks * 8;
1255 if (y0 < 0) {
1256 y0 += 8;
1257 }
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001258 }
1259 // Translate the selection back to screen coordinates if requested.
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001260 if (useScreenCoords && rfb.framebufferWidth != scaledWidth) {
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001261 x0 = (x0 * scalingFactor + 50) / 100;
1262 y0 = (y0 * scalingFactor + 50) / 100;
1263 x1 = (x1 * scalingFactor + 50) / 100;
1264 y1 = (y1 * scalingFactor + 50) / 100;
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001265 }
Constantin Kaplinsky4f374ff2008-09-03 04:51:28 +00001266 // Construct and return the result.
1267 return new Rectangle(x0, y0, x1 - x0, y1 - y0);
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001268 }
1269
1270 /**
1271 * Enable or disable the selection mode.
1272 * @param enable enables the selection mode if true, disables if fasle.
1273 */
1274 public synchronized void enableSelection(boolean enable) {
1275 if (enable && !inSelectionMode) {
1276 // Enter the selection mode.
1277 inSelectionMode = true;
1278 savedCursor = getCursor();
1279 setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
1280 repaint();
1281 } else if (!enable && inSelectionMode) {
1282 // Leave the selection mode.
1283 inSelectionMode = false;
1284 setCursor(savedCursor);
1285 repaint();
1286 }
1287 }
1288
1289 /**
1290 * Process mouse events in the selection mode.
enikeyc41ba1d2008-12-19 09:07:22 +00001291 *
Constantin Kaplinskyf7cb2bf2008-05-27 08:38:28 +00001292 * @param evt mouse event that was originally passed to
1293 * {@link MouseListener} or {@link MouseMotionListener}.
1294 */
1295 private synchronized void handleSelectionMouseEvent(MouseEvent evt) {
1296 int id = evt.getID();
1297 boolean button1 = (evt.getModifiers() & InputEvent.BUTTON1_MASK) != 0;
1298
1299 if (id == MouseEvent.MOUSE_PRESSED && button1) {
1300 selectionStart = selectionEnd = evt.getPoint();
1301 repaint();
1302 }
1303 if (id == MouseEvent.MOUSE_DRAGGED && button1) {
1304 selectionEnd = evt.getPoint();
1305 repaint();
1306 }
1307 if (id == MouseEvent.MOUSE_RELEASED && button1) {
1308 try {
1309 rfb.trySendVideoSelection(getSelection(false));
1310 } catch (IOException e) {
1311 e.printStackTrace();
1312 }
1313 }
1314 }
Constantin Kaplinsky2844fd52008-04-14 08:02:25 +00001315}