blob: cf6c2bde098ae31aaf4efb2474b8b81e7dc9ebc5 [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
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000070 //
71 // The constructor.
72 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000073 VncCanvas(RfbPlayer player) throws IOException {
74 this.player = player;
75 rfb = player.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000076 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000077
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000078 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
79
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000080 updateFramebufferSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000081 }
82
83 //
84 // Callback methods to determine geometry of our Component.
85 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000086 public Dimension getPreferredSize() {
87 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
88 }
89
90 public Dimension getMinimumSize() {
91 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
92 }
93
94 public Dimension getMaximumSize() {
95 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
96 }
97
98 //
99 // All painting is performed here.
100 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000101 public void update(Graphics g) {
102 paint(g);
103 }
104
105 public void paint(Graphics g) {
106 synchronized(memImage) {
107 g.drawImage(memImage, 0, 0, null);
108 }
wimba.coma5a4f4f2004-09-21 15:25:05 +0000109 if (showSoftCursor) {
110 int x0 = cursorX - hotX, y0 = cursorY - hotY;
111 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
112 if (r.intersects(g.getClipBounds())) {
113 g.drawImage(softCursor, x0, y0, null);
114 }
115 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000116 }
117
118 //
119 // Override the ImageObserver interface method to handle drawing of
120 // JPEG-encoded data.
121 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000122 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000123 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000124 if ((infoflags & (ALLBITS | ABORT)) == 0) {
125 return true; // We need more image data.
126 } else {
127 // If the whole image is available, draw it now.
128 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000129 if (jpegRect != null) {
130 synchronized(jpegRect) {
131 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
132 scheduleRepaint(jpegRect.x, jpegRect.y,
133 jpegRect.width, jpegRect.height);
134 jpegRect.notify();
135 }
136 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000137 }
138 return false; // All image data was processed.
139 }
140 }
141
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000142 void updateFramebufferSize() {
143
144 // Useful shortcuts.
145 int fbWidth = rfb.framebufferWidth;
146 int fbHeight = rfb.framebufferHeight;
147
148 // Create new off-screen image either if it does not exist, or if
149 // its geometry should be changed. It's not necessary to replace
150 // existing image if only pixel format should be changed.
151 if (memImage == null) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000152 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000153 memGraphics = memImage.getGraphics();
154 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000155 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000156 synchronized(memImage) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000157 memImage = player.createImage(fbWidth, fbHeight);
158 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000159 }
160 }
161
162 // Images with raw pixels should be re-allocated on every change
163 // of geometry or pixel format.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000164 pixels24 = new int[fbWidth * fbHeight];
165 pixelsSource =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000166 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000167 pixelsSource.setAnimated(true);
168 rawPixelsImage = createImage(pixelsSource);
169
170 // Update the size of desktop containers.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000171 if (player.inSeparateFrame) {
172 if (player.desktopScrollPane != null)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000173 resizeDesktopFrame();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000174 } else {
175 setSize(fbWidth, fbHeight);
176 }
177 }
178
179 void resizeDesktopFrame() {
180 setSize(rfb.framebufferWidth, rfb.framebufferHeight);
181
182 // FIXME: Find a better way to determine correct size of a
183 // ScrollPane. -- const
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000184 Insets insets = player.desktopScrollPane.getInsets();
185 player.desktopScrollPane.setSize(rfb.framebufferWidth +
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000186 2 * Math.min(insets.left, insets.right),
187 rfb.framebufferHeight +
188 2 * Math.min(insets.top, insets.bottom));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000189
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000190 player.vncFrame.pack();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000191
192 // Try to limit the frame size to the screen size.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000193 Dimension screenSize = player.vncFrame.getToolkit().getScreenSize();
194 Dimension frameSize = player.vncFrame.getSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000195 Dimension newSize = frameSize;
wimba.comc23aeb02004-09-16 00:00:00 +0000196
197 // Reduce Screen Size by 30 pixels in each direction;
198 // This is a (poor) attempt to account for
199 // 1) Menu bar on Macintosh (should really also account for
200 // Dock on OSX). Usually 22px on top of screen.
201 // 2) Taxkbar on Windows (usually about 28 px on bottom)
202 // 3) Other obstructions.
203
204 screenSize.height -= 30;
205 screenSize.width -= 30;
206
207
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000208 boolean needToResizeFrame = false;
209 if (frameSize.height > screenSize.height) {
210 newSize.height = screenSize.height;
211 needToResizeFrame = true;
212 }
213 if (frameSize.width > screenSize.width) {
214 newSize.width = screenSize.width;
215 needToResizeFrame = true;
216 }
217 if (needToResizeFrame) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000218 player.vncFrame.setSize(newSize);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000219 }
220
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000221 player.desktopScrollPane.doLayout();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000222 }
223
224 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000225 // processNormalProtocol() - executed by the rfbThread to deal with
226 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000227 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000228 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000229
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000230 zlibInflater = new Inflater();
231 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000232
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000233 // Show current time position in the control panel.
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000234 player.updatePos();
235
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000236 // Tell our FbsInputStream object to notify us when it goes to the
237 // `paused' mode.
238 rfb.fbs.addObserver(this);
239
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000240 // Main dispatch loop.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000241
242 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000243
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000244 int msgType = rfb.readServerMessageType();
245
246 switch (msgType) {
247 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000248 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000249
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000250 for (int i = 0; i < rfb.updateNRects; i++) {
251 rfb.readFramebufferUpdateRectHdr();
wimba.coma5a4f4f2004-09-21 15:25:05 +0000252
253 boolean cursorPosReceived = false;
254
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000255 int rx = rfb.updateRectX, ry = rfb.updateRectY;
256 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000257
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000258 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
259 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000260
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000261 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
wimba.comb7017b72004-09-16 16:11:55 +0000262 if (rfb.updateRectW != 0 && rfb.updateRectH != 0) {
263 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
264 updateFramebufferSize();
265 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000266 break;
267 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000268
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000269 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
270 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000271 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
272 continue;
273 }
274// if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
275// rfb.updateRectEncoding == rfb.EncodingRichCursor) {
276// throw new Exception("Sorry, no support for" +
277// " cursor shape updates yet");
278// }
279
280 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
281 softCursorMove(rx, ry);
282 cursorPosReceived = true;
283 continue;
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000284 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000285
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000286 switch (rfb.updateRectEncoding) {
287 case RfbProto.EncodingRaw:
288 handleRawRect(rx, ry, rw, rh);
289 break;
290 case RfbProto.EncodingCopyRect:
291 handleCopyRect(rx, ry, rw, rh);
292 break;
293 case RfbProto.EncodingRRE:
294 handleRRERect(rx, ry, rw, rh);
295 break;
296 case RfbProto.EncodingCoRRE:
297 handleCoRRERect(rx, ry, rw, rh);
298 break;
299 case RfbProto.EncodingHextile:
300 handleHextileRect(rx, ry, rw, rh);
301 break;
302 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000303 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000304 break;
305 case RfbProto.EncodingTight:
306 handleTightRect(rx, ry, rw, rh);
307 break;
308 default:
309 throw new Exception("Unknown RFB rectangle encoding " +
wimba.coma5a4f4f2004-09-21 15:25:05 +0000310 Integer.toString(rfb.updateRectEncoding, 16));
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000311 }
312 }
313 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000314
315 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000316 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000317
318 case RfbProto.Bell:
319 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000320 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000321
322 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000323 String s = rfb.readServerCutText();
324 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000325
326 default:
wimba.coma5a4f4f2004-09-21 15:25:05 +0000327 throw new Exception("Unknown RFB message type " +
328 Integer.toString(msgType, 16));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000329 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000330
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000331 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000332 }
333 }
334
335
336 //
337 // Handle a raw rectangle.
338 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000339 void handleRawRect(int x, int y, int w, int h) throws IOException {
340
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000341 byte[] buf = new byte[w * 4];
342 int i, offset;
343 for (int dy = y; dy < y + h; dy++) {
344 rfb.is.readFully(buf);
345 offset = dy * rfb.framebufferWidth + x;
346 for (i = 0; i < w; i++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000347 pixels24[offset + i] =
348 (buf[i * 4 + 2] & 0xFF) << 16 |
349 (buf[i * 4 + 1] & 0xFF) << 8 |
350 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000351 }
352 }
353
354 handleUpdatedPixels(x, y, w, h);
355 scheduleRepaint(x, y, w, h);
356 }
357
358
359 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000360 // Handle a CopyRect rectangle.
361 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000362 void handleCopyRect(int x, int y, int w, int h) throws IOException {
363
364 rfb.readCopyRect();
365 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000366 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000367
368 scheduleRepaint(x, y, w, h);
369 }
370
371 //
372 // Handle an RRE-encoded rectangle.
373 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000374 void handleRRERect(int x, int y, int w, int h) throws IOException {
375
376 int nSubrects = rfb.is.readInt();
377 int sx, sy, sw, sh;
378
379 byte[] buf = new byte[4];
380 rfb.is.readFully(buf);
381 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
382 memGraphics.setColor(pixel);
383 memGraphics.fillRect(x, y, w, h);
384
385 for (int j = 0; j < nSubrects; j++) {
386 rfb.is.readFully(buf);
387 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
388 sx = x + rfb.is.readUnsignedShort();
389 sy = y + rfb.is.readUnsignedShort();
390 sw = rfb.is.readUnsignedShort();
391 sh = rfb.is.readUnsignedShort();
392
393 memGraphics.setColor(pixel);
394 memGraphics.fillRect(sx, sy, sw, sh);
395 }
396
397 scheduleRepaint(x, y, w, h);
398 }
399
400 //
401 // Handle a CoRRE-encoded rectangle.
402 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000403 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
404
405 int nSubrects = rfb.is.readInt();
406 int sx, sy, sw, sh;
407
408 byte[] buf = new byte[4];
409 rfb.is.readFully(buf);
410 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
411 memGraphics.setColor(pixel);
412 memGraphics.fillRect(x, y, w, h);
413
414 for (int j = 0; j < nSubrects; j++) {
415 rfb.is.readFully(buf);
416 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
417 sx = x + rfb.is.readUnsignedByte();
418 sy = y + rfb.is.readUnsignedByte();
419 sw = rfb.is.readUnsignedByte();
420 sh = rfb.is.readUnsignedByte();
421
422 memGraphics.setColor(pixel);
423 memGraphics.fillRect(sx, sy, sw, sh);
424 }
425
426 scheduleRepaint(x, y, w, h);
427 }
428
429 //
430 // Handle a Hextile-encoded rectangle.
431 //
432
433 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000434 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000435
436 void handleHextileRect(int x, int y, int w, int h) throws IOException {
437
438 hextile_bg = new Color(0, 0, 0);
439 hextile_fg = new Color(0, 0, 0);
440
441 for (int ty = y; ty < y + h; ty += 16) {
442 int th = 16;
443 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000444 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000445
446 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000447 int tw = 16;
448 if (x + w - tx < 16)
449 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000450
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000451 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000452 }
453
454 // Finished with a row of tiles, now let's show it.
455 scheduleRepaint(x, y, w, h);
456 }
457 }
458
459 //
460 // Handle one tile in the Hextile-encoded data.
461 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000462 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000463 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000464
465 byte[] buf = new byte[256 * 4];
466
467 int subencoding = rfb.is.readUnsignedByte();
468
469 // Is it a raw-encoded sub-rectangle?
470 if ((subencoding & rfb.HextileRaw) != 0) {
471 int count, offset;
472 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000473 rfb.is.readFully(buf, 0, tw * 4);
474 offset = j * rfb.framebufferWidth + tx;
475 for (count = 0; count < tw; count++) {
476 pixels24[offset + count] =
477 (buf[count * 4 + 2] & 0xFF) << 16 |
478 (buf[count * 4 + 1] & 0xFF) << 8 |
479 (buf[count * 4] & 0xFF);
480 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000481 }
482 handleUpdatedPixels(tx, ty, tw, th);
483 return;
484 }
485
486 // Read and draw the background if specified.
487 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
488 rfb.is.readFully(buf, 0, 4);
489 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
490 }
491 memGraphics.setColor(hextile_bg);
492 memGraphics.fillRect(tx, ty, tw, th);
493
494 // Read the foreground color if specified.
495 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
496 rfb.is.readFully(buf, 0, 4);
497 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
498 }
499
500 // Done with this tile if there is no sub-rectangles.
501 if ((subencoding & rfb.HextileAnySubrects) == 0)
502 return;
503
504 int nSubrects = rfb.is.readUnsignedByte();
505
506 int b1, b2, sx, sy, sw, sh;
507 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
508 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000509 rfb.is.readFully(buf, 0, 4);
510 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
511 b1 = rfb.is.readUnsignedByte();
512 b2 = rfb.is.readUnsignedByte();
513 sx = tx + (b1 >> 4);
514 sy = ty + (b1 & 0xf);
515 sw = (b2 >> 4) + 1;
516 sh = (b2 & 0xf) + 1;
517 memGraphics.setColor(hextile_fg);
518 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000519 }
520 } else {
521 memGraphics.setColor(hextile_fg);
522 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000523 b1 = rfb.is.readUnsignedByte();
524 b2 = rfb.is.readUnsignedByte();
525 sx = tx + (b1 >> 4);
526 sy = ty + (b1 & 0xf);
527 sw = (b2 >> 4) + 1;
528 sh = (b2 & 0xf) + 1;
529 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000530 }
531 }
532 }
533
534 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000535 // Handle a Zlib-encoded rectangle.
536 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000537 void handleZlibRect(int x, int y, int w, int h) throws Exception {
538
539 int nBytes = rfb.is.readInt();
540
541 if (zlibBuf == null || zlibBufLen < nBytes) {
542 zlibBufLen = nBytes * 2;
543 zlibBuf = new byte[zlibBufLen];
544 }
545
546 rfb.is.readFully(zlibBuf, 0, nBytes);
547 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000548
549 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000550 byte[] buf = new byte[w * 4];
551 int i, offset;
552 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000553 zlibInflater.inflate(buf);
554 offset = dy * rfb.framebufferWidth + x;
555 for (i = 0; i < w; i++) {
556 pixels24[offset + i] =
557 (buf[i * 4 + 2] & 0xFF) << 16 |
558 (buf[i * 4 + 1] & 0xFF) << 8 |
559 (buf[i * 4] & 0xFF);
560 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000561 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000562 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000563 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000564 }
565
566 handleUpdatedPixels(x, y, w, h);
567 scheduleRepaint(x, y, w, h);
568 }
569
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000570 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000571 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000572 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000573 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000574
575 int comp_ctl = rfb.is.readUnsignedByte();
576
577 // Flush zlib streams if we are told by the server to do so.
578 for (int stream_id = 0; stream_id < 4; stream_id++) {
579 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000580 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000581 }
582 comp_ctl >>= 1;
583 }
584
585 // Check correctness of subencoding value.
586 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000587 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000588 }
589
590 // Handle solid-color rectangles.
591 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000592 byte[] buf = new byte[3];
593 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000594 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000595 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000596 memGraphics.fillRect(x, y, w, h);
597 scheduleRepaint(x, y, w, h);
598 return;
599 }
600
601 if (comp_ctl == rfb.TightJpeg) {
602
603 // Read JPEG data.
604 byte[] jpegData = new byte[rfb.readCompactLen()];
605 rfb.is.readFully(jpegData);
606
607 // Create an Image object from the JPEG data.
608 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
609
610 // Remember the rectangle where the image should be drawn.
611 jpegRect = new Rectangle(x, y, w, h);
612
613 // Let the imageUpdate() method do the actual drawing, here just
614 // wait until the image is fully loaded and drawn.
615 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000616 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
617 try {
618 // Wait no longer than three seconds.
619 jpegRect.wait(3000);
620 } catch (InterruptedException e) {
621 throw new Exception("Interrupted while decoding JPEG image");
622 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000623 }
624
625 // Done, jpegRect is not needed any more.
626 jpegRect = null;
627 return;
628
629 }
630
631 // Read filter id and parameters.
632 int numColors = 0, rowSize = w;
633 byte[] palette8 = new byte[2];
634 int[] palette24 = new int[256];
635 boolean useGradient = false;
636 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
637 int filter_id = rfb.is.readUnsignedByte();
638 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000639 numColors = rfb.is.readUnsignedByte() + 1;
640 byte[] buf = new byte[numColors * 3];
641 rfb.is.readFully(buf);
642 for (int i = 0; i < numColors; i++) {
643 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
644 (buf[i * 3 + 1] & 0xFF) << 8 |
645 (buf[i * 3 + 2] & 0xFF));
646 }
647 if (numColors == 2)
648 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000649 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000650 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000651 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000652 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000653 }
654 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000655 if (numColors == 0)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000656 rowSize *= 3;
657
658 // Read, optionally uncompress and decode data.
659 int dataSize = h * rowSize;
660 if (dataSize < rfb.TightMinToCompress) {
661 // Data size is small - not compressed with zlib.
662 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000663 // Indexed colors.
664 byte[] indexedData = new byte[dataSize];
665 rfb.is.readFully(indexedData);
666 if (numColors == 2) {
667 // Two colors.
668 decodeMonoData(x, y, w, h, indexedData, palette24);
669 } else {
670 // 3..255 colors.
671 int i = 0;
672 for (int dy = y; dy < y + h; dy++) {
673 for (int dx = x; dx < x + w; dx++) {
674 pixels24[dy * rfb.framebufferWidth + dx] =
675 palette24[indexedData[i++] & 0xFF];
676 }
677 }
678 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000679 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000680 // "Gradient"-processed data
681 byte[] buf = new byte[w * h * 3];
682 rfb.is.readFully(buf);
683 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000684 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000685 // Raw truecolor data.
686 byte[] buf = new byte[w * 3];
687 int i, offset;
688 for (int dy = y; dy < y + h; dy++) {
689 rfb.is.readFully(buf);
690 offset = dy * rfb.framebufferWidth + x;
691 for (i = 0; i < w; i++) {
692 pixels24[offset + i] =
693 (buf[i * 3] & 0xFF) << 16 |
694 (buf[i * 3 + 1] & 0xFF) << 8 |
695 (buf[i * 3 + 2] & 0xFF);
696 }
697 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000698 }
699 } else {
700 // Data was compressed with zlib.
701 int zlibDataLen = rfb.readCompactLen();
702 byte[] zlibData = new byte[zlibDataLen];
703 rfb.is.readFully(zlibData);
704 int stream_id = comp_ctl & 0x03;
705 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000706 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000707 }
708 Inflater myInflater = tightInflaters[stream_id];
709 myInflater.setInput(zlibData);
710 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000711 if (numColors != 0) {
712 // Indexed colors.
713 byte[] indexedData = new byte[dataSize];
714 myInflater.inflate(indexedData);
715 if (numColors == 2) {
716 // Two colors.
717 decodeMonoData(x, y, w, h, indexedData, palette24);
718 } else {
719 // More than two colors.
720 int i = 0;
721 for (int dy = y; dy < y + h; dy++) {
722 for (int dx = x; dx < x + w; dx++) {
723 pixels24[dy * rfb.framebufferWidth + dx] =
724 palette24[indexedData[i++] & 0xFF];
725 }
726 }
727 }
728 } else if (useGradient) {
729 // Compressed "Gradient"-filtered data.
730 byte[] buf = new byte[w * h * 3];
731 myInflater.inflate(buf);
732 decodeGradientData(x, y, w, h, buf);
733 } else {
734 // Compressed truecolor data.
735 byte[] buf = new byte[w * 3];
736 int i, offset;
737 for (int dy = y; dy < y + h; dy++) {
738 myInflater.inflate(buf);
739 offset = dy * rfb.framebufferWidth + x;
740 for (i = 0; i < w; i++) {
741 pixels24[offset + i] =
742 (buf[i * 3] & 0xFF) << 16 |
743 (buf[i * 3 + 1] & 0xFF) << 8 |
744 (buf[i * 3 + 2] & 0xFF);
745 }
746 }
747 }
748 } catch (DataFormatException dfe) {
749 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000750 }
751 }
752
753 handleUpdatedPixels(x, y, w, h);
754 scheduleRepaint(x, y, w, h);
755 }
756
757 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000758 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000759 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000760 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000761
762 int dx, dy, n;
763 int i = y * rfb.framebufferWidth + x;
764 int rowBytes = (w + 7) / 8;
765 byte b;
766
767 for (dy = 0; dy < h; dy++) {
768 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000769 b = src[dy * rowBytes + dx];
770 for (n = 7; n >= 0; n--)
771 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000772 }
773 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000774 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000775 }
776 i += (rfb.framebufferWidth - w);
777 }
778 }
779
780 //
781 // Decode data processed with the "Gradient" filter.
782 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000783 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000784
785 int dx, dy, c;
786 byte[] prevRow = new byte[w * 3];
787 byte[] thisRow = new byte[w * 3];
788 byte[] pix = new byte[3];
789 int[] est = new int[3];
790
791 int offset = y * rfb.framebufferWidth + x;
792
793 for (dy = 0; dy < h; dy++) {
794
795 /* First pixel in a row */
796 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000797 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
798 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000799 }
800 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000801 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000802
803 /* Remaining pixels of a row */
804 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000805 for (c = 0; c < 3; c++) {
806 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
807 (prevRow[(dx - 1) * 3 + c] & 0xFF));
808 if (est[c] > 0xFF) {
809 est[c] = 0xFF;
810 } else if (est[c] < 0x00) {
811 est[c] = 0x00;
812 }
813 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
814 thisRow[dx * 3 + c] = pix[c];
815 }
816 pixels24[offset++] =
817 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000818 }
819
820 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
821 offset += (rfb.framebufferWidth - w);
822 }
823 }
824
825
826 //
827 // Display newly updated area of pixels.
828 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000829 void handleUpdatedPixels(int x, int y, int w, int h) {
830
831 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000832
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000833 pixelsSource.newPixels(x, y, w, h);
834 memGraphics.setClip(x, y, w, h);
835 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
836 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
837 }
838
wimba.coma5a4f4f2004-09-21 15:25:05 +0000839 //////////////////////////////////////////////////////////////////
840 //
841 // Handle cursor shape updates (XCursor and RichCursor encodings).
842 //
843 boolean showSoftCursor = false;
844
845 int[] softCursorPixels;
846 MemoryImageSource softCursorSource;
847 Image softCursor;
848
849 int cursorX = 0, cursorY = 0;
850 int cursorWidth, cursorHeight;
851 int origCursorWidth, origCursorHeight;
852 int hotX, hotY;
853 int origHotX, origHotY;
854 int deferCursorUpdates = 10;
855
856 //
857 // Handle cursor shape update (XCursor and RichCursor encodings).
858 //
859 synchronized void handleCursorShapeUpdate(int encodingType,
860 int xhot, int yhot, int width,
861 int height)
862 throws IOException {
863
864 int bytesPerRow = (width + 7) / 8;
865 int bytesMaskData = bytesPerRow * height;
866
867 softCursorFree();
868
869 if (width * height == 0)
870 return;
871
872// // Ignore cursor shape data if requested by user.
873//
874// if (viewer.options.ignoreCursorUpdates) {
875// if (encodingType == rfb.EncodingXCursor) {
876// rfb.is.skipBytes(6 + bytesMaskData * 2);
877// } else {
878// // rfb.EncodingRichCursor
879// rfb.is.skipBytes(width * height + bytesMaskData);
880// }
881// return;
882// }
883
884 // Decode cursor pixel data.
885
886 softCursorPixels = new int[width * height];
887
888 if (encodingType == rfb.EncodingXCursor) {
889 System.out.println("Reading x cursor");
890
891 // Read foreground and background colors of the cursor.
892 byte[] rgb = new byte[6];
893 rfb.is.readFully(rgb);
894 int[] colors = {(0xFF000000 | (rgb[3] & 0xFF) << 16 |
895 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
896 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
897 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF))
898 };
899 for (int i = 0; i < 2; i++) {
900 System.out.println("Color is " + Integer.toString(colors[i], 16));
901 }
902
903 // Read pixel and mask data.
904 byte[] pixBuf = new byte[bytesMaskData];
905 rfb.is.readFully(pixBuf);
906 byte[] maskBuf = new byte[bytesMaskData];
907 rfb.is.readFully(maskBuf);
908
909 // Decode pixel data into softCursorPixels[].
910 byte pixByte, maskByte;
911 int x, y, n, result;
912 int i = 0;
913 for (y = 0; y < height; y++) {
914 for (x = 0; x < width / 8; x++) {
915 pixByte = pixBuf[y * bytesPerRow + x];
916 maskByte = maskBuf[y * bytesPerRow + x];
917 for (n = 7; n >= 0; n--) {
918 if ((maskByte >> n & 1) != 0) {
919 result = colors[pixByte >> n & 1];
920 } else {
921 result = 0; // Transparent pixel
922 }
923 softCursorPixels[i++] = result;
924 }
925 }
926 for (n = 7; n >= 8 - width % 8; n--) {
927 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
928 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
929 } else {
930 result = 0; // Transparent pixel
931 }
932 softCursorPixels[i++] = result;
933 }
934 }
935
936 } else {
937 // encodingType == rfb.EncodingRichCursor
938 System.out.println("Reading x cursor");
939
940 // Read pixel and mask data.
941 byte[] pixBuf = new byte[width * height * 4];
942 rfb.is.readFully(pixBuf);
943 byte[] maskBuf = new byte[bytesMaskData];
944 rfb.is.readFully(maskBuf);
945
946 // Decode pixel data into softCursorPixels[].
947 byte pixByte, maskByte;
948 int x, y, n, result;
949 int i = 0;
950 for (y = 0; y < height; y++) {
951 for (x = 0; x < width / 8; x++) {
952 maskByte = maskBuf[y * bytesPerRow + x];
953 for (n = 7; n >= 0; n--) {
954 if ((maskByte >> n & 1) != 0) {
955// if (bytesPerPixel == 1) {
956// result = cm8.getRGB(pixBuf[i]);
957// } else {
958 result = (pixBuf[i * 4] & 0xFF) << 24 |
959 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
960 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
961 (pixBuf[i * 4 + 3] & 0xFF);
962 //result = 0xFF000000 |
963 // (pixBuf[i * 4 + 1] & 0xFF) << 16 |
964 // (pixBuf[i * 4 + 2] & 0xFF) << 8 |
965 // (pixBuf[i * 4 + 3] & 0xFF);
966// }
967 } else {
968 result = 0; // Transparent pixel
969 }
970 softCursorPixels[i++] = result;
971 }
972 }
973
974 for (n = 7; n >= 8 - width % 8; n--) {
975 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
976// if (bytesPerPixel == 1) {
977// result = cm8.getRGB(pixBuf[i]);
978// } else {
979 result = 0xFF000000 |
980 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
981 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
982 (pixBuf[i * 4 + 3] & 0xFF);
983// }
984 } else {
985 result = 0; // Transparent pixel
986 }
987 softCursorPixels[i++] = result;
988 }
989 }
990
991 }
992
993 // Draw the cursor on an off-screen image.
994
995 softCursorSource =
996 new MemoryImageSource(width, height, softCursorPixels, 0, width);
997 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
998// if (inputEnabled && viewer.options.scaleCursor != 0) {
999// int w = (width * viewer.options.scaleCursor) / 100;
1000// int h = (height * viewer.options.scaleCursor) / 100;
1001// Image newCursor = softCursor.getScaledInstance(w, h, Image.SCALE_SMOOTH);
1002// softCursor = newCursor;
1003// cursorWidth = w;
1004// cursorHeight = h;
1005// hotX = (xhot * viewer.options.scaleCursor) / 100;
1006// hotY = (yhot * viewer.options.scaleCursor) / 100;
1007// } else {
1008 cursorWidth = width;
1009 cursorHeight = height;
1010 hotX = xhot;
1011 hotY = yhot;
1012// }
1013
1014 // Set data associated with cursor.
1015 origCursorWidth = width;
1016 origCursorHeight = height;
1017 origHotX = xhot;
1018 origHotY = yhot;
1019
1020 showSoftCursor = true;
1021
1022 // Show the cursor.
1023
1024 repaint(deferCursorUpdates,
1025 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1026 }
1027
1028 //
1029 // softCursorMove(). Moves soft cursor into a particular location.
1030 //
1031 synchronized void softCursorMove(int x, int y) {
1032 if (showSoftCursor) {
1033 repaint(deferCursorUpdates,
1034 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1035 repaint(deferCursorUpdates,
1036 x - hotX, y - hotY, cursorWidth, cursorHeight);
1037
1038 // Automatic viewport scrolling
1039// if (viewer.desktopScrollPane != null) {
1040// boolean needScroll = false;
1041// Dimension d = viewer.desktopScrollPane.getSize();
1042// Point topLeft = viewer.desktopScrollPane.getScrollPosition();
1043// Point botRight = new Point(topLeft.x + d.width, topLeft.y + d.height);
1044//
1045// if (x < topLeft.x + SCROLL_MARGIN) {
1046// // shift left
1047// topLeft.x = x - SCROLL_MARGIN;
1048// needScroll = true;
1049// } else if (x > botRight.x - SCROLL_MARGIN) {
1050// // shift right
1051// topLeft.x = x - d.width + SCROLL_MARGIN;
1052// needScroll = true;
1053// }
1054// if (y < topLeft.y + SCROLL_MARGIN) {
1055// // shift up
1056// topLeft.y = y - SCROLL_MARGIN;
1057// needScroll = true;
1058// } else if (y > botRight.y - SCROLL_MARGIN) {
1059// // shift down
1060// topLeft.y = y - d.height + SCROLL_MARGIN;
1061// needScroll = true;
1062// }
1063// viewer.desktopScrollPane.setScrollPosition(topLeft.x, topLeft.y);
1064// }
1065 }
1066
1067 cursorX = x;
1068 cursorY = y;
1069 }
1070 //
1071 // softCursorFree(). Remove soft cursor, dispose resources.
1072 //
1073 synchronized void softCursorFree() {
1074 if (showSoftCursor) {
1075 showSoftCursor = false;
1076 softCursor = null;
1077 softCursorSource = null;
1078 softCursorPixels = null;
1079
1080 repaint(deferCursorUpdates,
1081 cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1082 }
1083 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001084 //
1085 // Tell JVM to repaint specified desktop area.
1086 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001087 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +00001088 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001089 // Do nothing, and remember we are seeking.
1090 seekMode = true;
1091 } else {
1092 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001093 // Immediate repaint of the whole desktop after seeking.
1094 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001095 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001096 // Usual incremental repaint.
1097 repaint(player.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001098 }
1099 seekMode = false;
1100 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001101 }
1102
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +00001103 //
1104 // We are observing our FbsInputStream object to get notified on
1105 // switching to the `paused' mode. In such cases we want to repaint
1106 // our desktop if we were seeking.
1107 //
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +00001108 public void update(Observable o, Object arg) {
1109 // Immediate repaint of the whole desktop after seeking.
1110 repaint();
1111 // Let next scheduleRepaint() call invoke incremental drawing.
1112 seekMode = false;
1113 }
1114
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001115}