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