blob: 57bb8588d421a42a29efb7b641b48b4137541c53 [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//
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +000035class VncCanvas extends Canvas implements Observer {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000036
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000037 RfbPlayer player;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000038 RfbProto rfb;
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000039 ColorModel cm24;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000040
41 Image memImage;
42 Graphics memGraphics;
43
44 Image rawPixelsImage;
45 MemoryImageSource pixelsSource;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000046 int[] pixels24;
47
48 // Zlib encoder's data.
49 byte[] zlibBuf;
50 int zlibBufLen = 0;
51 Inflater zlibInflater;
52
53 // Tight encoder's data.
54 final static int tightZlibBufferSize = 512;
55 Inflater[] tightInflaters;
56
57 // Since JPEG images are loaded asynchronously, we have to remember
58 // their position in the framebuffer. Also, this jpegRect object is
59 // used for synchronization between the rfbThread and a JVM's thread
60 // which decodes and loads JPEG images.
61 Rectangle jpegRect;
62
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000063 // When we're in the seeking mode, we should not update the desktop.
64 // This variable helps us to remember that repainting the desktop at
65 // once is necessary when the seek operation is finished.
66 boolean seekMode;
67
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000068 //
69 // The constructor.
70 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000071 VncCanvas(RfbPlayer player) throws IOException {
72 this.player = player;
73 rfb = player.rfb;
Constantin Kaplinsky52c48242002-05-30 13:26:34 +000074 seekMode = false;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000075
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000076 cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
77
Constantin Kaplinsky903009e2002-05-20 10:55:47 +000078 updateFramebufferSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000079 }
80
81 //
82 // Callback methods to determine geometry of our Component.
83 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000084 public Dimension getPreferredSize() {
85 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
86 }
87
88 public Dimension getMinimumSize() {
89 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
90 }
91
92 public Dimension getMaximumSize() {
93 return new Dimension(rfb.framebufferWidth, rfb.framebufferHeight);
94 }
95
96 //
97 // All painting is performed here.
98 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +000099 public void update(Graphics g) {
100 paint(g);
101 }
102
103 public void paint(Graphics g) {
104 synchronized(memImage) {
105 g.drawImage(memImage, 0, 0, null);
106 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000107 }
108
109 //
110 // Override the ImageObserver interface method to handle drawing of
111 // JPEG-encoded data.
112 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000113 public boolean imageUpdate(Image img, int infoflags,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000114 int x, int y, int width, int height) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000115 if ((infoflags & (ALLBITS | ABORT)) == 0) {
116 return true; // We need more image data.
117 } else {
118 // If the whole image is available, draw it now.
119 if ((infoflags & ALLBITS) != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000120 if (jpegRect != null) {
121 synchronized(jpegRect) {
122 memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
123 scheduleRepaint(jpegRect.x, jpegRect.y,
124 jpegRect.width, jpegRect.height);
125 jpegRect.notify();
126 }
127 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000128 }
129 return false; // All image data was processed.
130 }
131 }
132
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000133 void updateFramebufferSize() {
134
135 // Useful shortcuts.
136 int fbWidth = rfb.framebufferWidth;
137 int fbHeight = rfb.framebufferHeight;
138
139 // Create new off-screen image either if it does not exist, or if
140 // its geometry should be changed. It's not necessary to replace
141 // existing image if only pixel format should be changed.
142 if (memImage == null) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000143 memImage = player.createImage(fbWidth, fbHeight);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000144 memGraphics = memImage.getGraphics();
145 } else if (memImage.getWidth(null) != fbWidth ||
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000146 memImage.getHeight(null) != fbHeight) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000147 synchronized(memImage) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000148 memImage = player.createImage(fbWidth, fbHeight);
149 memGraphics = memImage.getGraphics();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000150 }
151 }
152
153 // Images with raw pixels should be re-allocated on every change
154 // of geometry or pixel format.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000155 pixels24 = new int[fbWidth * fbHeight];
156 pixelsSource =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000157 new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000158 pixelsSource.setAnimated(true);
159 rawPixelsImage = createImage(pixelsSource);
160
161 // Update the size of desktop containers.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000162 if (player.inSeparateFrame) {
163 if (player.desktopScrollPane != null)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000164 resizeDesktopFrame();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000165 } else {
166 setSize(fbWidth, fbHeight);
167 }
168 }
169
170 void resizeDesktopFrame() {
171 setSize(rfb.framebufferWidth, rfb.framebufferHeight);
172
173 // FIXME: Find a better way to determine correct size of a
174 // ScrollPane. -- const
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000175 Insets insets = player.desktopScrollPane.getInsets();
176 player.desktopScrollPane.setSize(rfb.framebufferWidth +
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000177 2 * Math.min(insets.left, insets.right),
178 rfb.framebufferHeight +
179 2 * Math.min(insets.top, insets.bottom));
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000180
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000181 player.vncFrame.pack();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000182
183 // Try to limit the frame size to the screen size.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000184 Dimension screenSize = player.vncFrame.getToolkit().getScreenSize();
185 Dimension frameSize = player.vncFrame.getSize();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000186 Dimension newSize = frameSize;
187 boolean needToResizeFrame = false;
188 if (frameSize.height > screenSize.height) {
189 newSize.height = screenSize.height;
190 needToResizeFrame = true;
191 }
192 if (frameSize.width > screenSize.width) {
193 newSize.width = screenSize.width;
194 needToResizeFrame = true;
195 }
196 if (needToResizeFrame) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000197 player.vncFrame.setSize(newSize);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000198 }
199
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000200 player.desktopScrollPane.doLayout();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000201 }
202
203 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000204 // processNormalProtocol() - executed by the rfbThread to deal with
205 // the RFB data.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000206 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000207 public void processNormalProtocol() throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000208
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000209 zlibInflater = new Inflater();
210 tightInflaters = new Inflater[4];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000211
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000212 // Show current time position in the control panel.
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000213 player.updatePos();
214
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000215 // Tell our FbsInputStream object to notify us when it goes to the
216 // `paused' mode.
217 rfb.fbs.addObserver(this);
218
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000219 // Main dispatch loop.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000220
221 while (true) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000222
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000223 int msgType = rfb.readServerMessageType();
224
225 switch (msgType) {
226 case RfbProto.FramebufferUpdate:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000227 rfb.readFramebufferUpdate();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000228
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000229 for (int i = 0; i < rfb.updateNRects; i++) {
230 rfb.readFramebufferUpdateRectHdr();
231 int rx = rfb.updateRectX, ry = rfb.updateRectY;
232 int rw = rfb.updateRectW, rh = rfb.updateRectH;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000233
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000234 if (rfb.updateRectEncoding == rfb.EncodingLastRect)
235 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000236
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000237 if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
238 rfb.setFramebufferSize(rfb.updateRectW, rfb.updateRectH);
239 updateFramebufferSize();
240 break;
241 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000242
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000243 if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
244 rfb.updateRectEncoding == rfb.EncodingRichCursor) {
245 throw new Exception("Sorry, no support for" +
246 " cursor shape updates yet");
247 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000248
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000249 switch (rfb.updateRectEncoding) {
250 case RfbProto.EncodingRaw:
251 handleRawRect(rx, ry, rw, rh);
252 break;
253 case RfbProto.EncodingCopyRect:
254 handleCopyRect(rx, ry, rw, rh);
255 break;
256 case RfbProto.EncodingRRE:
257 handleRRERect(rx, ry, rw, rh);
258 break;
259 case RfbProto.EncodingCoRRE:
260 handleCoRRERect(rx, ry, rw, rh);
261 break;
262 case RfbProto.EncodingHextile:
263 handleHextileRect(rx, ry, rw, rh);
264 break;
265 case RfbProto.EncodingZlib:
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000266 handleZlibRect(rx, ry, rw, rh);
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000267 break;
268 case RfbProto.EncodingTight:
269 handleTightRect(rx, ry, rw, rh);
270 break;
271 default:
272 throw new Exception("Unknown RFB rectangle encoding " +
273 rfb.updateRectEncoding);
274 }
275 }
276 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000277
278 case RfbProto.SetColourMapEntries:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000279 throw new Exception("Can't handle SetColourMapEntries message");
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000280
281 case RfbProto.Bell:
282 Toolkit.getDefaultToolkit().beep();
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000283 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000284
285 case RfbProto.ServerCutText:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000286 String s = rfb.readServerCutText();
287 break;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000288
289 default:
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000290 throw new Exception("Unknown RFB message type " + msgType);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000291 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000292
Constantin Kaplinskyfe079832002-05-29 00:52:32 +0000293 player.updatePos();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000294 }
295 }
296
297
298 //
299 // Handle a raw rectangle.
300 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000301 void handleRawRect(int x, int y, int w, int h) throws IOException {
302
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000303 byte[] buf = new byte[w * 4];
304 int i, offset;
305 for (int dy = y; dy < y + h; dy++) {
306 rfb.is.readFully(buf);
307 offset = dy * rfb.framebufferWidth + x;
308 for (i = 0; i < w; i++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000309 pixels24[offset + i] =
310 (buf[i * 4 + 2] & 0xFF) << 16 |
311 (buf[i * 4 + 1] & 0xFF) << 8 |
312 (buf[i * 4] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000313 }
314 }
315
316 handleUpdatedPixels(x, y, w, h);
317 scheduleRepaint(x, y, w, h);
318 }
319
320
321 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000322 // Handle a CopyRect rectangle.
323 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000324 void handleCopyRect(int x, int y, int w, int h) throws IOException {
325
326 rfb.readCopyRect();
327 memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000328 x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000329
330 scheduleRepaint(x, y, w, h);
331 }
332
333 //
334 // Handle an RRE-encoded rectangle.
335 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000336 void handleRRERect(int x, int y, int w, int h) throws IOException {
337
338 int nSubrects = rfb.is.readInt();
339 int sx, sy, sw, sh;
340
341 byte[] buf = new byte[4];
342 rfb.is.readFully(buf);
343 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
344 memGraphics.setColor(pixel);
345 memGraphics.fillRect(x, y, w, h);
346
347 for (int j = 0; j < nSubrects; j++) {
348 rfb.is.readFully(buf);
349 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
350 sx = x + rfb.is.readUnsignedShort();
351 sy = y + rfb.is.readUnsignedShort();
352 sw = rfb.is.readUnsignedShort();
353 sh = rfb.is.readUnsignedShort();
354
355 memGraphics.setColor(pixel);
356 memGraphics.fillRect(sx, sy, sw, sh);
357 }
358
359 scheduleRepaint(x, y, w, h);
360 }
361
362 //
363 // Handle a CoRRE-encoded rectangle.
364 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000365 void handleCoRRERect(int x, int y, int w, int h) throws IOException {
366
367 int nSubrects = rfb.is.readInt();
368 int sx, sy, sw, sh;
369
370 byte[] buf = new byte[4];
371 rfb.is.readFully(buf);
372 Color pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
373 memGraphics.setColor(pixel);
374 memGraphics.fillRect(x, y, w, h);
375
376 for (int j = 0; j < nSubrects; j++) {
377 rfb.is.readFully(buf);
378 pixel = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
379 sx = x + rfb.is.readUnsignedByte();
380 sy = y + rfb.is.readUnsignedByte();
381 sw = rfb.is.readUnsignedByte();
382 sh = rfb.is.readUnsignedByte();
383
384 memGraphics.setColor(pixel);
385 memGraphics.fillRect(sx, sy, sw, sh);
386 }
387
388 scheduleRepaint(x, y, w, h);
389 }
390
391 //
392 // Handle a Hextile-encoded rectangle.
393 //
394
395 // These colors should be kept between handleHextileSubrect() calls.
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000396 private Color hextile_bg, hextile_fg;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000397
398 void handleHextileRect(int x, int y, int w, int h) throws IOException {
399
400 hextile_bg = new Color(0, 0, 0);
401 hextile_fg = new Color(0, 0, 0);
402
403 for (int ty = y; ty < y + h; ty += 16) {
404 int th = 16;
405 if (y + h - ty < 16)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000406 th = y + h - ty;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000407
408 for (int tx = x; tx < x + w; tx += 16) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000409 int tw = 16;
410 if (x + w - tx < 16)
411 tw = x + w - tx;
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000412
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000413 handleHextileSubrect(tx, ty, tw, th);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000414 }
415
416 // Finished with a row of tiles, now let's show it.
417 scheduleRepaint(x, y, w, h);
418 }
419 }
420
421 //
422 // Handle one tile in the Hextile-encoded data.
423 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000424 void handleHextileSubrect(int tx, int ty, int tw, int th)
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000425 throws IOException {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000426
427 byte[] buf = new byte[256 * 4];
428
429 int subencoding = rfb.is.readUnsignedByte();
430
431 // Is it a raw-encoded sub-rectangle?
432 if ((subencoding & rfb.HextileRaw) != 0) {
433 int count, offset;
434 for (int j = ty; j < ty + th; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000435 rfb.is.readFully(buf, 0, tw * 4);
436 offset = j * rfb.framebufferWidth + tx;
437 for (count = 0; count < tw; count++) {
438 pixels24[offset + count] =
439 (buf[count * 4 + 2] & 0xFF) << 16 |
440 (buf[count * 4 + 1] & 0xFF) << 8 |
441 (buf[count * 4] & 0xFF);
442 }
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000443 }
444 handleUpdatedPixels(tx, ty, tw, th);
445 return;
446 }
447
448 // Read and draw the background if specified.
449 if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
450 rfb.is.readFully(buf, 0, 4);
451 hextile_bg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
452 }
453 memGraphics.setColor(hextile_bg);
454 memGraphics.fillRect(tx, ty, tw, th);
455
456 // Read the foreground color if specified.
457 if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
458 rfb.is.readFully(buf, 0, 4);
459 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
460 }
461
462 // Done with this tile if there is no sub-rectangles.
463 if ((subencoding & rfb.HextileAnySubrects) == 0)
464 return;
465
466 int nSubrects = rfb.is.readUnsignedByte();
467
468 int b1, b2, sx, sy, sw, sh;
469 if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
470 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000471 rfb.is.readFully(buf, 0, 4);
472 hextile_fg = new Color(buf[2] & 0xFF, buf[1] & 0xFF, buf[0] & 0xFF);
473 b1 = rfb.is.readUnsignedByte();
474 b2 = rfb.is.readUnsignedByte();
475 sx = tx + (b1 >> 4);
476 sy = ty + (b1 & 0xf);
477 sw = (b2 >> 4) + 1;
478 sh = (b2 & 0xf) + 1;
479 memGraphics.setColor(hextile_fg);
480 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000481 }
482 } else {
483 memGraphics.setColor(hextile_fg);
484 for (int j = 0; j < nSubrects; j++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000485 b1 = rfb.is.readUnsignedByte();
486 b2 = rfb.is.readUnsignedByte();
487 sx = tx + (b1 >> 4);
488 sy = ty + (b1 & 0xf);
489 sw = (b2 >> 4) + 1;
490 sh = (b2 & 0xf) + 1;
491 memGraphics.fillRect(sx, sy, sw, sh);
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000492 }
493 }
494 }
495
496 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000497 // Handle a Zlib-encoded rectangle.
498 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000499 void handleZlibRect(int x, int y, int w, int h) throws Exception {
500
501 int nBytes = rfb.is.readInt();
502
503 if (zlibBuf == null || zlibBufLen < nBytes) {
504 zlibBufLen = nBytes * 2;
505 zlibBuf = new byte[zlibBufLen];
506 }
507
508 rfb.is.readFully(zlibBuf, 0, nBytes);
509 zlibInflater.setInput(zlibBuf, 0, nBytes);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000510
511 try {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000512 byte[] buf = new byte[w * 4];
513 int i, offset;
514 for (int dy = y; dy < y + h; dy++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000515 zlibInflater.inflate(buf);
516 offset = dy * rfb.framebufferWidth + x;
517 for (i = 0; i < w; i++) {
518 pixels24[offset + i] =
519 (buf[i * 4 + 2] & 0xFF) << 16 |
520 (buf[i * 4 + 1] & 0xFF) << 8 |
521 (buf[i * 4] & 0xFF);
522 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000523 }
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000524 } catch (DataFormatException dfe) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000525 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000526 }
527
528 handleUpdatedPixels(x, y, w, h);
529 scheduleRepaint(x, y, w, h);
530 }
531
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000532 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000533 // Handle a Tight-encoded rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000534 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000535 void handleTightRect(int x, int y, int w, int h) throws Exception {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000536
537 int comp_ctl = rfb.is.readUnsignedByte();
538
539 // Flush zlib streams if we are told by the server to do so.
540 for (int stream_id = 0; stream_id < 4; stream_id++) {
541 if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000542 tightInflaters[stream_id] = null;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000543 }
544 comp_ctl >>= 1;
545 }
546
547 // Check correctness of subencoding value.
548 if (comp_ctl > rfb.TightMaxSubencoding) {
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000549 throw new Exception("Incorrect tight subencoding: " + comp_ctl);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000550 }
551
552 // Handle solid-color rectangles.
553 if (comp_ctl == rfb.TightFill) {
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000554 byte[] buf = new byte[3];
555 rfb.is.readFully(buf);
Constantin Kaplinskya628bf02002-05-20 13:33:46 +0000556 Color bg = new Color(buf[0] & 0xFF, buf[1] & 0xFF, buf[2] & 0xFF);
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000557 memGraphics.setColor(bg);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000558 memGraphics.fillRect(x, y, w, h);
559 scheduleRepaint(x, y, w, h);
560 return;
561 }
562
563 if (comp_ctl == rfb.TightJpeg) {
564
565 // Read JPEG data.
566 byte[] jpegData = new byte[rfb.readCompactLen()];
567 rfb.is.readFully(jpegData);
568
569 // Create an Image object from the JPEG data.
570 Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
571
572 // Remember the rectangle where the image should be drawn.
573 jpegRect = new Rectangle(x, y, w, h);
574
575 // Let the imageUpdate() method do the actual drawing, here just
576 // wait until the image is fully loaded and drawn.
577 synchronized(jpegRect) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000578 Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
579 try {
580 // Wait no longer than three seconds.
581 jpegRect.wait(3000);
582 } catch (InterruptedException e) {
583 throw new Exception("Interrupted while decoding JPEG image");
584 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000585 }
586
587 // Done, jpegRect is not needed any more.
588 jpegRect = null;
589 return;
590
591 }
592
593 // Read filter id and parameters.
594 int numColors = 0, rowSize = w;
595 byte[] palette8 = new byte[2];
596 int[] palette24 = new int[256];
597 boolean useGradient = false;
598 if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
599 int filter_id = rfb.is.readUnsignedByte();
600 if (filter_id == rfb.TightFilterPalette) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000601 numColors = rfb.is.readUnsignedByte() + 1;
602 byte[] buf = new byte[numColors * 3];
603 rfb.is.readFully(buf);
604 for (int i = 0; i < numColors; i++) {
605 palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
606 (buf[i * 3 + 1] & 0xFF) << 8 |
607 (buf[i * 3 + 2] & 0xFF));
608 }
609 if (numColors == 2)
610 rowSize = (w + 7) / 8;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000611 } else if (filter_id == rfb.TightFilterGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000612 useGradient = true;
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000613 } else if (filter_id != rfb.TightFilterCopy) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000614 throw new Exception("Incorrect tight filter id: " + filter_id);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000615 }
616 }
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000617 if (numColors == 0)
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000618 rowSize *= 3;
619
620 // Read, optionally uncompress and decode data.
621 int dataSize = h * rowSize;
622 if (dataSize < rfb.TightMinToCompress) {
623 // Data size is small - not compressed with zlib.
624 if (numColors != 0) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000625 // Indexed colors.
626 byte[] indexedData = new byte[dataSize];
627 rfb.is.readFully(indexedData);
628 if (numColors == 2) {
629 // Two colors.
630 decodeMonoData(x, y, w, h, indexedData, palette24);
631 } else {
632 // 3..255 colors.
633 int i = 0;
634 for (int dy = y; dy < y + h; dy++) {
635 for (int dx = x; dx < x + w; dx++) {
636 pixels24[dy * rfb.framebufferWidth + dx] =
637 palette24[indexedData[i++] & 0xFF];
638 }
639 }
640 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000641 } else if (useGradient) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000642 // "Gradient"-processed data
643 byte[] buf = new byte[w * h * 3];
644 rfb.is.readFully(buf);
645 decodeGradientData(x, y, w, h, buf);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000646 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000647 // Raw truecolor data.
648 byte[] buf = new byte[w * 3];
649 int i, offset;
650 for (int dy = y; dy < y + h; dy++) {
651 rfb.is.readFully(buf);
652 offset = dy * rfb.framebufferWidth + x;
653 for (i = 0; i < w; i++) {
654 pixels24[offset + i] =
655 (buf[i * 3] & 0xFF) << 16 |
656 (buf[i * 3 + 1] & 0xFF) << 8 |
657 (buf[i * 3 + 2] & 0xFF);
658 }
659 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000660 }
661 } else {
662 // Data was compressed with zlib.
663 int zlibDataLen = rfb.readCompactLen();
664 byte[] zlibData = new byte[zlibDataLen];
665 rfb.is.readFully(zlibData);
666 int stream_id = comp_ctl & 0x03;
667 if (tightInflaters[stream_id] == null) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000668 tightInflaters[stream_id] = new Inflater();
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000669 }
670 Inflater myInflater = tightInflaters[stream_id];
671 myInflater.setInput(zlibData);
672 try {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000673 if (numColors != 0) {
674 // Indexed colors.
675 byte[] indexedData = new byte[dataSize];
676 myInflater.inflate(indexedData);
677 if (numColors == 2) {
678 // Two colors.
679 decodeMonoData(x, y, w, h, indexedData, palette24);
680 } else {
681 // More than two colors.
682 int i = 0;
683 for (int dy = y; dy < y + h; dy++) {
684 for (int dx = x; dx < x + w; dx++) {
685 pixels24[dy * rfb.framebufferWidth + dx] =
686 palette24[indexedData[i++] & 0xFF];
687 }
688 }
689 }
690 } else if (useGradient) {
691 // Compressed "Gradient"-filtered data.
692 byte[] buf = new byte[w * h * 3];
693 myInflater.inflate(buf);
694 decodeGradientData(x, y, w, h, buf);
695 } else {
696 // Compressed truecolor data.
697 byte[] buf = new byte[w * 3];
698 int i, offset;
699 for (int dy = y; dy < y + h; dy++) {
700 myInflater.inflate(buf);
701 offset = dy * rfb.framebufferWidth + x;
702 for (i = 0; i < w; i++) {
703 pixels24[offset + i] =
704 (buf[i * 3] & 0xFF) << 16 |
705 (buf[i * 3 + 1] & 0xFF) << 8 |
706 (buf[i * 3 + 2] & 0xFF);
707 }
708 }
709 }
710 } catch (DataFormatException dfe) {
711 throw new Exception(dfe.toString());
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000712 }
713 }
714
715 handleUpdatedPixels(x, y, w, h);
716 scheduleRepaint(x, y, w, h);
717 }
718
719 //
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000720 // Decode 1bpp-encoded bi-color rectangle.
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000721 //
Constantin Kaplinsky99df0a72002-06-04 06:13:20 +0000722 void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000723
724 int dx, dy, n;
725 int i = y * rfb.framebufferWidth + x;
726 int rowBytes = (w + 7) / 8;
727 byte b;
728
729 for (dy = 0; dy < h; dy++) {
730 for (dx = 0; dx < w / 8; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000731 b = src[dy * rowBytes + dx];
732 for (n = 7; n >= 0; n--)
733 pixels24[i++] = palette[b >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000734 }
735 for (n = 7; n >= 8 - w % 8; n--) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000736 pixels24[i++] = palette[src[dy * rowBytes + dx] >> n & 1];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000737 }
738 i += (rfb.framebufferWidth - w);
739 }
740 }
741
742 //
743 // Decode data processed with the "Gradient" filter.
744 //
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000745 void decodeGradientData(int x, int y, int w, int h, byte[] buf) {
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000746
747 int dx, dy, c;
748 byte[] prevRow = new byte[w * 3];
749 byte[] thisRow = new byte[w * 3];
750 byte[] pix = new byte[3];
751 int[] est = new int[3];
752
753 int offset = y * rfb.framebufferWidth + x;
754
755 for (dy = 0; dy < h; dy++) {
756
757 /* First pixel in a row */
758 for (c = 0; c < 3; c++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000759 pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
760 thisRow[c] = pix[c];
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000761 }
762 pixels24[offset++] =
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000763 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000764
765 /* Remaining pixels of a row */
766 for (dx = 1; dx < w; dx++) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000767 for (c = 0; c < 3; c++) {
768 est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
769 (prevRow[(dx - 1) * 3 + c] & 0xFF));
770 if (est[c] > 0xFF) {
771 est[c] = 0xFF;
772 } else if (est[c] < 0x00) {
773 est[c] = 0x00;
774 }
775 pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
776 thisRow[dx * 3 + c] = pix[c];
777 }
778 pixels24[offset++] =
779 (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000780 }
781
782 System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
783 offset += (rfb.framebufferWidth - w);
784 }
785 }
786
787
788 //
789 // Display newly updated area of pixels.
790 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000791 void handleUpdatedPixels(int x, int y, int w, int h) {
792
793 // Draw updated pixels of the off-screen image.
Constantin Kaplinsky903009e2002-05-20 10:55:47 +0000794
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000795 pixelsSource.newPixels(x, y, w, h);
796 memGraphics.setClip(x, y, w, h);
797 memGraphics.drawImage(rawPixelsImage, 0, 0, null);
798 memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
799 }
800
801 //
802 // Tell JVM to repaint specified desktop area.
803 //
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000804 void scheduleRepaint(int x, int y, int w, int h) {
Constantin Kaplinsky37cc43e2002-05-30 17:30:11 +0000805 if (rfb.fbs.isSeeking()) {
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000806 // Do nothing, and remember we are seeking.
807 seekMode = true;
808 } else {
809 if (seekMode) {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000810 // Immediate repaint of the whole desktop after seeking.
811 repaint();
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000812 } else {
Constantin Kaplinsky72e47ef2008-04-18 17:48:16 +0000813 // Usual incremental repaint.
814 repaint(player.deferScreenUpdates, x, y, w, h);
Constantin Kaplinsky52c48242002-05-30 13:26:34 +0000815 }
816 seekMode = false;
817 }
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000818 }
819
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000820 //
821 // We are observing our FbsInputStream object to get notified on
822 // switching to the `paused' mode. In such cases we want to repaint
823 // our desktop if we were seeking.
824 //
Constantin Kaplinsky5a7133a2002-07-24 17:02:04 +0000825 public void update(Observable o, Object arg) {
826 // Immediate repaint of the whole desktop after seeking.
827 repaint();
828 // Let next scheduleRepaint() call invoke incremental drawing.
829 seekMode = false;
830 }
831
Constantin Kaplinsky1215b992008-04-18 09:51:44 +0000832}