blob: 3b4a30b9488e64bca44522f9edf25222a6c16bfe [file] [log] [blame]
Constantin Kaplinsky1215b992008-04-18 09:51:44 +00001//
2// Copyright (C) 2001,2002 HorizonLive.com, Inc. All Rights Reserved.
3// Copyright (C) 2001 Constantin Kaplinsky. All Rights Reserved.
4// Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
5// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
6//
7// This is free software; you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation; either version 2 of the License, or
10// (at your option) any later version.
11//
12// This software is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this software; if not, write to the Free Software
19// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20// USA.
21//
22
wimba.comc23aeb02004-09-16 00:00:00 +000023package com.HorizonLive.RfbPlayer;
24
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000025import java.awt.*;
26import java.awt.event.*;
27import java.awt.image.*;
28import java.io.*;
29import java.lang.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000030import java.util.*;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000031import java.util.zip.*;
32
33
34//
35// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
36//
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000037class VncCanvas extends Canvas implements Observer {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000038
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000039 RfbPlayer player;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000040 RfbProto rfb;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000041 ColorModel cm24;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000042
43 Image memImage;
44 Graphics memGraphics;
45
46 Image rawPixelsImage;
47 MemoryImageSource pixelsSource;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000048 int[] pixels24;
49
50 // Zlib encoder's data.
51 byte[] zlibBuf;
52 int zlibBufLen = 0;
53 Inflater zlibInflater;
54
55 // Tight encoder's data.
56 final static int tightZlibBufferSize = 512;
57 Inflater[] tightInflaters;
58
59 // Since JPEG images are loaded asynchronously, we have to remember
60 // their position in the framebuffer. Also, this jpegRect object is
61 // used for synchronization between the rfbThread and a JVM's thread
62 // which decodes and loads JPEG images.
63 Rectangle jpegRect;
64
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000065 // When we're in the seeking mode, we should not update the desktop.
66 // This variable helps us to remember that repainting the desktop at
67 // once is necessary when the seek operation is finished.
68 boolean seekMode;
69
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000070 //
71 // The constructor.
72 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000073 VncCanvas(RfbPlayer player) throws IOException {
74 this.player = player;
75 rfb = player.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000076 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000077
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000078 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
79
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000080 updateFramebufferSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000081 }
82
83 //
84 // Callback methods to determine geometry of our Component.
85 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000086 public Dimension getPreferredSize() {
87 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
88 }
89
90 public Dimension getMinimumSize() {
91 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
92 }
93
94 public Dimension getMaximumSize() {
95 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
96 }
97
98 //
99 // All painting is performed here.
100 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000101 public void update(Graphics g) {
102 paint(g);
103 }
104
105 public void paint(Graphics g) {
106 synchronized(memImage) {
107 g.drawImage(memImage, 0, 0, null);
108 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000109 }
110
111 //
112 // Override the ImageObserver interface method to handle drawing of
113 // JPEG-encoded data.
114 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000115 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000116 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000117 if ((infoflags & (ALLBITS | ABORT)) == 0) {
118 return true; // We need more image data.
119 } else {
120 // If the whole image is available, draw it now.
121 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000122 if (jpegRect != null) {
123 synchronized(jpegRect) {
124 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
125 scheduleRepaint(jpegRect.x, jpegRect.y,
126 jpegRect.width, jpegRect.height);
127 jpegRect.notify();
128 }
129 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000130 }
131 return false; // All image data was processed.
132 }
133 }
134
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000135 void updateFramebufferSize() {
136
137 // Useful shortcuts.
138 int fbWidth = rfb.framebufferWidth;
139 int fbHeight = rfb.framebufferHeight;
140
141 // Create new off-screen image either if it does not exist, or if
142 // its geometry should be changed. It's not necessary to replace
143 // existing image if only pixel format should be changed.
144 if (memImage == null) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000145 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000146 memGraphics = memImage.getGraphics();
147 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000148 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000149 synchronized(memImage) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000150 memImage = player.createImage(fbWidth, fbHeight);
151 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000152 }
153 }
154
155 // Images with raw pixels should be re-allocated on every change
156 // of geometry or pixel format.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000157 pixels24 = new int[fbWidth * fbHeight];
158 pixelsSource =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000159 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000160 pixelsSource.setAnimated(true);
161 rawPixelsImage = createImage(pixelsSource);
162
163 // Update the size of desktop containers.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000164 if (player.inSeparateFrame) {
165 if (player.desktopScrollPane != null)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000166 resizeDesktopFrame();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000167 } else {
168 setSize(fbWidth, fbHeight);
169 }
170 }
171
172 void resizeDesktopFrame() {
173 setSize(rfb.framebufferWidth, rfb.framebufferHeight);
174
175 // FIXME: Find a better way to determine correct size of a
176 // ScrollPane. -- const
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000177 Insets insets = player.desktopScrollPane.getInsets();
178 player.desktopScrollPane.setSize(rfb.framebufferWidth +
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000179 2 * Math.min(insets.left, insets.right),
180 rfb.framebufferHeight +
181 2 * Math.min(insets.top, insets.bottom));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000182
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000183 player.vncFrame.pack();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000184
185 // Try to limit the frame size to the screen size.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000186 Dimension screenSize = player.vncFrame.getToolkit().getScreenSize();
187 Dimension frameSize = player.vncFrame.getSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000188 Dimension newSize = frameSize;
wimba.comc23aeb02004-09-16 00:00:00 +0000189
190 // Reduce Screen Size by 30 pixels in each direction;
191 // This is a (poor) attempt to account for
192 // 1) Menu bar on Macintosh (should really also account for
193 // Dock on OSX). Usually 22px on top of screen.
194 // 2) Taxkbar on Windows (usually about 28 px on bottom)
195 // 3) Other obstructions.
196
197 screenSize.height -= 30;
198 screenSize.width -= 30;
199
200
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000201 boolean needToResizeFrame = false;
202 if (frameSize.height > screenSize.height) {
203 newSize.height = screenSize.height;
204 needToResizeFrame = true;
205 }
206 if (frameSize.width > screenSize.width) {
207 newSize.width = screenSize.width;
208 needToResizeFrame = true;
209 }
210 if (needToResizeFrame) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000211 player.vncFrame.setSize(newSize);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000212 }
213
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000214 player.desktopScrollPane.doLayout();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000215 }
216
217 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000218 // processNormalProtocol() - executed by the rfbThread to deal with
219 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000220 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000221 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000222
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000223 zlibInflater = new Inflater();
224 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000225
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000226 // Show current time position in the control panel.
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000227 player.updatePos();
228
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000229 // Tell our FbsInputStream object to notify us when it goes to the
230 // `paused' mode.
231 rfb.fbs.addObserver(this);
232
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000233 // Main dispatch loop.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000234
235 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000236
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000237 int msgType = rfb.readServerMessageType();
238
239 switch (msgType) {
240 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000241 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000242
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000243 for (int i = 0; i < rfb.updateNRects; i++) {
244 rfb.readFramebufferUpdateRectHdr();
245 int rx = rfb.updateRectX, ry = rfb.updateRectY;
246 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000247
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000248 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
249 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000250
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000251 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
252 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
253 updateFramebufferSize();
254 break;
255 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000256
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000257 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
258 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
259 throw new Exception("Sorry, no support for" +
260 " cursor shape updates yet");
261 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000262
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000263 switch (rfb.updateRectEncoding) {
264 case RfbProto.EncodingRaw:
265 handleRawRect(rx, ry, rw, rh);
266 break;
267 case RfbProto.EncodingCopyRect:
268 handleCopyRect(rx, ry, rw, rh);
269 break;
270 case RfbProto.EncodingRRE:
271 handleRRERect(rx, ry, rw, rh);
272 break;
273 case RfbProto.EncodingCoRRE:
274 handleCoRRERect(rx, ry, rw, rh);
275 break;
276 case RfbProto.EncodingHextile:
277 handleHextileRect(rx, ry, rw, rh);
278 break;
279 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000280 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000281 break;
282 case RfbProto.EncodingTight:
283 handleTightRect(rx, ry, rw, rh);
284 break;
285 default:
286 throw new Exception("Unknown RFB rectangle encoding " +
287 rfb.updateRectEncoding);
288 }
289 }
290 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000291
292 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000293 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000294
295 case RfbProto.Bell:
296 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000297 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000298
299 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000300 String s = rfb.readServerCutText();
301 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000302
303 default:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000304 throw new Exception("Unknown RFB message type " + msgType);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000305 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000306
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000307 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000308 }
309 }
310
311
312 //
313 // Handle a raw rectangle.
314 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000315 void handleRawRect(int x, int y, int w, int h) throws IOException {
316
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000317 byte[] buf = new byte[w * 4];
318 int i, offset;
319 for (int dy = y; dy < y + h; dy++) {
320 rfb.is.readFully(buf);
321 offset = dy * rfb.framebufferWidth + x;
322 for (i = 0; i < w; i++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000323 pixels24[offset + i] =
324 (buf[i * 4 + 2] & 0xFF) << 16 |
325 (buf[i * 4 + 1] & 0xFF) << 8 |
326 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000327 }
328 }
329
330 handleUpdatedPixels(x, y, w, h);
331 scheduleRepaint(x, y, w, h);
332 }
333
334
335 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000336 // Handle a CopyRect rectangle.
337 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000338 void handleCopyRect(int x, int y, int w, int h) throws IOException {
339
340 rfb.readCopyRect();
341 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000342 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000343
344 scheduleRepaint(x, y, w, h);
345 }
346
347 //
348 // Handle an RRE-encoded rectangle.
349 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000350 void handleRRERect(int x, int y, int w, int h) throws IOException {
351
352 int nSubrects = rfb.is.readInt();
353 int sx, sy, sw, sh;
354
355 byte[] buf = new byte[4];
356 rfb.is.readFully(buf);
357 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
358 memGraphics.setColor(pixel);
359 memGraphics.fillRect(x, y, w, h);
360
361 for (int j = 0; j < nSubrects; j++) {
362 rfb.is.readFully(buf);
363 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
364 sx = x + rfb.is.readUnsignedShort();
365 sy = y + rfb.is.readUnsignedShort();
366 sw = rfb.is.readUnsignedShort();
367 sh = rfb.is.readUnsignedShort();
368
369 memGraphics.setColor(pixel);
370 memGraphics.fillRect(sx, sy, sw, sh);
371 }
372
373 scheduleRepaint(x, y, w, h);
374 }
375
376 //
377 // Handle a CoRRE-encoded rectangle.
378 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000379 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
380
381 int nSubrects = rfb.is.readInt();
382 int sx, sy, sw, sh;
383
384 byte[] buf = new byte[4];
385 rfb.is.readFully(buf);
386 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
387 memGraphics.setColor(pixel);
388 memGraphics.fillRect(x, y, w, h);
389
390 for (int j = 0; j < nSubrects; j++) {
391 rfb.is.readFully(buf);
392 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
393 sx = x + rfb.is.readUnsignedByte();
394 sy = y + rfb.is.readUnsignedByte();
395 sw = rfb.is.readUnsignedByte();
396 sh = rfb.is.readUnsignedByte();
397
398 memGraphics.setColor(pixel);
399 memGraphics.fillRect(sx, sy, sw, sh);
400 }
401
402 scheduleRepaint(x, y, w, h);
403 }
404
405 //
406 // Handle a Hextile-encoded rectangle.
407 //
408
409 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000410 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000411
412 void handleHextileRect(int x, int y, int w, int h) throws IOException {
413
414 hextile_bg = new Color(0, 0, 0);
415 hextile_fg = new Color(0, 0, 0);
416
417 for (int ty = y; ty < y + h; ty += 16) {
418 int th = 16;
419 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000420 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000421
422 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000423 int tw = 16;
424 if (x + w - tx < 16)
425 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000426
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000427 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000428 }
429
430 // Finished with a row of tiles, now let's show it.
431 scheduleRepaint(x, y, w, h);
432 }
433 }
434
435 //
436 // Handle one tile in the Hextile-encoded data.
437 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000438 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000439 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000440
441 byte[] buf = new byte[256 * 4];
442
443 int subencoding = rfb.is.readUnsignedByte();
444
445 // Is it a raw-encoded sub-rectangle?
446 if ((subencoding & rfb.HextileRaw) != 0) {
447 int count, offset;
448 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000449 rfb.is.readFully(buf, 0, tw * 4);
450 offset = j * rfb.framebufferWidth + tx;
451 for (count = 0; count < tw; count++) {
452 pixels24[offset + count] =
453 (buf[count * 4 + 2] & 0xFF) << 16 |
454 (buf[count * 4 + 1] & 0xFF) << 8 |
455 (buf[count * 4] & 0xFF);
456 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000457 }
458 handleUpdatedPixels(tx, ty, tw, th);
459 return;
460 }
461
462 // Read and draw the background if specified.
463 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
464 rfb.is.readFully(buf, 0, 4);
465 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
466 }
467 memGraphics.setColor(hextile_bg);
468 memGraphics.fillRect(tx, ty, tw, th);
469
470 // Read the foreground color if specified.
471 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
472 rfb.is.readFully(buf, 0, 4);
473 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
474 }
475
476 // Done with this tile if there is no sub-rectangles.
477 if ((subencoding & rfb.HextileAnySubrects) == 0)
478 return;
479
480 int nSubrects = rfb.is.readUnsignedByte();
481
482 int b1, b2, sx, sy, sw, sh;
483 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
484 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000485 rfb.is.readFully(buf, 0, 4);
486 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
487 b1 = rfb.is.readUnsignedByte();
488 b2 = rfb.is.readUnsignedByte();
489 sx = tx + (b1 >> 4);
490 sy = ty + (b1 & 0xf);
491 sw = (b2 >> 4) + 1;
492 sh = (b2 & 0xf) + 1;
493 memGraphics.setColor(hextile_fg);
494 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000495 }
496 } else {
497 memGraphics.setColor(hextile_fg);
498 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000499 b1 = rfb.is.readUnsignedByte();
500 b2 = rfb.is.readUnsignedByte();
501 sx = tx + (b1 >> 4);
502 sy = ty + (b1 & 0xf);
503 sw = (b2 >> 4) + 1;
504 sh = (b2 & 0xf) + 1;
505 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000506 }
507 }
508 }
509
510 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000511 // Handle a Zlib-encoded rectangle.
512 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000513 void handleZlibRect(int x, int y, int w, int h) throws Exception {
514
515 int nBytes = rfb.is.readInt();
516
517 if (zlibBuf == null || zlibBufLen < nBytes) {
518 zlibBufLen = nBytes * 2;
519 zlibBuf = new byte[zlibBufLen];
520 }
521
522 rfb.is.readFully(zlibBuf, 0, nBytes);
523 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000524
525 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000526 byte[] buf = new byte[w * 4];
527 int i, offset;
528 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000529 zlibInflater.inflate(buf);
530 offset = dy * rfb.framebufferWidth + x;
531 for (i = 0; i < w; i++) {
532 pixels24[offset + i] =
533 (buf[i * 4 + 2] & 0xFF) << 16 |
534 (buf[i * 4 + 1] & 0xFF) << 8 |
535 (buf[i * 4] & 0xFF);
536 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000537 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000538 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000539 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000540 }
541
542 handleUpdatedPixels(x, y, w, h);
543 scheduleRepaint(x, y, w, h);
544 }
545
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000546 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000547 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000548 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000549 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000550
551 int comp_ctl = rfb.is.readUnsignedByte();
552
553 // Flush zlib streams if we are told by the server to do so.
554 for (int stream_id = 0; stream_id < 4; stream_id++) {
555 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000556 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000557 }
558 comp_ctl >>= 1;
559 }
560
561 // Check correctness of subencoding value.
562 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000563 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000564 }
565
566 // Handle solid-color rectangles.
567 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000568 byte[] buf = new byte[3];
569 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000570 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000571 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000572 memGraphics.fillRect(x, y, w, h);
573 scheduleRepaint(x, y, w, h);
574 return;
575 }
576
577 if (comp_ctl == rfb.TightJpeg) {
578
579 // Read JPEG data.
580 byte[] jpegData = new byte[rfb.readCompactLen()];
581 rfb.is.readFully(jpegData);
582
583 // Create an Image object from the JPEG data.
584 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
585
586 // Remember the rectangle where the image should be drawn.
587 jpegRect = new Rectangle(x, y, w, h);
588
589 // Let the imageUpdate() method do the actual drawing, here just
590 // wait until the image is fully loaded and drawn.
591 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000592 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
593 try {
594 // Wait no longer than three seconds.
595 jpegRect.wait(3000);
596 } catch (InterruptedException e) {
597 throw new Exception("Interrupted while decoding JPEG image");
598 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000599 }
600
601 // Done, jpegRect is not needed any more.
602 jpegRect = null;
603 return;
604
605 }
606
607 // Read filter id and parameters.
608 int numColors = 0, rowSize = w;
609 byte[] palette8 = new byte[2];
610 int[] palette24 = new int[256];
611 boolean useGradient = false;
612 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
613 int filter_id = rfb.is.readUnsignedByte();
614 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000615 numColors = rfb.is.readUnsignedByte() + 1;
616 byte[] buf = new byte[numColors * 3];
617 rfb.is.readFully(buf);
618 for (int i = 0; i < numColors; i++) {
619 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
620 (buf[i * 3 + 1] & 0xFF) << 8 |
621 (buf[i * 3 + 2] & 0xFF));
622 }
623 if (numColors == 2)
624 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000625 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000626 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000627 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000628 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000629 }
630 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000631 if (numColors == 0)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000632 rowSize *= 3;
633
634 // Read, optionally uncompress and decode data.
635 int dataSize = h * rowSize;
636 if (dataSize < rfb.TightMinToCompress) {
637 // Data size is small - not compressed with zlib.
638 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000639 // Indexed colors.
640 byte[] indexedData = new byte[dataSize];
641 rfb.is.readFully(indexedData);
642 if (numColors == 2) {
643 // Two colors.
644 decodeMonoData(x, y, w, h, indexedData, palette24);
645 } else {
646 // 3..255 colors.
647 int i = 0;
648 for (int dy = y; dy < y + h; dy++) {
649 for (int dx = x; dx < x + w; dx++) {
650 pixels24[dy * rfb.framebufferWidth + dx] =
651 palette24[indexedData[i++] & 0xFF];
652 }
653 }
654 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000655 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000656 // "Gradient"-processed data
657 byte[] buf = new byte[w * h * 3];
658 rfb.is.readFully(buf);
659 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000660 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000661 // Raw truecolor data.
662 byte[] buf = new byte[w * 3];
663 int i, offset;
664 for (int dy = y; dy < y + h; dy++) {
665 rfb.is.readFully(buf);
666 offset = dy * rfb.framebufferWidth + x;
667 for (i = 0; i < w; i++) {
668 pixels24[offset + i] =
669 (buf[i * 3] & 0xFF) << 16 |
670 (buf[i * 3 + 1] & 0xFF) << 8 |
671 (buf[i * 3 + 2] & 0xFF);
672 }
673 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000674 }
675 } else {
676 // Data was compressed with zlib.
677 int zlibDataLen = rfb.readCompactLen();
678 byte[] zlibData = new byte[zlibDataLen];
679 rfb.is.readFully(zlibData);
680 int stream_id = comp_ctl & 0x03;
681 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000682 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000683 }
684 Inflater myInflater = tightInflaters[stream_id];
685 myInflater.setInput(zlibData);
686 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000687 if (numColors != 0) {
688 // Indexed colors.
689 byte[] indexedData = new byte[dataSize];
690 myInflater.inflate(indexedData);
691 if (numColors == 2) {
692 // Two colors.
693 decodeMonoData(x, y, w, h, indexedData, palette24);
694 } else {
695 // More than two colors.
696 int i = 0;
697 for (int dy = y; dy < y + h; dy++) {
698 for (int dx = x; dx < x + w; dx++) {
699 pixels24[dy * rfb.framebufferWidth + dx] =
700 palette24[indexedData[i++] & 0xFF];
701 }
702 }
703 }
704 } else if (useGradient) {
705 // Compressed "Gradient"-filtered data.
706 byte[] buf = new byte[w * h * 3];
707 myInflater.inflate(buf);
708 decodeGradientData(x, y, w, h, buf);
709 } else {
710 // Compressed truecolor data.
711 byte[] buf = new byte[w * 3];
712 int i, offset;
713 for (int dy = y; dy < y + h; dy++) {
714 myInflater.inflate(buf);
715 offset = dy * rfb.framebufferWidth + x;
716 for (i = 0; i < w; i++) {
717 pixels24[offset + i] =
718 (buf[i * 3] & 0xFF) << 16 |
719 (buf[i * 3 + 1] & 0xFF) << 8 |
720 (buf[i * 3 + 2] & 0xFF);
721 }
722 }
723 }
724 } catch (DataFormatException dfe) {
725 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000726 }
727 }
728
729 handleUpdatedPixels(x, y, w, h);
730 scheduleRepaint(x, y, w, h);
731 }
732
733 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000734 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000735 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000736 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000737
738 int dx, dy, n;
739 int i = y * rfb.framebufferWidth + x;
740 int rowBytes = (w + 7) / 8;
741 byte b;
742
743 for (dy = 0; dy < h; dy++) {
744 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000745 b = src[dy * rowBytes + dx];
746 for (n = 7; n >= 0; n--)
747 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000748 }
749 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000750 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000751 }
752 i += (rfb.framebufferWidth - w);
753 }
754 }
755
756 //
757 // Decode data processed with the "Gradient" filter.
758 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000759 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000760
761 int dx, dy, c;
762 byte[] prevRow = new byte[w * 3];
763 byte[] thisRow = new byte[w * 3];
764 byte[] pix = new byte[3];
765 int[] est = new int[3];
766
767 int offset = y * rfb.framebufferWidth + x;
768
769 for (dy = 0; dy < h; dy++) {
770
771 /* First pixel in a row */
772 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000773 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
774 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000775 }
776 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000777 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000778
779 /* Remaining pixels of a row */
780 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000781 for (c = 0; c < 3; c++) {
782 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
783 (prevRow[(dx - 1) * 3 + c] & 0xFF));
784 if (est[c] > 0xFF) {
785 est[c] = 0xFF;
786 } else if (est[c] < 0x00) {
787 est[c] = 0x00;
788 }
789 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
790 thisRow[dx * 3 + c] = pix[c];
791 }
792 pixels24[offset++] =
793 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000794 }
795
796 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
797 offset += (rfb.framebufferWidth - w);
798 }
799 }
800
801
802 //
803 // Display newly updated area of pixels.
804 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000805 void handleUpdatedPixels(int x, int y, int w, int h) {
806
807 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000808
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000809 pixelsSource.newPixels(x, y, w, h);
810 memGraphics.setClip(x, y, w, h);
811 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
812 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
813 }
814
815 //
816 // Tell JVM to repaint specified desktop area.
817 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000818 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +0000819 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000820 // Do nothing, and remember we are seeking.
821 seekMode = true;
822 } else {
823 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000824 // Immediate repaint of the whole desktop after seeking.
825 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000826 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000827 // Usual incremental repaint.
828 repaint(player.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000829 }
830 seekMode = false;
831 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000832 }
833
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000834 //
835 // We are observing our FbsInputStream object to get notified on
836 // switching to the `paused' mode. In such cases we want to repaint
837 // our desktop if we were seeking.
838 //
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000839 public void update(Observable o, Object arg) {
840 // Immediate repaint of the whole desktop after seeking.
841 repaint();
842 // Let next scheduleRepaint() call invoke incremental drawing.
843 seekMode = false;
844 }
845
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000846}