blob: 48d0f6d5f407feef5ab0aa787d187e2a29c72686 [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
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000073 //
74 // The constructor.
75 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000076 VncCanvas(RfbPlayer player) throws IOException {
77 this.player = player;
78 rfb = player.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000079 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000080
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000081 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
82
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000083 updateFramebufferSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000084 }
85
86 //
87 // Callback methods to determine geometry of our Component.
88 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000089 public Dimension getPreferredSize() {
90 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
91 }
92
93 public Dimension getMinimumSize() {
94 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
95 }
96
97 public Dimension getMaximumSize() {
98 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
99 }
100
101 //
102 // All painting is performed here.
103 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000104 public void update(Graphics g) {
105 paint(g);
106 }
107
108 public void paint(Graphics g) {
109 synchronized(memImage) {
110 g.drawImage(memImage, 0, 0, null);
111 }
wimba.coma5a4f4f2004-09-21 15:25:05 +0000112 if (showSoftCursor) {
113 int x0 = cursorX - hotX, y0 = cursorY - hotY;
114 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
115 if (r.intersects(g.getClipBounds())) {
116 g.drawImage(softCursor, x0, y0, null);
117 }
118 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000119 }
120
121 //
122 // Override the ImageObserver interface method to handle drawing of
123 // JPEG-encoded data.
124 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000125 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000126 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000127 if ((infoflags & (ALLBITS | ABORT)) == 0) {
128 return true; // We need more image data.
129 } else {
130 // If the whole image is available, draw it now.
131 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000132 if (jpegRect != null) {
133 synchronized(jpegRect) {
134 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
135 scheduleRepaint(jpegRect.x, jpegRect.y,
136 jpegRect.width, jpegRect.height);
137 jpegRect.notify();
138 }
139 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000140 }
141 return false; // All image data was processed.
142 }
143 }
144
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000145 void updateFramebufferSize() {
146
147 // Useful shortcuts.
148 int fbWidth = rfb.framebufferWidth;
149 int fbHeight = rfb.framebufferHeight;
150
151 // Create new off-screen image either if it does not exist, or if
152 // its geometry should be changed. It's not necessary to replace
153 // existing image if only pixel format should be changed.
154 if (memImage == null) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000155 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000156 memGraphics = memImage.getGraphics();
157 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000158 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000159 synchronized(memImage) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000160 memImage = player.createImage(fbWidth, fbHeight);
161 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000162 }
163 }
164
165 // Images with raw pixels should be re-allocated on every change
166 // of geometry or pixel format.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000167 pixels24 = new int[fbWidth * fbHeight];
168 pixelsSource =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000169 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000170 pixelsSource.setAnimated(true);
171 rawPixelsImage = createImage(pixelsSource);
172
173 // Update the size of desktop containers.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000174 if (player.inSeparateFrame) {
175 if (player.desktopScrollPane != null)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000176 resizeDesktopFrame();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000177 } else {
178 setSize(fbWidth, fbHeight);
179 }
180 }
181
182 void resizeDesktopFrame() {
183 setSize(rfb.framebufferWidth, rfb.framebufferHeight);
184
185 // FIXME: Find a better way to determine correct size of a
186 // ScrollPane. -- const
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000187 Insets insets = player.desktopScrollPane.getInsets();
188 player.desktopScrollPane.setSize(rfb.framebufferWidth +
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000189 2 * Math.min(insets.left, insets.right),
190 rfb.framebufferHeight +
191 2 * Math.min(insets.top, insets.bottom));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000192
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000193 player.vncFrame.pack();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000194
195 // Try to limit the frame size to the screen size.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000196 Dimension screenSize = player.vncFrame.getToolkit().getScreenSize();
197 Dimension frameSize = player.vncFrame.getSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000198 Dimension newSize = frameSize;
wimba.comc23aeb02004-09-16 00:00:00 +0000199
200 // Reduce Screen Size by 30 pixels in each direction;
201 // This is a (poor) attempt to account for
202 // 1) Menu bar on Macintosh (should really also account for
203 // Dock on OSX). Usually 22px on top of screen.
204 // 2) Taxkbar on Windows (usually about 28 px on bottom)
205 // 3) Other obstructions.
206
207 screenSize.height -= 30;
208 screenSize.width -= 30;
209
210
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000211 boolean needToResizeFrame = false;
212 if (frameSize.height > screenSize.height) {
213 newSize.height = screenSize.height;
214 needToResizeFrame = true;
215 }
216 if (frameSize.width > screenSize.width) {
217 newSize.width = screenSize.width;
218 needToResizeFrame = true;
219 }
220 if (needToResizeFrame) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000221 player.vncFrame.setSize(newSize);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000222 }
223
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000224 player.desktopScrollPane.doLayout();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000225 }
226
227 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000228 // processNormalProtocol() - executed by the rfbThread to deal with
229 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000230 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000231 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000232
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000233 zlibInflater = new Inflater();
234 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000235
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000236 // Show current time position in the control panel.
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000237 player.updatePos();
238
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000239 // Tell our FbsInputStream object to notify us when it goes to the
240 // `paused' mode.
241 rfb.fbs.addObserver(this);
242
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000243 // Main dispatch loop.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000244
245 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000246
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000247 int msgType = rfb.readServerMessageType();
248
249 switch (msgType) {
250 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000251 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000252
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000253 for (int i = 0; i < rfb.updateNRects; i++) {
254 rfb.readFramebufferUpdateRectHdr();
wimba.coma5a4f4f2004-09-21 15:25:05 +0000255
256 boolean cursorPosReceived = false;
257
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000258 int rx = rfb.updateRectX, ry = rfb.updateRectY;
259 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000260
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000261 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
262 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000263
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000264 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
wimba.comb7017b72004-09-16 16:11:55 +0000265 if (rfb.updateRectW != 0 && rfb.updateRectH != 0) {
266 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
267 updateFramebufferSize();
268 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000269 break;
270 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000271
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000272 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
273 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000274 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
275 continue;
276 }
277// if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
278// rfb.updateRectEncoding == rfb.EncodingRichCursor) {
279// throw new Exception("Sorry, no support for" +
280// " cursor shape updates yet");
281// }
282
283 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
284 softCursorMove(rx, ry);
285 cursorPosReceived = true;
286 continue;
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000287 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000288
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000289 switch (rfb.updateRectEncoding) {
290 case RfbProto.EncodingRaw:
291 handleRawRect(rx, ry, rw, rh);
292 break;
293 case RfbProto.EncodingCopyRect:
294 handleCopyRect(rx, ry, rw, rh);
295 break;
296 case RfbProto.EncodingRRE:
297 handleRRERect(rx, ry, rw, rh);
298 break;
299 case RfbProto.EncodingCoRRE:
300 handleCoRRERect(rx, ry, rw, rh);
301 break;
302 case RfbProto.EncodingHextile:
303 handleHextileRect(rx, ry, rw, rh);
304 break;
305 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000306 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000307 break;
308 case RfbProto.EncodingTight:
309 handleTightRect(rx, ry, rw, rh);
310 break;
311 default:
312 throw new Exception("Unknown RFB rectangle encoding " +
wimba.coma5a4f4f2004-09-21 15:25:05 +0000313 Integer.toString(rfb.updateRectEncoding, 16));
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000314 }
315 }
316 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000317
318 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000319 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000320
321 case RfbProto.Bell:
322 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000323 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000324
325 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000326 String s = rfb.readServerCutText();
327 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000328
329 default:
wimba.coma5a4f4f2004-09-21 15:25:05 +0000330 throw new Exception("Unknown RFB message type " +
331 Integer.toString(msgType, 16));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000332 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000333
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000334 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000335 }
336 }
337
338
339 //
340 // Handle a raw rectangle.
341 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000342 void handleRawRect(int x, int y, int w, int h) throws IOException {
343
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000344 byte[] buf = new byte[w * 4];
345 int i, offset;
346 for (int dy = y; dy < y + h; dy++) {
347 rfb.is.readFully(buf);
348 offset = dy * rfb.framebufferWidth + x;
349 for (i = 0; i < w; i++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000350 pixels24[offset + i] =
351 (buf[i * 4 + 2] & 0xFF) << 16 |
352 (buf[i * 4 + 1] & 0xFF) << 8 |
353 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000354 }
355 }
356
357 handleUpdatedPixels(x, y, w, h);
358 scheduleRepaint(x, y, w, h);
359 }
360
361
362 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000363 // Handle a CopyRect rectangle.
364 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000365 void handleCopyRect(int x, int y, int w, int h) throws IOException {
366
367 rfb.readCopyRect();
368 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000369 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000370
371 scheduleRepaint(x, y, w, h);
372 }
373
374 //
375 // Handle an RRE-encoded rectangle.
376 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000377 void handleRRERect(int x, int y, int w, int h) throws IOException {
378
379 int nSubrects = rfb.is.readInt();
380 int sx, sy, sw, sh;
381
382 byte[] buf = new byte[4];
383 rfb.is.readFully(buf);
384 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
385 memGraphics.setColor(pixel);
386 memGraphics.fillRect(x, y, w, h);
387
388 for (int j = 0; j < nSubrects; j++) {
389 rfb.is.readFully(buf);
390 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
391 sx = x + rfb.is.readUnsignedShort();
392 sy = y + rfb.is.readUnsignedShort();
393 sw = rfb.is.readUnsignedShort();
394 sh = rfb.is.readUnsignedShort();
395
396 memGraphics.setColor(pixel);
397 memGraphics.fillRect(sx, sy, sw, sh);
398 }
399
400 scheduleRepaint(x, y, w, h);
401 }
402
403 //
404 // Handle a CoRRE-encoded rectangle.
405 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000406 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
407
408 int nSubrects = rfb.is.readInt();
409 int sx, sy, sw, sh;
410
411 byte[] buf = new byte[4];
412 rfb.is.readFully(buf);
413 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
414 memGraphics.setColor(pixel);
415 memGraphics.fillRect(x, y, w, h);
416
417 for (int j = 0; j < nSubrects; j++) {
418 rfb.is.readFully(buf);
419 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
420 sx = x + rfb.is.readUnsignedByte();
421 sy = y + rfb.is.readUnsignedByte();
422 sw = rfb.is.readUnsignedByte();
423 sh = rfb.is.readUnsignedByte();
424
425 memGraphics.setColor(pixel);
426 memGraphics.fillRect(sx, sy, sw, sh);
427 }
428
429 scheduleRepaint(x, y, w, h);
430 }
431
432 //
433 // Handle a Hextile-encoded rectangle.
434 //
435
436 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000437 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000438
439 void handleHextileRect(int x, int y, int w, int h) throws IOException {
440
441 hextile_bg = new Color(0, 0, 0);
442 hextile_fg = new Color(0, 0, 0);
443
444 for (int ty = y; ty < y + h; ty += 16) {
445 int th = 16;
446 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000447 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000448
449 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000450 int tw = 16;
451 if (x + w - tx < 16)
452 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000453
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000454 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000455 }
456
457 // Finished with a row of tiles, now let's show it.
458 scheduleRepaint(x, y, w, h);
459 }
460 }
461
462 //
463 // Handle one tile in the Hextile-encoded data.
464 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000465 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000466 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000467
468 byte[] buf = new byte[256 * 4];
469
470 int subencoding = rfb.is.readUnsignedByte();
471
472 // Is it a raw-encoded sub-rectangle?
473 if ((subencoding & rfb.HextileRaw) != 0) {
474 int count, offset;
475 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000476 rfb.is.readFully(buf, 0, tw * 4);
477 offset = j * rfb.framebufferWidth + tx;
478 for (count = 0; count < tw; count++) {
479 pixels24[offset + count] =
480 (buf[count * 4 + 2] & 0xFF) << 16 |
481 (buf[count * 4 + 1] & 0xFF) << 8 |
482 (buf[count * 4] & 0xFF);
483 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000484 }
485 handleUpdatedPixels(tx, ty, tw, th);
486 return;
487 }
488
489 // Read and draw the background if specified.
490 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
491 rfb.is.readFully(buf, 0, 4);
492 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
493 }
494 memGraphics.setColor(hextile_bg);
495 memGraphics.fillRect(tx, ty, tw, th);
496
497 // Read the foreground color if specified.
498 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
499 rfb.is.readFully(buf, 0, 4);
500 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
501 }
502
503 // Done with this tile if there is no sub-rectangles.
504 if ((subencoding & rfb.HextileAnySubrects) == 0)
505 return;
506
507 int nSubrects = rfb.is.readUnsignedByte();
508
509 int b1, b2, sx, sy, sw, sh;
510 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
511 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000512 rfb.is.readFully(buf, 0, 4);
513 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
514 b1 = rfb.is.readUnsignedByte();
515 b2 = rfb.is.readUnsignedByte();
516 sx = tx + (b1 >> 4);
517 sy = ty + (b1 & 0xf);
518 sw = (b2 >> 4) + 1;
519 sh = (b2 & 0xf) + 1;
520 memGraphics.setColor(hextile_fg);
521 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000522 }
523 } else {
524 memGraphics.setColor(hextile_fg);
525 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000526 b1 = rfb.is.readUnsignedByte();
527 b2 = rfb.is.readUnsignedByte();
528 sx = tx + (b1 >> 4);
529 sy = ty + (b1 & 0xf);
530 sw = (b2 >> 4) + 1;
531 sh = (b2 & 0xf) + 1;
532 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000533 }
534 }
535 }
536
537 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000538 // Handle a Zlib-encoded rectangle.
539 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000540 void handleZlibRect(int x, int y, int w, int h) throws Exception {
541
542 int nBytes = rfb.is.readInt();
543
544 if (zlibBuf == null || zlibBufLen < nBytes) {
545 zlibBufLen = nBytes * 2;
546 zlibBuf = new byte[zlibBufLen];
547 }
548
549 rfb.is.readFully(zlibBuf, 0, nBytes);
550 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000551
552 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000553 byte[] buf = new byte[w * 4];
554 int i, offset;
555 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000556 zlibInflater.inflate(buf);
557 offset = dy * rfb.framebufferWidth + x;
558 for (i = 0; i < w; i++) {
559 pixels24[offset + i] =
560 (buf[i * 4 + 2] & 0xFF) << 16 |
561 (buf[i * 4 + 1] & 0xFF) << 8 |
562 (buf[i * 4] & 0xFF);
563 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000564 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000565 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000566 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000567 }
568
569 handleUpdatedPixels(x, y, w, h);
570 scheduleRepaint(x, y, w, h);
571 }
572
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000573 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000574 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000575 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000576 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000577
578 int comp_ctl = rfb.is.readUnsignedByte();
579
580 // Flush zlib streams if we are told by the server to do so.
581 for (int stream_id = 0; stream_id < 4; stream_id++) {
582 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000583 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000584 }
585 comp_ctl >>= 1;
586 }
587
588 // Check correctness of subencoding value.
589 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000590 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000591 }
592
593 // Handle solid-color rectangles.
594 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000595 byte[] buf = new byte[3];
596 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000597 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000598 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000599 memGraphics.fillRect(x, y, w, h);
600 scheduleRepaint(x, y, w, h);
601 return;
602 }
603
604 if (comp_ctl == rfb.TightJpeg) {
605
606 // Read JPEG data.
607 byte[] jpegData = new byte[rfb.readCompactLen()];
608 rfb.is.readFully(jpegData);
609
610 // Create an Image object from the JPEG data.
611 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
612
613 // Remember the rectangle where the image should be drawn.
614 jpegRect = new Rectangle(x, y, w, h);
615
616 // Let the imageUpdate() method do the actual drawing, here just
617 // wait until the image is fully loaded and drawn.
618 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000619 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
620 try {
621 // Wait no longer than three seconds.
622 jpegRect.wait(3000);
623 } catch (InterruptedException e) {
624 throw new Exception("Interrupted while decoding JPEG image");
625 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000626 }
627
628 // Done, jpegRect is not needed any more.
629 jpegRect = null;
630 return;
631
632 }
633
634 // Read filter id and parameters.
635 int numColors = 0, rowSize = w;
636 byte[] palette8 = new byte[2];
637 int[] palette24 = new int[256];
638 boolean useGradient = false;
639 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
640 int filter_id = rfb.is.readUnsignedByte();
641 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000642 numColors = rfb.is.readUnsignedByte() + 1;
643 byte[] buf = new byte[numColors * 3];
644 rfb.is.readFully(buf);
645 for (int i = 0; i < numColors; i++) {
646 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
647 (buf[i * 3 + 1] & 0xFF) << 8 |
648 (buf[i * 3 + 2] & 0xFF));
649 }
650 if (numColors == 2)
651 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000652 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000653 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000654 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000655 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000656 }
657 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000658 if (numColors == 0)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000659 rowSize *= 3;
660
661 // Read, optionally uncompress and decode data.
662 int dataSize = h * rowSize;
663 if (dataSize < rfb.TightMinToCompress) {
664 // Data size is small - not compressed with zlib.
665 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000666 // Indexed colors.
667 byte[] indexedData = new byte[dataSize];
668 rfb.is.readFully(indexedData);
669 if (numColors == 2) {
670 // Two colors.
671 decodeMonoData(x, y, w, h, indexedData, palette24);
672 } else {
673 // 3..255 colors.
674 int i = 0;
675 for (int dy = y; dy < y + h; dy++) {
676 for (int dx = x; dx < x + w; dx++) {
677 pixels24[dy * rfb.framebufferWidth + dx] =
678 palette24[indexedData[i++] & 0xFF];
679 }
680 }
681 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000682 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000683 // "Gradient"-processed data
684 byte[] buf = new byte[w * h * 3];
685 rfb.is.readFully(buf);
686 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000687 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000688 // Raw truecolor data.
689 byte[] buf = new byte[w * 3];
690 int i, offset;
691 for (int dy = y; dy < y + h; dy++) {
692 rfb.is.readFully(buf);
693 offset = dy * rfb.framebufferWidth + x;
694 for (i = 0; i < w; i++) {
695 pixels24[offset + i] =
696 (buf[i * 3] & 0xFF) << 16 |
697 (buf[i * 3 + 1] & 0xFF) << 8 |
698 (buf[i * 3 + 2] & 0xFF);
699 }
700 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000701 }
702 } else {
703 // Data was compressed with zlib.
704 int zlibDataLen = rfb.readCompactLen();
705 byte[] zlibData = new byte[zlibDataLen];
706 rfb.is.readFully(zlibData);
707 int stream_id = comp_ctl & 0x03;
708 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000709 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000710 }
711 Inflater myInflater = tightInflaters[stream_id];
712 myInflater.setInput(zlibData);
713 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000714 if (numColors != 0) {
715 // Indexed colors.
716 byte[] indexedData = new byte[dataSize];
717 myInflater.inflate(indexedData);
718 if (numColors == 2) {
719 // Two colors.
720 decodeMonoData(x, y, w, h, indexedData, palette24);
721 } else {
722 // More than two colors.
723 int i = 0;
724 for (int dy = y; dy < y + h; dy++) {
725 for (int dx = x; dx < x + w; dx++) {
726 pixels24[dy * rfb.framebufferWidth + dx] =
727 palette24[indexedData[i++] & 0xFF];
728 }
729 }
730 }
731 } else if (useGradient) {
732 // Compressed "Gradient"-filtered data.
733 byte[] buf = new byte[w * h * 3];
734 myInflater.inflate(buf);
735 decodeGradientData(x, y, w, h, buf);
736 } else {
737 // Compressed truecolor data.
738 byte[] buf = new byte[w * 3];
739 int i, offset;
740 for (int dy = y; dy < y + h; dy++) {
741 myInflater.inflate(buf);
742 offset = dy * rfb.framebufferWidth + x;
743 for (i = 0; i < w; i++) {
744 pixels24[offset + i] =
745 (buf[i * 3] & 0xFF) << 16 |
746 (buf[i * 3 + 1] & 0xFF) << 8 |
747 (buf[i * 3 + 2] & 0xFF);
748 }
749 }
750 }
751 } catch (DataFormatException dfe) {
752 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000753 }
754 }
755
756 handleUpdatedPixels(x, y, w, h);
757 scheduleRepaint(x, y, w, h);
758 }
759
760 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000761 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000762 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000763 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000764
765 int dx, dy, n;
766 int i = y * rfb.framebufferWidth + x;
767 int rowBytes = (w + 7) / 8;
768 byte b;
769
770 for (dy = 0; dy < h; dy++) {
771 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000772 b = src[dy * rowBytes + dx];
773 for (n = 7; n >= 0; n--)
774 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000775 }
776 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000777 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000778 }
779 i += (rfb.framebufferWidth - w);
780 }
781 }
782
783 //
784 // Decode data processed with the "Gradient" filter.
785 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000786 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000787
788 int dx, dy, c;
789 byte[] prevRow = new byte[w * 3];
790 byte[] thisRow = new byte[w * 3];
791 byte[] pix = new byte[3];
792 int[] est = new int[3];
793
794 int offset = y * rfb.framebufferWidth + x;
795
796 for (dy = 0; dy < h; dy++) {
797
798 /* First pixel in a row */
799 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000800 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
801 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000802 }
803 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000804 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000805
806 /* Remaining pixels of a row */
807 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000808 for (c = 0; c < 3; c++) {
809 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
810 (prevRow[(dx - 1) * 3 + c] & 0xFF));
811 if (est[c] > 0xFF) {
812 est[c] = 0xFF;
813 } else if (est[c] < 0x00) {
814 est[c] = 0x00;
815 }
816 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
817 thisRow[dx * 3 + c] = pix[c];
818 }
819 pixels24[offset++] =
820 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000821 }
822
823 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
824 offset += (rfb.framebufferWidth - w);
825 }
826 }
827
828
829 //
830 // Display newly updated area of pixels.
831 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000832 void handleUpdatedPixels(int x, int y, int w, int h) {
833
834 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000835
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000836 pixelsSource.newPixels(x, y, w, h);
837 memGraphics.setClip(x, y, w, h);
838 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
839 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
840 }
841
wimba.coma5a4f4f2004-09-21 15:25:05 +0000842 //////////////////////////////////////////////////////////////////
843 //
844 // Handle cursor shape updates (XCursor and RichCursor encodings).
845 //
846 boolean showSoftCursor = false;
847
848 int[] softCursorPixels;
849 MemoryImageSource softCursorSource;
850 Image softCursor;
851
852 int cursorX = 0, cursorY = 0;
853 int cursorWidth, cursorHeight;
854 int origCursorWidth, origCursorHeight;
855 int hotX, hotY;
856 int origHotX, origHotY;
857 int deferCursorUpdates = 10;
858
859 //
860 // Handle cursor shape update (XCursor and RichCursor encodings).
861 //
862 synchronized void handleCursorShapeUpdate(int encodingType,
863 int xhot, int yhot, int width,
864 int height)
865 throws IOException {
866
867 int bytesPerRow = (width + 7) / 8;
868 int bytesMaskData = bytesPerRow * height;
869
870 softCursorFree();
871
872 if (width * height == 0)
873 return;
874
875// // Ignore cursor shape data if requested by user.
876//
877// if (viewer.options.ignoreCursorUpdates) {
878// if (encodingType == rfb.EncodingXCursor) {
879// rfb.is.skipBytes(6 + bytesMaskData * 2);
880// } else {
881// // rfb.EncodingRichCursor
882// rfb.is.skipBytes(width * height + bytesMaskData);
883// }
884// return;
885// }
886
887 // Decode cursor pixel data.
888
889 softCursorPixels = new int[width * height];
890
891 if (encodingType == rfb.EncodingXCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000892
893 // Read foreground and background colors of the cursor.
894 byte[] rgb = new byte[6];
895 rfb.is.readFully(rgb);
896 int[] colors = {(0xFF000000 | (rgb[3] & 0xFF) << 16 |
897 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
898 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
899 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF))
900 };
wimba.coma5a4f4f2004-09-21 15:25:05 +0000901
902 // Read pixel and mask data.
903 byte[] pixBuf = new byte[bytesMaskData];
904 rfb.is.readFully(pixBuf);
905 byte[] maskBuf = new byte[bytesMaskData];
906 rfb.is.readFully(maskBuf);
907
908 // Decode pixel data into softCursorPixels[].
909 byte pixByte, maskByte;
910 int x, y, n, result;
911 int i = 0;
912 for (y = 0; y < height; y++) {
913 for (x = 0; x < width / 8; x++) {
914 pixByte = pixBuf[y * bytesPerRow + x];
915 maskByte = maskBuf[y * bytesPerRow + x];
916 for (n = 7; n >= 0; n--) {
917 if ((maskByte >> n & 1) != 0) {
918 result = colors[pixByte >> n & 1];
919 } else {
920 result = 0; // Transparent pixel
921 }
922 softCursorPixels[i++] = result;
923 }
924 }
925 for (n = 7; n >= 8 - width % 8; n--) {
926 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
927 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
928 } else {
929 result = 0; // Transparent pixel
930 }
931 softCursorPixels[i++] = result;
932 }
933 }
934
935 } else {
936 // encodingType == rfb.EncodingRichCursor
wimba.coma5a4f4f2004-09-21 15:25:05 +0000937
938 // Read pixel and mask data.
939 byte[] pixBuf = new byte[width * height * 4];
940 rfb.is.readFully(pixBuf);
941 byte[] maskBuf = new byte[bytesMaskData];
942 rfb.is.readFully(maskBuf);
943
944 // Decode pixel data into softCursorPixels[].
945 byte pixByte, maskByte;
946 int x, y, n, result;
947 int i = 0;
948 for (y = 0; y < height; y++) {
949 for (x = 0; x < width / 8; x++) {
950 maskByte = maskBuf[y * bytesPerRow + x];
951 for (n = 7; n >= 0; n--) {
952 if ((maskByte >> n & 1) != 0) {
953// if (bytesPerPixel == 1) {
954// result = cm8.getRGB(pixBuf[i]);
955// } else {
956 result = (pixBuf[i * 4] & 0xFF) << 24 |
957 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
958 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
959 (pixBuf[i * 4 + 3] & 0xFF);
960 //result = 0xFF000000 |
961 // (pixBuf[i * 4 + 1] & 0xFF) << 16 |
962 // (pixBuf[i * 4 + 2] & 0xFF) << 8 |
963 // (pixBuf[i * 4 + 3] & 0xFF);
964// }
965 } else {
966 result = 0; // Transparent pixel
967 }
968 softCursorPixels[i++] = result;
969 }
970 }
971
972 for (n = 7; n >= 8 - width % 8; n--) {
973 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
974// if (bytesPerPixel == 1) {
975// result = cm8.getRGB(pixBuf[i]);
976// } else {
977 result = 0xFF000000 |
978 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
979 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
980 (pixBuf[i * 4 + 3] & 0xFF);
981// }
982 } else {
983 result = 0; // Transparent pixel
984 }
985 softCursorPixels[i++] = result;
986 }
987 }
988
989 }
990
991 // Draw the cursor on an off-screen image.
992
993 softCursorSource =
994 new MemoryImageSource(width, height, softCursorPixels, 0, width);
995 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
996// if (inputEnabled && viewer.options.scaleCursor != 0) {
997// int w = (width * viewer.options.scaleCursor) / 100;
998// int h = (height * viewer.options.scaleCursor) / 100;
999// Image newCursor = softCursor.getScaledInstance(w, h, Image.SCALE_SMOOTH);
1000// softCursor = newCursor;
1001// cursorWidth = w;
1002// cursorHeight = h;
1003// hotX = (xhot * viewer.options.scaleCursor) / 100;
1004// hotY = (yhot * viewer.options.scaleCursor) / 100;
1005// } else {
1006 cursorWidth = width;
1007 cursorHeight = height;
1008 hotX = xhot;
1009 hotY = yhot;
1010// }
1011
1012 // Set data associated with cursor.
1013 origCursorWidth = width;
1014 origCursorHeight = height;
1015 origHotX = xhot;
1016 origHotY = yhot;
1017
1018 showSoftCursor = true;
1019
1020 // Show the cursor.
1021
1022 repaint(deferCursorUpdates,
1023 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1024 }
1025
1026 //
1027 // softCursorMove(). Moves soft cursor into a particular location.
1028 //
1029 synchronized void softCursorMove(int x, int y) {
1030 if (showSoftCursor) {
1031 repaint(deferCursorUpdates,
1032 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1033 repaint(deferCursorUpdates,
1034 x - hotX, y - hotY, cursorWidth, cursorHeight);
1035
wimba.com16092722004-09-21 20:54:37 +00001036 // Automatic viewport scrolling
1037 if (player.desktopScrollPane != null) {
1038 boolean needScroll = false;
1039 Dimension d = player.desktopScrollPane.getSize();
1040 Point topLeft = player.desktopScrollPane.getScrollPosition();
1041 Point botRight = new Point(topLeft.x + d.width, topLeft.y + d.height);
1042
1043 if (x < topLeft.x + SCROLL_MARGIN) {
1044 // shift left
1045 topLeft.x = x - SCROLL_MARGIN;
1046 needScroll = true;
1047 } else if (x > botRight.x - SCROLL_MARGIN) {
1048 // shift right
1049 topLeft.x = x - d.width + SCROLL_MARGIN;
1050 needScroll = true;
1051 }
1052 if (y < topLeft.y + SCROLL_MARGIN) {
1053 // shift up
1054 topLeft.y = y - SCROLL_MARGIN;
1055 needScroll = true;
1056 } else if (y > botRight.y - SCROLL_MARGIN) {
1057 // shift down
1058 topLeft.y = y - d.height + SCROLL_MARGIN;
1059 needScroll = true;
1060 }
1061 player.desktopScrollPane.setScrollPosition(topLeft.x, topLeft.y);
1062 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001063 }
1064
1065 cursorX = x;
1066 cursorY = y;
1067 }
1068 //
1069 // softCursorFree(). Remove soft cursor, dispose resources.
1070 //
1071 synchronized void softCursorFree() {
1072 if (showSoftCursor) {
1073 showSoftCursor = false;
1074 softCursor = null;
1075 softCursorSource = null;
1076 softCursorPixels = null;
1077
1078 repaint(deferCursorUpdates,
1079 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1080 }
1081 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001082 //
1083 // Tell JVM to repaint specified desktop area.
1084 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001085 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +00001086 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001087 // Do nothing, and remember we are seeking.
1088 seekMode = true;
1089 } else {
1090 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001091 // Immediate repaint of the whole desktop after seeking.
1092 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001093 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001094 // Usual incremental repaint.
1095 repaint(player.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001096 }
1097 seekMode = false;
1098 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001099 }
1100
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +00001101 //
1102 // We are observing our FbsInputStream object to get notified on
1103 // switching to the `paused' mode. In such cases we want to repaint
1104 // our desktop if we were seeking.
1105 //
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +00001106 public void update(Observable o, Object arg) {
1107 // Immediate repaint of the whole desktop after seeking.
1108 repaint();
1109 // Let next scheduleRepaint() call invoke incremental drawing.
1110 seekMode = false;
1111 }
1112
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001113}