blob: 6c1beecc1b22379b5f30fa50989bd65639c4d2c1 [file] [log] [blame]
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001//
2// Copyright (C) 2001,2002 HorizonLive.com, Inc. All Rights Reserved.
3// Copyright (C) 2001 Constantin Kaplinsky. All Rights Reserved.
4// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
5// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
6//
7// This is free software; you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation; either version 2 of the License, or
10// (at your option) any later version.
11//
12// This software is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this software; if not, write to the Free Software
19// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20// USA.
21//
22
wimba.comc23aeb02004-09-16 00:00:00 +000023package com.HorizonLive.RfbPlayer;
24
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000025import java.awt.*;
26import java.awt.event.*;
27import java.awt.image.*;
28import java.io.*;
29import java.lang.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000030import java.util.*;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000031import java.util.zip.*;
32
33
34//
35// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
36//
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000037class VncCanvas extends Canvas implements Observer {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000038
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000039 RfbPlayer player;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000040 RfbProto rfb;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000041 ColorModel cm24;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000042
43 Image memImage;
44 Graphics memGraphics;
45
46 Image rawPixelsImage;
47 MemoryImageSource pixelsSource;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000048 int[] pixels24;
49
50 // Zlib encoder's data.
51 byte[] zlibBuf;
52 int zlibBufLen = 0;
53 Inflater zlibInflater;
54
55 // Tight encoder's data.
56 final static int tightZlibBufferSize = 512;
57 Inflater[] tightInflaters;
58
59 // Since JPEG images are loaded asynchronously, we have to remember
60 // their position in the framebuffer. Also, this jpegRect object is
61 // used for synchronization between the rfbThread and a JVM's thread
62 // which decodes and loads JPEG images.
63 Rectangle jpegRect;
64
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000065 // When we're in the seeking mode, we should not update the desktop.
66 // This variable helps us to remember that repainting the desktop at
67 // once is necessary when the seek operation is finished.
68 boolean seekMode;
69
wimba.com16092722004-09-21 20:54:37 +000070 // Distance of mouse from viewer border to trigger automatic scrolling
71 final static int SCROLL_MARGIN = 50;
72
wimba.comccb67c32005-01-03 16:20:38 +000073 // Color for border around small shared area
74 static final Color DARK_GRAY = new Color(132, 138, 156);
75
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000076 //
77 // The constructor.
78 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000079 VncCanvas(RfbPlayer player) throws IOException {
80 this.player = player;
81 rfb = player.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000082 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000083
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000084 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
85
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000086 updateFramebufferSize();
wimba.comccb67c32005-01-03 16:20:38 +000087
88 setBackground(Color.white);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000089 }
90
91 //
92 // Callback methods to determine geometry of our Component.
93 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000094 public Dimension getPreferredSize() {
wimba.comccb67c32005-01-03 16:20:38 +000095 Dimension d = player.desktopScrollPane.getViewportSize();
96 Dimension sz = new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
97 if (d.width > sz.width)
98 sz.width = d.width;
99 if (d.height > sz.height)
100 sz.height = d.height;
101 return sz;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000102 }
103
104 public Dimension getMinimumSize() {
wimba.comccb67c32005-01-03 16:20:38 +0000105 return getPreferredSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000106 }
107
108 public Dimension getMaximumSize() {
wimba.comccb67c32005-01-03 16:20:38 +0000109 return getPreferredSize();
110 }
111
112 Point getImageOrigin() {
113 int x = 0, y = 0;
114 Dimension d = player.desktopScrollPane.getViewportSize();
115 if (rfb.framebufferWidth < d.width)
116 x = d.width / 2 - rfb.framebufferWidth / 2;
117 if (rfb.framebufferHeight < d.height)
118 y = d.height / 2 - rfb.framebufferHeight / 2;
119 return new Point(x, y);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000120 }
121
122 //
123 // All painting is performed here.
124 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000125 public void update(Graphics g) {
126 paint(g);
127 }
128
wimba.comc219ca82004-09-22 15:10:14 +0000129 public synchronized void paint(Graphics g) {
wimba.comccb67c32005-01-03 16:20:38 +0000130 Point o = getImageOrigin();
131 Dimension d = getPreferredSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000132 synchronized(memImage) {
wimba.comccb67c32005-01-03 16:20:38 +0000133 g.drawImage(memImage, o.x, o.y, null);
134
135 // fill in background
136 if (o.x > 0 || o.y > 0) {
137 // left, right, top, bottom
138 Color c = g.getColor();
139 g.setColor(Color.white);
140 g.fillRect(0, 0, o.x, d.height);
141 g.fillRect(o.x + rfb.framebufferWidth, 0, d.width - (o.x +
142 rfb.framebufferWidth), d.height);
143 g.fillRect(o.x, 0, rfb.framebufferWidth, o.y);
144 g.fillRect(o.x, o.y + rfb.framebufferHeight, rfb.framebufferWidth,
145 d.height - (o.y + rfb.framebufferHeight));
146 g.setColor(DARK_GRAY);
147 g.drawRect(o.x - 1, o.y - 1, rfb.framebufferWidth + 1,
148 rfb.framebufferHeight + 1);
149 g.setColor(c);
150 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000151 }
wimba.coma5a4f4f2004-09-21 15:25:05 +0000152 if (showSoftCursor) {
153 int x0 = cursorX - hotX, y0 = cursorY - hotY;
wimba.comccb67c32005-01-03 16:20:38 +0000154 x0 += o.x;
155 y0 += o.y;
wimba.coma5a4f4f2004-09-21 15:25:05 +0000156 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
157 if (r.intersects(g.getClipBounds())) {
wimba.comccb67c32005-01-03 16:20:38 +0000158 Rectangle c = g.getClipBounds();
159 g.setClip(o.x, o.y, rfb.framebufferWidth, rfb.framebufferHeight);
wimba.coma5a4f4f2004-09-21 15:25:05 +0000160 g.drawImage(softCursor, x0, y0, null);
wimba.comccb67c32005-01-03 16:20:38 +0000161 g.setClip(c.x, c.y, c.width, c.height);
wimba.coma5a4f4f2004-09-21 15:25:05 +0000162 }
163 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000164 }
165
166 //
167 // Override the ImageObserver interface method to handle drawing of
168 // JPEG-encoded data.
169 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000170 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000171 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000172 if ((infoflags & (ALLBITS | ABORT)) == 0) {
173 return true; // We need more image data.
174 } else {
175 // If the whole image is available, draw it now.
176 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000177 if (jpegRect != null) {
178 synchronized(jpegRect) {
179 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
180 scheduleRepaint(jpegRect.x, jpegRect.y,
181 jpegRect.width, jpegRect.height);
182 jpegRect.notify();
183 }
184 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000185 }
186 return false; // All image data was processed.
187 }
188 }
189
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000190 void updateFramebufferSize() {
191
192 // Useful shortcuts.
193 int fbWidth = rfb.framebufferWidth;
194 int fbHeight = rfb.framebufferHeight;
195
196 // Create new off-screen image either if it does not exist, or if
197 // its geometry should be changed. It's not necessary to replace
198 // existing image if only pixel format should be changed.
199 if (memImage == null) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000200 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000201 memGraphics = memImage.getGraphics();
202 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000203 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000204 synchronized(memImage) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000205 memImage = player.createImage(fbWidth, fbHeight);
206 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000207 }
208 }
209
210 // Images with raw pixels should be re-allocated on every change
211 // of geometry or pixel format.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000212 pixels24 = new int[fbWidth * fbHeight];
213 pixelsSource =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000214 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000215 pixelsSource.setAnimated(true);
216 rawPixelsImage = createImage(pixelsSource);
217
wimba.comeee33e32005-01-07 20:54:33 +0000218 // Update the size of desktop containers unless seeking
219 if (!seekMode) {
220 if (player.inSeparateFrame) {
221 if (player.desktopScrollPane != null)
222 resizeDesktopFrame();
223 } else {
224 setSize(fbWidth, fbHeight);
225 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000226 }
227 }
228
229 void resizeDesktopFrame() {
wimba.com252eb3b2004-09-21 21:27:54 +0000230 // determine screen size
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000231 Dimension screenSize = player.vncFrame.getToolkit().getScreenSize();
wimba.com252eb3b2004-09-21 21:27:54 +0000232 Dimension scrollSize = player.desktopScrollPane.getSize();
wimba.comc23aeb02004-09-16 00:00:00 +0000233
234 // Reduce Screen Size by 30 pixels in each direction;
235 // This is a (poor) attempt to account for
236 // 1) Menu bar on Macintosh (should really also account for
237 // Dock on OSX). Usually 22px on top of screen.
wimba.com252eb3b2004-09-21 21:27:54 +0000238 // 2) Taxkbar on Windows (usually about 28 px on bottom)
239 // 3) Other obstructions.
wimba.comc23aeb02004-09-16 00:00:00 +0000240 screenSize.height -= 30;
241 screenSize.width -= 30;
242
wimba.com252eb3b2004-09-21 21:27:54 +0000243 // Further reduce the screen size to account for the
244 // scroll pane's insets.
245 Insets insets = player.desktopScrollPane.getInsets();
246 screenSize.height -= insets.top + insets.bottom;
247 screenSize.width -= insets.left + insets.right;
wimba.comc23aeb02004-09-16 00:00:00 +0000248
wimba.com252eb3b2004-09-21 21:27:54 +0000249 // Limit pane size to fit on screen.
250 boolean needResize = false;
251 if (scrollSize.width != rfb.framebufferWidth ||
252 scrollSize.height != rfb.framebufferHeight)
253 needResize = true;
254 int w = rfb.framebufferWidth, h = rfb.framebufferHeight;
255 if (w > screenSize.width) {
256 w = screenSize.width;
257 needResize = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000258 }
wimba.com252eb3b2004-09-21 21:27:54 +0000259 if (h > screenSize.height) {
260 h = screenSize.height;
261 needResize = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000262 }
wimba.com252eb3b2004-09-21 21:27:54 +0000263 if (needResize)
264 player.desktopScrollPane.setSize(w, h);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000265
wimba.com252eb3b2004-09-21 21:27:54 +0000266 player.vncFrame.pack();
wimba.comccb67c32005-01-03 16:20:38 +0000267
268 // size the canvas
269 setSize(getPreferredSize());
wimba.com252eb3b2004-09-21 21:27:54 +0000270 }
271
272 void resizeEmbeddedApplet() {
wimba.com252eb3b2004-09-21 21:27:54 +0000273 // resize scroll pane if necessary
274 Dimension scrollSize = player.desktopScrollPane.getSize();
275 if (scrollSize.width != player.dispW ||
wimba.comccb67c32005-01-03 16:20:38 +0000276 scrollSize.height != player.dispH) {
wimba.com252eb3b2004-09-21 21:27:54 +0000277 player.desktopScrollPane.setSize(player.dispW, player.dispH);
wimba.comccb67c32005-01-03 16:20:38 +0000278 player.desktopScrollPane.validate();
279 }
wimba.com252eb3b2004-09-21 21:27:54 +0000280
wimba.comccb67c32005-01-03 16:20:38 +0000281 // size the canvas
282 setSize(getPreferredSize());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000283 }
284
285 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000286 // processNormalProtocol() - executed by the rfbThread to deal with
287 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000288 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000289 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000290
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000291 zlibInflater = new Inflater();
292 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000293
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000294 // Show current time position in the control panel.
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000295 player.updatePos();
296
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000297 // Tell our FbsInputStream object to notify us when it goes to the
298 // `paused' mode.
299 rfb.fbs.addObserver(this);
300
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000301 // Main dispatch loop.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000302
303 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000304
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000305 int msgType = rfb.readServerMessageType();
306
307 switch (msgType) {
308 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000309 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000310
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000311 for (int i = 0; i < rfb.updateNRects; i++) {
312 rfb.readFramebufferUpdateRectHdr();
wimba.coma5a4f4f2004-09-21 15:25:05 +0000313
314 boolean cursorPosReceived = false;
315
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000316 int rx = rfb.updateRectX, ry = rfb.updateRectY;
317 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000318
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000319 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
320 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000321
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000322 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
wimba.comb7017b72004-09-16 16:11:55 +0000323 if (rfb.updateRectW != 0 && rfb.updateRectH != 0) {
324 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
325 updateFramebufferSize();
326 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000327 break;
328 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000329
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000330 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
331 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000332 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
333 continue;
334 }
335// if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
336// rfb.updateRectEncoding == rfb.EncodingRichCursor) {
337// throw new Exception("Sorry, no support for" +
338// " cursor shape updates yet");
339// }
340
341 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
342 softCursorMove(rx, ry);
343 cursorPosReceived = true;
344 continue;
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000345 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000346
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000347 switch (rfb.updateRectEncoding) {
348 case RfbProto.EncodingRaw:
349 handleRawRect(rx, ry, rw, rh);
350 break;
351 case RfbProto.EncodingCopyRect:
352 handleCopyRect(rx, ry, rw, rh);
353 break;
354 case RfbProto.EncodingRRE:
355 handleRRERect(rx, ry, rw, rh);
356 break;
357 case RfbProto.EncodingCoRRE:
358 handleCoRRERect(rx, ry, rw, rh);
359 break;
360 case RfbProto.EncodingHextile:
361 handleHextileRect(rx, ry, rw, rh);
362 break;
363 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000364 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000365 break;
366 case RfbProto.EncodingTight:
367 handleTightRect(rx, ry, rw, rh);
368 break;
369 default:
370 throw new Exception("Unknown RFB rectangle encoding " +
wimba.coma5a4f4f2004-09-21 15:25:05 +0000371 Integer.toString(rfb.updateRectEncoding, 16));
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000372 }
373 }
374 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000375
376 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000377 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000378
379 case RfbProto.Bell:
380 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000381 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000382
383 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000384 String s = rfb.readServerCutText();
385 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000386
387 default:
wimba.coma5a4f4f2004-09-21 15:25:05 +0000388 throw new Exception("Unknown RFB message type " +
389 Integer.toString(msgType, 16));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000390 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000391
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000392 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000393 }
394 }
395
396
397 //
398 // Handle a raw rectangle.
399 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000400 void handleRawRect(int x, int y, int w, int h) throws IOException {
401
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000402 byte[] buf = new byte[w * 4];
403 int i, offset;
404 for (int dy = y; dy < y + h; dy++) {
405 rfb.is.readFully(buf);
406 offset = dy * rfb.framebufferWidth + x;
407 for (i = 0; i < w; i++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000408 pixels24[offset + i] =
409 (buf[i * 4 + 2] & 0xFF) << 16 |
410 (buf[i * 4 + 1] & 0xFF) << 8 |
411 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000412 }
413 }
414
415 handleUpdatedPixels(x, y, w, h);
416 scheduleRepaint(x, y, w, h);
417 }
418
419
420 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000421 // Handle a CopyRect rectangle.
422 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000423 void handleCopyRect(int x, int y, int w, int h) throws IOException {
424
425 rfb.readCopyRect();
426 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000427 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000428
429 scheduleRepaint(x, y, w, h);
430 }
431
432 //
433 // Handle an RRE-encoded rectangle.
434 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000435 void handleRRERect(int x, int y, int w, int h) throws IOException {
436
437 int nSubrects = rfb.is.readInt();
438 int sx, sy, sw, sh;
439
440 byte[] buf = new byte[4];
441 rfb.is.readFully(buf);
442 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
443 memGraphics.setColor(pixel);
444 memGraphics.fillRect(x, y, w, h);
445
446 for (int j = 0; j < nSubrects; j++) {
447 rfb.is.readFully(buf);
448 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
449 sx = x + rfb.is.readUnsignedShort();
450 sy = y + rfb.is.readUnsignedShort();
451 sw = rfb.is.readUnsignedShort();
452 sh = rfb.is.readUnsignedShort();
453
454 memGraphics.setColor(pixel);
455 memGraphics.fillRect(sx, sy, sw, sh);
456 }
457
458 scheduleRepaint(x, y, w, h);
459 }
460
461 //
462 // Handle a CoRRE-encoded rectangle.
463 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000464 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
465
466 int nSubrects = rfb.is.readInt();
467 int sx, sy, sw, sh;
468
469 byte[] buf = new byte[4];
470 rfb.is.readFully(buf);
471 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
472 memGraphics.setColor(pixel);
473 memGraphics.fillRect(x, y, w, h);
474
475 for (int j = 0; j < nSubrects; j++) {
476 rfb.is.readFully(buf);
477 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
478 sx = x + rfb.is.readUnsignedByte();
479 sy = y + rfb.is.readUnsignedByte();
480 sw = rfb.is.readUnsignedByte();
481 sh = rfb.is.readUnsignedByte();
482
483 memGraphics.setColor(pixel);
484 memGraphics.fillRect(sx, sy, sw, sh);
485 }
486
487 scheduleRepaint(x, y, w, h);
488 }
489
490 //
491 // Handle a Hextile-encoded rectangle.
492 //
493
494 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000495 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000496
497 void handleHextileRect(int x, int y, int w, int h) throws IOException {
498
499 hextile_bg = new Color(0, 0, 0);
500 hextile_fg = new Color(0, 0, 0);
501
502 for (int ty = y; ty < y + h; ty += 16) {
503 int th = 16;
504 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000505 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000506
507 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000508 int tw = 16;
509 if (x + w - tx < 16)
510 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000511
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000512 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000513 }
514
515 // Finished with a row of tiles, now let's show it.
516 scheduleRepaint(x, y, w, h);
517 }
518 }
519
520 //
521 // Handle one tile in the Hextile-encoded data.
522 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000523 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000524 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000525
526 byte[] buf = new byte[256 * 4];
527
528 int subencoding = rfb.is.readUnsignedByte();
529
530 // Is it a raw-encoded sub-rectangle?
531 if ((subencoding & rfb.HextileRaw) != 0) {
532 int count, offset;
533 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000534 rfb.is.readFully(buf, 0, tw * 4);
535 offset = j * rfb.framebufferWidth + tx;
536 for (count = 0; count < tw; count++) {
537 pixels24[offset + count] =
538 (buf[count * 4 + 2] & 0xFF) << 16 |
539 (buf[count * 4 + 1] & 0xFF) << 8 |
540 (buf[count * 4] & 0xFF);
541 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000542 }
543 handleUpdatedPixels(tx, ty, tw, th);
544 return;
545 }
546
547 // Read and draw the background if specified.
548 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
549 rfb.is.readFully(buf, 0, 4);
550 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
551 }
552 memGraphics.setColor(hextile_bg);
553 memGraphics.fillRect(tx, ty, tw, th);
554
555 // Read the foreground color if specified.
556 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
557 rfb.is.readFully(buf, 0, 4);
558 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
559 }
560
561 // Done with this tile if there is no sub-rectangles.
562 if ((subencoding & rfb.HextileAnySubrects) == 0)
563 return;
564
565 int nSubrects = rfb.is.readUnsignedByte();
566
567 int b1, b2, sx, sy, sw, sh;
568 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
569 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000570 rfb.is.readFully(buf, 0, 4);
571 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
572 b1 = rfb.is.readUnsignedByte();
573 b2 = rfb.is.readUnsignedByte();
574 sx = tx + (b1 >> 4);
575 sy = ty + (b1 & 0xf);
576 sw = (b2 >> 4) + 1;
577 sh = (b2 & 0xf) + 1;
578 memGraphics.setColor(hextile_fg);
579 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000580 }
581 } else {
582 memGraphics.setColor(hextile_fg);
583 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000584 b1 = rfb.is.readUnsignedByte();
585 b2 = rfb.is.readUnsignedByte();
586 sx = tx + (b1 >> 4);
587 sy = ty + (b1 & 0xf);
588 sw = (b2 >> 4) + 1;
589 sh = (b2 & 0xf) + 1;
590 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000591 }
592 }
593 }
594
595 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000596 // Handle a Zlib-encoded rectangle.
597 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000598 void handleZlibRect(int x, int y, int w, int h) throws Exception {
599
600 int nBytes = rfb.is.readInt();
601
602 if (zlibBuf == null || zlibBufLen < nBytes) {
603 zlibBufLen = nBytes * 2;
604 zlibBuf = new byte[zlibBufLen];
605 }
606
607 rfb.is.readFully(zlibBuf, 0, nBytes);
608 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000609
610 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000611 byte[] buf = new byte[w * 4];
612 int i, offset;
613 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000614 zlibInflater.inflate(buf);
615 offset = dy * rfb.framebufferWidth + x;
616 for (i = 0; i < w; i++) {
617 pixels24[offset + i] =
618 (buf[i * 4 + 2] & 0xFF) << 16 |
619 (buf[i * 4 + 1] & 0xFF) << 8 |
620 (buf[i * 4] & 0xFF);
621 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000622 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000623 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000624 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000625 }
626
627 handleUpdatedPixels(x, y, w, h);
628 scheduleRepaint(x, y, w, h);
629 }
630
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000631 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000632 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000633 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000634 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000635
636 int comp_ctl = rfb.is.readUnsignedByte();
637
638 // Flush zlib streams if we are told by the server to do so.
639 for (int stream_id = 0; stream_id < 4; stream_id++) {
640 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000641 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000642 }
643 comp_ctl >>= 1;
644 }
645
646 // Check correctness of subencoding value.
647 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000648 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000649 }
650
651 // Handle solid-color rectangles.
652 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000653 byte[] buf = new byte[3];
654 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000655 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000656 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000657 memGraphics.fillRect(x, y, w, h);
658 scheduleRepaint(x, y, w, h);
659 return;
660 }
661
662 if (comp_ctl == rfb.TightJpeg) {
663
664 // Read JPEG data.
665 byte[] jpegData = new byte[rfb.readCompactLen()];
666 rfb.is.readFully(jpegData);
667
668 // Create an Image object from the JPEG data.
669 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
670
671 // Remember the rectangle where the image should be drawn.
672 jpegRect = new Rectangle(x, y, w, h);
673
674 // Let the imageUpdate() method do the actual drawing, here just
675 // wait until the image is fully loaded and drawn.
676 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000677 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
678 try {
679 // Wait no longer than three seconds.
680 jpegRect.wait(3000);
681 } catch (InterruptedException e) {
682 throw new Exception("Interrupted while decoding JPEG image");
683 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000684 }
685
686 // Done, jpegRect is not needed any more.
687 jpegRect = null;
688 return;
689
690 }
691
692 // Read filter id and parameters.
693 int numColors = 0, rowSize = w;
694 byte[] palette8 = new byte[2];
695 int[] palette24 = new int[256];
696 boolean useGradient = false;
697 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
698 int filter_id = rfb.is.readUnsignedByte();
699 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000700 numColors = rfb.is.readUnsignedByte() + 1;
701 byte[] buf = new byte[numColors * 3];
702 rfb.is.readFully(buf);
703 for (int i = 0; i < numColors; i++) {
704 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
705 (buf[i * 3 + 1] & 0xFF) << 8 |
706 (buf[i * 3 + 2] & 0xFF));
707 }
708 if (numColors == 2)
709 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000710 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000711 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000712 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000713 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000714 }
715 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000716 if (numColors == 0)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000717 rowSize *= 3;
718
719 // Read, optionally uncompress and decode data.
720 int dataSize = h * rowSize;
721 if (dataSize < rfb.TightMinToCompress) {
722 // Data size is small - not compressed with zlib.
723 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000724 // Indexed colors.
725 byte[] indexedData = new byte[dataSize];
726 rfb.is.readFully(indexedData);
727 if (numColors == 2) {
728 // Two colors.
729 decodeMonoData(x, y, w, h, indexedData, palette24);
730 } else {
731 // 3..255 colors.
732 int i = 0;
733 for (int dy = y; dy < y + h; dy++) {
734 for (int dx = x; dx < x + w; dx++) {
735 pixels24[dy * rfb.framebufferWidth + dx] =
736 palette24[indexedData[i++] & 0xFF];
737 }
738 }
739 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000740 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000741 // "Gradient"-processed data
742 byte[] buf = new byte[w * h * 3];
743 rfb.is.readFully(buf);
744 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000745 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000746 // Raw truecolor data.
747 byte[] buf = new byte[w * 3];
748 int i, offset;
749 for (int dy = y; dy < y + h; dy++) {
750 rfb.is.readFully(buf);
751 offset = dy * rfb.framebufferWidth + x;
752 for (i = 0; i < w; i++) {
753 pixels24[offset + i] =
754 (buf[i * 3] & 0xFF) << 16 |
755 (buf[i * 3 + 1] & 0xFF) << 8 |
756 (buf[i * 3 + 2] & 0xFF);
757 }
758 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000759 }
760 } else {
761 // Data was compressed with zlib.
762 int zlibDataLen = rfb.readCompactLen();
763 byte[] zlibData = new byte[zlibDataLen];
764 rfb.is.readFully(zlibData);
765 int stream_id = comp_ctl & 0x03;
766 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000767 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000768 }
769 Inflater myInflater = tightInflaters[stream_id];
770 myInflater.setInput(zlibData);
771 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000772 if (numColors != 0) {
773 // Indexed colors.
774 byte[] indexedData = new byte[dataSize];
775 myInflater.inflate(indexedData);
776 if (numColors == 2) {
777 // Two colors.
778 decodeMonoData(x, y, w, h, indexedData, palette24);
779 } else {
780 // More than two colors.
781 int i = 0;
782 for (int dy = y; dy < y + h; dy++) {
783 for (int dx = x; dx < x + w; dx++) {
784 pixels24[dy * rfb.framebufferWidth + dx] =
785 palette24[indexedData[i++] & 0xFF];
786 }
787 }
788 }
789 } else if (useGradient) {
790 // Compressed "Gradient"-filtered data.
791 byte[] buf = new byte[w * h * 3];
792 myInflater.inflate(buf);
793 decodeGradientData(x, y, w, h, buf);
794 } else {
795 // Compressed truecolor data.
796 byte[] buf = new byte[w * 3];
797 int i, offset;
798 for (int dy = y; dy < y + h; dy++) {
799 myInflater.inflate(buf);
800 offset = dy * rfb.framebufferWidth + x;
801 for (i = 0; i < w; i++) {
802 pixels24[offset + i] =
803 (buf[i * 3] & 0xFF) << 16 |
804 (buf[i * 3 + 1] & 0xFF) << 8 |
805 (buf[i * 3 + 2] & 0xFF);
806 }
807 }
808 }
809 } catch (DataFormatException dfe) {
810 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000811 }
812 }
813
814 handleUpdatedPixels(x, y, w, h);
815 scheduleRepaint(x, y, w, h);
816 }
817
818 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000819 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000820 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000821 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000822
823 int dx, dy, n;
824 int i = y * rfb.framebufferWidth + x;
825 int rowBytes = (w + 7) / 8;
826 byte b;
827
828 for (dy = 0; dy < h; dy++) {
829 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000830 b = src[dy * rowBytes + dx];
831 for (n = 7; n >= 0; n--)
832 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000833 }
834 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000835 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000836 }
837 i += (rfb.framebufferWidth - w);
838 }
839 }
840
841 //
842 // Decode data processed with the "Gradient" filter.
843 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000844 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000845
846 int dx, dy, c;
847 byte[] prevRow = new byte[w * 3];
848 byte[] thisRow = new byte[w * 3];
849 byte[] pix = new byte[3];
850 int[] est = new int[3];
851
852 int offset = y * rfb.framebufferWidth + x;
853
854 for (dy = 0; dy < h; dy++) {
855
856 /* First pixel in a row */
857 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000858 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
859 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000860 }
861 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000862 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000863
864 /* Remaining pixels of a row */
865 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000866 for (c = 0; c < 3; c++) {
867 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
868 (prevRow[(dx - 1) * 3 + c] & 0xFF));
869 if (est[c] > 0xFF) {
870 est[c] = 0xFF;
871 } else if (est[c] < 0x00) {
872 est[c] = 0x00;
873 }
874 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
875 thisRow[dx * 3 + c] = pix[c];
876 }
877 pixels24[offset++] =
878 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000879 }
880
881 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
882 offset += (rfb.framebufferWidth - w);
883 }
884 }
885
886
887 //
888 // Display newly updated area of pixels.
889 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000890 void handleUpdatedPixels(int x, int y, int w, int h) {
891
892 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000893
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000894 pixelsSource.newPixels(x, y, w, h);
895 memGraphics.setClip(x, y, w, h);
896 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
897 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
898 }
899
wimba.coma5a4f4f2004-09-21 15:25:05 +0000900 //////////////////////////////////////////////////////////////////
901 //
902 // Handle cursor shape updates (XCursor and RichCursor encodings).
903 //
904 boolean showSoftCursor = false;
905
906 int[] softCursorPixels;
907 MemoryImageSource softCursorSource;
908 Image softCursor;
909
910 int cursorX = 0, cursorY = 0;
911 int cursorWidth, cursorHeight;
912 int origCursorWidth, origCursorHeight;
913 int hotX, hotY;
914 int origHotX, origHotY;
915 int deferCursorUpdates = 10;
916
917 //
918 // Handle cursor shape update (XCursor and RichCursor encodings).
919 //
920 synchronized void handleCursorShapeUpdate(int encodingType,
921 int xhot, int yhot, int width,
922 int height)
923 throws IOException {
924
925 int bytesPerRow = (width + 7) / 8;
926 int bytesMaskData = bytesPerRow * height;
927
928 softCursorFree();
929
930 if (width * height == 0)
931 return;
932
933// // Ignore cursor shape data if requested by user.
934//
935// if (viewer.options.ignoreCursorUpdates) {
936// if (encodingType == rfb.EncodingXCursor) {
937// rfb.is.skipBytes(6 + bytesMaskData * 2);
938// } else {
939// // rfb.EncodingRichCursor
940// rfb.is.skipBytes(width * height + bytesMaskData);
941// }
942// return;
943// }
944
945 // Decode cursor pixel data.
946
947 softCursorPixels = new int[width * height];
948
949 if (encodingType == rfb.EncodingXCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000950
951 // Read foreground and background colors of the cursor.
952 byte[] rgb = new byte[6];
953 rfb.is.readFully(rgb);
954 int[] colors = {(0xFF000000 | (rgb[3] & 0xFF) << 16 |
955 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
956 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
957 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF))
958 };
wimba.coma5a4f4f2004-09-21 15:25:05 +0000959
960 // Read pixel and mask data.
961 byte[] pixBuf = new byte[bytesMaskData];
962 rfb.is.readFully(pixBuf);
963 byte[] maskBuf = new byte[bytesMaskData];
964 rfb.is.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
wimba.coma5a4f4f2004-09-21 15:25:05 +0000995
996 // Read pixel and mask data.
997 byte[] pixBuf = new byte[width * height * 4];
998 rfb.is.readFully(pixBuf);
999 byte[] maskBuf = new byte[bytesMaskData];
1000 rfb.is.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 (bytesPerPixel == 1) {
1012// result = cm8.getRGB(pixBuf[i]);
1013// } else {
1014 result = (pixBuf[i * 4] & 0xFF) << 24 |
1015 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1016 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1017 (pixBuf[i * 4 + 3] & 0xFF);
1018 //result = 0xFF000000 |
1019 // (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1020 // (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1021 // (pixBuf[i * 4 + 3] & 0xFF);
1022// }
1023 } else {
1024 result = 0; // Transparent pixel
1025 }
1026 softCursorPixels[i++] = result;
1027 }
1028 }
1029
1030 for (n = 7; n >= 8 - width % 8; n--) {
1031 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1032// if (bytesPerPixel == 1) {
1033// result = cm8.getRGB(pixBuf[i]);
1034// } else {
1035 result = 0xFF000000 |
1036 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1037 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1038 (pixBuf[i * 4 + 3] & 0xFF);
1039// }
1040 } else {
1041 result = 0; // Transparent pixel
1042 }
1043 softCursorPixels[i++] = result;
1044 }
1045 }
1046
1047 }
1048
1049 // Draw the cursor on an off-screen image.
1050
1051 softCursorSource =
1052 new MemoryImageSource(width, height, softCursorPixels, 0, width);
1053 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1054// if (inputEnabled && viewer.options.scaleCursor != 0) {
1055// int w = (width * viewer.options.scaleCursor) / 100;
1056// int h = (height * viewer.options.scaleCursor) / 100;
1057// Image newCursor = softCursor.getScaledInstance(w, h, Image.SCALE_SMOOTH);
1058// softCursor = newCursor;
1059// cursorWidth = w;
1060// cursorHeight = h;
1061// hotX = (xhot * viewer.options.scaleCursor) / 100;
1062// hotY = (yhot * viewer.options.scaleCursor) / 100;
1063// } else {
1064 cursorWidth = width;
1065 cursorHeight = height;
1066 hotX = xhot;
1067 hotY = yhot;
1068// }
1069
1070 // Set data associated with cursor.
1071 origCursorWidth = width;
1072 origCursorHeight = height;
1073 origHotX = xhot;
1074 origHotY = yhot;
1075
1076 showSoftCursor = true;
1077
1078 // Show the cursor.
wimba.comeee33e32005-01-07 20:54:33 +00001079 scheduleRepaint(cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1080 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001081
wimba.comeee33e32005-01-07 20:54:33 +00001082 private void scrollToPoint(int x, int y) {
1083 boolean needScroll = false;
1084
1085 if (player.desktopScrollPane == null)
1086 return;
1087
1088 Dimension d = player.desktopScrollPane.getSize();
1089 Point topLeft = player.desktopScrollPane.getScrollPosition();
1090 Point botRight = new Point(topLeft.x + d.width, topLeft.y + d.height);
1091
1092 if (x < topLeft.x + SCROLL_MARGIN) {
1093 // shift left
1094 topLeft.x = x - SCROLL_MARGIN;
1095 needScroll = true;
1096 } else if (x > botRight.x - SCROLL_MARGIN) {
1097 // shift right
1098 topLeft.x = x - d.width + SCROLL_MARGIN;
1099 needScroll = true;
1100 }
1101 if (y < topLeft.y + SCROLL_MARGIN) {
1102 // shift up
1103 topLeft.y = y - SCROLL_MARGIN;
1104 needScroll = true;
1105 } else if (y > botRight.y - SCROLL_MARGIN) {
1106 // shift down
1107 topLeft.y = y - d.height + SCROLL_MARGIN;
1108 needScroll = true;
1109 }
1110 player.desktopScrollPane.setScrollPosition(topLeft.x, topLeft.y);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001111 }
1112
1113 //
1114 // softCursorMove(). Moves soft cursor into a particular location.
1115 //
1116 synchronized void softCursorMove(int x, int y) {
wimba.comccb67c32005-01-03 16:20:38 +00001117 Point o = getImageOrigin();
1118 int oldX = cursorX + o.x;
1119 int oldY = cursorY + o.y;
1120 cursorX = x;
1121 cursorY = y;
wimba.comeee33e32005-01-07 20:54:33 +00001122
1123 // paint and scroll
wimba.coma5a4f4f2004-09-21 15:25:05 +00001124 if (showSoftCursor) {
wimba.comeee33e32005-01-07 20:54:33 +00001125 scheduleRepaint(oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1126 scheduleRepaint(cursorX - hotX + o.x, cursorY - hotY + o.y, cursorWidth,
1127 cursorHeight);
1128 if (!seekMode)
1129 scrollToPoint(x, y);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001130 }
1131
1132 cursorX = x;
1133 cursorY = y;
1134 }
1135 //
1136 // softCursorFree(). Remove soft cursor, dispose resources.
1137 //
1138 synchronized void softCursorFree() {
1139 if (showSoftCursor) {
1140 showSoftCursor = false;
1141 softCursor = null;
1142 softCursorSource = null;
1143 softCursorPixels = null;
1144
wimba.comccb67c32005-01-03 16:20:38 +00001145 Point o = getImageOrigin();
wimba.comeee33e32005-01-07 20:54:33 +00001146 scheduleRepaint(cursorX - hotX + o.x, cursorY - hotY + o.y, cursorWidth,
1147 cursorHeight);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001148 }
1149 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001150 //
1151 // Tell JVM to repaint specified desktop area.
1152 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001153 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +00001154 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001155 // Do nothing, and remember we are seeking.
1156 seekMode = true;
1157 } else {
1158 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001159 // Immediate repaint of the whole desktop after seeking.
wimba.comeee33e32005-01-07 20:54:33 +00001160 seekMode = false;
1161 if (showSoftCursor)
1162 scrollToPoint(cursorX, cursorY);
1163 updateFramebufferSize();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001164 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001165 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001166 // Usual incremental repaint.
wimba.comccb67c32005-01-03 16:20:38 +00001167 Point o = getImageOrigin();
1168 repaint(player.deferScreenUpdates, o.x + x, o.y + y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001169 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001170 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001171 }
1172
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +00001173 //
1174 // We are observing our FbsInputStream object to get notified on
1175 // switching to the `paused' mode. In such cases we want to repaint
1176 // our desktop if we were seeking.
1177 //
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +00001178 public void update(Observable o, Object arg) {
1179 // Immediate repaint of the whole desktop after seeking.
1180 repaint();
1181 // Let next scheduleRepaint() call invoke incremental drawing.
1182 seekMode = false;
1183 }
1184
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001185}