blob: 8c3313bf4e7c3ea39a88e2bd69bfae88ce75c88f [file] [log] [blame]
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001//
wimba.comd2ddbc72005-02-11 23:06:24 +00002// Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
3// Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00004// Copyright (C) 2001 Constantin Kaplinsky. All Rights Reserved.
5// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
6// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
7//
8// This is free software; you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by
10// the Free Software Foundation; either version 2 of the License, or
11// (at your option) any later version.
12//
13// This software is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16// GNU General Public License for more details.
17//
18// You should have received a copy of the GNU General Public License
19// along with this software; if not, write to the Free Software
20// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
21// USA.
22//
23
Constantin Kaplinskycf689b32008-04-30 12:50:34 +000024package com.tightvnc.rfbplayer;
wimba.comc23aeb02004-09-16 00:00:00 +000025
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000026import java.awt.*;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000027import java.awt.image.*;
28import java.io.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000029import java.util.*;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000030import java.util.zip.*;
31
32
33//
34// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
35//
wimba.comd2ddbc72005-02-11 23:06:24 +000036class VncCanvas extends Component
37 implements Observer {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000038
wimba.comd2ddbc72005-02-11 23:06:24 +000039 RfbPlayer appClass;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000040 RfbProto rfb;
wimba.comd2ddbc72005-02-11 23:06:24 +000041 ColorModel cm8, cm24;
42 int bytesPixel;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000043
44 Image memImage;
45 Graphics memGraphics;
46
wimba.comd2ddbc72005-02-11 23:06:24 +000047 Image offImage;
48 Graphics offGraphics;
49
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000050 Image rawPixelsImage;
51 MemoryImageSource pixelsSource;
wimba.comd2ddbc72005-02-11 23:06:24 +000052 byte[] pixels8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000053 int[] pixels24;
54
55 // Zlib encoder's data.
56 byte[] zlibBuf;
57 int zlibBufLen = 0;
58 Inflater zlibInflater;
59
60 // Tight encoder's data.
61 final static int tightZlibBufferSize = 512;
62 Inflater[] tightInflaters;
63
64 // Since JPEG images are loaded asynchronously, we have to remember
65 // their position in the framebuffer. Also, this jpegRect object is
66 // used for synchronization between the rfbThread and a JVM's thread
67 // which decodes and loads JPEG images.
68 Rectangle jpegRect;
69
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000070 // When we're in the seeking mode, we should not update the desktop.
71 // This variable helps us to remember that repainting the desktop at
72 // once is necessary when the seek operation is finished.
73 boolean seekMode;
74
wimba.com16092722004-09-21 20:54:37 +000075 // Distance of mouse from viewer border to trigger automatic scrolling
76 final static int SCROLL_MARGIN = 50;
77
wimba.comccb67c32005-01-03 16:20:38 +000078 // Color for border around small shared area
79 static final Color DARK_GRAY = new Color(132, 138, 156);
80
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000081 //
82 // The constructor.
83 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000084 VncCanvas(RfbPlayer player) throws IOException {
wimba.comd2ddbc72005-02-11 23:06:24 +000085 this.appClass = player;
86 rfb = appClass.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000087 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000088
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000089 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
90
wimba.comd2ddbc72005-02-11 23:06:24 +000091 setPixelFormat();
wimba.comccb67c32005-01-03 16:20:38 +000092
wimba.comd2ddbc72005-02-11 23:06:24 +000093 // Style canvas.
wimba.comccb67c32005-01-03 16:20:38 +000094 setBackground(Color.white);
wimba.comd2ddbc72005-02-11 23:06:24 +000095
96 }
97
98 //
99 // Override the 1.4 focus traversal keys accesor to allow
100 // our keyListener to receive tab presses.
101 //
102 public boolean getFocusTraversalKeysEnabled() {
103 return false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000104 }
105
106 //
107 // Callback methods to determine geometry of our Component.
108 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000109 public Dimension getPreferredSize() {
wimba.comd2ddbc72005-02-11 23:06:24 +0000110 Dimension d = appClass.desktopScrollPane.getViewportSize();
wimba.comccb67c32005-01-03 16:20:38 +0000111 Dimension sz = new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
112 if (d.width > sz.width)
113 sz.width = d.width;
114 if (d.height > sz.height)
115 sz.height = d.height;
116 return sz;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000117 }
118
119 public Dimension getMinimumSize() {
wimba.comccb67c32005-01-03 16:20:38 +0000120 return getPreferredSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000121 }
122
123 public Dimension getMaximumSize() {
wimba.comccb67c32005-01-03 16:20:38 +0000124 return getPreferredSize();
125 }
126
127 Point getImageOrigin() {
128 int x = 0, y = 0;
wimba.comd2ddbc72005-02-11 23:06:24 +0000129 Dimension d = appClass.desktopScrollPane.getViewportSize();
wimba.comccb67c32005-01-03 16:20:38 +0000130 if (rfb.framebufferWidth < d.width)
131 x = d.width / 2 - rfb.framebufferWidth / 2;
132 if (rfb.framebufferHeight < d.height)
133 y = d.height / 2 - rfb.framebufferHeight / 2;
134 return new Point(x, y);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000135 }
136
137 //
138 // All painting is performed here.
139 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000140 public void update(Graphics g) {
141 paint(g);
142 }
143
wimba.comd2ddbc72005-02-11 23:06:24 +0000144 public void paint(Graphics g) {
wimba.comccb67c32005-01-03 16:20:38 +0000145 Point o = getImageOrigin();
146 Dimension d = getPreferredSize();
wimba.comccb67c32005-01-03 16:20:38 +0000147
wimba.comd2ddbc72005-02-11 23:06:24 +0000148 // create new offscreen
149 if (offImage == null) {
150 offImage = createImage(d.width, d.height);
151 offGraphics = offImage.getGraphics();
152 } else if (offImage.getWidth(null) != d.width ||
153 offImage.getHeight(null) != d.height) {
154 offGraphics.dispose();
155 offGraphics = null;
156 offImage.flush();
157 offImage = null;
158 offImage = createImage(d.width, d.height);
159 offGraphics = offImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000160 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000161
162 synchronized(memImage) {
163 offGraphics.drawImage(memImage, o.x, o.y, null);
164 }
165
166 // fill in background
167 if (o.x > 0 || o.y > 0) {
168 // left, right, top, bottom
169 Color c = g.getColor();
170 offGraphics.setColor(Color.white);
171 offGraphics.fillRect(0, 0, o.x, d.height);
172 offGraphics.fillRect(o.x + rfb.framebufferWidth, 0, d.width - (o.x +
173 rfb.framebufferWidth), d.height);
174 offGraphics.fillRect(o.x, 0, rfb.framebufferWidth, o.y);
175 offGraphics.fillRect(o.x, o.y + rfb.framebufferHeight,
176 rfb.framebufferWidth, d.height - (o.y +
177 rfb.framebufferHeight));
178 offGraphics.setColor(c);
179 }
180
181 // draw border
182 Color oc = g.getColor();
183 offGraphics.setColor(DARK_GRAY);
184 offGraphics.drawRect(o.x - 1, o.y - 1, rfb.framebufferWidth + 1,
185 rfb.framebufferHeight + 1);
186 offGraphics.setColor(oc);
187
188 // draw cursor
wimba.coma5a4f4f2004-09-21 15:25:05 +0000189 if (showSoftCursor) {
190 int x0 = cursorX - hotX, y0 = cursorY - hotY;
wimba.comccb67c32005-01-03 16:20:38 +0000191 x0 += o.x;
192 y0 += o.y;
wimba.coma5a4f4f2004-09-21 15:25:05 +0000193 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
194 if (r.intersects(g.getClipBounds())) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000195 offGraphics.setClip(o.x, o.y, rfb.framebufferWidth,
196 rfb.framebufferHeight);
197 offGraphics.drawImage(softCursor, x0, y0, null);
198 offGraphics.setClip(null);
wimba.coma5a4f4f2004-09-21 15:25:05 +0000199 }
200 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000201
202 // draw offscreen
203 g.drawImage(offImage, 0, 0, null);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000204 }
205
206 //
207 // Override the ImageObserver interface method to handle drawing of
208 // JPEG-encoded data.
209 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000210 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000211 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000212 if ((infoflags & (ALLBITS | ABORT)) == 0) {
213 return true; // We need more image data.
214 } else {
215 // If the whole image is available, draw it now.
216 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000217 if (jpegRect != null) {
218 synchronized(jpegRect) {
219 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
220 scheduleRepaint(jpegRect.x, jpegRect.y,
221 jpegRect.width, jpegRect.height);
222 jpegRect.notify();
223 }
224 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000225 }
226 return false; // All image data was processed.
227 }
228 }
229
wimba.comd2ddbc72005-02-11 23:06:24 +0000230 private void setPixelFormat() throws IOException {
231 bytesPixel = 4;
232 updateFramebufferSize();
233 }
234
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000235 void updateFramebufferSize() {
236
237 // Useful shortcuts.
238 int fbWidth = rfb.framebufferWidth;
239 int fbHeight = rfb.framebufferHeight;
240
241 // Create new off-screen image either if it does not exist, or if
242 // its geometry should be changed. It's not necessary to replace
243 // existing image if only pixel format should be changed.
244 if (memImage == null) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000245 memImage = appClass.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000246 memGraphics = memImage.getGraphics();
247 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000248 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000249 synchronized(memImage) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000250 memImage = appClass.createImage(fbWidth, fbHeight);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000251 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000252 }
253 }
254
255 // Images with raw pixels should be re-allocated on every change
256 // of geometry or pixel format.
wimba.comd2ddbc72005-02-11 23:06:24 +0000257 if (bytesPixel == 1) {
258 pixels24 = null;
259 pixels8 = new byte[fbWidth * fbHeight];
260
261 pixelsSource =
262 new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
263 } else {
264 pixels8 = null;
265 pixels24 = new int[fbWidth * fbHeight];
266
267 pixelsSource =
268 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
269 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000270 pixelsSource.setAnimated(true);
271 rawPixelsImage = createImage(pixelsSource);
272
wimba.comeee33e32005-01-07 20:54:33 +0000273 // Update the size of desktop containers unless seeking
wimba.comd2ddbc72005-02-11 23:06:24 +0000274 if (!seekMode && appClass.desktopScrollPane != null) {
275 if (appClass.inSeparateFrame) {
276 resizeDesktopFrame();
wimba.comeee33e32005-01-07 20:54:33 +0000277 } else {
wimba.comd2ddbc72005-02-11 23:06:24 +0000278 resizeEmbeddedApplet();
279 // if the framebuffer shrunk, need to clear and repaint
280 appClass.desktopScrollPane.clearAndRepaint();
wimba.comeee33e32005-01-07 20:54:33 +0000281 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000282 }
283 }
284
285 void resizeDesktopFrame() {
wimba.com252eb3b2004-09-21 21:27:54 +0000286 // determine screen size
wimba.comd2ddbc72005-02-11 23:06:24 +0000287 Dimension screenSize = appClass.vncFrame.getToolkit().getScreenSize();
288 Dimension scrollSize = appClass.desktopScrollPane.getSize();
wimba.comc23aeb02004-09-16 00:00:00 +0000289
290 // Reduce Screen Size by 30 pixels in each direction;
291 // This is a (poor) attempt to account for
292 // 1) Menu bar on Macintosh (should really also account for
293 // Dock on OSX). Usually 22px on top of screen.
wimba.com252eb3b2004-09-21 21:27:54 +0000294 // 2) Taxkbar on Windows (usually about 28 px on bottom)
295 // 3) Other obstructions.
wimba.comc23aeb02004-09-16 00:00:00 +0000296 screenSize.height -= 30;
297 screenSize.width -= 30;
298
wimba.com252eb3b2004-09-21 21:27:54 +0000299 // Further reduce the screen size to account for the
300 // scroll pane's insets.
wimba.comd2ddbc72005-02-11 23:06:24 +0000301 Insets insets = appClass.desktopScrollPane.getInsets();
wimba.com252eb3b2004-09-21 21:27:54 +0000302 screenSize.height -= insets.top + insets.bottom;
303 screenSize.width -= insets.left + insets.right;
wimba.comc23aeb02004-09-16 00:00:00 +0000304
wimba.com252eb3b2004-09-21 21:27:54 +0000305 // Limit pane size to fit on screen.
306 boolean needResize = false;
307 if (scrollSize.width != rfb.framebufferWidth ||
308 scrollSize.height != rfb.framebufferHeight)
309 needResize = true;
310 int w = rfb.framebufferWidth, h = rfb.framebufferHeight;
311 if (w > screenSize.width) {
312 w = screenSize.width;
313 needResize = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000314 }
wimba.com252eb3b2004-09-21 21:27:54 +0000315 if (h > screenSize.height) {
316 h = screenSize.height;
317 needResize = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000318 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000319 if (needResize) {
320 appClass.desktopScrollPane.setSize(w, h);
321 appClass.desktopScrollPane.validate();
322 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000323
wimba.comccb67c32005-01-03 16:20:38 +0000324 // size the canvas
325 setSize(getPreferredSize());
wimba.come8e19102005-02-08 17:15:23 +0000326
wimba.comd2ddbc72005-02-11 23:06:24 +0000327 appClass.vncFrame.pack();
wimba.com252eb3b2004-09-21 21:27:54 +0000328 }
329
330 void resizeEmbeddedApplet() {
wimba.com252eb3b2004-09-21 21:27:54 +0000331 // resize scroll pane if necessary
wimba.comd2ddbc72005-02-11 23:06:24 +0000332 Dimension scrollSize = appClass.desktopScrollPane.getSize();
333 if (scrollSize.width != appClass.dispW ||
334 scrollSize.height != appClass.dispH) {
335 appClass.desktopScrollPane.setSize(appClass.dispW, appClass.dispH);
wimba.comccb67c32005-01-03 16:20:38 +0000336 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000337 appClass.desktopScrollPane.validate();
wimba.comccb67c32005-01-03 16:20:38 +0000338 // size the canvas
339 setSize(getPreferredSize());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000340 }
341
342 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000343 // processNormalProtocol() - executed by the rfbThread to deal with
344 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000345 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000346 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000347
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000348 zlibInflater = new Inflater();
349 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000350
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000351 // Show current time position in the control panel.
wimba.comd2ddbc72005-02-11 23:06:24 +0000352 appClass.updatePos();
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000353
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000354 // Tell our FbsInputStream object to notify us when it goes to the
355 // `paused' mode.
356 rfb.fbs.addObserver(this);
357
wimba.comd2ddbc72005-02-11 23:06:24 +0000358 //
359 // main dispatch loop
360 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000361
362 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000363
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000364 int msgType = rfb.readServerMessageType();
365
wimba.comd2ddbc72005-02-11 23:06:24 +0000366 // Process the message depending on its type.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000367 switch (msgType) {
368 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000369 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000370
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000371 for (int i = 0; i < rfb.updateNRects; i++) {
372 rfb.readFramebufferUpdateRectHdr();
373 int rx = rfb.updateRectX, ry = rfb.updateRectY;
374 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000375
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000376 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
377 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000378
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000379 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000380 if (rw != 0 && rh != 0) {
381 rfb.setFramebufferSize(rw, rh);
wimba.comb7017b72004-09-16 16:11:55 +0000382 updateFramebufferSize();
383 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000384 break;
385 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000386
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000387 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
388 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000389 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
390 continue;
391 }
wimba.coma5a4f4f2004-09-21 15:25:05 +0000392
393 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
394 softCursorMove(rx, ry);
wimba.coma5a4f4f2004-09-21 15:25:05 +0000395 continue;
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000396 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000397
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000398 switch (rfb.updateRectEncoding) {
399 case RfbProto.EncodingRaw:
400 handleRawRect(rx, ry, rw, rh);
401 break;
402 case RfbProto.EncodingCopyRect:
403 handleCopyRect(rx, ry, rw, rh);
404 break;
405 case RfbProto.EncodingRRE:
406 handleRRERect(rx, ry, rw, rh);
407 break;
408 case RfbProto.EncodingCoRRE:
409 handleCoRRERect(rx, ry, rw, rh);
410 break;
411 case RfbProto.EncodingHextile:
412 handleHextileRect(rx, ry, rw, rh);
413 break;
414 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000415 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000416 break;
417 case RfbProto.EncodingTight:
418 handleTightRect(rx, ry, rw, rh);
419 break;
420 default:
421 throw new Exception("Unknown RFB rectangle encoding " +
wimba.coma5a4f4f2004-09-21 15:25:05 +0000422 Integer.toString(rfb.updateRectEncoding, 16));
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000423 }
424 }
425 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000426
427 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000428 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000429
430 case RfbProto.Bell:
431 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000432 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000433
434 case RfbProto.ServerCutText:
wimba.com8091a2f2007-11-19 19:05:01 +0000435 rfb.readServerCutText();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000436 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000437
438 default:
wimba.comd2ddbc72005-02-11 23:06:24 +0000439 throw new Exception("Unknown RFB message type " + msgType);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000440 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000441
wimba.comd2ddbc72005-02-11 23:06:24 +0000442 appClass.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000443 }
444 }
445
446
447 //
wimba.comd2ddbc72005-02-11 23:06:24 +0000448 // Handle a raw rectangle. The second form with paint==false is used
449 // by the Hextile decoder for raw-encoded tiles.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000450 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000451 void handleRawRect(int x, int y, int w, int h) throws IOException {
wimba.comd2ddbc72005-02-11 23:06:24 +0000452 handleRawRect(x, y, w, h, true);
453 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000454
wimba.comd2ddbc72005-02-11 23:06:24 +0000455 void handleRawRect(int x, int y, int w, int h, boolean paint)
456 throws IOException {
457
458 if (bytesPixel == 1) {
459 for (int dy = y; dy < y + h; dy++) {
460 rfb.is.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
461 //if (rfb.rec != null) {
462 // rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
463 //}
464 }
465 } else {
466 byte[] buf = new byte[w * 4];
467 int i, offset;
468 for (int dy = y; dy < y + h; dy++) {
469 rfb.is.readFully(buf);
470 //if (rfb.rec != null) {
471 // rfb.rec.write(buf);
472 //}
473 offset = dy * rfb.framebufferWidth + x;
474 for (i = 0; i < w; i++) {
475 pixels24[offset + i] =
476 (buf[i * 4 + 2] & 0xFF) << 16 |
477 (buf[i * 4 + 1] & 0xFF) << 8 |
478 (buf[i * 4] & 0xFF);
479 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000480 }
481 }
482
483 handleUpdatedPixels(x, y, w, h);
wimba.comd2ddbc72005-02-11 23:06:24 +0000484 if (paint)
485 scheduleRepaint(x, y, w, h);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000486 }
487
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000488 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000489 // Handle a CopyRect rectangle.
490 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000491 void handleCopyRect(int x, int y, int w, int h) throws IOException {
492
493 rfb.readCopyRect();
494 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000495 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000496
497 scheduleRepaint(x, y, w, h);
498 }
499
500 //
501 // Handle an RRE-encoded rectangle.
502 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000503 void handleRRERect(int x, int y, int w, int h) throws IOException {
504
505 int nSubrects = rfb.is.readInt();
506 int sx, sy, sw, sh;
507
508 byte[] buf = new byte[4];
509 rfb.is.readFully(buf);
510 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
511 memGraphics.setColor(pixel);
512 memGraphics.fillRect(x, y, w, h);
513
514 for (int j = 0; j < nSubrects; j++) {
515 rfb.is.readFully(buf);
516 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
517 sx = x + rfb.is.readUnsignedShort();
518 sy = y + rfb.is.readUnsignedShort();
519 sw = rfb.is.readUnsignedShort();
520 sh = rfb.is.readUnsignedShort();
521
522 memGraphics.setColor(pixel);
523 memGraphics.fillRect(sx, sy, sw, sh);
524 }
525
526 scheduleRepaint(x, y, w, h);
527 }
528
529 //
530 // Handle a CoRRE-encoded rectangle.
531 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000532 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000533 int nSubrects = rfb.is.readInt();
534 int sx, sy, sw, sh;
535
536 byte[] buf = new byte[4];
537 rfb.is.readFully(buf);
538 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
539 memGraphics.setColor(pixel);
540 memGraphics.fillRect(x, y, w, h);
541
542 for (int j = 0; j < nSubrects; j++) {
543 rfb.is.readFully(buf);
544 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
545 sx = x + rfb.is.readUnsignedByte();
546 sy = y + rfb.is.readUnsignedByte();
547 sw = rfb.is.readUnsignedByte();
548 sh = rfb.is.readUnsignedByte();
549
550 memGraphics.setColor(pixel);
551 memGraphics.fillRect(sx, sy, sw, sh);
552 }
553
554 scheduleRepaint(x, y, w, h);
555 }
556
557 //
558 // Handle a Hextile-encoded rectangle.
559 //
560
561 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000562 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000563
564 void handleHextileRect(int x, int y, int w, int h) throws IOException {
565
wimba.comd2ddbc72005-02-11 23:06:24 +0000566 hextile_bg = new Color(0);
567 hextile_fg = new Color(0);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000568
569 for (int ty = y; ty < y + h; ty += 16) {
570 int th = 16;
571 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000572 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000573
574 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000575 int tw = 16;
576 if (x + w - tx < 16)
577 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000578
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000579 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000580 }
581
582 // Finished with a row of tiles, now let's show it.
583 scheduleRepaint(x, y, w, h);
584 }
585 }
586
587 //
588 // Handle one tile in the Hextile-encoded data.
589 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000590 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000591 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000592
593 byte[] buf = new byte[256 * 4];
594
595 int subencoding = rfb.is.readUnsignedByte();
596
597 // Is it a raw-encoded sub-rectangle?
598 if ((subencoding & rfb.HextileRaw) != 0) {
599 int count, offset;
600 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000601 rfb.is.readFully(buf, 0, tw * 4);
602 offset = j * rfb.framebufferWidth + tx;
603 for (count = 0; count < tw; count++) {
604 pixels24[offset + count] =
605 (buf[count * 4 + 2] & 0xFF) << 16 |
606 (buf[count * 4 + 1] & 0xFF) << 8 |
607 (buf[count * 4] & 0xFF);
608 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000609 }
610 handleUpdatedPixels(tx, ty, tw, th);
611 return;
612 }
613
614 // Read and draw the background if specified.
615 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
616 rfb.is.readFully(buf, 0, 4);
617 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
618 }
619 memGraphics.setColor(hextile_bg);
620 memGraphics.fillRect(tx, ty, tw, th);
621
622 // Read the foreground color if specified.
623 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
624 rfb.is.readFully(buf, 0, 4);
625 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
626 }
627
628 // Done with this tile if there is no sub-rectangles.
629 if ((subencoding & rfb.HextileAnySubrects) == 0)
630 return;
631
632 int nSubrects = rfb.is.readUnsignedByte();
633
634 int b1, b2, sx, sy, sw, sh;
635 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
636 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000637 rfb.is.readFully(buf, 0, 4);
638 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
639 b1 = rfb.is.readUnsignedByte();
640 b2 = rfb.is.readUnsignedByte();
641 sx = tx + (b1 >> 4);
642 sy = ty + (b1 & 0xf);
643 sw = (b2 >> 4) + 1;
644 sh = (b2 & 0xf) + 1;
645 memGraphics.setColor(hextile_fg);
646 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000647 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000648
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000649 } else {
650 memGraphics.setColor(hextile_fg);
651 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000652 b1 = rfb.is.readUnsignedByte();
653 b2 = rfb.is.readUnsignedByte();
654 sx = tx + (b1 >> 4);
655 sy = ty + (b1 & 0xf);
656 sw = (b2 >> 4) + 1;
657 sh = (b2 & 0xf) + 1;
658 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000659 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000660
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000661 }
662 }
663
664 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000665 // Handle a Zlib-encoded rectangle.
666 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000667 void handleZlibRect(int x, int y, int w, int h) throws Exception {
668
669 int nBytes = rfb.is.readInt();
670
671 if (zlibBuf == null || zlibBufLen < nBytes) {
672 zlibBufLen = nBytes * 2;
673 zlibBuf = new byte[zlibBufLen];
674 }
675
676 rfb.is.readFully(zlibBuf, 0, nBytes);
677 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000678
679 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000680 byte[] buf = new byte[w * 4];
681 int i, offset;
682 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000683 zlibInflater.inflate(buf);
684 offset = dy * rfb.framebufferWidth + x;
685 for (i = 0; i < w; i++) {
686 pixels24[offset + i] =
687 (buf[i * 4 + 2] & 0xFF) << 16 |
688 (buf[i * 4 + 1] & 0xFF) << 8 |
689 (buf[i * 4] & 0xFF);
690 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000691 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000692 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000693 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000694 }
695
696 handleUpdatedPixels(x, y, w, h);
697 scheduleRepaint(x, y, w, h);
698 }
699
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000700 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000701 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000702 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000703 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000704
705 int comp_ctl = rfb.is.readUnsignedByte();
706
707 // Flush zlib streams if we are told by the server to do so.
708 for (int stream_id = 0; stream_id < 4; stream_id++) {
709 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000710 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000711 }
712 comp_ctl >>= 1;
713 }
714
715 // Check correctness of subencoding value.
716 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000717 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000718 }
719
720 // Handle solid-color rectangles.
721 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000722 byte[] buf = new byte[3];
723 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000724 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000725 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000726 memGraphics.fillRect(x, y, w, h);
727 scheduleRepaint(x, y, w, h);
728 return;
wimba.comd2ddbc72005-02-11 23:06:24 +0000729
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000730 }
731
732 if (comp_ctl == rfb.TightJpeg) {
733
734 // Read JPEG data.
735 byte[] jpegData = new byte[rfb.readCompactLen()];
736 rfb.is.readFully(jpegData);
737
738 // Create an Image object from the JPEG data.
739 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
740
741 // Remember the rectangle where the image should be drawn.
742 jpegRect = new Rectangle(x, y, w, h);
743
744 // Let the imageUpdate() method do the actual drawing, here just
745 // wait until the image is fully loaded and drawn.
746 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000747 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
748 try {
749 // Wait no longer than three seconds.
750 jpegRect.wait(3000);
751 } catch (InterruptedException e) {
752 throw new Exception("Interrupted while decoding JPEG image");
753 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000754 }
755
756 // Done, jpegRect is not needed any more.
757 jpegRect = null;
758 return;
759
760 }
761
762 // Read filter id and parameters.
763 int numColors = 0, rowSize = w;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000764 int[] palette24 = new int[256];
765 boolean useGradient = false;
766 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
767 int filter_id = rfb.is.readUnsignedByte();
768 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000769 numColors = rfb.is.readUnsignedByte() + 1;
770 byte[] buf = new byte[numColors * 3];
771 rfb.is.readFully(buf);
772 for (int i = 0; i < numColors; i++) {
773 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
774 (buf[i * 3 + 1] & 0xFF) << 8 |
775 (buf[i * 3 + 2] & 0xFF));
776 }
777 if (numColors == 2)
778 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000779 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000780 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000781 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000782 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000783 }
784 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000785 if (numColors == 0 && bytesPixel == 4)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000786 rowSize *= 3;
787
788 // Read, optionally uncompress and decode data.
789 int dataSize = h * rowSize;
790 if (dataSize < rfb.TightMinToCompress) {
791 // Data size is small - not compressed with zlib.
792 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000793 // Indexed colors.
794 byte[] indexedData = new byte[dataSize];
795 rfb.is.readFully(indexedData);
796 if (numColors == 2) {
797 // Two colors.
wimba.comd2ddbc72005-02-11 23:06:24 +0000798 //if (bytesPixel == 1) {
799 // decodeMonoData(x, y, w, h, indexedData, palette8);
800 //} else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000801 decodeMonoData(x, y, w, h, indexedData, palette24);
wimba.comd2ddbc72005-02-11 23:06:24 +0000802 //}
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000803 } else {
wimba.comd2ddbc72005-02-11 23:06:24 +0000804 // 3..255 colors (assuming bytesPixel == 4).
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000805 int i = 0;
806 for (int dy = y; dy < y + h; dy++) {
807 for (int dx = x; dx < x + w; dx++) {
808 pixels24[dy * rfb.framebufferWidth + dx] =
809 palette24[indexedData[i++] & 0xFF];
810 }
811 }
812 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000813 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000814 // "Gradient"-processed data
815 byte[] buf = new byte[w * h * 3];
816 rfb.is.readFully(buf);
817 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000818 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000819 // Raw truecolor data.
wimba.comd2ddbc72005-02-11 23:06:24 +0000820 if (bytesPixel == 1) {
821 for (int dy = y; dy < y + h; dy++) {
822 rfb.is.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
823 //if (rfb.rec != null) {
824 // rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
825 //}
826 }
827 } else {
828 byte[] buf = new byte[w * 3];
829 int i, offset;
830 for (int dy = y; dy < y + h; dy++) {
831 rfb.is.readFully(buf);
832 //if (rfb.rec != null) {
833 // rfb.rec.write(buf);
834 //}
835 offset = dy * rfb.framebufferWidth + x;
836 for (i = 0; i < w; i++) {
837 pixels24[offset + i] =
838 (buf[i * 3] & 0xFF) << 16 |
839 (buf[i * 3 + 1] & 0xFF) << 8 |
840 (buf[i * 3 + 2] & 0xFF);
841 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000842 }
843 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000844 }
845 } else {
846 // Data was compressed with zlib.
847 int zlibDataLen = rfb.readCompactLen();
848 byte[] zlibData = new byte[zlibDataLen];
849 rfb.is.readFully(zlibData);
850 int stream_id = comp_ctl & 0x03;
851 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000852 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000853 }
854 Inflater myInflater = tightInflaters[stream_id];
855 myInflater.setInput(zlibData);
856 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000857 if (numColors != 0) {
858 // Indexed colors.
859 byte[] indexedData = new byte[dataSize];
860 myInflater.inflate(indexedData);
861 if (numColors == 2) {
862 // Two colors.
863 decodeMonoData(x, y, w, h, indexedData, palette24);
864 } else {
865 // More than two colors.
866 int i = 0;
867 for (int dy = y; dy < y + h; dy++) {
868 for (int dx = x; dx < x + w; dx++) {
869 pixels24[dy * rfb.framebufferWidth + dx] =
870 palette24[indexedData[i++] & 0xFF];
871 }
872 }
873 }
874 } else if (useGradient) {
875 // Compressed "Gradient"-filtered data.
876 byte[] buf = new byte[w * h * 3];
877 myInflater.inflate(buf);
878 decodeGradientData(x, y, w, h, buf);
879 } else {
880 // Compressed truecolor data.
881 byte[] buf = new byte[w * 3];
882 int i, offset;
883 for (int dy = y; dy < y + h; dy++) {
884 myInflater.inflate(buf);
885 offset = dy * rfb.framebufferWidth + x;
886 for (i = 0; i < w; i++) {
887 pixels24[offset + i] =
888 (buf[i * 3] & 0xFF) << 16 |
889 (buf[i * 3 + 1] & 0xFF) << 8 |
890 (buf[i * 3 + 2] & 0xFF);
891 }
892 }
893 }
894 } catch (DataFormatException dfe) {
895 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000896 }
897 }
898
899 handleUpdatedPixels(x, y, w, h);
900 scheduleRepaint(x, y, w, h);
901 }
902
903 //
wimba.comd2ddbc72005-02-11 23:06:24 +0000904 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000905 //
wimba.comd2ddbc72005-02-11 23:06:24 +0000906 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
907
908 int dx, dy, n;
909 int i = y * rfb.framebufferWidth + x;
910 int rowBytes = (w + 7) / 8;
911 byte b;
912
913 for (dy = 0; dy < h; dy++) {
914 for (dx = 0; dx < w / 8; dx++) {
915 b = src[dy * rowBytes + dx];
916 for (n = 7; n >= 0; n--)
917 pixels8[i++] = palette[b >> n & 1];
918 }
919 for (n = 7; n >= 8 - w % 8; n--) {
920 pixels8[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
921 }
922 i += (rfb.framebufferWidth - w);
923 }
924 }
925
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000926 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000927
928 int dx, dy, n;
929 int i = y * rfb.framebufferWidth + x;
930 int rowBytes = (w + 7) / 8;
931 byte b;
932
933 for (dy = 0; dy < h; dy++) {
934 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000935 b = src[dy * rowBytes + dx];
936 for (n = 7; n >= 0; n--)
937 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000938 }
939 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000940 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000941 }
942 i += (rfb.framebufferWidth - w);
943 }
944 }
945
946 //
947 // Decode data processed with the "Gradient" filter.
948 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000949 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000950
951 int dx, dy, c;
952 byte[] prevRow = new byte[w * 3];
953 byte[] thisRow = new byte[w * 3];
954 byte[] pix = new byte[3];
955 int[] est = new int[3];
956
957 int offset = y * rfb.framebufferWidth + x;
958
959 for (dy = 0; dy < h; dy++) {
960
961 /* First pixel in a row */
962 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000963 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
964 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000965 }
966 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000967 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000968
969 /* Remaining pixels of a row */
970 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000971 for (c = 0; c < 3; c++) {
972 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
973 (prevRow[(dx - 1) * 3 + c] & 0xFF));
974 if (est[c] > 0xFF) {
975 est[c] = 0xFF;
976 } else if (est[c] < 0x00) {
977 est[c] = 0x00;
978 }
979 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
980 thisRow[dx * 3 + c] = pix[c];
981 }
982 pixels24[offset++] =
983 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000984 }
985
986 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
987 offset += (rfb.framebufferWidth - w);
988 }
989 }
990
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000991 //
992 // Display newly updated area of pixels.
993 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000994 void handleUpdatedPixels(int x, int y, int w, int h) {
995
996 // Draw updated pixels of the off-screen image.
997 pixelsSource.newPixels(x, y, w, h);
998 memGraphics.setClip(x, y, w, h);
999 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1000 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1001 }
1002
wimba.comd2ddbc72005-02-11 23:06:24 +00001003 //
1004 // Tell JVM to repaint specified desktop area.
1005 //
1006 void scheduleRepaint(int x, int y, int w, int h) {
1007 if (rfb.fbs.isSeeking()) {
1008 // Do nothing, and remember we are seeking.
1009 seekMode = true;
1010 } else {
1011 if (seekMode) {
1012 // Immediate repaint of the whole desktop after seeking.
1013 seekMode = false;
1014 if (showSoftCursor)
1015 scrollToPoint(cursorX, cursorY);
1016 updateFramebufferSize();
1017 repaint();
1018 } else {
1019 // Usual incremental repaint.
1020 Point o = getImageOrigin();
1021 repaint(appClass.deferScreenUpdates, o.x + x, o.y + y, w, h);
1022 }
1023 }
1024 }
1025
1026
wimba.coma5a4f4f2004-09-21 15:25:05 +00001027 //////////////////////////////////////////////////////////////////
1028 //
1029 // Handle cursor shape updates (XCursor and RichCursor encodings).
1030 //
1031 boolean showSoftCursor = false;
1032
wimba.coma5a4f4f2004-09-21 15:25:05 +00001033 MemoryImageSource softCursorSource;
1034 Image softCursor;
1035
1036 int cursorX = 0, cursorY = 0;
1037 int cursorWidth, cursorHeight;
1038 int origCursorWidth, origCursorHeight;
1039 int hotX, hotY;
1040 int origHotX, origHotY;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001041
1042 //
1043 // Handle cursor shape update (XCursor and RichCursor encodings).
1044 //
1045 synchronized void handleCursorShapeUpdate(int encodingType,
1046 int xhot, int yhot, int width,
1047 int height)
1048 throws IOException {
1049
wimba.coma5a4f4f2004-09-21 15:25:05 +00001050 softCursorFree();
1051
1052 if (width * height == 0)
1053 return;
1054
wimba.comd2ddbc72005-02-11 23:06:24 +00001055 // Ignore cursor shape data if requested by user.
1056 //if (appClass.options.ignoreCursorUpdates) {
1057 if (false) {
1058 int bytesPerRow = (width + 7) / 8;
1059 int bytesMaskData = bytesPerRow * height;
1060
1061 if (encodingType == rfb.EncodingXCursor) {
1062 rfb.is.skipBytes(6 + bytesMaskData * 2);
1063 } else {
1064 // rfb.EncodingRichCursor
1065 rfb.is.skipBytes(width * height + bytesMaskData);
1066 }
1067 return;
1068 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001069
1070 // Decode cursor pixel data.
wimba.comd2ddbc72005-02-11 23:06:24 +00001071 softCursorSource = decodeCursorShape(encodingType, width, height);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001072
wimba.comd2ddbc72005-02-11 23:06:24 +00001073 // Set original (non-scaled) cursor dimensions.
1074 origCursorWidth = width;
1075 origCursorHeight = height;
1076 origHotX = xhot;
1077 origHotY = yhot;
1078
1079 // Create off-screen cursor image.
1080 createSoftCursor();
1081
1082 // Show the cursor.
1083 showSoftCursor = true;
1084 Point p = getImageOrigin();
1085 scheduleCursorRepaint(cursorX - hotX + p.x, cursorY - hotY + p.y,
1086 cursorWidth, cursorHeight, 5);
1087 }
1088
1089 //
1090 // decodeCursorShape(). Decode cursor pixel data and return
1091 // corresponding MemoryImageSource instance.
1092 //
1093 synchronized MemoryImageSource decodeCursorShape(int encodingType, int width,
1094 int height)
1095 throws IOException {
1096
1097 int bytesPerRow = (width + 7) / 8;
1098 int bytesMaskData = bytesPerRow * height;
1099
1100 int[] softCursorPixels = new int[width * height];
wimba.coma5a4f4f2004-09-21 15:25:05 +00001101
1102 if (encodingType == rfb.EncodingXCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +00001103
1104 // Read foreground and background colors of the cursor.
1105 byte[] rgb = new byte[6];
1106 rfb.is.readFully(rgb);
1107 int[] colors = {(0xFF000000 | (rgb[3] & 0xFF) << 16 |
1108 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1109 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1110 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF))
1111 };
wimba.coma5a4f4f2004-09-21 15:25:05 +00001112
1113 // Read pixel and mask data.
1114 byte[] pixBuf = new byte[bytesMaskData];
1115 rfb.is.readFully(pixBuf);
1116 byte[] maskBuf = new byte[bytesMaskData];
1117 rfb.is.readFully(maskBuf);
1118
1119 // Decode pixel data into softCursorPixels[].
1120 byte pixByte, maskByte;
1121 int x, y, n, result;
1122 int i = 0;
1123 for (y = 0; y < height; y++) {
1124 for (x = 0; x < width / 8; x++) {
1125 pixByte = pixBuf[y * bytesPerRow + x];
1126 maskByte = maskBuf[y * bytesPerRow + x];
1127 for (n = 7; n >= 0; n--) {
1128 if ((maskByte >> n & 1) != 0) {
1129 result = colors[pixByte >> n & 1];
1130 } else {
1131 result = 0; // Transparent pixel
1132 }
1133 softCursorPixels[i++] = result;
1134 }
1135 }
1136 for (n = 7; n >= 8 - width % 8; n--) {
1137 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1138 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1139 } else {
1140 result = 0; // Transparent pixel
1141 }
1142 softCursorPixels[i++] = result;
1143 }
1144 }
1145
1146 } else {
1147 // encodingType == rfb.EncodingRichCursor
wimba.coma5a4f4f2004-09-21 15:25:05 +00001148
1149 // Read pixel and mask data.
wimba.comd2ddbc72005-02-11 23:06:24 +00001150 byte[] pixBuf = new byte[width * height * bytesPixel];
wimba.coma5a4f4f2004-09-21 15:25:05 +00001151 rfb.is.readFully(pixBuf);
1152 byte[] maskBuf = new byte[bytesMaskData];
1153 rfb.is.readFully(maskBuf);
1154
1155 // Decode pixel data into softCursorPixels[].
wimba.com8091a2f2007-11-19 19:05:01 +00001156 byte maskByte;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001157 int x, y, n, result;
1158 int i = 0;
1159 for (y = 0; y < height; y++) {
1160 for (x = 0; x < width / 8; x++) {
1161 maskByte = maskBuf[y * bytesPerRow + x];
1162 for (n = 7; n >= 0; n--) {
1163 if ((maskByte >> n & 1) != 0) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001164 if (bytesPixel == 1) {
1165 result = cm8.getRGB(pixBuf[i]);
1166 } else {
1167 result = (pixBuf[i * 4] & 0xFF) << 24 |
1168 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1169 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1170 (pixBuf[i * 4 + 3] & 0xFF);
1171 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001172 } else {
1173 result = 0; // Transparent pixel
1174 }
1175 softCursorPixels[i++] = result;
1176 }
1177 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001178 for (n = 7; n >= 8 - width % 8; n--) {
1179 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001180 if (bytesPixel == 1) {
1181 result = cm8.getRGB(pixBuf[i]);
1182 } else {
1183 result = 0xFF000000 |
1184 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1185 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1186 (pixBuf[i * 4 + 3] & 0xFF);
1187 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001188 } else {
1189 result = 0; // Transparent pixel
1190 }
1191 softCursorPixels[i++] = result;
1192 }
1193 }
1194
1195 }
1196
wimba.comd2ddbc72005-02-11 23:06:24 +00001197 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1198 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001199
wimba.comd2ddbc72005-02-11 23:06:24 +00001200 //
1201 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1202 // Uses softCursorSource as a source for new cursor image.
1203 //
1204 synchronized void createSoftCursor() {
1205
1206 if (softCursorSource == null)
1207 return;
1208
1209 int scaleCursor = 100;
1210 //int scaleCursor = appClass.options.scaleCursor;
1211 //if (scaleCursor == 0 || !inputEnabled)
1212 //scaleCursor = 100;
1213
wimba.comd2ddbc72005-02-11 23:06:24 +00001214 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1215 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1216 hotX = (origHotX * scaleCursor + 50) / 100;
1217 hotY = (origHotY * scaleCursor + 50) / 100;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001218 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001219
wimba.comd2ddbc72005-02-11 23:06:24 +00001220 if (scaleCursor != 100) {
1221 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1222 Image.SCALE_SMOOTH);
1223 }
wimba.comeee33e32005-01-07 20:54:33 +00001224 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001225
wimba.comeee33e32005-01-07 20:54:33 +00001226 private void scrollToPoint(int x, int y) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001227 // Automatic viewport scrolling
1228 if (appClass.desktopScrollPane != null) {
1229 boolean needScroll = false;
1230 Dimension d = appClass.desktopScrollPane.getViewportSize();
1231 Point topLeft = appClass.desktopScrollPane.getScrollPosition();
1232 Point botRight = new Point(topLeft.x + d.width, topLeft.y + d.height);
wimba.comeee33e32005-01-07 20:54:33 +00001233
wimba.comd2ddbc72005-02-11 23:06:24 +00001234 if (x < topLeft.x + SCROLL_MARGIN) {
1235 // shift left
1236 topLeft.x = x - SCROLL_MARGIN;
1237 needScroll = true;
1238 } else if (x > botRight.x - SCROLL_MARGIN) {
1239 // shift right
1240 topLeft.x = x - d.width + SCROLL_MARGIN;
1241 needScroll = true;
1242 }
1243 if (y < topLeft.y + SCROLL_MARGIN) {
1244 // shift up
1245 topLeft.y = y - SCROLL_MARGIN;
1246 needScroll = true;
1247 } else if (y > botRight.y - SCROLL_MARGIN) {
1248 // shift down
1249 topLeft.y = y - d.height + SCROLL_MARGIN;
1250 needScroll = true;
1251 }
1252 if (needScroll)
1253 appClass.desktopScrollPane.setScrollPosition(topLeft.x, topLeft.y);
wimba.comeee33e32005-01-07 20:54:33 +00001254 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001255 }
1256
1257 //
1258 // softCursorMove(). Moves soft cursor into a particular location.
1259 //
1260 synchronized void softCursorMove(int x, int y) {
wimba.comccb67c32005-01-03 16:20:38 +00001261 Point o = getImageOrigin();
1262 int oldX = cursorX + o.x;
1263 int oldY = cursorY + o.y;
1264 cursorX = x;
1265 cursorY = y;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001266 if (showSoftCursor) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001267 scheduleCursorRepaint(oldX - hotX, oldY - hotY, cursorWidth, cursorHeight,
1268 0);
1269 scheduleCursorRepaint(cursorX - hotX + o.x, cursorY - hotY + o.y,
1270 cursorWidth, cursorHeight, 1);
wimba.comeee33e32005-01-07 20:54:33 +00001271 if (!seekMode)
1272 scrollToPoint(x, y);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001273 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001274 }
wimba.comd2ddbc72005-02-11 23:06:24 +00001275
wimba.coma5a4f4f2004-09-21 15:25:05 +00001276 //
1277 // softCursorFree(). Remove soft cursor, dispose resources.
1278 //
1279 synchronized void softCursorFree() {
1280 if (showSoftCursor) {
1281 showSoftCursor = false;
1282 softCursor = null;
1283 softCursorSource = null;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001284
wimba.comd2ddbc72005-02-11 23:06:24 +00001285 Point p = getImageOrigin();
1286 scheduleCursorRepaint(cursorX - hotX + p.x, cursorY - hotY + p.y,
1287 cursorWidth, cursorHeight, 3);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001288 }
1289 }
wimba.comd2ddbc72005-02-11 23:06:24 +00001290
1291 Point getFrameCenter() {
1292 Dimension d = appClass.desktopScrollPane.getViewportSize();
1293 Point p = appClass.desktopScrollPane.getScrollPosition();
1294 if (d.width > rfb.framebufferWidth)
1295 p.x = d.width / 2;
1296 else
1297 p.x += d.width / 2;
1298 if (d.height > rfb.framebufferHeight)
1299 p.y = d.height / 2;
1300 else
1301 p.y += d.height / 2;
1302 return p;
1303 }
1304
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001305 //
wimba.comd2ddbc72005-02-11 23:06:24 +00001306 // We are observing our FbsInputStream object to get notified on
1307 // switching to the `paused' mode. In such cases we want to repaint
1308 // our desktop if we were seeking.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001309 //
wimba.comd2ddbc72005-02-11 23:06:24 +00001310 public void update(Observable o, Object arg) {
1311 // Immediate repaint of the whole desktop after seeking.
1312 repaint();
1313 // Let next scheduleRepaint() call invoke incremental drawing.
1314 seekMode = false;
1315 }
1316
1317 void scheduleCursorRepaint(int x, int y, int w, int h, int n) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +00001318 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001319 // Do nothing, and remember we are seeking.
1320 seekMode = true;
1321 } else {
1322 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001323 // Immediate repaint of the whole desktop after seeking.
wimba.comeee33e32005-01-07 20:54:33 +00001324 seekMode = false;
1325 if (showSoftCursor)
1326 scrollToPoint(cursorX, cursorY);
1327 updateFramebufferSize();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001328 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001329 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001330 // Usual incremental repaint.
wimba.comd2ddbc72005-02-11 23:06:24 +00001331 repaint(appClass.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001332 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001333 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001334 }
1335
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001336}