blob: 96e8035d75194fea887e8e6ccd961d2b9df9eb82 [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) {
wimba.comb7017b72004-09-16 16:11:55 +0000252 if (rfb.updateRectW != 0 && rfb.updateRectH != 0) {
253 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
254 updateFramebufferSize();
255 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000256 break;
257 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000258
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000259 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
260 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
261 throw new Exception("Sorry, no support for" +
262 " cursor shape updates yet");
263 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000264
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000265 switch (rfb.updateRectEncoding) {
266 case RfbProto.EncodingRaw:
267 handleRawRect(rx, ry, rw, rh);
268 break;
269 case RfbProto.EncodingCopyRect:
270 handleCopyRect(rx, ry, rw, rh);
271 break;
272 case RfbProto.EncodingRRE:
273 handleRRERect(rx, ry, rw, rh);
274 break;
275 case RfbProto.EncodingCoRRE:
276 handleCoRRERect(rx, ry, rw, rh);
277 break;
278 case RfbProto.EncodingHextile:
279 handleHextileRect(rx, ry, rw, rh);
280 break;
281 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000282 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000283 break;
284 case RfbProto.EncodingTight:
285 handleTightRect(rx, ry, rw, rh);
286 break;
287 default:
288 throw new Exception("Unknown RFB rectangle encoding " +
289 rfb.updateRectEncoding);
290 }
291 }
292 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000293
294 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000295 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000296
297 case RfbProto.Bell:
298 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000299 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000300
301 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000302 String s = rfb.readServerCutText();
303 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000304
305 default:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000306 throw new Exception("Unknown RFB message type " + msgType);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000307 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000308
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000309 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000310 }
311 }
312
313
314 //
315 // Handle a raw rectangle.
316 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000317 void handleRawRect(int x, int y, int w, int h) throws IOException {
318
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000319 byte[] buf = new byte[w * 4];
320 int i, offset;
321 for (int dy = y; dy < y + h; dy++) {
322 rfb.is.readFully(buf);
323 offset = dy * rfb.framebufferWidth + x;
324 for (i = 0; i < w; i++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000325 pixels24[offset + i] =
326 (buf[i * 4 + 2] & 0xFF) << 16 |
327 (buf[i * 4 + 1] & 0xFF) << 8 |
328 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000329 }
330 }
331
332 handleUpdatedPixels(x, y, w, h);
333 scheduleRepaint(x, y, w, h);
334 }
335
336
337 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000338 // Handle a CopyRect rectangle.
339 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000340 void handleCopyRect(int x, int y, int w, int h) throws IOException {
341
342 rfb.readCopyRect();
343 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000344 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000345
346 scheduleRepaint(x, y, w, h);
347 }
348
349 //
350 // Handle an RRE-encoded rectangle.
351 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000352 void handleRRERect(int x, int y, int w, int h) throws IOException {
353
354 int nSubrects = rfb.is.readInt();
355 int sx, sy, sw, sh;
356
357 byte[] buf = new byte[4];
358 rfb.is.readFully(buf);
359 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
360 memGraphics.setColor(pixel);
361 memGraphics.fillRect(x, y, w, h);
362
363 for (int j = 0; j < nSubrects; j++) {
364 rfb.is.readFully(buf);
365 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
366 sx = x + rfb.is.readUnsignedShort();
367 sy = y + rfb.is.readUnsignedShort();
368 sw = rfb.is.readUnsignedShort();
369 sh = rfb.is.readUnsignedShort();
370
371 memGraphics.setColor(pixel);
372 memGraphics.fillRect(sx, sy, sw, sh);
373 }
374
375 scheduleRepaint(x, y, w, h);
376 }
377
378 //
379 // Handle a CoRRE-encoded rectangle.
380 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000381 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
382
383 int nSubrects = rfb.is.readInt();
384 int sx, sy, sw, sh;
385
386 byte[] buf = new byte[4];
387 rfb.is.readFully(buf);
388 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
389 memGraphics.setColor(pixel);
390 memGraphics.fillRect(x, y, w, h);
391
392 for (int j = 0; j < nSubrects; j++) {
393 rfb.is.readFully(buf);
394 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
395 sx = x + rfb.is.readUnsignedByte();
396 sy = y + rfb.is.readUnsignedByte();
397 sw = rfb.is.readUnsignedByte();
398 sh = rfb.is.readUnsignedByte();
399
400 memGraphics.setColor(pixel);
401 memGraphics.fillRect(sx, sy, sw, sh);
402 }
403
404 scheduleRepaint(x, y, w, h);
405 }
406
407 //
408 // Handle a Hextile-encoded rectangle.
409 //
410
411 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000412 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000413
414 void handleHextileRect(int x, int y, int w, int h) throws IOException {
415
416 hextile_bg = new Color(0, 0, 0);
417 hextile_fg = new Color(0, 0, 0);
418
419 for (int ty = y; ty < y + h; ty += 16) {
420 int th = 16;
421 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000422 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000423
424 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000425 int tw = 16;
426 if (x + w - tx < 16)
427 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000428
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000429 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000430 }
431
432 // Finished with a row of tiles, now let's show it.
433 scheduleRepaint(x, y, w, h);
434 }
435 }
436
437 //
438 // Handle one tile in the Hextile-encoded data.
439 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000440 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000441 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000442
443 byte[] buf = new byte[256 * 4];
444
445 int subencoding = rfb.is.readUnsignedByte();
446
447 // Is it a raw-encoded sub-rectangle?
448 if ((subencoding & rfb.HextileRaw) != 0) {
449 int count, offset;
450 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000451 rfb.is.readFully(buf, 0, tw * 4);
452 offset = j * rfb.framebufferWidth + tx;
453 for (count = 0; count < tw; count++) {
454 pixels24[offset + count] =
455 (buf[count * 4 + 2] & 0xFF) << 16 |
456 (buf[count * 4 + 1] & 0xFF) << 8 |
457 (buf[count * 4] & 0xFF);
458 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000459 }
460 handleUpdatedPixels(tx, ty, tw, th);
461 return;
462 }
463
464 // Read and draw the background if specified.
465 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
466 rfb.is.readFully(buf, 0, 4);
467 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
468 }
469 memGraphics.setColor(hextile_bg);
470 memGraphics.fillRect(tx, ty, tw, th);
471
472 // Read the foreground color if specified.
473 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
474 rfb.is.readFully(buf, 0, 4);
475 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
476 }
477
478 // Done with this tile if there is no sub-rectangles.
479 if ((subencoding & rfb.HextileAnySubrects) == 0)
480 return;
481
482 int nSubrects = rfb.is.readUnsignedByte();
483
484 int b1, b2, sx, sy, sw, sh;
485 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
486 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000487 rfb.is.readFully(buf, 0, 4);
488 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
489 b1 = rfb.is.readUnsignedByte();
490 b2 = rfb.is.readUnsignedByte();
491 sx = tx + (b1 >> 4);
492 sy = ty + (b1 & 0xf);
493 sw = (b2 >> 4) + 1;
494 sh = (b2 & 0xf) + 1;
495 memGraphics.setColor(hextile_fg);
496 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000497 }
498 } else {
499 memGraphics.setColor(hextile_fg);
500 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000501 b1 = rfb.is.readUnsignedByte();
502 b2 = rfb.is.readUnsignedByte();
503 sx = tx + (b1 >> 4);
504 sy = ty + (b1 & 0xf);
505 sw = (b2 >> 4) + 1;
506 sh = (b2 & 0xf) + 1;
507 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000508 }
509 }
510 }
511
512 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000513 // Handle a Zlib-encoded rectangle.
514 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000515 void handleZlibRect(int x, int y, int w, int h) throws Exception {
516
517 int nBytes = rfb.is.readInt();
518
519 if (zlibBuf == null || zlibBufLen < nBytes) {
520 zlibBufLen = nBytes * 2;
521 zlibBuf = new byte[zlibBufLen];
522 }
523
524 rfb.is.readFully(zlibBuf, 0, nBytes);
525 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000526
527 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000528 byte[] buf = new byte[w * 4];
529 int i, offset;
530 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000531 zlibInflater.inflate(buf);
532 offset = dy * rfb.framebufferWidth + x;
533 for (i = 0; i < w; i++) {
534 pixels24[offset + i] =
535 (buf[i * 4 + 2] & 0xFF) << 16 |
536 (buf[i * 4 + 1] & 0xFF) << 8 |
537 (buf[i * 4] & 0xFF);
538 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000539 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000540 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000541 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000542 }
543
544 handleUpdatedPixels(x, y, w, h);
545 scheduleRepaint(x, y, w, h);
546 }
547
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000548 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000549 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000550 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000551 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000552
553 int comp_ctl = rfb.is.readUnsignedByte();
554
555 // Flush zlib streams if we are told by the server to do so.
556 for (int stream_id = 0; stream_id < 4; stream_id++) {
557 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000558 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000559 }
560 comp_ctl >>= 1;
561 }
562
563 // Check correctness of subencoding value.
564 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000565 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000566 }
567
568 // Handle solid-color rectangles.
569 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000570 byte[] buf = new byte[3];
571 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000572 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000573 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000574 memGraphics.fillRect(x, y, w, h);
575 scheduleRepaint(x, y, w, h);
576 return;
577 }
578
579 if (comp_ctl == rfb.TightJpeg) {
580
581 // Read JPEG data.
582 byte[] jpegData = new byte[rfb.readCompactLen()];
583 rfb.is.readFully(jpegData);
584
585 // Create an Image object from the JPEG data.
586 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
587
588 // Remember the rectangle where the image should be drawn.
589 jpegRect = new Rectangle(x, y, w, h);
590
591 // Let the imageUpdate() method do the actual drawing, here just
592 // wait until the image is fully loaded and drawn.
593 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000594 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
595 try {
596 // Wait no longer than three seconds.
597 jpegRect.wait(3000);
598 } catch (InterruptedException e) {
599 throw new Exception("Interrupted while decoding JPEG image");
600 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000601 }
602
603 // Done, jpegRect is not needed any more.
604 jpegRect = null;
605 return;
606
607 }
608
609 // Read filter id and parameters.
610 int numColors = 0, rowSize = w;
611 byte[] palette8 = new byte[2];
612 int[] palette24 = new int[256];
613 boolean useGradient = false;
614 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
615 int filter_id = rfb.is.readUnsignedByte();
616 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000617 numColors = rfb.is.readUnsignedByte() + 1;
618 byte[] buf = new byte[numColors * 3];
619 rfb.is.readFully(buf);
620 for (int i = 0; i < numColors; i++) {
621 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
622 (buf[i * 3 + 1] & 0xFF) << 8 |
623 (buf[i * 3 + 2] & 0xFF));
624 }
625 if (numColors == 2)
626 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000627 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000628 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000629 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000630 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000631 }
632 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000633 if (numColors == 0)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000634 rowSize *= 3;
635
636 // Read, optionally uncompress and decode data.
637 int dataSize = h * rowSize;
638 if (dataSize < rfb.TightMinToCompress) {
639 // Data size is small - not compressed with zlib.
640 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000641 // Indexed colors.
642 byte[] indexedData = new byte[dataSize];
643 rfb.is.readFully(indexedData);
644 if (numColors == 2) {
645 // Two colors.
646 decodeMonoData(x, y, w, h, indexedData, palette24);
647 } else {
648 // 3..255 colors.
649 int i = 0;
650 for (int dy = y; dy < y + h; dy++) {
651 for (int dx = x; dx < x + w; dx++) {
652 pixels24[dy * rfb.framebufferWidth + dx] =
653 palette24[indexedData[i++] & 0xFF];
654 }
655 }
656 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000657 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000658 // "Gradient"-processed data
659 byte[] buf = new byte[w * h * 3];
660 rfb.is.readFully(buf);
661 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000662 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000663 // Raw truecolor data.
664 byte[] buf = new byte[w * 3];
665 int i, offset;
666 for (int dy = y; dy < y + h; dy++) {
667 rfb.is.readFully(buf);
668 offset = dy * rfb.framebufferWidth + x;
669 for (i = 0; i < w; i++) {
670 pixels24[offset + i] =
671 (buf[i * 3] & 0xFF) << 16 |
672 (buf[i * 3 + 1] & 0xFF) << 8 |
673 (buf[i * 3 + 2] & 0xFF);
674 }
675 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000676 }
677 } else {
678 // Data was compressed with zlib.
679 int zlibDataLen = rfb.readCompactLen();
680 byte[] zlibData = new byte[zlibDataLen];
681 rfb.is.readFully(zlibData);
682 int stream_id = comp_ctl & 0x03;
683 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000684 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000685 }
686 Inflater myInflater = tightInflaters[stream_id];
687 myInflater.setInput(zlibData);
688 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000689 if (numColors != 0) {
690 // Indexed colors.
691 byte[] indexedData = new byte[dataSize];
692 myInflater.inflate(indexedData);
693 if (numColors == 2) {
694 // Two colors.
695 decodeMonoData(x, y, w, h, indexedData, palette24);
696 } else {
697 // More than two colors.
698 int i = 0;
699 for (int dy = y; dy < y + h; dy++) {
700 for (int dx = x; dx < x + w; dx++) {
701 pixels24[dy * rfb.framebufferWidth + dx] =
702 palette24[indexedData[i++] & 0xFF];
703 }
704 }
705 }
706 } else if (useGradient) {
707 // Compressed "Gradient"-filtered data.
708 byte[] buf = new byte[w * h * 3];
709 myInflater.inflate(buf);
710 decodeGradientData(x, y, w, h, buf);
711 } else {
712 // Compressed truecolor data.
713 byte[] buf = new byte[w * 3];
714 int i, offset;
715 for (int dy = y; dy < y + h; dy++) {
716 myInflater.inflate(buf);
717 offset = dy * rfb.framebufferWidth + x;
718 for (i = 0; i < w; i++) {
719 pixels24[offset + i] =
720 (buf[i * 3] & 0xFF) << 16 |
721 (buf[i * 3 + 1] & 0xFF) << 8 |
722 (buf[i * 3 + 2] & 0xFF);
723 }
724 }
725 }
726 } catch (DataFormatException dfe) {
727 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000728 }
729 }
730
731 handleUpdatedPixels(x, y, w, h);
732 scheduleRepaint(x, y, w, h);
733 }
734
735 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000736 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000737 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000738 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000739
740 int dx, dy, n;
741 int i = y * rfb.framebufferWidth + x;
742 int rowBytes = (w + 7) / 8;
743 byte b;
744
745 for (dy = 0; dy < h; dy++) {
746 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000747 b = src[dy * rowBytes + dx];
748 for (n = 7; n >= 0; n--)
749 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000750 }
751 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000752 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000753 }
754 i += (rfb.framebufferWidth - w);
755 }
756 }
757
758 //
759 // Decode data processed with the "Gradient" filter.
760 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000761 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000762
763 int dx, dy, c;
764 byte[] prevRow = new byte[w * 3];
765 byte[] thisRow = new byte[w * 3];
766 byte[] pix = new byte[3];
767 int[] est = new int[3];
768
769 int offset = y * rfb.framebufferWidth + x;
770
771 for (dy = 0; dy < h; dy++) {
772
773 /* First pixel in a row */
774 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000775 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
776 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000777 }
778 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000779 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000780
781 /* Remaining pixels of a row */
782 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000783 for (c = 0; c < 3; c++) {
784 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
785 (prevRow[(dx - 1) * 3 + c] & 0xFF));
786 if (est[c] > 0xFF) {
787 est[c] = 0xFF;
788 } else if (est[c] < 0x00) {
789 est[c] = 0x00;
790 }
791 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
792 thisRow[dx * 3 + c] = pix[c];
793 }
794 pixels24[offset++] =
795 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000796 }
797
798 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
799 offset += (rfb.framebufferWidth - w);
800 }
801 }
802
803
804 //
805 // Display newly updated area of pixels.
806 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000807 void handleUpdatedPixels(int x, int y, int w, int h) {
808
809 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000810
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000811 pixelsSource.newPixels(x, y, w, h);
812 memGraphics.setClip(x, y, w, h);
813 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
814 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
815 }
816
817 //
818 // Tell JVM to repaint specified desktop area.
819 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000820 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +0000821 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000822 // Do nothing, and remember we are seeking.
823 seekMode = true;
824 } else {
825 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000826 // Immediate repaint of the whole desktop after seeking.
827 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000828 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000829 // Usual incremental repaint.
830 repaint(player.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000831 }
832 seekMode = false;
833 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000834 }
835
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000836 //
837 // We are observing our FbsInputStream object to get notified on
838 // switching to the `paused' mode. In such cases we want to repaint
839 // our desktop if we were seeking.
840 //
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000841 public void update(Observable o, Object arg) {
842 // Immediate repaint of the whole desktop after seeking.
843 repaint();
844 // Let next scheduleRepaint() call invoke incremental drawing.
845 seekMode = false;
846 }
847
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000848}