blob: f27a35004f6b14c5a57201b304219c147ea8ebaf [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
wimba.comc23aeb02004-09-16 00:00:00 +000024package com.HorizonLive.RfbPlayer;
25
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000026import java.awt.*;
27import java.awt.event.*;
28import java.awt.image.*;
29import java.io.*;
30import java.lang.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000031import java.util.*;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000032import java.util.zip.*;
33
34
35//
36// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
37//
wimba.comd2ddbc72005-02-11 23:06:24 +000038class VncCanvas extends Component
39 implements Observer {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000040
wimba.comd2ddbc72005-02-11 23:06:24 +000041 RfbPlayer appClass;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000042 RfbProto rfb;
wimba.comd2ddbc72005-02-11 23:06:24 +000043 ColorModel cm8, cm24;
44 int bytesPixel;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000045
46 Image memImage;
47 Graphics memGraphics;
48
wimba.comd2ddbc72005-02-11 23:06:24 +000049 Image offImage;
50 Graphics offGraphics;
51
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000052 Image rawPixelsImage;
53 MemoryImageSource pixelsSource;
wimba.comd2ddbc72005-02-11 23:06:24 +000054 byte[] pixels8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000055 int[] pixels24;
56
57 // Zlib encoder's data.
58 byte[] zlibBuf;
59 int zlibBufLen = 0;
60 Inflater zlibInflater;
61
62 // Tight encoder's data.
63 final static int tightZlibBufferSize = 512;
64 Inflater[] tightInflaters;
65
66 // Since JPEG images are loaded asynchronously, we have to remember
67 // their position in the framebuffer. Also, this jpegRect object is
68 // used for synchronization between the rfbThread and a JVM's thread
69 // which decodes and loads JPEG images.
70 Rectangle jpegRect;
71
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000072 // When we're in the seeking mode, we should not update the desktop.
73 // This variable helps us to remember that repainting the desktop at
74 // once is necessary when the seek operation is finished.
75 boolean seekMode;
76
wimba.com16092722004-09-21 20:54:37 +000077 // Distance of mouse from viewer border to trigger automatic scrolling
78 final static int SCROLL_MARGIN = 50;
79
wimba.comccb67c32005-01-03 16:20:38 +000080 // Color for border around small shared area
81 static final Color DARK_GRAY = new Color(132, 138, 156);
82
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000083 //
84 // The constructor.
85 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000086 VncCanvas(RfbPlayer player) throws IOException {
wimba.comd2ddbc72005-02-11 23:06:24 +000087 this.appClass = player;
88 rfb = appClass.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000089 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000090
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000091 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
92
wimba.comd2ddbc72005-02-11 23:06:24 +000093 setPixelFormat();
wimba.comccb67c32005-01-03 16:20:38 +000094
wimba.comd2ddbc72005-02-11 23:06:24 +000095 // Style canvas.
wimba.comccb67c32005-01-03 16:20:38 +000096 setBackground(Color.white);
wimba.comd2ddbc72005-02-11 23:06:24 +000097
98 }
99
100 //
101 // Override the 1.4 focus traversal keys accesor to allow
102 // our keyListener to receive tab presses.
103 //
104 public boolean getFocusTraversalKeysEnabled() {
105 return false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000106 }
107
108 //
109 // Callback methods to determine geometry of our Component.
110 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000111 public Dimension getPreferredSize() {
wimba.comd2ddbc72005-02-11 23:06:24 +0000112 Dimension d = appClass.desktopScrollPane.getViewportSize();
wimba.comccb67c32005-01-03 16:20:38 +0000113 Dimension sz = new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
114 if (d.width > sz.width)
115 sz.width = d.width;
116 if (d.height > sz.height)
117 sz.height = d.height;
118 return sz;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000119 }
120
121 public Dimension getMinimumSize() {
wimba.comccb67c32005-01-03 16:20:38 +0000122 return getPreferredSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000123 }
124
125 public Dimension getMaximumSize() {
wimba.comccb67c32005-01-03 16:20:38 +0000126 return getPreferredSize();
127 }
128
129 Point getImageOrigin() {
130 int x = 0, y = 0;
wimba.comd2ddbc72005-02-11 23:06:24 +0000131 Dimension d = appClass.desktopScrollPane.getViewportSize();
wimba.comccb67c32005-01-03 16:20:38 +0000132 if (rfb.framebufferWidth < d.width)
133 x = d.width / 2 - rfb.framebufferWidth / 2;
134 if (rfb.framebufferHeight < d.height)
135 y = d.height / 2 - rfb.framebufferHeight / 2;
136 return new Point(x, y);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000137 }
138
139 //
140 // All painting is performed here.
141 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000142 public void update(Graphics g) {
143 paint(g);
144 }
145
wimba.comd2ddbc72005-02-11 23:06:24 +0000146 public void paint(Graphics g) {
wimba.comccb67c32005-01-03 16:20:38 +0000147 Point o = getImageOrigin();
148 Dimension d = getPreferredSize();
wimba.comccb67c32005-01-03 16:20:38 +0000149
wimba.comd2ddbc72005-02-11 23:06:24 +0000150 // create new offscreen
151 if (offImage == null) {
152 offImage = createImage(d.width, d.height);
153 offGraphics = offImage.getGraphics();
154 } else if (offImage.getWidth(null) != d.width ||
155 offImage.getHeight(null) != d.height) {
156 offGraphics.dispose();
157 offGraphics = null;
158 offImage.flush();
159 offImage = null;
160 offImage = createImage(d.width, d.height);
161 offGraphics = offImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000162 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000163
164 synchronized(memImage) {
165 offGraphics.drawImage(memImage, o.x, o.y, null);
166 }
167
168 // fill in background
169 if (o.x > 0 || o.y > 0) {
170 // left, right, top, bottom
171 Color c = g.getColor();
172 offGraphics.setColor(Color.white);
173 offGraphics.fillRect(0, 0, o.x, d.height);
174 offGraphics.fillRect(o.x + rfb.framebufferWidth, 0, d.width - (o.x +
175 rfb.framebufferWidth), d.height);
176 offGraphics.fillRect(o.x, 0, rfb.framebufferWidth, o.y);
177 offGraphics.fillRect(o.x, o.y + rfb.framebufferHeight,
178 rfb.framebufferWidth, d.height - (o.y +
179 rfb.framebufferHeight));
180 offGraphics.setColor(c);
181 }
182
183 // draw border
184 Color oc = g.getColor();
185 offGraphics.setColor(DARK_GRAY);
186 offGraphics.drawRect(o.x - 1, o.y - 1, rfb.framebufferWidth + 1,
187 rfb.framebufferHeight + 1);
188 offGraphics.setColor(oc);
189
190 // draw cursor
wimba.coma5a4f4f2004-09-21 15:25:05 +0000191 if (showSoftCursor) {
192 int x0 = cursorX - hotX, y0 = cursorY - hotY;
wimba.comccb67c32005-01-03 16:20:38 +0000193 x0 += o.x;
194 y0 += o.y;
wimba.coma5a4f4f2004-09-21 15:25:05 +0000195 Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
196 if (r.intersects(g.getClipBounds())) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000197 offGraphics.setClip(o.x, o.y, rfb.framebufferWidth,
198 rfb.framebufferHeight);
199 offGraphics.drawImage(softCursor, x0, y0, null);
200 offGraphics.setClip(null);
wimba.coma5a4f4f2004-09-21 15:25:05 +0000201 }
202 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000203
204 // draw offscreen
205 g.drawImage(offImage, 0, 0, null);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000206 }
207
208 //
209 // Override the ImageObserver interface method to handle drawing of
210 // JPEG-encoded data.
211 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000212 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000213 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000214 if ((infoflags & (ALLBITS | ABORT)) == 0) {
215 return true; // We need more image data.
216 } else {
217 // If the whole image is available, draw it now.
218 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000219 if (jpegRect != null) {
220 synchronized(jpegRect) {
221 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
222 scheduleRepaint(jpegRect.x, jpegRect.y,
223 jpegRect.width, jpegRect.height);
224 jpegRect.notify();
225 }
226 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000227 }
228 return false; // All image data was processed.
229 }
230 }
231
wimba.comd2ddbc72005-02-11 23:06:24 +0000232 private void setPixelFormat() throws IOException {
233 bytesPixel = 4;
234 updateFramebufferSize();
235 }
236
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000237 void updateFramebufferSize() {
238
239 // Useful shortcuts.
240 int fbWidth = rfb.framebufferWidth;
241 int fbHeight = rfb.framebufferHeight;
242
243 // Create new off-screen image either if it does not exist, or if
244 // its geometry should be changed. It's not necessary to replace
245 // existing image if only pixel format should be changed.
246 if (memImage == null) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000247 memImage = appClass.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000248 memGraphics = memImage.getGraphics();
249 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000250 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000251 synchronized(memImage) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000252 memImage = appClass.createImage(fbWidth, fbHeight);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000253 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000254 }
255 }
256
257 // Images with raw pixels should be re-allocated on every change
258 // of geometry or pixel format.
wimba.comd2ddbc72005-02-11 23:06:24 +0000259 if (bytesPixel == 1) {
260 pixels24 = null;
261 pixels8 = new byte[fbWidth * fbHeight];
262
263 pixelsSource =
264 new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
265 } else {
266 pixels8 = null;
267 pixels24 = new int[fbWidth * fbHeight];
268
269 pixelsSource =
270 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
271 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000272 pixelsSource.setAnimated(true);
273 rawPixelsImage = createImage(pixelsSource);
274
wimba.comeee33e32005-01-07 20:54:33 +0000275 // Update the size of desktop containers unless seeking
wimba.comd2ddbc72005-02-11 23:06:24 +0000276 if (!seekMode && appClass.desktopScrollPane != null) {
277 if (appClass.inSeparateFrame) {
278 resizeDesktopFrame();
wimba.comeee33e32005-01-07 20:54:33 +0000279 } else {
wimba.comd2ddbc72005-02-11 23:06:24 +0000280 resizeEmbeddedApplet();
281 // if the framebuffer shrunk, need to clear and repaint
282 appClass.desktopScrollPane.clearAndRepaint();
wimba.comeee33e32005-01-07 20:54:33 +0000283 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000284 }
285 }
286
287 void resizeDesktopFrame() {
wimba.com252eb3b2004-09-21 21:27:54 +0000288 // determine screen size
wimba.comd2ddbc72005-02-11 23:06:24 +0000289 Dimension screenSize = appClass.vncFrame.getToolkit().getScreenSize();
290 Dimension scrollSize = appClass.desktopScrollPane.getSize();
wimba.comc23aeb02004-09-16 00:00:00 +0000291
292 // Reduce Screen Size by 30 pixels in each direction;
293 // This is a (poor) attempt to account for
294 // 1) Menu bar on Macintosh (should really also account for
295 // Dock on OSX). Usually 22px on top of screen.
wimba.com252eb3b2004-09-21 21:27:54 +0000296 // 2) Taxkbar on Windows (usually about 28 px on bottom)
297 // 3) Other obstructions.
wimba.comc23aeb02004-09-16 00:00:00 +0000298 screenSize.height -= 30;
299 screenSize.width -= 30;
300
wimba.com252eb3b2004-09-21 21:27:54 +0000301 // Further reduce the screen size to account for the
302 // scroll pane's insets.
wimba.comd2ddbc72005-02-11 23:06:24 +0000303 Insets insets = appClass.desktopScrollPane.getInsets();
wimba.com252eb3b2004-09-21 21:27:54 +0000304 screenSize.height -= insets.top + insets.bottom;
305 screenSize.width -= insets.left + insets.right;
wimba.comc23aeb02004-09-16 00:00:00 +0000306
wimba.com252eb3b2004-09-21 21:27:54 +0000307 // Limit pane size to fit on screen.
308 boolean needResize = false;
309 if (scrollSize.width != rfb.framebufferWidth ||
310 scrollSize.height != rfb.framebufferHeight)
311 needResize = true;
312 int w = rfb.framebufferWidth, h = rfb.framebufferHeight;
313 if (w > screenSize.width) {
314 w = screenSize.width;
315 needResize = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000316 }
wimba.com252eb3b2004-09-21 21:27:54 +0000317 if (h > screenSize.height) {
318 h = screenSize.height;
319 needResize = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000320 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000321 if (needResize) {
322 appClass.desktopScrollPane.setSize(w, h);
323 appClass.desktopScrollPane.validate();
324 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000325
wimba.comccb67c32005-01-03 16:20:38 +0000326 // size the canvas
327 setSize(getPreferredSize());
wimba.come8e19102005-02-08 17:15:23 +0000328
wimba.comd2ddbc72005-02-11 23:06:24 +0000329 appClass.vncFrame.pack();
wimba.com252eb3b2004-09-21 21:27:54 +0000330 }
331
332 void resizeEmbeddedApplet() {
wimba.com252eb3b2004-09-21 21:27:54 +0000333 // resize scroll pane if necessary
wimba.comd2ddbc72005-02-11 23:06:24 +0000334 Dimension scrollSize = appClass.desktopScrollPane.getSize();
335 if (scrollSize.width != appClass.dispW ||
336 scrollSize.height != appClass.dispH) {
337 appClass.desktopScrollPane.setSize(appClass.dispW, appClass.dispH);
wimba.comccb67c32005-01-03 16:20:38 +0000338 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000339 appClass.desktopScrollPane.validate();
wimba.comccb67c32005-01-03 16:20:38 +0000340 // size the canvas
341 setSize(getPreferredSize());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000342 }
343
344 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000345 // processNormalProtocol() - executed by the rfbThread to deal with
346 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000347 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000348 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000349
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000350 zlibInflater = new Inflater();
351 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000352
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000353 // Show current time position in the control panel.
wimba.comd2ddbc72005-02-11 23:06:24 +0000354 appClass.updatePos();
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000355
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000356 // Tell our FbsInputStream object to notify us when it goes to the
357 // `paused' mode.
358 rfb.fbs.addObserver(this);
359
wimba.comd2ddbc72005-02-11 23:06:24 +0000360 //
361 // main dispatch loop
362 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000363
364 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000365
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000366 int msgType = rfb.readServerMessageType();
367
wimba.comd2ddbc72005-02-11 23:06:24 +0000368 // Process the message depending on its type.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000369 switch (msgType) {
370 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000371 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000372
wimba.comd2ddbc72005-02-11 23:06:24 +0000373 boolean cursorPosReceived = false;
374
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000375 for (int i = 0; i < rfb.updateNRects; i++) {
376 rfb.readFramebufferUpdateRectHdr();
377 int rx = rfb.updateRectX, ry = rfb.updateRectY;
378 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000379
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000380 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
381 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000382
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000383 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
wimba.comd2ddbc72005-02-11 23:06:24 +0000384 if (rw != 0 && rh != 0) {
385 rfb.setFramebufferSize(rw, rh);
wimba.comb7017b72004-09-16 16:11:55 +0000386 updateFramebufferSize();
387 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000388 break;
389 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000390
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000391 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
392 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +0000393 handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
394 continue;
395 }
wimba.coma5a4f4f2004-09-21 15:25:05 +0000396
397 if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
398 softCursorMove(rx, ry);
399 cursorPosReceived = true;
400 continue;
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000401 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000402
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000403 switch (rfb.updateRectEncoding) {
404 case RfbProto.EncodingRaw:
405 handleRawRect(rx, ry, rw, rh);
406 break;
407 case RfbProto.EncodingCopyRect:
408 handleCopyRect(rx, ry, rw, rh);
409 break;
410 case RfbProto.EncodingRRE:
411 handleRRERect(rx, ry, rw, rh);
412 break;
413 case RfbProto.EncodingCoRRE:
414 handleCoRRERect(rx, ry, rw, rh);
415 break;
416 case RfbProto.EncodingHextile:
417 handleHextileRect(rx, ry, rw, rh);
418 break;
419 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000420 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000421 break;
422 case RfbProto.EncodingTight:
423 handleTightRect(rx, ry, rw, rh);
424 break;
425 default:
426 throw new Exception("Unknown RFB rectangle encoding " +
wimba.coma5a4f4f2004-09-21 15:25:05 +0000427 Integer.toString(rfb.updateRectEncoding, 16));
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000428 }
429 }
430 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000431
432 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000433 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000434
435 case RfbProto.Bell:
436 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000437 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000438
439 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000440 String s = rfb.readServerCutText();
441 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000442
443 default:
wimba.comd2ddbc72005-02-11 23:06:24 +0000444 throw new Exception("Unknown RFB message type " + msgType);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000445 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000446
wimba.comd2ddbc72005-02-11 23:06:24 +0000447 appClass.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000448 }
449 }
450
451
452 //
wimba.comd2ddbc72005-02-11 23:06:24 +0000453 // Handle a raw rectangle. The second form with paint==false is used
454 // by the Hextile decoder for raw-encoded tiles.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000455 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000456 void handleRawRect(int x, int y, int w, int h) throws IOException {
wimba.comd2ddbc72005-02-11 23:06:24 +0000457 handleRawRect(x, y, w, h, true);
458 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000459
wimba.comd2ddbc72005-02-11 23:06:24 +0000460 void handleRawRect(int x, int y, int w, int h, boolean paint)
461 throws IOException {
462
463 if (bytesPixel == 1) {
464 for (int dy = y; dy < y + h; dy++) {
465 rfb.is.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
466 //if (rfb.rec != null) {
467 // rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
468 //}
469 }
470 } else {
471 byte[] buf = new byte[w * 4];
472 int i, offset;
473 for (int dy = y; dy < y + h; dy++) {
474 rfb.is.readFully(buf);
475 //if (rfb.rec != null) {
476 // rfb.rec.write(buf);
477 //}
478 offset = dy * rfb.framebufferWidth + x;
479 for (i = 0; i < w; i++) {
480 pixels24[offset + i] =
481 (buf[i * 4 + 2] & 0xFF) << 16 |
482 (buf[i * 4 + 1] & 0xFF) << 8 |
483 (buf[i * 4] & 0xFF);
484 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000485 }
486 }
487
488 handleUpdatedPixels(x, y, w, h);
wimba.comd2ddbc72005-02-11 23:06:24 +0000489 if (paint)
490 scheduleRepaint(x, y, w, h);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000491 }
492
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000493 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000494 // Handle a CopyRect rectangle.
495 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000496 void handleCopyRect(int x, int y, int w, int h) throws IOException {
497
498 rfb.readCopyRect();
499 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000500 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000501
502 scheduleRepaint(x, y, w, h);
503 }
504
505 //
506 // Handle an RRE-encoded rectangle.
507 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000508 void handleRRERect(int x, int y, int w, int h) throws IOException {
509
510 int nSubrects = rfb.is.readInt();
511 int sx, sy, sw, sh;
512
513 byte[] buf = new byte[4];
514 rfb.is.readFully(buf);
515 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
516 memGraphics.setColor(pixel);
517 memGraphics.fillRect(x, y, w, h);
518
519 for (int j = 0; j < nSubrects; j++) {
520 rfb.is.readFully(buf);
521 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
522 sx = x + rfb.is.readUnsignedShort();
523 sy = y + rfb.is.readUnsignedShort();
524 sw = rfb.is.readUnsignedShort();
525 sh = rfb.is.readUnsignedShort();
526
527 memGraphics.setColor(pixel);
528 memGraphics.fillRect(sx, sy, sw, sh);
529 }
530
531 scheduleRepaint(x, y, w, h);
532 }
533
534 //
535 // Handle a CoRRE-encoded rectangle.
536 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000537 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000538 int nSubrects = rfb.is.readInt();
539 int sx, sy, sw, sh;
540
541 byte[] buf = new byte[4];
542 rfb.is.readFully(buf);
543 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
544 memGraphics.setColor(pixel);
545 memGraphics.fillRect(x, y, w, h);
546
547 for (int j = 0; j < nSubrects; j++) {
548 rfb.is.readFully(buf);
549 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
550 sx = x + rfb.is.readUnsignedByte();
551 sy = y + rfb.is.readUnsignedByte();
552 sw = rfb.is.readUnsignedByte();
553 sh = rfb.is.readUnsignedByte();
554
555 memGraphics.setColor(pixel);
556 memGraphics.fillRect(sx, sy, sw, sh);
557 }
558
559 scheduleRepaint(x, y, w, h);
560 }
561
562 //
563 // Handle a Hextile-encoded rectangle.
564 //
565
566 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000567 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000568
569 void handleHextileRect(int x, int y, int w, int h) throws IOException {
570
wimba.comd2ddbc72005-02-11 23:06:24 +0000571 hextile_bg = new Color(0);
572 hextile_fg = new Color(0);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000573
574 for (int ty = y; ty < y + h; ty += 16) {
575 int th = 16;
576 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000577 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000578
579 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000580 int tw = 16;
581 if (x + w - tx < 16)
582 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000583
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000584 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000585 }
586
587 // Finished with a row of tiles, now let's show it.
588 scheduleRepaint(x, y, w, h);
589 }
590 }
591
592 //
593 // Handle one tile in the Hextile-encoded data.
594 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000595 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000596 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000597
598 byte[] buf = new byte[256 * 4];
599
600 int subencoding = rfb.is.readUnsignedByte();
601
602 // Is it a raw-encoded sub-rectangle?
603 if ((subencoding & rfb.HextileRaw) != 0) {
604 int count, offset;
605 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000606 rfb.is.readFully(buf, 0, tw * 4);
607 offset = j * rfb.framebufferWidth + tx;
608 for (count = 0; count < tw; count++) {
609 pixels24[offset + count] =
610 (buf[count * 4 + 2] & 0xFF) << 16 |
611 (buf[count * 4 + 1] & 0xFF) << 8 |
612 (buf[count * 4] & 0xFF);
613 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000614 }
615 handleUpdatedPixels(tx, ty, tw, th);
616 return;
617 }
618
619 // Read and draw the background if specified.
620 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
621 rfb.is.readFully(buf, 0, 4);
622 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
623 }
624 memGraphics.setColor(hextile_bg);
625 memGraphics.fillRect(tx, ty, tw, th);
626
627 // Read the foreground color if specified.
628 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
629 rfb.is.readFully(buf, 0, 4);
630 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
631 }
632
633 // Done with this tile if there is no sub-rectangles.
634 if ((subencoding & rfb.HextileAnySubrects) == 0)
635 return;
636
637 int nSubrects = rfb.is.readUnsignedByte();
638
639 int b1, b2, sx, sy, sw, sh;
640 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
641 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000642 rfb.is.readFully(buf, 0, 4);
643 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
644 b1 = rfb.is.readUnsignedByte();
645 b2 = rfb.is.readUnsignedByte();
646 sx = tx + (b1 >> 4);
647 sy = ty + (b1 & 0xf);
648 sw = (b2 >> 4) + 1;
649 sh = (b2 & 0xf) + 1;
650 memGraphics.setColor(hextile_fg);
651 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000652 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000653
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000654 } else {
655 memGraphics.setColor(hextile_fg);
656 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000657 b1 = rfb.is.readUnsignedByte();
658 b2 = rfb.is.readUnsignedByte();
659 sx = tx + (b1 >> 4);
660 sy = ty + (b1 & 0xf);
661 sw = (b2 >> 4) + 1;
662 sh = (b2 & 0xf) + 1;
663 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000664 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000665
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000666 }
667 }
668
669 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000670 // Handle a Zlib-encoded rectangle.
671 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000672 void handleZlibRect(int x, int y, int w, int h) throws Exception {
673
674 int nBytes = rfb.is.readInt();
675
676 if (zlibBuf == null || zlibBufLen < nBytes) {
677 zlibBufLen = nBytes * 2;
678 zlibBuf = new byte[zlibBufLen];
679 }
680
681 rfb.is.readFully(zlibBuf, 0, nBytes);
682 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000683
684 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000685 byte[] buf = new byte[w * 4];
686 int i, offset;
687 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000688 zlibInflater.inflate(buf);
689 offset = dy * rfb.framebufferWidth + x;
690 for (i = 0; i < w; i++) {
691 pixels24[offset + i] =
692 (buf[i * 4 + 2] & 0xFF) << 16 |
693 (buf[i * 4 + 1] & 0xFF) << 8 |
694 (buf[i * 4] & 0xFF);
695 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000696 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000697 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000698 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000699 }
700
701 handleUpdatedPixels(x, y, w, h);
702 scheduleRepaint(x, y, w, h);
703 }
704
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000705 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000706 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000707 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000708 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000709
710 int comp_ctl = rfb.is.readUnsignedByte();
711
712 // Flush zlib streams if we are told by the server to do so.
713 for (int stream_id = 0; stream_id < 4; stream_id++) {
714 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000715 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000716 }
717 comp_ctl >>= 1;
718 }
719
720 // Check correctness of subencoding value.
721 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000722 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000723 }
724
725 // Handle solid-color rectangles.
726 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000727 byte[] buf = new byte[3];
728 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000729 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000730 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000731 memGraphics.fillRect(x, y, w, h);
732 scheduleRepaint(x, y, w, h);
733 return;
wimba.comd2ddbc72005-02-11 23:06:24 +0000734
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000735 }
736
737 if (comp_ctl == rfb.TightJpeg) {
738
739 // Read JPEG data.
740 byte[] jpegData = new byte[rfb.readCompactLen()];
741 rfb.is.readFully(jpegData);
742
743 // Create an Image object from the JPEG data.
744 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
745
746 // Remember the rectangle where the image should be drawn.
747 jpegRect = new Rectangle(x, y, w, h);
748
749 // Let the imageUpdate() method do the actual drawing, here just
750 // wait until the image is fully loaded and drawn.
751 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000752 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
753 try {
754 // Wait no longer than three seconds.
755 jpegRect.wait(3000);
756 } catch (InterruptedException e) {
757 throw new Exception("Interrupted while decoding JPEG image");
758 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000759 }
760
761 // Done, jpegRect is not needed any more.
762 jpegRect = null;
763 return;
764
765 }
766
767 // Read filter id and parameters.
768 int numColors = 0, rowSize = w;
769 byte[] palette8 = new byte[2];
770 int[] palette24 = new int[256];
771 boolean useGradient = false;
772 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
773 int filter_id = rfb.is.readUnsignedByte();
774 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000775 numColors = rfb.is.readUnsignedByte() + 1;
776 byte[] buf = new byte[numColors * 3];
777 rfb.is.readFully(buf);
778 for (int i = 0; i < numColors; i++) {
779 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
780 (buf[i * 3 + 1] & 0xFF) << 8 |
781 (buf[i * 3 + 2] & 0xFF));
782 }
783 if (numColors == 2)
784 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000785 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000786 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000787 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000788 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000789 }
790 }
wimba.comd2ddbc72005-02-11 23:06:24 +0000791 if (numColors == 0 && bytesPixel == 4)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000792 rowSize *= 3;
793
794 // Read, optionally uncompress and decode data.
795 int dataSize = h * rowSize;
796 if (dataSize < rfb.TightMinToCompress) {
797 // Data size is small - not compressed with zlib.
798 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000799 // Indexed colors.
800 byte[] indexedData = new byte[dataSize];
801 rfb.is.readFully(indexedData);
802 if (numColors == 2) {
803 // Two colors.
wimba.comd2ddbc72005-02-11 23:06:24 +0000804 //if (bytesPixel == 1) {
805 // decodeMonoData(x, y, w, h, indexedData, palette8);
806 //} else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000807 decodeMonoData(x, y, w, h, indexedData, palette24);
wimba.comd2ddbc72005-02-11 23:06:24 +0000808 //}
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000809 } else {
wimba.comd2ddbc72005-02-11 23:06:24 +0000810 // 3..255 colors (assuming bytesPixel == 4).
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000811 int i = 0;
812 for (int dy = y; dy < y + h; dy++) {
813 for (int dx = x; dx < x + w; dx++) {
814 pixels24[dy * rfb.framebufferWidth + dx] =
815 palette24[indexedData[i++] & 0xFF];
816 }
817 }
818 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000819 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000820 // "Gradient"-processed data
821 byte[] buf = new byte[w * h * 3];
822 rfb.is.readFully(buf);
823 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000824 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000825 // Raw truecolor data.
wimba.comd2ddbc72005-02-11 23:06:24 +0000826 if (bytesPixel == 1) {
827 for (int dy = y; dy < y + h; dy++) {
828 rfb.is.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
829 //if (rfb.rec != null) {
830 // rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
831 //}
832 }
833 } else {
834 byte[] buf = new byte[w * 3];
835 int i, offset;
836 for (int dy = y; dy < y + h; dy++) {
837 rfb.is.readFully(buf);
838 //if (rfb.rec != null) {
839 // rfb.rec.write(buf);
840 //}
841 offset = dy * rfb.framebufferWidth + x;
842 for (i = 0; i < w; i++) {
843 pixels24[offset + i] =
844 (buf[i * 3] & 0xFF) << 16 |
845 (buf[i * 3 + 1] & 0xFF) << 8 |
846 (buf[i * 3 + 2] & 0xFF);
847 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000848 }
849 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000850 }
851 } else {
852 // Data was compressed with zlib.
853 int zlibDataLen = rfb.readCompactLen();
854 byte[] zlibData = new byte[zlibDataLen];
855 rfb.is.readFully(zlibData);
856 int stream_id = comp_ctl & 0x03;
857 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000858 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000859 }
860 Inflater myInflater = tightInflaters[stream_id];
861 myInflater.setInput(zlibData);
862 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000863 if (numColors != 0) {
864 // Indexed colors.
865 byte[] indexedData = new byte[dataSize];
866 myInflater.inflate(indexedData);
867 if (numColors == 2) {
868 // Two colors.
869 decodeMonoData(x, y, w, h, indexedData, palette24);
870 } else {
871 // More than two colors.
872 int i = 0;
873 for (int dy = y; dy < y + h; dy++) {
874 for (int dx = x; dx < x + w; dx++) {
875 pixels24[dy * rfb.framebufferWidth + dx] =
876 palette24[indexedData[i++] & 0xFF];
877 }
878 }
879 }
880 } else if (useGradient) {
881 // Compressed "Gradient"-filtered data.
882 byte[] buf = new byte[w * h * 3];
883 myInflater.inflate(buf);
884 decodeGradientData(x, y, w, h, buf);
885 } else {
886 // Compressed truecolor data.
887 byte[] buf = new byte[w * 3];
888 int i, offset;
889 for (int dy = y; dy < y + h; dy++) {
890 myInflater.inflate(buf);
891 offset = dy * rfb.framebufferWidth + x;
892 for (i = 0; i < w; i++) {
893 pixels24[offset + i] =
894 (buf[i * 3] & 0xFF) << 16 |
895 (buf[i * 3 + 1] & 0xFF) << 8 |
896 (buf[i * 3 + 2] & 0xFF);
897 }
898 }
899 }
900 } catch (DataFormatException dfe) {
901 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000902 }
903 }
904
905 handleUpdatedPixels(x, y, w, h);
906 scheduleRepaint(x, y, w, h);
907 }
908
909 //
wimba.comd2ddbc72005-02-11 23:06:24 +0000910 // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000911 //
wimba.comd2ddbc72005-02-11 23:06:24 +0000912 void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
913
914 int dx, dy, n;
915 int i = y * rfb.framebufferWidth + x;
916 int rowBytes = (w + 7) / 8;
917 byte b;
918
919 for (dy = 0; dy < h; dy++) {
920 for (dx = 0; dx < w / 8; dx++) {
921 b = src[dy * rowBytes + dx];
922 for (n = 7; n >= 0; n--)
923 pixels8[i++] = palette[b >> n & 1];
924 }
925 for (n = 7; n >= 8 - w % 8; n--) {
926 pixels8[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
927 }
928 i += (rfb.framebufferWidth - w);
929 }
930 }
931
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000932 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000933
934 int dx, dy, n;
935 int i = y * rfb.framebufferWidth + x;
936 int rowBytes = (w + 7) / 8;
937 byte b;
938
939 for (dy = 0; dy < h; dy++) {
940 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000941 b = src[dy * rowBytes + dx];
942 for (n = 7; n >= 0; n--)
943 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000944 }
945 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000946 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000947 }
948 i += (rfb.framebufferWidth - w);
949 }
950 }
951
952 //
953 // Decode data processed with the "Gradient" filter.
954 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000955 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000956
957 int dx, dy, c;
958 byte[] prevRow = new byte[w * 3];
959 byte[] thisRow = new byte[w * 3];
960 byte[] pix = new byte[3];
961 int[] est = new int[3];
962
963 int offset = y * rfb.framebufferWidth + x;
964
965 for (dy = 0; dy < h; dy++) {
966
967 /* First pixel in a row */
968 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000969 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
970 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000971 }
972 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000973 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000974
975 /* Remaining pixels of a row */
976 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000977 for (c = 0; c < 3; c++) {
978 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
979 (prevRow[(dx - 1) * 3 + c] & 0xFF));
980 if (est[c] > 0xFF) {
981 est[c] = 0xFF;
982 } else if (est[c] < 0x00) {
983 est[c] = 0x00;
984 }
985 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
986 thisRow[dx * 3 + c] = pix[c];
987 }
988 pixels24[offset++] =
989 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000990 }
991
992 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
993 offset += (rfb.framebufferWidth - w);
994 }
995 }
996
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000997 //
998 // Display newly updated area of pixels.
999 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001000 void handleUpdatedPixels(int x, int y, int w, int h) {
1001
1002 // Draw updated pixels of the off-screen image.
1003 pixelsSource.newPixels(x, y, w, h);
1004 memGraphics.setClip(x, y, w, h);
1005 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1006 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1007 }
1008
wimba.comd2ddbc72005-02-11 23:06:24 +00001009 //
1010 // Tell JVM to repaint specified desktop area.
1011 //
1012 void scheduleRepaint(int x, int y, int w, int h) {
1013 if (rfb.fbs.isSeeking()) {
1014 // Do nothing, and remember we are seeking.
1015 seekMode = true;
1016 } else {
1017 if (seekMode) {
1018 // Immediate repaint of the whole desktop after seeking.
1019 seekMode = false;
1020 if (showSoftCursor)
1021 scrollToPoint(cursorX, cursorY);
1022 updateFramebufferSize();
1023 repaint();
1024 } else {
1025 // Usual incremental repaint.
1026 Point o = getImageOrigin();
1027 repaint(appClass.deferScreenUpdates, o.x + x, o.y + y, w, h);
1028 }
1029 }
1030 }
1031
1032
wimba.coma5a4f4f2004-09-21 15:25:05 +00001033 //////////////////////////////////////////////////////////////////
1034 //
1035 // Handle cursor shape updates (XCursor and RichCursor encodings).
1036 //
1037 boolean showSoftCursor = false;
1038
wimba.coma5a4f4f2004-09-21 15:25:05 +00001039 MemoryImageSource softCursorSource;
1040 Image softCursor;
1041
1042 int cursorX = 0, cursorY = 0;
1043 int cursorWidth, cursorHeight;
1044 int origCursorWidth, origCursorHeight;
1045 int hotX, hotY;
1046 int origHotX, origHotY;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001047
1048 //
1049 // Handle cursor shape update (XCursor and RichCursor encodings).
1050 //
1051 synchronized void handleCursorShapeUpdate(int encodingType,
1052 int xhot, int yhot, int width,
1053 int height)
1054 throws IOException {
1055
wimba.coma5a4f4f2004-09-21 15:25:05 +00001056 softCursorFree();
1057
1058 if (width * height == 0)
1059 return;
1060
wimba.comd2ddbc72005-02-11 23:06:24 +00001061 // Ignore cursor shape data if requested by user.
1062 //if (appClass.options.ignoreCursorUpdates) {
1063 if (false) {
1064 int bytesPerRow = (width + 7) / 8;
1065 int bytesMaskData = bytesPerRow * height;
1066
1067 if (encodingType == rfb.EncodingXCursor) {
1068 rfb.is.skipBytes(6 + bytesMaskData * 2);
1069 } else {
1070 // rfb.EncodingRichCursor
1071 rfb.is.skipBytes(width * height + bytesMaskData);
1072 }
1073 return;
1074 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001075
1076 // Decode cursor pixel data.
wimba.comd2ddbc72005-02-11 23:06:24 +00001077 softCursorSource = decodeCursorShape(encodingType, width, height);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001078
wimba.comd2ddbc72005-02-11 23:06:24 +00001079 // Set original (non-scaled) cursor dimensions.
1080 origCursorWidth = width;
1081 origCursorHeight = height;
1082 origHotX = xhot;
1083 origHotY = yhot;
1084
1085 // Create off-screen cursor image.
1086 createSoftCursor();
1087
1088 // Show the cursor.
1089 showSoftCursor = true;
1090 Point p = getImageOrigin();
1091 scheduleCursorRepaint(cursorX - hotX + p.x, cursorY - hotY + p.y,
1092 cursorWidth, cursorHeight, 5);
1093 }
1094
1095 //
1096 // decodeCursorShape(). Decode cursor pixel data and return
1097 // corresponding MemoryImageSource instance.
1098 //
1099 synchronized MemoryImageSource decodeCursorShape(int encodingType, int width,
1100 int height)
1101 throws IOException {
1102
1103 int bytesPerRow = (width + 7) / 8;
1104 int bytesMaskData = bytesPerRow * height;
1105
1106 int[] softCursorPixels = new int[width * height];
wimba.coma5a4f4f2004-09-21 15:25:05 +00001107
1108 if (encodingType == rfb.EncodingXCursor) {
wimba.coma5a4f4f2004-09-21 15:25:05 +00001109
1110 // Read foreground and background colors of the cursor.
1111 byte[] rgb = new byte[6];
1112 rfb.is.readFully(rgb);
1113 int[] colors = {(0xFF000000 | (rgb[3] & 0xFF) << 16 |
1114 (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1115 (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1116 (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF))
1117 };
wimba.coma5a4f4f2004-09-21 15:25:05 +00001118
1119 // Read pixel and mask data.
1120 byte[] pixBuf = new byte[bytesMaskData];
1121 rfb.is.readFully(pixBuf);
1122 byte[] maskBuf = new byte[bytesMaskData];
1123 rfb.is.readFully(maskBuf);
1124
1125 // Decode pixel data into softCursorPixels[].
1126 byte pixByte, maskByte;
1127 int x, y, n, result;
1128 int i = 0;
1129 for (y = 0; y < height; y++) {
1130 for (x = 0; x < width / 8; x++) {
1131 pixByte = pixBuf[y * bytesPerRow + x];
1132 maskByte = maskBuf[y * bytesPerRow + x];
1133 for (n = 7; n >= 0; n--) {
1134 if ((maskByte >> n & 1) != 0) {
1135 result = colors[pixByte >> n & 1];
1136 } else {
1137 result = 0; // Transparent pixel
1138 }
1139 softCursorPixels[i++] = result;
1140 }
1141 }
1142 for (n = 7; n >= 8 - width % 8; n--) {
1143 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1144 result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1145 } else {
1146 result = 0; // Transparent pixel
1147 }
1148 softCursorPixels[i++] = result;
1149 }
1150 }
1151
1152 } else {
1153 // encodingType == rfb.EncodingRichCursor
wimba.coma5a4f4f2004-09-21 15:25:05 +00001154
1155 // Read pixel and mask data.
wimba.comd2ddbc72005-02-11 23:06:24 +00001156 byte[] pixBuf = new byte[width * height * bytesPixel];
wimba.coma5a4f4f2004-09-21 15:25:05 +00001157 rfb.is.readFully(pixBuf);
1158 byte[] maskBuf = new byte[bytesMaskData];
1159 rfb.is.readFully(maskBuf);
1160
1161 // Decode pixel data into softCursorPixels[].
1162 byte pixByte, maskByte;
1163 int x, y, n, result;
1164 int i = 0;
1165 for (y = 0; y < height; y++) {
1166 for (x = 0; x < width / 8; x++) {
1167 maskByte = maskBuf[y * bytesPerRow + x];
1168 for (n = 7; n >= 0; n--) {
1169 if ((maskByte >> n & 1) != 0) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001170 if (bytesPixel == 1) {
1171 result = cm8.getRGB(pixBuf[i]);
1172 } else {
1173 result = (pixBuf[i * 4] & 0xFF) << 24 |
1174 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1175 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1176 (pixBuf[i * 4 + 3] & 0xFF);
1177 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001178 } else {
1179 result = 0; // Transparent pixel
1180 }
1181 softCursorPixels[i++] = result;
1182 }
1183 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001184 for (n = 7; n >= 8 - width % 8; n--) {
1185 if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001186 if (bytesPixel == 1) {
1187 result = cm8.getRGB(pixBuf[i]);
1188 } else {
1189 result = 0xFF000000 |
1190 (pixBuf[i * 4 + 1] & 0xFF) << 16 |
1191 (pixBuf[i * 4 + 2] & 0xFF) << 8 |
1192 (pixBuf[i * 4 + 3] & 0xFF);
1193 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001194 } else {
1195 result = 0; // Transparent pixel
1196 }
1197 softCursorPixels[i++] = result;
1198 }
1199 }
1200
1201 }
1202
wimba.comd2ddbc72005-02-11 23:06:24 +00001203 return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1204 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001205
wimba.comd2ddbc72005-02-11 23:06:24 +00001206 //
1207 // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1208 // Uses softCursorSource as a source for new cursor image.
1209 //
1210 synchronized void createSoftCursor() {
1211
1212 if (softCursorSource == null)
1213 return;
1214
1215 int scaleCursor = 100;
1216 //int scaleCursor = appClass.options.scaleCursor;
1217 //if (scaleCursor == 0 || !inputEnabled)
1218 //scaleCursor = 100;
1219
1220 // Save original cursor coordinates.
1221 int x = cursorX - hotX;
1222 int y = cursorY - hotY;
1223 int w = cursorWidth;
1224 int h = cursorHeight;
1225
1226 cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1227 cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1228 hotX = (origHotX * scaleCursor + 50) / 100;
1229 hotY = (origHotY * scaleCursor + 50) / 100;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001230 softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001231
wimba.comd2ddbc72005-02-11 23:06:24 +00001232 if (scaleCursor != 100) {
1233 softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1234 Image.SCALE_SMOOTH);
1235 }
wimba.comeee33e32005-01-07 20:54:33 +00001236 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001237
wimba.comeee33e32005-01-07 20:54:33 +00001238 private void scrollToPoint(int x, int y) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001239 // Automatic viewport scrolling
1240 if (appClass.desktopScrollPane != null) {
1241 boolean needScroll = false;
1242 Dimension d = appClass.desktopScrollPane.getViewportSize();
1243 Point topLeft = appClass.desktopScrollPane.getScrollPosition();
1244 Point botRight = new Point(topLeft.x + d.width, topLeft.y + d.height);
wimba.comeee33e32005-01-07 20:54:33 +00001245
wimba.comd2ddbc72005-02-11 23:06:24 +00001246 if (x < topLeft.x + SCROLL_MARGIN) {
1247 // shift left
1248 topLeft.x = x - SCROLL_MARGIN;
1249 needScroll = true;
1250 } else if (x > botRight.x - SCROLL_MARGIN) {
1251 // shift right
1252 topLeft.x = x - d.width + SCROLL_MARGIN;
1253 needScroll = true;
1254 }
1255 if (y < topLeft.y + SCROLL_MARGIN) {
1256 // shift up
1257 topLeft.y = y - SCROLL_MARGIN;
1258 needScroll = true;
1259 } else if (y > botRight.y - SCROLL_MARGIN) {
1260 // shift down
1261 topLeft.y = y - d.height + SCROLL_MARGIN;
1262 needScroll = true;
1263 }
1264 if (needScroll)
1265 appClass.desktopScrollPane.setScrollPosition(topLeft.x, topLeft.y);
wimba.comeee33e32005-01-07 20:54:33 +00001266 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001267 }
1268
1269 //
1270 // softCursorMove(). Moves soft cursor into a particular location.
1271 //
1272 synchronized void softCursorMove(int x, int y) {
wimba.comccb67c32005-01-03 16:20:38 +00001273 Point o = getImageOrigin();
1274 int oldX = cursorX + o.x;
1275 int oldY = cursorY + o.y;
1276 cursorX = x;
1277 cursorY = y;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001278 if (showSoftCursor) {
wimba.comd2ddbc72005-02-11 23:06:24 +00001279 scheduleCursorRepaint(oldX - hotX, oldY - hotY, cursorWidth, cursorHeight,
1280 0);
1281 scheduleCursorRepaint(cursorX - hotX + o.x, cursorY - hotY + o.y,
1282 cursorWidth, cursorHeight, 1);
wimba.comeee33e32005-01-07 20:54:33 +00001283 if (!seekMode)
1284 scrollToPoint(x, y);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001285 }
wimba.coma5a4f4f2004-09-21 15:25:05 +00001286 }
wimba.comd2ddbc72005-02-11 23:06:24 +00001287
wimba.coma5a4f4f2004-09-21 15:25:05 +00001288 //
1289 // softCursorFree(). Remove soft cursor, dispose resources.
1290 //
1291 synchronized void softCursorFree() {
1292 if (showSoftCursor) {
1293 showSoftCursor = false;
1294 softCursor = null;
1295 softCursorSource = null;
wimba.coma5a4f4f2004-09-21 15:25:05 +00001296
wimba.comd2ddbc72005-02-11 23:06:24 +00001297 Point p = getImageOrigin();
1298 scheduleCursorRepaint(cursorX - hotX + p.x, cursorY - hotY + p.y,
1299 cursorWidth, cursorHeight, 3);
wimba.coma5a4f4f2004-09-21 15:25:05 +00001300 }
1301 }
wimba.comd2ddbc72005-02-11 23:06:24 +00001302
1303 Point getFrameCenter() {
1304 Dimension d = appClass.desktopScrollPane.getViewportSize();
1305 Point p = appClass.desktopScrollPane.getScrollPosition();
1306 if (d.width > rfb.framebufferWidth)
1307 p.x = d.width / 2;
1308 else
1309 p.x += d.width / 2;
1310 if (d.height > rfb.framebufferHeight)
1311 p.y = d.height / 2;
1312 else
1313 p.y += d.height / 2;
1314 return p;
1315 }
1316
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001317 //
wimba.comd2ddbc72005-02-11 23:06:24 +00001318 // We are observing our FbsInputStream object to get notified on
1319 // switching to the `paused' mode. In such cases we want to repaint
1320 // our desktop if we were seeking.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001321 //
wimba.comd2ddbc72005-02-11 23:06:24 +00001322 public void update(Observable o, Object arg) {
1323 // Immediate repaint of the whole desktop after seeking.
1324 repaint();
1325 // Let next scheduleRepaint() call invoke incremental drawing.
1326 seekMode = false;
1327 }
1328
1329 void scheduleCursorRepaint(int x, int y, int w, int h, int n) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +00001330 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001331 // Do nothing, and remember we are seeking.
1332 seekMode = true;
1333 } else {
1334 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001335 // Immediate repaint of the whole desktop after seeking.
wimba.comeee33e32005-01-07 20:54:33 +00001336 seekMode = false;
1337 if (showSoftCursor)
1338 scrollToPoint(cursorX, cursorY);
1339 updateFramebufferSize();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001340 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001341 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +00001342 // Usual incremental repaint.
wimba.comd2ddbc72005-02-11 23:06:24 +00001343 repaint(appClass.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001344 }
Constantin Kaplinsky52c48242002-05-30 13:26:34 +00001345 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001346 }
1347
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001348}