blob: 82720164f7b0049ecd6a5e57247e25cf304664e6 [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
23import java.awt.*;
24import java.awt.event.*;
25import java.awt.image.*;
26import java.io.*;
27import java.lang.*;
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000028import java.util.*;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000029import java.util.zip.*;
30
31
32//
33// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
34//
35
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000036class VncCanvas extends Canvas implements Observer {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000037
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000038 RfbPlayer player;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000039 RfbProto rfb;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000040 ColorModel cm24;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000041
42 Image memImage;
43 Graphics memGraphics;
44
45 Image rawPixelsImage;
46 MemoryImageSource pixelsSource;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000047 int[] pixels24;
48
49 // Zlib encoder's data.
50 byte[] zlibBuf;
51 int zlibBufLen = 0;
52 Inflater zlibInflater;
53
54 // Tight encoder's data.
55 final static int tightZlibBufferSize = 512;
56 Inflater[] tightInflaters;
57
58 // Since JPEG images are loaded asynchronously, we have to remember
59 // their position in the framebuffer. Also, this jpegRect object is
60 // used for synchronization between the rfbThread and a JVM's thread
61 // which decodes and loads JPEG images.
62 Rectangle jpegRect;
63
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000064 // When we're in the seeking mode, we should not update the desktop.
65 // This variable helps us to remember that repainting the desktop at
66 // once is necessary when the seek operation is finished.
67 boolean seekMode;
68
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000069 //
70 // The constructor.
71 //
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 //
86
87 public Dimension getPreferredSize() {
88 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
89 }
90
91 public Dimension getMinimumSize() {
92 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
93 }
94
95 public Dimension getMaximumSize() {
96 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
97 }
98
99 //
100 // All painting is performed here.
101 //
102
103 public void update(Graphics g) {
104 paint(g);
105 }
106
107 public void paint(Graphics g) {
108 synchronized(memImage) {
109 g.drawImage(memImage, 0, 0, null);
110 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000111 }
112
113 //
114 // Override the ImageObserver interface method to handle drawing of
115 // JPEG-encoded data.
116 //
117
118 public boolean imageUpdate(Image img, int infoflags,
119 int x, int y, int width, int height) {
120 if ((infoflags & (ALLBITS | ABORT)) == 0) {
121 return true; // We need more image data.
122 } else {
123 // If the whole image is available, draw it now.
124 if ((infoflags & ALLBITS) != 0) {
125 if (jpegRect != null) {
126 synchronized(jpegRect) {
127 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
128 scheduleRepaint(jpegRect.x, jpegRect.y,
129 jpegRect.width, jpegRect.height);
130 jpegRect.notify();
131 }
132 }
133 }
134 return false; // All image data was processed.
135 }
136 }
137
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000138 void updateFramebufferSize() {
139
140 // Useful shortcuts.
141 int fbWidth = rfb.framebufferWidth;
142 int fbHeight = rfb.framebufferHeight;
143
144 // Create new off-screen image either if it does not exist, or if
145 // its geometry should be changed. It's not necessary to replace
146 // existing image if only pixel format should be changed.
147 if (memImage == null) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000148 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000149 memGraphics = memImage.getGraphics();
150 } else if (memImage.getWidth(null) != fbWidth ||
151 memImage.getHeight(null) != fbHeight) {
152 synchronized(memImage) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000153 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000154 memGraphics = memImage.getGraphics();
155 }
156 }
157
158 // Images with raw pixels should be re-allocated on every change
159 // of geometry or pixel format.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000160 pixels24 = new int[fbWidth * fbHeight];
161 pixelsSource =
162 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000163 pixelsSource.setAnimated(true);
164 rawPixelsImage = createImage(pixelsSource);
165
166 // Update the size of desktop containers.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000167 if (player.inSeparateFrame) {
168 if (player.desktopScrollPane != null)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000169 resizeDesktopFrame();
170 } else {
171 setSize(fbWidth, fbHeight);
172 }
173 }
174
175 void resizeDesktopFrame() {
176 setSize(rfb.framebufferWidth, rfb.framebufferHeight);
177
178 // FIXME: Find a better way to determine correct size of a
179 // ScrollPane. -- const
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000180 Insets insets = player.desktopScrollPane.getInsets();
181 player.desktopScrollPane.setSize(rfb.framebufferWidth +
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000182 2 * Math.min(insets.left, insets.right),
183 rfb.framebufferHeight +
184 2 * Math.min(insets.top, insets.bottom));
185
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000186 player.vncFrame.pack();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000187
188 // Try to limit the frame size to the screen size.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000189 Dimension screenSize = player.vncFrame.getToolkit().getScreenSize();
190 Dimension frameSize = player.vncFrame.getSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000191 Dimension newSize = frameSize;
192 boolean needToResizeFrame = false;
193 if (frameSize.height > screenSize.height) {
194 newSize.height = screenSize.height;
195 needToResizeFrame = true;
196 }
197 if (frameSize.width > screenSize.width) {
198 newSize.width = screenSize.width;
199 needToResizeFrame = true;
200 }
201 if (needToResizeFrame) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000202 player.vncFrame.setSize(newSize);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000203 }
204
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000205 player.desktopScrollPane.doLayout();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000206 }
207
208 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000209 // processNormalProtocol() - executed by the rfbThread to deal with
210 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000211 //
212
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000213 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000214
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000215 zlibInflater = new Inflater();
216 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000217
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000218 // Show current time position in the control panel.
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000219 player.updatePos();
220
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000221 // Tell our FbsInputStream object to notify us when it goes to the
222 // `paused' mode.
223 rfb.fbs.addObserver(this);
224
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000225 // Main dispatch loop.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000226
227 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000228
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000229 int msgType = rfb.readServerMessageType();
230
231 switch (msgType) {
232 case RfbProto.FramebufferUpdate:
233 rfb.readFramebufferUpdate();
234
235 for (int i = 0; i < rfb.updateNRects; i++) {
236 rfb.readFramebufferUpdateRectHdr();
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000237 int rx = rfb.updateRectX, ry = rfb.updateRectY;
238 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000239
240 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
241 break;
242
243 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
244 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
245 updateFramebufferSize();
246 break;
247 }
248
249 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
250 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000251 throw new Exception("Sorry, no support for" +
252 " cursor shape updates yet");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000253 }
254
255 switch (rfb.updateRectEncoding) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000256 case RfbProto.EncodingRaw:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000257 handleRawRect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000258 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000259 case RfbProto.EncodingCopyRect:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000260 handleCopyRect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000261 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000262 case RfbProto.EncodingRRE:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000263 handleRRERect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000264 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000265 case RfbProto.EncodingCoRRE:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000266 handleCoRRERect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000267 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000268 case RfbProto.EncodingHextile:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000269 handleHextileRect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000270 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000271 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000272 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000273 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000274 case RfbProto.EncodingTight:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000275 handleTightRect(rx, ry, rw, rh);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000276 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000277 default:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000278 throw new Exception("Unknown RFB rectangle encoding " +
279 rfb.updateRectEncoding);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000280 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000281 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000282 break;
283
284 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000285 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000286
287 case RfbProto.Bell:
288 Toolkit.getDefaultToolkit().beep();
289 break;
290
291 case RfbProto.ServerCutText:
292 String s = rfb.readServerCutText();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000293 break;
294
295 default:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000296 throw new Exception("Unknown RFB message type " + msgType);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000297 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000298
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000299 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000300 }
301 }
302
303
304 //
305 // Handle a raw rectangle.
306 //
307
308 void handleRawRect(int x, int y, int w, int h) throws IOException {
309
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000310 byte[] buf = new byte[w * 4];
311 int i, offset;
312 for (int dy = y; dy < y + h; dy++) {
313 rfb.is.readFully(buf);
314 offset = dy * rfb.framebufferWidth + x;
315 for (i = 0; i < w; i++) {
316 pixels24[offset + i] =
Constantin Kaplinskya5fcd982002-05-20 13:06:33 +0000317 (buf[i * 4 + 2] & 0xFF) << 16 |
318 (buf[i * 4 + 1] & 0xFF) << 8 |
319 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000320 }
321 }
322
323 handleUpdatedPixels(x, y, w, h);
324 scheduleRepaint(x, y, w, h);
325 }
326
327
328 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000329 // Handle a CopyRect rectangle.
330 //
331
332 void handleCopyRect(int x, int y, int w, int h) throws IOException {
333
334 rfb.readCopyRect();
335 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
336 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
337
338 scheduleRepaint(x, y, w, h);
339 }
340
341 //
342 // Handle an RRE-encoded rectangle.
343 //
344
345 void handleRRERect(int x, int y, int w, int h) throws IOException {
346
347 int nSubrects = rfb.is.readInt();
348 int sx, sy, sw, sh;
349
350 byte[] buf = new byte[4];
351 rfb.is.readFully(buf);
352 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
353 memGraphics.setColor(pixel);
354 memGraphics.fillRect(x, y, w, h);
355
356 for (int j = 0; j < nSubrects; j++) {
357 rfb.is.readFully(buf);
358 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
359 sx = x + rfb.is.readUnsignedShort();
360 sy = y + rfb.is.readUnsignedShort();
361 sw = rfb.is.readUnsignedShort();
362 sh = rfb.is.readUnsignedShort();
363
364 memGraphics.setColor(pixel);
365 memGraphics.fillRect(sx, sy, sw, sh);
366 }
367
368 scheduleRepaint(x, y, w, h);
369 }
370
371 //
372 // Handle a CoRRE-encoded rectangle.
373 //
374
375 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
376
377 int nSubrects = rfb.is.readInt();
378 int sx, sy, sw, sh;
379
380 byte[] buf = new byte[4];
381 rfb.is.readFully(buf);
382 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
383 memGraphics.setColor(pixel);
384 memGraphics.fillRect(x, y, w, h);
385
386 for (int j = 0; j < nSubrects; j++) {
387 rfb.is.readFully(buf);
388 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
389 sx = x + rfb.is.readUnsignedByte();
390 sy = y + rfb.is.readUnsignedByte();
391 sw = rfb.is.readUnsignedByte();
392 sh = rfb.is.readUnsignedByte();
393
394 memGraphics.setColor(pixel);
395 memGraphics.fillRect(sx, sy, sw, sh);
396 }
397
398 scheduleRepaint(x, y, w, h);
399 }
400
401 //
402 // Handle a Hextile-encoded rectangle.
403 //
404
405 // These colors should be kept between handleHextileSubrect() calls.
406 private Color hextile_bg, hextile_fg;
407
408 void handleHextileRect(int x, int y, int w, int h) throws IOException {
409
410 hextile_bg = new Color(0, 0, 0);
411 hextile_fg = new Color(0, 0, 0);
412
413 for (int ty = y; ty < y + h; ty += 16) {
414 int th = 16;
415 if (y + h - ty < 16)
416 th = y + h - ty;
417
418 for (int tx = x; tx < x + w; tx += 16) {
419 int tw = 16;
420 if (x + w - tx < 16)
421 tw = x + w - tx;
422
423 handleHextileSubrect(tx, ty, tw, th);
424 }
425
426 // Finished with a row of tiles, now let's show it.
427 scheduleRepaint(x, y, w, h);
428 }
429 }
430
431 //
432 // Handle one tile in the Hextile-encoded data.
433 //
434
435 void handleHextileSubrect(int tx, int ty, int tw, int th)
436 throws IOException {
437
438 byte[] buf = new byte[256 * 4];
439
440 int subencoding = rfb.is.readUnsignedByte();
441
442 // Is it a raw-encoded sub-rectangle?
443 if ((subencoding & rfb.HextileRaw) != 0) {
444 int count, offset;
445 for (int j = ty; j < ty + th; j++) {
446 rfb.is.readFully(buf, 0, tw * 4);
447 offset = j * rfb.framebufferWidth + tx;
448 for (count = 0; count < tw; count++) {
449 pixels24[offset + count] =
450 (buf[count * 4 + 2] & 0xFF) << 16 |
451 (buf[count * 4 + 1] & 0xFF) << 8 |
452 (buf[count * 4] & 0xFF);
453 }
454 }
455 handleUpdatedPixels(tx, ty, tw, th);
456 return;
457 }
458
459 // Read and draw the background if specified.
460 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
461 rfb.is.readFully(buf, 0, 4);
462 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
463 }
464 memGraphics.setColor(hextile_bg);
465 memGraphics.fillRect(tx, ty, tw, th);
466
467 // Read the foreground color if specified.
468 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
469 rfb.is.readFully(buf, 0, 4);
470 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
471 }
472
473 // Done with this tile if there is no sub-rectangles.
474 if ((subencoding & rfb.HextileAnySubrects) == 0)
475 return;
476
477 int nSubrects = rfb.is.readUnsignedByte();
478
479 int b1, b2, sx, sy, sw, sh;
480 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
481 for (int j = 0; j < nSubrects; j++) {
482 rfb.is.readFully(buf, 0, 4);
483 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
484 b1 = rfb.is.readUnsignedByte();
485 b2 = rfb.is.readUnsignedByte();
486 sx = tx + (b1 >> 4);
487 sy = ty + (b1 & 0xf);
488 sw = (b2 >> 4) + 1;
489 sh = (b2 & 0xf) + 1;
490 memGraphics.setColor(hextile_fg);
491 memGraphics.fillRect(sx, sy, sw, sh);
492 }
493 } else {
494 memGraphics.setColor(hextile_fg);
495 for (int j = 0; j < nSubrects; j++) {
496 b1 = rfb.is.readUnsignedByte();
497 b2 = rfb.is.readUnsignedByte();
498 sx = tx + (b1 >> 4);
499 sy = ty + (b1 & 0xf);
500 sw = (b2 >> 4) + 1;
501 sh = (b2 & 0xf) + 1;
502 memGraphics.fillRect(sx, sy, sw, sh);
503 }
504 }
505 }
506
507 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000508 // Handle a Zlib-encoded rectangle.
509 //
510
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000511 void handleZlibRect(int x, int y, int w, int h) throws Exception {
512
513 int nBytes = rfb.is.readInt();
514
515 if (zlibBuf == null || zlibBufLen < nBytes) {
516 zlibBufLen = nBytes * 2;
517 zlibBuf = new byte[zlibBufLen];
518 }
519
520 rfb.is.readFully(zlibBuf, 0, nBytes);
521 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000522
523 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000524 byte[] buf = new byte[w * 4];
525 int i, offset;
526 for (int dy = y; dy < y + h; dy++) {
527 zlibInflater.inflate(buf);
528 offset = dy * rfb.framebufferWidth + x;
529 for (i = 0; i < w; i++) {
530 pixels24[offset + i] =
Constantin Kaplinskya5fcd982002-05-20 13:06:33 +0000531 (buf[i * 4 + 2] & 0xFF) << 16 |
532 (buf[i * 4 + 1] & 0xFF) << 8 |
533 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000534 }
535 }
536 }
537 catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000538 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000539 }
540
541 handleUpdatedPixels(x, y, w, h);
542 scheduleRepaint(x, y, w, h);
543 }
544
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000545 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000546 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000547 //
548
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) {
556 tightInflaters[stream_id] = null;
557 }
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) {
592 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
593 try {
594 // Wait no longer than three seconds.
595 jpegRect.wait(3000);
596 } catch (InterruptedException e) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000597 throw new Exception("Interrupted while decoding JPEG image");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000598 }
599 }
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) {
615 numColors = rfb.is.readUnsignedByte() + 1;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000616 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));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000622 }
623 if (numColors == 2)
624 rowSize = (w + 7) / 8;
625 } else if (filter_id == rfb.TightFilterGradient) {
626 useGradient = true;
627 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +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) {
639 // Indexed colors.
640 byte[] indexedData = new byte[dataSize];
641 rfb.is.readFully(indexedData);
642 if (numColors == 2) {
643 // Two colors.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000644 decodeMonoData(x, y, w, h, indexedData, palette24);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000645 } else {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000646 // 3..255 colors.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000647 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 }
655 } else if (useGradient) {
656 // "Gradient"-processed data
657 byte[] buf = new byte[w * h * 3];
658 rfb.is.readFully(buf);
659 decodeGradientData(x, y, w, h, buf);
660 } else {
661 // Raw truecolor data.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000662 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);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000672 }
673 }
674 }
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) {
682 tightInflaters[stream_id] = new Inflater();
683 }
684 Inflater myInflater = tightInflaters[stream_id];
685 myInflater.setInput(zlibData);
686 try {
687 if (numColors != 0) {
688 // Indexed colors.
689 byte[] indexedData = new byte[dataSize];
690 myInflater.inflate(indexedData);
691 if (numColors == 2) {
692 // Two colors.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000693 decodeMonoData(x, y, w, h, indexedData, palette24);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000694 } else {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000695 // More than two colors.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000696 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) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000705 // Compressed "Gradient"-filtered data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000706 byte[] buf = new byte[w * h * 3];
707 myInflater.inflate(buf);
708 decodeGradientData(x, y, w, h, buf);
709 } else {
710 // Compressed truecolor data.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000711 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);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000721 }
722 }
723 }
724 }
725 catch(DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000726 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000727 }
728 }
729
730 handleUpdatedPixels(x, y, w, h);
731 scheduleRepaint(x, y, w, h);
732 }
733
734 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000735 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000736 //
737
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++) {
747 b = src[dy*rowBytes+dx];
748 for (n = 7; n >= 0; n--)
749 pixels24[i++] = palette[b >> n & 1];
750 }
751 for (n = 7; n >= 8 - w % 8; n--) {
752 pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
753 }
754 i += (rfb.framebufferWidth - w);
755 }
756 }
757
758 //
759 // Decode data processed with the "Gradient" filter.
760 //
761
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000762 void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000763
764 int dx, dy, c;
765 byte[] prevRow = new byte[w * 3];
766 byte[] thisRow = new byte[w * 3];
767 byte[] pix = new byte[3];
768 int[] est = new int[3];
769
770 int offset = y * rfb.framebufferWidth + x;
771
772 for (dy = 0; dy < h; dy++) {
773
774 /* First pixel in a row */
775 for (c = 0; c < 3; c++) {
776 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
777 thisRow[c] = pix[c];
778 }
779 pixels24[offset++] =
780 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
781
782 /* Remaining pixels of a row */
783 for (dx = 1; dx < w; dx++) {
784 for (c = 0; c < 3; c++) {
785 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
786 (prevRow[(dx-1) * 3 + c] & 0xFF));
787 if (est[c] > 0xFF) {
788 est[c] = 0xFF;
789 } else if (est[c] < 0x00) {
790 est[c] = 0x00;
791 }
792 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
793 thisRow[dx * 3 + c] = pix[c];
794 }
795 pixels24[offset++] =
796 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
797 }
798
799 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
800 offset += (rfb.framebufferWidth - w);
801 }
802 }
803
804
805 //
806 // Display newly updated area of pixels.
807 //
808
809 void handleUpdatedPixels(int x, int y, int w, int h) {
810
811 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000812
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000813 pixelsSource.newPixels(x, y, w, h);
814 memGraphics.setClip(x, y, w, h);
815 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
816 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
817 }
818
819 //
820 // Tell JVM to repaint specified desktop area.
821 //
822
823 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +0000824 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000825 // Do nothing, and remember we are seeking.
826 seekMode = true;
827 } else {
828 if (seekMode) {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000829 // Immediate repaint of the whole desktop after seeking.
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000830 repaint();
831 } else {
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000832 // Usual incremental repaint.
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000833 repaint(player.deferScreenUpdates, x, y, w, h);
834 }
835 seekMode = false;
836 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000837 }
838
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000839 //
840 // We are observing our FbsInputStream object to get notified on
841 // switching to the `paused' mode. In such cases we want to repaint
842 // our desktop if we were seeking.
843 //
844
845 public void update(Observable o, Object arg) {
846 // Immediate repaint of the whole desktop after seeking.
847 repaint();
848 // Let next scheduleRepaint() call invoke incremental drawing.
849 seekMode = false;
850 }
851
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000852}