If I'm lucky, I might have a sipb-xen-vnc-client package, too
[invirt/packages/invirt-vnc-client.git] / code / VncCanvas.java
1 //
2 //  Copyright (C) 2004 Horizon Wimba.  All Rights Reserved.
3 //  Copyright (C) 2001-2003 HorizonLive.com, Inc.  All Rights Reserved.
4 //  Copyright (C) 2001,2002 Constantin Kaplinsky.  All Rights Reserved.
5 //  Copyright (C) 2000 Tridia Corporation.  All Rights Reserved.
6 //  Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved.
7 //
8 //  This is free software; you can redistribute it and/or modify
9 //  it under the terms of the GNU General Public License as published by
10 //  the Free Software Foundation; either version 2 of the License, or
11 //  (at your option) any later version.
12 //
13 //  This software is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 //
18 //  You should have received a copy of the GNU General Public License
19 //  along with this software; if not, write to the Free Software
20 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
21 //  USA.
22 //
23
24 import java.awt.*;
25 import java.awt.event.*;
26 import java.awt.image.*;
27 import java.io.*;
28 import java.lang.*;
29 import java.util.zip.*;
30
31
32 //
33 // VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
34 //
35
36 class VncCanvas extends Canvas
37   implements KeyListener, MouseListener, MouseMotionListener {
38
39   VncViewer viewer;
40   RfbProto rfb;
41   ColorModel cm8, cm24;
42   Color[] colors;
43   int bytesPixel;
44
45   int maxWidth = 0, maxHeight = 0;
46   int scalingFactor;
47   int scaledWidth, scaledHeight;
48
49   Image memImage;
50   Graphics memGraphics;
51
52   Image rawPixelsImage;
53   MemoryImageSource pixelsSource;
54   byte[] pixels8;
55   int[] pixels24;
56
57   // ZRLE encoder's data.
58   byte[] zrleBuf;
59   int zrleBufLen = 0;
60   byte[] zrleTilePixels8;
61   int[] zrleTilePixels24;
62   ZlibInStream zrleInStream;
63   boolean zrleRecWarningShown = false;
64
65   // Zlib encoder's data.
66   byte[] zlibBuf;
67   int zlibBufLen = 0;
68   Inflater zlibInflater;
69
70   // Tight encoder's data.
71   final static int tightZlibBufferSize = 512;
72   Inflater[] tightInflaters;
73
74   // Since JPEG images are loaded asynchronously, we have to remember
75   // their position in the framebuffer. Also, this jpegRect object is
76   // used for synchronization between the rfbThread and a JVM's thread
77   // which decodes and loads JPEG images.
78   Rectangle jpegRect;
79
80   // True if we process keyboard and mouse events.
81   boolean inputEnabled;
82   int extraModifiers = 0;
83
84   //
85   // The constructors.
86   //
87
88   public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
89     throws IOException {
90
91     viewer = v;
92     maxWidth = maxWidth_;
93     maxHeight = maxHeight_;
94
95     rfb = viewer.rfb;
96     scalingFactor = viewer.options.scalingFactor;
97
98     tightInflaters = new Inflater[4];
99
100     cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
101     cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
102
103     colors = new Color[256];
104     for (int i = 0; i < 256; i++)
105       colors[i] = new Color(cm8.getRGB(i));
106
107     setPixelFormat();
108
109     inputEnabled = false;
110     if (!viewer.options.viewOnly)
111       enableInput(true);
112
113     // Keyboard listener is enabled even in view-only mode, to catch
114     // 'r' or 'R' key presses used to request screen update.
115     addKeyListener(this);
116   }
117
118   public VncCanvas(VncViewer v) throws IOException {
119     this(v, 0, 0);
120   }
121
122   //
123   // Callback methods to determine geometry of our Component.
124   //
125
126   public Dimension getPreferredSize() {
127     return new Dimension(scaledWidth, scaledHeight);
128   }
129
130   public Dimension getMinimumSize() {
131     return new Dimension(scaledWidth, scaledHeight);
132   }
133
134   public Dimension getMaximumSize() {
135     return new Dimension(scaledWidth, scaledHeight);
136   }
137
138   //
139   // All painting is performed here.
140   //
141
142   public void update(Graphics g) {
143     paint(g);
144   }
145
146   public void paint(Graphics g) {
147     synchronized(memImage) {
148       if (rfb.framebufferWidth == scaledWidth) {
149         g.drawImage(memImage, 0, 0, null);
150       } else {
151         paintScaledFrameBuffer(g);
152       }
153     }
154     if (showSoftCursor) {
155       int x0 = cursorX - hotX, y0 = cursorY - hotY;
156       Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
157       if (r.intersects(g.getClipBounds())) {
158         g.drawImage(softCursor, x0, y0, null);
159       }
160     }
161   }
162
163   public void paintScaledFrameBuffer(Graphics g) {
164     g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
165   }
166
167   //
168   // Override the ImageObserver interface method to handle drawing of
169   // JPEG-encoded data.
170   //
171
172   public boolean imageUpdate(Image img, int infoflags,
173                              int x, int y, int width, int height) {
174     if ((infoflags & (ALLBITS | ABORT)) == 0) {
175       return true;              // We need more image data.
176     } else {
177       // If the whole image is available, draw it now.
178       if ((infoflags & ALLBITS) != 0) {
179         if (jpegRect != null) {
180           synchronized(jpegRect) {
181             memGraphics.drawImage(img, jpegRect.x, jpegRect.y, null);
182             scheduleRepaint(jpegRect.x, jpegRect.y,
183                             jpegRect.width, jpegRect.height);
184             jpegRect.notify();
185           }
186         }
187       }
188       return false;             // All image data was processed.
189     }
190   }
191
192   //
193   // Start/stop receiving mouse events. Keyboard events are received
194   // even in view-only mode, because we want to map the 'r' key to the
195   // screen refreshing function.
196   //
197
198   public synchronized void enableInput(boolean enable) {
199     if (enable && !inputEnabled) {
200       inputEnabled = true;
201       addMouseListener(this);
202       addMouseMotionListener(this);
203       if (viewer.showControls) {
204         viewer.buttonPanel.enableRemoteAccessControls(true);
205       }
206       createSoftCursor();       // scaled cursor
207     } else if (!enable && inputEnabled) {
208       inputEnabled = false;
209       removeMouseListener(this);
210       removeMouseMotionListener(this);
211       if (viewer.showControls) {
212         viewer.buttonPanel.enableRemoteAccessControls(false);
213       }
214       createSoftCursor();       // non-scaled cursor
215     }
216   }
217
218   public void setPixelFormat() throws IOException {
219     if (viewer.options.eightBitColors) {
220       rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
221       bytesPixel = 1;
222     } else {
223       rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8, 0);
224       bytesPixel = 4;
225     }
226     updateFramebufferSize();
227   }
228
229   void updateFramebufferSize() {
230
231     // Useful shortcuts.
232     int fbWidth = rfb.framebufferWidth;
233     int fbHeight = rfb.framebufferHeight;
234
235     // Calculate scaling factor for auto scaling.
236     if (maxWidth > 0 && maxHeight > 0) {
237       int f1 = maxWidth * 100 / fbWidth;
238       int f2 = maxHeight * 100 / fbHeight;
239       scalingFactor = Math.min(f1, f2);
240       if (scalingFactor > 100)
241         scalingFactor = 100;
242       System.out.println("Scaling desktop at " + scalingFactor + "%");
243     }
244
245     // Update scaled framebuffer geometry.
246     scaledWidth = (fbWidth * scalingFactor + 50) / 100;
247     scaledHeight = (fbHeight * scalingFactor + 50) / 100;
248
249     // Create new off-screen image either if it does not exist, or if
250     // its geometry should be changed. It's not necessary to replace
251     // existing image if only pixel format should be changed.
252     if (memImage == null) {
253       memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
254       memGraphics = memImage.getGraphics();
255     } else if (memImage.getWidth(null) != fbWidth ||
256                memImage.getHeight(null) != fbHeight) {
257       synchronized(memImage) {
258         memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
259         memGraphics = memImage.getGraphics();
260       }
261     }
262
263     // Images with raw pixels should be re-allocated on every change
264     // of geometry or pixel format.
265     if (bytesPixel == 1) {
266
267       pixels24 = null;
268       pixels8 = new byte[fbWidth * fbHeight];
269
270       pixelsSource =
271         new MemoryImageSource(fbWidth, fbHeight, cm8, pixels8, 0, fbWidth);
272
273       zrleTilePixels24 = null;
274       zrleTilePixels8 = new byte[64 * 64];
275
276     } else {
277
278       pixels8 = null;
279       pixels24 = new int[fbWidth * fbHeight];
280
281       pixelsSource =
282         new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth);
283
284       zrleTilePixels8 = null;
285       zrleTilePixels24 = new int[64 * 64];
286
287     }
288     pixelsSource.setAnimated(true);
289     rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
290
291     // Update the size of desktop containers.
292     if (viewer.inSeparateFrame) {
293       if (viewer.desktopScrollPane != null)
294         resizeDesktopFrame();
295     } else {
296       setSize(scaledWidth, scaledHeight);
297     }
298     viewer.moveFocusToDesktop();
299   }
300
301   void resizeDesktopFrame() {
302     setSize(scaledWidth, scaledHeight);
303
304     // FIXME: Find a better way to determine correct size of a
305     // ScrollPane.  -- const
306     Insets insets = viewer.desktopScrollPane.getInsets();
307     viewer.desktopScrollPane.setSize(scaledWidth +
308                                      2 * Math.min(insets.left, insets.right),
309                                      scaledHeight +
310                                      2 * Math.min(insets.top, insets.bottom));
311
312     viewer.vncFrame.pack();
313
314     // Try to limit the frame size to the screen size.
315
316     Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
317     Dimension frameSize = viewer.vncFrame.getSize();
318     Dimension newSize = frameSize;
319
320     // Reduce Screen Size by 30 pixels in each direction;
321     // This is a (poor) attempt to account for
322     //     1) Menu bar on Macintosh (should really also account for
323     //        Dock on OSX).  Usually 22px on top of screen.
324     //     2) Taxkbar on Windows (usually about 28 px on bottom)
325     //     3) Other obstructions.
326
327     screenSize.height -= 30;
328     screenSize.width  -= 30;
329
330     boolean needToResizeFrame = false;
331     if (frameSize.height > screenSize.height) {
332       newSize.height = screenSize.height;
333       needToResizeFrame = true;
334     }
335     if (frameSize.width > screenSize.width) {
336       newSize.width = screenSize.width;
337       needToResizeFrame = true;
338     }
339     if (needToResizeFrame) {
340       viewer.vncFrame.setSize(newSize);
341     }
342
343     viewer.desktopScrollPane.doLayout();
344   }
345
346   //
347   // processNormalProtocol() - executed by the rfbThread to deal with the
348   // RFB socket.
349   //
350
351   public void processNormalProtocol() throws Exception {
352
353     // Start/stop session recording if necessary.
354     viewer.checkRecordingStatus();
355
356     rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
357                                       rfb.framebufferHeight, false);
358
359     //
360     // main dispatch loop
361     //
362
363     while (true) {
364
365       // Read message type from the server.
366       int msgType = rfb.readServerMessageType();
367
368       // Process the message depending on its type.
369       switch (msgType) {
370       case RfbProto.FramebufferUpdate:
371         rfb.readFramebufferUpdate();
372
373         boolean cursorPosReceived = false;
374
375         for (int i = 0; i < rfb.updateNRects; i++) {
376           rfb.readFramebufferUpdateRectHdr();
377           int rx = rfb.updateRectX, ry = rfb.updateRectY;
378           int rw = rfb.updateRectW, rh = rfb.updateRectH;
379
380           if (rfb.updateRectEncoding == rfb.EncodingLastRect)
381             break;
382
383           if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
384             rfb.setFramebufferSize(rw, rh);
385             updateFramebufferSize();
386             break;
387           }
388
389           if (rfb.updateRectEncoding == rfb.EncodingXCursor ||
390               rfb.updateRectEncoding == rfb.EncodingRichCursor) {
391             handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry, rw, rh);
392             continue;
393           }
394
395           if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
396             softCursorMove(rx, ry);
397             cursorPosReceived = true;
398             continue;
399           }
400
401           rfb.startTiming();
402
403           switch (rfb.updateRectEncoding) {
404           case RfbProto.EncodingRaw:
405             handleRawRect(rx, ry, rw, rh);
406             break;
407           case RfbProto.EncodingCopyRect:
408             handleCopyRect(rx, ry, rw, rh);
409             break;
410           case RfbProto.EncodingRRE:
411             handleRRERect(rx, ry, rw, rh);
412             break;
413           case RfbProto.EncodingCoRRE:
414             handleCoRRERect(rx, ry, rw, rh);
415             break;
416           case RfbProto.EncodingHextile:
417             handleHextileRect(rx, ry, rw, rh);
418             break;
419           case RfbProto.EncodingZRLE:
420             handleZRLERect(rx, ry, rw, rh);
421             break;
422           case RfbProto.EncodingZlib:
423             handleZlibRect(rx, ry, rw, rh);
424             break;
425           case RfbProto.EncodingTight:
426             handleTightRect(rx, ry, rw, rh);
427             break;
428           default:
429             throw new Exception("Unknown RFB rectangle encoding " +
430                                 rfb.updateRectEncoding);
431           }
432
433           rfb.stopTiming();
434         }
435
436         boolean fullUpdateNeeded = false;
437
438         // Start/stop session recording if necessary. Request full
439         // update if a new session file was opened.
440         if (viewer.checkRecordingStatus())
441           fullUpdateNeeded = true;
442
443         // Defer framebuffer update request if necessary. But wake up
444         // immediately on keyboard or mouse event. Also, don't sleep
445         // if there is some data to receive, or if the last update
446         // included a PointerPos message.
447         if (viewer.deferUpdateRequests > 0 &&
448             rfb.is.available() == 0 && !cursorPosReceived) {
449           synchronized(rfb) {
450             try {
451               rfb.wait(viewer.deferUpdateRequests);
452             } catch (InterruptedException e) {
453             }
454           }
455         }
456
457         // Before requesting framebuffer update, check if the pixel
458         // format should be changed. If it should, request full update
459         // instead of an incremental one.
460         if (viewer.options.eightBitColors != (bytesPixel == 1)) {
461           setPixelFormat();
462           fullUpdateNeeded = true;
463         }
464
465         viewer.autoSelectEncodings();
466
467         rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
468                                           rfb.framebufferHeight,
469                                           !fullUpdateNeeded);
470
471         break;
472
473       case RfbProto.SetColourMapEntries:
474         throw new Exception("Can't handle SetColourMapEntries message");
475
476       case RfbProto.Bell:
477         Toolkit.getDefaultToolkit().beep();
478         break;
479
480       case RfbProto.ServerCutText:
481         String s = rfb.readServerCutText();
482         viewer.clipboard.setCutText(s);
483         break;
484
485       default:
486         throw new Exception("Unknown RFB message type " + msgType);
487       }
488     }
489   }
490
491
492   //
493   // Handle a raw rectangle. The second form with paint==false is used
494   // by the Hextile decoder for raw-encoded tiles.
495   //
496
497   void handleRawRect(int x, int y, int w, int h) throws IOException {
498     handleRawRect(x, y, w, h, true);
499   }
500
501   void handleRawRect(int x, int y, int w, int h, boolean paint)
502     throws IOException {
503
504     if (bytesPixel == 1) {
505       for (int dy = y; dy < y + h; dy++) {
506         rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
507         if (rfb.rec != null) {
508           rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
509         }
510       }
511     } else {
512       byte[] buf = new byte[w * 4];
513       int i, offset;
514       for (int dy = y; dy < y + h; dy++) {
515         rfb.readFully(buf);
516         if (rfb.rec != null) {
517           rfb.rec.write(buf);
518         }
519         offset = dy * rfb.framebufferWidth + x;
520         for (i = 0; i < w; i++) {
521           pixels24[offset + i] =
522             (buf[i * 4 + 2] & 0xFF) << 16 |
523             (buf[i * 4 + 1] & 0xFF) << 8 |
524             (buf[i * 4] & 0xFF);
525         }
526       }
527     }
528
529     handleUpdatedPixels(x, y, w, h);
530     if (paint)
531       scheduleRepaint(x, y, w, h);
532   }
533
534   //
535   // Handle a CopyRect rectangle.
536   //
537
538   void handleCopyRect(int x, int y, int w, int h) throws IOException {
539
540     rfb.readCopyRect();
541     memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h,
542                          x - rfb.copyRectSrcX, y - rfb.copyRectSrcY);
543
544     scheduleRepaint(x, y, w, h);
545   }
546
547   //
548   // Handle an RRE-encoded rectangle.
549   //
550
551   void handleRRERect(int x, int y, int w, int h) throws IOException {
552
553     int nSubrects = rfb.is.readInt();
554
555     byte[] bg_buf = new byte[bytesPixel];
556     rfb.readFully(bg_buf);
557     Color pixel;
558     if (bytesPixel == 1) {
559       pixel = colors[bg_buf[0] & 0xFF];
560     } else {
561       pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
562     }
563     memGraphics.setColor(pixel);
564     memGraphics.fillRect(x, y, w, h);
565
566     byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
567     rfb.readFully(buf);
568     DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));
569
570     if (rfb.rec != null) {
571       rfb.rec.writeIntBE(nSubrects);
572       rfb.rec.write(bg_buf);
573       rfb.rec.write(buf);
574     }
575
576     int sx, sy, sw, sh;
577
578     for (int j = 0; j < nSubrects; j++) {
579       if (bytesPixel == 1) {
580         pixel = colors[ds.readUnsignedByte()];
581       } else {
582         ds.skip(4);
583         pixel = new Color(buf[j*12+2] & 0xFF,
584                           buf[j*12+1] & 0xFF,
585                           buf[j*12]   & 0xFF);
586       }
587       sx = x + ds.readUnsignedShort();
588       sy = y + ds.readUnsignedShort();
589       sw = ds.readUnsignedShort();
590       sh = ds.readUnsignedShort();
591
592       memGraphics.setColor(pixel);
593       memGraphics.fillRect(sx, sy, sw, sh);
594     }
595
596     scheduleRepaint(x, y, w, h);
597   }
598
599   //
600   // Handle a CoRRE-encoded rectangle.
601   //
602
603   void handleCoRRERect(int x, int y, int w, int h) throws IOException {
604     int nSubrects = rfb.is.readInt();
605
606     byte[] bg_buf = new byte[bytesPixel];
607     rfb.readFully(bg_buf);
608     Color pixel;
609     if (bytesPixel == 1) {
610       pixel = colors[bg_buf[0] & 0xFF];
611     } else {
612       pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
613     }
614     memGraphics.setColor(pixel);
615     memGraphics.fillRect(x, y, w, h);
616
617     byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
618     rfb.readFully(buf);
619
620     if (rfb.rec != null) {
621       rfb.rec.writeIntBE(nSubrects);
622       rfb.rec.write(bg_buf);
623       rfb.rec.write(buf);
624     }
625
626     int sx, sy, sw, sh;
627     int i = 0;
628
629     for (int j = 0; j < nSubrects; j++) {
630       if (bytesPixel == 1) {
631         pixel = colors[buf[i++] & 0xFF];
632       } else {
633         pixel = new Color(buf[i+2] & 0xFF, buf[i+1] & 0xFF, buf[i] & 0xFF);
634         i += 4;
635       }
636       sx = x + (buf[i++] & 0xFF);
637       sy = y + (buf[i++] & 0xFF);
638       sw = buf[i++] & 0xFF;
639       sh = buf[i++] & 0xFF;
640
641       memGraphics.setColor(pixel);
642       memGraphics.fillRect(sx, sy, sw, sh);
643     }
644
645     scheduleRepaint(x, y, w, h);
646   }
647
648   //
649   // Handle a Hextile-encoded rectangle.
650   //
651
652   // These colors should be kept between handleHextileSubrect() calls.
653   private Color hextile_bg, hextile_fg;
654
655   void handleHextileRect(int x, int y, int w, int h) throws IOException {
656
657     hextile_bg = new Color(0);
658     hextile_fg = new Color(0);
659
660     for (int ty = y; ty < y + h; ty += 16) {
661       int th = 16;
662       if (y + h - ty < 16)
663         th = y + h - ty;
664
665       for (int tx = x; tx < x + w; tx += 16) {
666         int tw = 16;
667         if (x + w - tx < 16)
668           tw = x + w - tx;
669
670         handleHextileSubrect(tx, ty, tw, th);
671       }
672
673       // Finished with a row of tiles, now let's show it.
674       scheduleRepaint(x, y, w, h);
675     }
676   }
677
678   //
679   // Handle one tile in the Hextile-encoded data.
680   //
681
682   void handleHextileSubrect(int tx, int ty, int tw, int th)
683     throws IOException {
684
685     int subencoding = rfb.is.readUnsignedByte();
686     if (rfb.rec != null) {
687       rfb.rec.writeByte(subencoding);
688     }
689
690     // Is it a raw-encoded sub-rectangle?
691     if ((subencoding & rfb.HextileRaw) != 0) {
692       handleRawRect(tx, ty, tw, th, false);
693       return;
694     }
695
696     // Read and draw the background if specified.
697     byte[] cbuf = new byte[bytesPixel];
698     if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
699       rfb.readFully(cbuf);
700       if (bytesPixel == 1) {
701         hextile_bg = colors[cbuf[0] & 0xFF];
702       } else {
703         hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
704       }
705       if (rfb.rec != null) {
706         rfb.rec.write(cbuf);
707       }
708     }
709     memGraphics.setColor(hextile_bg);
710     memGraphics.fillRect(tx, ty, tw, th);
711
712     // Read the foreground color if specified.
713     if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
714       rfb.readFully(cbuf);
715       if (bytesPixel == 1) {
716         hextile_fg = colors[cbuf[0] & 0xFF];
717       } else {
718         hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF);
719       }
720       if (rfb.rec != null) {
721         rfb.rec.write(cbuf);
722       }
723     }
724
725     // Done with this tile if there is no sub-rectangles.
726     if ((subencoding & rfb.HextileAnySubrects) == 0)
727       return;
728
729     int nSubrects = rfb.is.readUnsignedByte();
730     int bufsize = nSubrects * 2;
731     if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
732       bufsize += nSubrects * bytesPixel;
733     }
734     byte[] buf = new byte[bufsize];
735     rfb.readFully(buf);
736     if (rfb.rec != null) {
737       rfb.rec.writeByte(nSubrects);
738       rfb.rec.write(buf);
739     }
740
741     int b1, b2, sx, sy, sw, sh;
742     int i = 0;
743
744     if ((subencoding & rfb.HextileSubrectsColoured) == 0) {
745
746       // Sub-rectangles are all of the same color.
747       memGraphics.setColor(hextile_fg);
748       for (int j = 0; j < nSubrects; j++) {
749         b1 = buf[i++] & 0xFF;
750         b2 = buf[i++] & 0xFF;
751         sx = tx + (b1 >> 4);
752         sy = ty + (b1 & 0xf);
753         sw = (b2 >> 4) + 1;
754         sh = (b2 & 0xf) + 1;
755         memGraphics.fillRect(sx, sy, sw, sh);
756       }
757     } else if (bytesPixel == 1) {
758
759       // BGR233 (8-bit color) version for colored sub-rectangles.
760       for (int j = 0; j < nSubrects; j++) {
761         hextile_fg = colors[buf[i++] & 0xFF];
762         b1 = buf[i++] & 0xFF;
763         b2 = buf[i++] & 0xFF;
764         sx = tx + (b1 >> 4);
765         sy = ty + (b1 & 0xf);
766         sw = (b2 >> 4) + 1;
767         sh = (b2 & 0xf) + 1;
768         memGraphics.setColor(hextile_fg);
769         memGraphics.fillRect(sx, sy, sw, sh);
770       }
771
772     } else {
773
774       // Full-color (24-bit) version for colored sub-rectangles.
775       for (int j = 0; j < nSubrects; j++) {
776         hextile_fg = new Color(buf[i+2] & 0xFF,
777                                buf[i+1] & 0xFF,
778                                buf[i] & 0xFF);
779         i += 4;
780         b1 = buf[i++] & 0xFF;
781         b2 = buf[i++] & 0xFF;
782         sx = tx + (b1 >> 4);
783         sy = ty + (b1 & 0xf);
784         sw = (b2 >> 4) + 1;
785         sh = (b2 & 0xf) + 1;
786         memGraphics.setColor(hextile_fg);
787         memGraphics.fillRect(sx, sy, sw, sh);
788       }
789
790     }
791   }
792
793   //
794   // Handle a ZRLE-encoded rectangle.
795   //
796   // FIXME: Currently, session recording is not fully supported for ZRLE.
797   //
798
799   void handleZRLERect(int x, int y, int w, int h) throws Exception {
800
801     if (zrleInStream == null)
802       zrleInStream = new ZlibInStream();
803
804     int nBytes = rfb.is.readInt();
805     if (nBytes > 64 * 1024 * 1024)
806       throw new Exception("ZRLE decoder: illegal compressed data size");
807
808     if (zrleBuf == null || zrleBufLen < nBytes) {
809       zrleBufLen = nBytes + 4096;
810       zrleBuf = new byte[zrleBufLen];
811     }
812
813     // FIXME: Do not wait for all the data before decompression.
814     rfb.readFully(zrleBuf, 0, nBytes);
815
816     if (rfb.rec != null) {
817       if (rfb.recordFromBeginning) {
818         rfb.rec.writeIntBE(nBytes);
819         rfb.rec.write(zrleBuf, 0, nBytes);
820       } else if (!zrleRecWarningShown) {
821         System.out.println("Warning: ZRLE session can be recorded" +
822                            " only from the beginning");
823         System.out.println("Warning: Recorded file may be corrupted");
824         zrleRecWarningShown = true;
825       }
826     }
827
828     zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
829
830     for (int ty = y; ty < y+h; ty += 64) {
831
832       int th = Math.min(y+h-ty, 64);
833
834       for (int tx = x; tx < x+w; tx += 64) {
835
836         int tw = Math.min(x+w-tx, 64);
837
838         int mode = zrleInStream.readU8();
839         boolean rle = (mode & 128) != 0;
840         int palSize = mode & 127;
841         int[] palette = new int[128];
842
843         readZrlePalette(palette, palSize);
844
845         if (palSize == 1) {
846           int pix = palette[0];
847           Color c = (bytesPixel == 1) ?
848             colors[pix] : new Color(0xFF000000 | pix);
849           memGraphics.setColor(c);
850           memGraphics.fillRect(tx, ty, tw, th);
851           continue;
852         }
853
854         if (!rle) {
855           if (palSize == 0) {
856             readZrleRawPixels(tw, th);
857           } else {
858             readZrlePackedPixels(tw, th, palette, palSize);
859           }
860         } else {
861           if (palSize == 0) {
862             readZrlePlainRLEPixels(tw, th);
863           } else {
864             readZrlePackedRLEPixels(tw, th, palette);
865           }
866         }
867         handleUpdatedZrleTile(tx, ty, tw, th);
868       }
869     }
870
871     zrleInStream.reset();
872
873     scheduleRepaint(x, y, w, h);
874   }
875
876   int readPixel(InStream is) throws Exception {
877     int pix;
878     if (bytesPixel == 1) {
879       pix = is.readU8();
880     } else {
881       int p1 = is.readU8();
882       int p2 = is.readU8();
883       int p3 = is.readU8();
884       pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
885     }
886     return pix;
887   }
888
889   void readPixels(InStream is, int[] dst, int count) throws Exception {
890     int pix;
891     if (bytesPixel == 1) {
892       byte[] buf = new byte[count];
893       is.readBytes(buf, 0, count);
894       for (int i = 0; i < count; i++) {
895         dst[i] = (int)buf[i] & 0xFF;
896       }
897     } else {
898       byte[] buf = new byte[count * 3];
899       is.readBytes(buf, 0, count * 3);
900       for (int i = 0; i < count; i++) {
901         dst[i] = ((buf[i*3+2] & 0xFF) << 16 |
902                   (buf[i*3+1] & 0xFF) << 8 |
903                   (buf[i*3] & 0xFF));
904       }
905     }
906   }
907
908   void readZrlePalette(int[] palette, int palSize) throws Exception {
909     readPixels(zrleInStream, palette, palSize);
910   }
911
912   void readZrleRawPixels(int tw, int th) throws Exception {
913     if (bytesPixel == 1) {
914       zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
915     } else {
916       readPixels(zrleInStream, zrleTilePixels24, tw * th); ///
917     }
918   }
919
920   void readZrlePackedPixels(int tw, int th, int[] palette, int palSize)
921     throws Exception {
922
923     int bppp = ((palSize > 16) ? 8 :
924                 ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
925     int ptr = 0;
926
927     for (int i = 0; i < th; i++) {
928       int eol = ptr + tw;
929       int b = 0;
930       int nbits = 0;
931
932       while (ptr < eol) {
933         if (nbits == 0) {
934           b = zrleInStream.readU8();
935           nbits = 8;
936         }
937         nbits -= bppp;
938         int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
939         if (bytesPixel == 1) {
940           zrleTilePixels8[ptr++] = (byte)palette[index];
941         } else {
942           zrleTilePixels24[ptr++] = palette[index];
943         }
944       }
945     }
946   }
947
948   void readZrlePlainRLEPixels(int tw, int th) throws Exception {
949     int ptr = 0;
950     int end = ptr + tw * th;
951     while (ptr < end) {
952       int pix = readPixel(zrleInStream);
953       int len = 1;
954       int b;
955       do {
956         b = zrleInStream.readU8();
957         len += b;
958       } while (b == 255);
959
960       if (!(len <= end - ptr))
961         throw new Exception("ZRLE decoder: assertion failed" +
962                             " (len <= end-ptr)");
963
964       if (bytesPixel == 1) {
965         while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
966       } else {
967         while (len-- > 0) zrleTilePixels24[ptr++] = pix;
968       }
969     }
970   }
971
972   void readZrlePackedRLEPixels(int tw, int th, int[] palette)
973     throws Exception {
974
975     int ptr = 0;
976     int end = ptr + tw * th;
977     while (ptr < end) {
978       int index = zrleInStream.readU8();
979       int len = 1;
980       if ((index & 128) != 0) {
981         int b;
982         do {
983           b = zrleInStream.readU8();
984           len += b;
985         } while (b == 255);
986         
987         if (!(len <= end - ptr))
988           throw new Exception("ZRLE decoder: assertion failed" +
989                               " (len <= end - ptr)");
990       }
991
992       index &= 127;
993       int pix = palette[index];
994
995       if (bytesPixel == 1) {
996         while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix;
997       } else {
998         while (len-- > 0) zrleTilePixels24[ptr++] = pix;
999       }
1000     }
1001   }
1002
1003   //
1004   // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
1005   //
1006
1007   void handleUpdatedZrleTile(int x, int y, int w, int h) {
1008     Object src, dst;
1009     if (bytesPixel == 1) {
1010       src = zrleTilePixels8; dst = pixels8;
1011     } else {
1012       src = zrleTilePixels24; dst = pixels24;
1013     }
1014     int offsetSrc = 0;
1015     int offsetDst = (y * rfb.framebufferWidth + x);
1016     for (int j = 0; j < h; j++) {
1017       System.arraycopy(src, offsetSrc, dst, offsetDst, w);
1018       offsetSrc += w;
1019       offsetDst += rfb.framebufferWidth;
1020     }
1021     handleUpdatedPixels(x, y, w, h);
1022   }
1023
1024   //
1025   // Handle a Zlib-encoded rectangle.
1026   //
1027
1028   void handleZlibRect(int x, int y, int w, int h) throws Exception {
1029
1030     int nBytes = rfb.is.readInt();
1031
1032     if (zlibBuf == null || zlibBufLen < nBytes) {
1033       zlibBufLen = nBytes * 2;
1034       zlibBuf = new byte[zlibBufLen];
1035     }
1036
1037     rfb.readFully(zlibBuf, 0, nBytes);
1038
1039     if (rfb.rec != null && rfb.recordFromBeginning) {
1040       rfb.rec.writeIntBE(nBytes);
1041       rfb.rec.write(zlibBuf, 0, nBytes);
1042     }
1043
1044     if (zlibInflater == null) {
1045       zlibInflater = new Inflater();
1046     }
1047     zlibInflater.setInput(zlibBuf, 0, nBytes);
1048
1049     if (bytesPixel == 1) {
1050       for (int dy = y; dy < y + h; dy++) {
1051         zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
1052         if (rfb.rec != null && !rfb.recordFromBeginning)
1053           rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1054       }
1055     } else {
1056       byte[] buf = new byte[w * 4];
1057       int i, offset;
1058       for (int dy = y; dy < y + h; dy++) {
1059         zlibInflater.inflate(buf);
1060         offset = dy * rfb.framebufferWidth + x;
1061         for (i = 0; i < w; i++) {
1062           pixels24[offset + i] =
1063             (buf[i * 4 + 2] & 0xFF) << 16 |
1064             (buf[i * 4 + 1] & 0xFF) << 8 |
1065             (buf[i * 4] & 0xFF);
1066         }
1067         if (rfb.rec != null && !rfb.recordFromBeginning)
1068           rfb.rec.write(buf);
1069       }
1070     }
1071
1072     handleUpdatedPixels(x, y, w, h);
1073     scheduleRepaint(x, y, w, h);
1074   }
1075
1076   //
1077   // Handle a Tight-encoded rectangle.
1078   //
1079
1080   void handleTightRect(int x, int y, int w, int h) throws Exception {
1081
1082     int comp_ctl = rfb.is.readUnsignedByte();
1083     if (rfb.rec != null) {
1084       if (rfb.recordFromBeginning ||
1085           comp_ctl == (rfb.TightFill << 4) ||
1086           comp_ctl == (rfb.TightJpeg << 4)) {
1087         // Send data exactly as received.
1088         rfb.rec.writeByte(comp_ctl);
1089       } else {
1090         // Tell the decoder to flush each of the four zlib streams.
1091         rfb.rec.writeByte(comp_ctl | 0x0F);
1092       }
1093     }
1094
1095     // Flush zlib streams if we are told by the server to do so.
1096     for (int stream_id = 0; stream_id < 4; stream_id++) {
1097       if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
1098         tightInflaters[stream_id] = null;
1099       }
1100       comp_ctl >>= 1;
1101     }
1102
1103     // Check correctness of subencoding value.
1104     if (comp_ctl > rfb.TightMaxSubencoding) {
1105       throw new Exception("Incorrect tight subencoding: " + comp_ctl);
1106     }
1107
1108     // Handle solid-color rectangles.
1109     if (comp_ctl == rfb.TightFill) {
1110
1111       if (bytesPixel == 1) {
1112         int idx = rfb.is.readUnsignedByte();
1113         memGraphics.setColor(colors[idx]);
1114         if (rfb.rec != null) {
1115           rfb.rec.writeByte(idx);
1116         }
1117       } else {
1118         byte[] buf = new byte[3];
1119         rfb.readFully(buf);
1120         if (rfb.rec != null) {
1121           rfb.rec.write(buf);
1122         }
1123         Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
1124                              (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
1125         memGraphics.setColor(bg);
1126       }
1127       memGraphics.fillRect(x, y, w, h);
1128       scheduleRepaint(x, y, w, h);
1129       return;
1130
1131     }
1132
1133     if (comp_ctl == rfb.TightJpeg) {
1134
1135       // Read JPEG data.
1136       byte[] jpegData = new byte[rfb.readCompactLen()];
1137       rfb.readFully(jpegData);
1138       if (rfb.rec != null) {
1139         if (!rfb.recordFromBeginning) {
1140           rfb.recordCompactLen(jpegData.length);
1141         }
1142         rfb.rec.write(jpegData);
1143       }
1144
1145       // Create an Image object from the JPEG data.
1146       Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
1147
1148       // Remember the rectangle where the image should be drawn.
1149       jpegRect = new Rectangle(x, y, w, h);
1150
1151       // Let the imageUpdate() method do the actual drawing, here just
1152       // wait until the image is fully loaded and drawn.
1153       synchronized(jpegRect) {
1154         Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
1155         try {
1156           // Wait no longer than three seconds.
1157           jpegRect.wait(3000);
1158         } catch (InterruptedException e) {
1159           throw new Exception("Interrupted while decoding JPEG image");
1160         }
1161       }
1162
1163       // Done, jpegRect is not needed any more.
1164       jpegRect = null;
1165       return;
1166
1167     }
1168
1169     // Read filter id and parameters.
1170     int numColors = 0, rowSize = w;
1171     byte[] palette8 = new byte[2];
1172     int[] palette24 = new int[256];
1173     boolean useGradient = false;
1174     if ((comp_ctl & rfb.TightExplicitFilter) != 0) {
1175       int filter_id = rfb.is.readUnsignedByte();
1176       if (rfb.rec != null) {
1177         rfb.rec.writeByte(filter_id);
1178       }
1179       if (filter_id == rfb.TightFilterPalette) {
1180         numColors = rfb.is.readUnsignedByte() + 1;
1181         if (rfb.rec != null) {
1182           rfb.rec.writeByte(numColors - 1);
1183         }
1184         if (bytesPixel == 1) {
1185           if (numColors != 2) {
1186             throw new Exception("Incorrect tight palette size: " + numColors);
1187           }
1188           rfb.readFully(palette8);
1189           if (rfb.rec != null) {
1190             rfb.rec.write(palette8);
1191           }
1192         } else {
1193           byte[] buf = new byte[numColors * 3];
1194           rfb.readFully(buf);
1195           if (rfb.rec != null) {
1196             rfb.rec.write(buf);
1197           }
1198           for (int i = 0; i < numColors; i++) {
1199             palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
1200                             (buf[i * 3 + 1] & 0xFF) << 8 |
1201                             (buf[i * 3 + 2] & 0xFF));
1202           }
1203         }
1204         if (numColors == 2)
1205           rowSize = (w + 7) / 8;
1206       } else if (filter_id == rfb.TightFilterGradient) {
1207         useGradient = true;
1208       } else if (filter_id != rfb.TightFilterCopy) {
1209         throw new Exception("Incorrect tight filter id: " + filter_id);
1210       }
1211     }
1212     if (numColors == 0 && bytesPixel == 4)
1213       rowSize *= 3;
1214
1215     // Read, optionally uncompress and decode data.
1216     int dataSize = h * rowSize;
1217     if (dataSize < rfb.TightMinToCompress) {
1218       // Data size is small - not compressed with zlib.
1219       if (numColors != 0) {
1220         // Indexed colors.
1221         byte[] indexedData = new byte[dataSize];
1222         rfb.readFully(indexedData);
1223         if (rfb.rec != null) {
1224           rfb.rec.write(indexedData);
1225         }
1226         if (numColors == 2) {
1227           // Two colors.
1228           if (bytesPixel == 1) {
1229             decodeMonoData(x, y, w, h, indexedData, palette8);
1230           } else {
1231             decodeMonoData(x, y, w, h, indexedData, palette24);
1232           }
1233         } else {
1234           // 3..255 colors (assuming bytesPixel == 4).
1235           int i = 0;
1236           for (int dy = y; dy < y + h; dy++) {
1237             for (int dx = x; dx < x + w; dx++) {
1238               pixels24[dy * rfb.framebufferWidth + dx] =
1239                 palette24[indexedData[i++] & 0xFF];
1240             }
1241           }
1242         }
1243       } else if (useGradient) {
1244         // "Gradient"-processed data
1245         byte[] buf = new byte[w * h * 3];
1246         rfb.readFully(buf);
1247         if (rfb.rec != null) {
1248           rfb.rec.write(buf);
1249         }
1250         decodeGradientData(x, y, w, h, buf);
1251       } else {
1252         // Raw truecolor data.
1253         if (bytesPixel == 1) {
1254           for (int dy = y; dy < y + h; dy++) {
1255             rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
1256             if (rfb.rec != null) {
1257               rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
1258             }
1259           }
1260         } else {
1261           byte[] buf = new byte[w * 3];
1262           int i, offset;
1263           for (int dy = y; dy < y + h; dy++) {
1264             rfb.readFully(buf);
1265             if (rfb.rec != null) {
1266               rfb.rec.write(buf);
1267             }
1268             offset = dy * rfb.framebufferWidth + x;
1269             for (i = 0; i < w; i++) {
1270               pixels24[offset + i] =
1271                 (buf[i * 3] & 0xFF) << 16 |
1272                 (buf[i * 3 + 1] & 0xFF) << 8 |
1273                 (buf[i * 3 + 2] & 0xFF);
1274             }
1275           }
1276         }
1277       }
1278     } else {
1279       // Data was compressed with zlib.
1280       int zlibDataLen = rfb.readCompactLen();
1281       byte[] zlibData = new byte[zlibDataLen];
1282       rfb.readFully(zlibData);
1283       if (rfb.rec != null && rfb.recordFromBeginning) {
1284         rfb.rec.write(zlibData);
1285       }
1286       int stream_id = comp_ctl & 0x03;
1287       if (tightInflaters[stream_id] == null) {
1288         tightInflaters[stream_id] = new Inflater();
1289       }
1290       Inflater myInflater = tightInflaters[stream_id];
1291       myInflater.setInput(zlibData);
1292       byte[] buf = new byte[dataSize];
1293       myInflater.inflate(buf);
1294       if (rfb.rec != null && !rfb.recordFromBeginning) {
1295         rfb.recordCompressedData(buf);
1296       }
1297
1298       if (numColors != 0) {
1299         // Indexed colors.
1300         if (numColors == 2) {
1301           // Two colors.
1302           if (bytesPixel == 1) {
1303             decodeMonoData(x, y, w, h, buf, palette8);
1304           } else {
1305             decodeMonoData(x, y, w, h, buf, palette24);
1306           }
1307         } else {
1308           // More than two colors (assuming bytesPixel == 4).
1309           int i = 0;
1310           for (int dy = y; dy < y + h; dy++) {
1311             for (int dx = x; dx < x + w; dx++) {
1312               pixels24[dy * rfb.framebufferWidth + dx] =
1313                 palette24[buf[i++] & 0xFF];
1314             }
1315           }
1316         }
1317       } else if (useGradient) {
1318         // Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
1319         decodeGradientData(x, y, w, h, buf);
1320       } else {
1321         // Compressed truecolor data.
1322         if (bytesPixel == 1) {
1323           int destOffset = y * rfb.framebufferWidth + x;
1324           for (int dy = 0; dy < h; dy++) {
1325             System.arraycopy(buf, dy * w, pixels8, destOffset, w);
1326             destOffset += rfb.framebufferWidth;
1327           }
1328         } else {
1329           int srcOffset = 0;
1330           int destOffset, i;
1331           for (int dy = 0; dy < h; dy++) {
1332             myInflater.inflate(buf);
1333             destOffset = (y + dy) * rfb.framebufferWidth + x;
1334             for (i = 0; i < w; i++) {
1335               pixels24[destOffset + i] =
1336                 (buf[srcOffset] & 0xFF) << 16 |
1337                 (buf[srcOffset + 1] & 0xFF) << 8 |
1338                 (buf[srcOffset + 2] & 0xFF);
1339               srcOffset += 3;
1340             }
1341           }
1342         }
1343       }
1344     }
1345
1346     handleUpdatedPixels(x, y, w, h);
1347     scheduleRepaint(x, y, w, h);
1348   }
1349
1350   //
1351   // Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
1352   //
1353
1354   void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
1355
1356     int dx, dy, n;
1357     int i = y * rfb.framebufferWidth + x;
1358     int rowBytes = (w + 7) / 8;
1359     byte b;
1360
1361     for (dy = 0; dy < h; dy++) {
1362       for (dx = 0; dx < w / 8; dx++) {
1363         b = src[dy*rowBytes+dx];
1364         for (n = 7; n >= 0; n--)
1365           pixels8[i++] = palette[b >> n & 1];
1366       }
1367       for (n = 7; n >= 8 - w % 8; n--) {
1368         pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1369       }
1370       i += (rfb.framebufferWidth - w);
1371     }
1372   }
1373
1374   void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
1375
1376     int dx, dy, n;
1377     int i = y * rfb.framebufferWidth + x;
1378     int rowBytes = (w + 7) / 8;
1379     byte b;
1380
1381     for (dy = 0; dy < h; dy++) {
1382       for (dx = 0; dx < w / 8; dx++) {
1383         b = src[dy*rowBytes+dx];
1384         for (n = 7; n >= 0; n--)
1385           pixels24[i++] = palette[b >> n & 1];
1386       }
1387       for (n = 7; n >= 8 - w % 8; n--) {
1388         pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
1389       }
1390       i += (rfb.framebufferWidth - w);
1391     }
1392   }
1393
1394   //
1395   // Decode data processed with the "Gradient" filter.
1396   //
1397
1398   void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
1399
1400     int dx, dy, c;
1401     byte[] prevRow = new byte[w * 3];
1402     byte[] thisRow = new byte[w * 3];
1403     byte[] pix = new byte[3];
1404     int[] est = new int[3];
1405
1406     int offset = y * rfb.framebufferWidth + x;
1407
1408     for (dy = 0; dy < h; dy++) {
1409
1410       /* First pixel in a row */
1411       for (c = 0; c < 3; c++) {
1412         pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
1413         thisRow[c] = pix[c];
1414       }
1415       pixels24[offset++] =
1416         (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1417
1418       /* Remaining pixels of a row */
1419       for (dx = 1; dx < w; dx++) {
1420         for (c = 0; c < 3; c++) {
1421           est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
1422                     (prevRow[(dx-1) * 3 + c] & 0xFF));
1423           if (est[c] > 0xFF) {
1424             est[c] = 0xFF;
1425           } else if (est[c] < 0x00) {
1426             est[c] = 0x00;
1427           }
1428           pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
1429           thisRow[dx * 3 + c] = pix[c];
1430         }
1431         pixels24[offset++] =
1432           (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
1433       }
1434
1435       System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
1436       offset += (rfb.framebufferWidth - w);
1437     }
1438   }
1439
1440   //
1441   // Display newly updated area of pixels.
1442   //
1443
1444   void handleUpdatedPixels(int x, int y, int w, int h) {
1445
1446     // Draw updated pixels of the off-screen image.
1447     pixelsSource.newPixels(x, y, w, h);
1448     memGraphics.setClip(x, y, w, h);
1449     memGraphics.drawImage(rawPixelsImage, 0, 0, null);
1450     memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
1451   }
1452
1453   //
1454   // Tell JVM to repaint specified desktop area.
1455   //
1456
1457   void scheduleRepaint(int x, int y, int w, int h) {
1458     // Request repaint, deferred if necessary.
1459     if (rfb.framebufferWidth == scaledWidth) {
1460       repaint(viewer.deferScreenUpdates, x, y, w, h);
1461     } else {
1462       int sx = x * scalingFactor / 100;
1463       int sy = y * scalingFactor / 100;
1464       int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
1465       int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
1466       repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
1467     }
1468   }
1469
1470   //
1471   // Handle events.
1472   //
1473
1474   public void keyPressed(KeyEvent evt) {
1475     processLocalKeyEvent(evt);
1476   }
1477   public void keyReleased(KeyEvent evt) {
1478     processLocalKeyEvent(evt);
1479   }
1480   public void keyTyped(KeyEvent evt) {
1481     evt.consume();
1482   }
1483
1484   public void mousePressed(MouseEvent evt) {
1485     processLocalMouseEvent(evt, false);
1486   }
1487   public void mouseReleased(MouseEvent evt) {
1488     processLocalMouseEvent(evt, false);
1489   }
1490   public void mouseMoved(MouseEvent evt) {
1491     processLocalMouseEvent(evt, true);
1492   }
1493   public void mouseDragged(MouseEvent evt) {
1494     processLocalMouseEvent(evt, true);
1495   }
1496
1497   public void processLocalKeyEvent(KeyEvent evt) {
1498     if (viewer.rfb != null && rfb.inNormalProtocol) {
1499       if (!inputEnabled) {
1500         if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R') &&
1501             evt.getID() == KeyEvent.KEY_PRESSED ) {
1502           // Request screen update.
1503           try {
1504             rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
1505                                               rfb.framebufferHeight, false);
1506           } catch (IOException e) {
1507             e.printStackTrace();
1508           }
1509         }
1510       } else {
1511         // Input enabled.
1512         synchronized(rfb) {
1513           try {
1514             if (extraModifiers != 0) {
1515               evt.setModifiers(evt.getModifiers() | extraModifiers);
1516             }
1517             rfb.writeKeyEvent(evt);
1518           } catch (Exception e) {
1519             e.printStackTrace();
1520           }
1521           rfb.notify();
1522         }
1523       }
1524     }
1525     // Don't ever pass keyboard events to AWT for default processing. 
1526     // Otherwise, pressing Tab would switch focus to ButtonPanel etc.
1527     evt.consume();
1528   }
1529
1530   public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
1531     if (viewer.rfb != null && rfb.inNormalProtocol) {
1532       if (moved) {
1533         softCursorMove(evt.getX(), evt.getY());
1534       }
1535       if (rfb.framebufferWidth != scaledWidth) {
1536         int sx = (evt.getX() * 100 + scalingFactor/2) / scalingFactor;
1537         int sy = (evt.getY() * 100 + scalingFactor/2) / scalingFactor;
1538         evt.translatePoint(sx - evt.getX(), sy - evt.getY());
1539       }
1540       synchronized(rfb) {
1541         try {
1542           rfb.writePointerEvent(evt);
1543         } catch (Exception e) {
1544           e.printStackTrace();
1545         }
1546         rfb.notify();
1547       }
1548     }
1549   }
1550
1551   //
1552   // Ignored events.
1553   //
1554
1555   public void mouseClicked(MouseEvent evt) {}
1556   public void mouseEntered(MouseEvent evt) {}
1557   public void mouseExited(MouseEvent evt) {}
1558
1559
1560   //////////////////////////////////////////////////////////////////
1561   //
1562   // Handle cursor shape updates (XCursor and RichCursor encodings).
1563   //
1564
1565   boolean showSoftCursor = false;
1566
1567   MemoryImageSource softCursorSource;
1568   Image softCursor;
1569
1570   int cursorX = 0, cursorY = 0;
1571   int cursorWidth, cursorHeight;
1572   int origCursorWidth, origCursorHeight;
1573   int hotX, hotY;
1574   int origHotX, origHotY;
1575
1576   //
1577   // Handle cursor shape update (XCursor and RichCursor encodings).
1578   //
1579
1580   synchronized void
1581     handleCursorShapeUpdate(int encodingType,
1582                             int xhot, int yhot, int width, int height)
1583     throws IOException {
1584
1585     softCursorFree();
1586
1587     if (width * height == 0)
1588       return;
1589
1590     // Ignore cursor shape data if requested by user.
1591     if (viewer.options.ignoreCursorUpdates) {
1592       int bytesPerRow = (width + 7) / 8;
1593       int bytesMaskData = bytesPerRow * height;
1594
1595       if (encodingType == rfb.EncodingXCursor) {
1596         rfb.is.skipBytes(6 + bytesMaskData * 2);
1597       } else {
1598         // rfb.EncodingRichCursor
1599         rfb.is.skipBytes(width * height + bytesMaskData);
1600       }
1601       return;
1602     }
1603
1604     // Decode cursor pixel data.
1605     softCursorSource = decodeCursorShape(encodingType, width, height);
1606
1607     // Set original (non-scaled) cursor dimensions.
1608     origCursorWidth = width;
1609     origCursorHeight = height;
1610     origHotX = xhot;
1611     origHotY = yhot;
1612
1613     // Create off-screen cursor image.
1614     createSoftCursor();
1615
1616     // Show the cursor.
1617     showSoftCursor = true;
1618     repaint(viewer.deferCursorUpdates,
1619             cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1620   }
1621
1622   //
1623   // decodeCursorShape(). Decode cursor pixel data and return
1624   // corresponding MemoryImageSource instance.
1625   //
1626
1627   synchronized MemoryImageSource
1628     decodeCursorShape(int encodingType, int width, int height)
1629     throws IOException {
1630
1631     int bytesPerRow = (width + 7) / 8;
1632     int bytesMaskData = bytesPerRow * height;
1633
1634     int[] softCursorPixels = new int[width * height];
1635
1636     if (encodingType == rfb.EncodingXCursor) {
1637
1638       // Read foreground and background colors of the cursor.
1639       byte[] rgb = new byte[6];
1640       rfb.readFully(rgb);
1641       int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
1642                         (rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
1643                        (0xFF000000 | (rgb[0] & 0xFF) << 16 |
1644                         (rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
1645
1646       // Read pixel and mask data.
1647       byte[] pixBuf = new byte[bytesMaskData];
1648       rfb.readFully(pixBuf);
1649       byte[] maskBuf = new byte[bytesMaskData];
1650       rfb.readFully(maskBuf);
1651
1652       // Decode pixel data into softCursorPixels[].
1653       byte pixByte, maskByte;
1654       int x, y, n, result;
1655       int i = 0;
1656       for (y = 0; y < height; y++) {
1657         for (x = 0; x < width / 8; x++) {
1658           pixByte = pixBuf[y * bytesPerRow + x];
1659           maskByte = maskBuf[y * bytesPerRow + x];
1660           for (n = 7; n >= 0; n--) {
1661             if ((maskByte >> n & 1) != 0) {
1662               result = colors[pixByte >> n & 1];
1663             } else {
1664               result = 0;       // Transparent pixel
1665             }
1666             softCursorPixels[i++] = result;
1667           }
1668         }
1669         for (n = 7; n >= 8 - width % 8; n--) {
1670           if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1671             result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
1672           } else {
1673             result = 0;         // Transparent pixel
1674           }
1675           softCursorPixels[i++] = result;
1676         }
1677       }
1678
1679     } else {
1680       // encodingType == rfb.EncodingRichCursor
1681
1682       // Read pixel and mask data.
1683       byte[] pixBuf = new byte[width * height * bytesPixel];
1684       rfb.readFully(pixBuf);
1685       byte[] maskBuf = new byte[bytesMaskData];
1686       rfb.readFully(maskBuf);
1687
1688       // Decode pixel data into softCursorPixels[].
1689       byte pixByte, maskByte;
1690       int x, y, n, result;
1691       int i = 0;
1692       for (y = 0; y < height; y++) {
1693         for (x = 0; x < width / 8; x++) {
1694           maskByte = maskBuf[y * bytesPerRow + x];
1695           for (n = 7; n >= 0; n--) {
1696             if ((maskByte >> n & 1) != 0) {
1697               if (bytesPixel == 1) {
1698                 result = cm8.getRGB(pixBuf[i]);
1699               } else {
1700                 result = 0xFF000000 |
1701                   (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1702                   (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1703                   (pixBuf[i * 4] & 0xFF);
1704               }
1705             } else {
1706               result = 0;       // Transparent pixel
1707             }
1708             softCursorPixels[i++] = result;
1709           }
1710         }
1711         for (n = 7; n >= 8 - width % 8; n--) {
1712           if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
1713             if (bytesPixel == 1) {
1714               result = cm8.getRGB(pixBuf[i]);
1715             } else {
1716               result = 0xFF000000 |
1717                 (pixBuf[i * 4 + 2] & 0xFF) << 16 |
1718                 (pixBuf[i * 4 + 1] & 0xFF) << 8 |
1719                 (pixBuf[i * 4] & 0xFF);
1720             }
1721           } else {
1722             result = 0;         // Transparent pixel
1723           }
1724           softCursorPixels[i++] = result;
1725         }
1726       }
1727
1728     }
1729
1730     return new MemoryImageSource(width, height, softCursorPixels, 0, width);
1731   }
1732
1733   //
1734   // createSoftCursor(). Assign softCursor new Image (scaled if necessary).
1735   // Uses softCursorSource as a source for new cursor image.
1736   //
1737
1738   synchronized void
1739     createSoftCursor() {
1740
1741     if (softCursorSource == null)
1742       return;
1743
1744     int scaleCursor = viewer.options.scaleCursor;
1745     if (scaleCursor == 0 || !inputEnabled)
1746       scaleCursor = 100;
1747
1748     // Save original cursor coordinates.
1749     int x = cursorX - hotX;
1750     int y = cursorY - hotY;
1751     int w = cursorWidth;
1752     int h = cursorHeight;
1753
1754     cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
1755     cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
1756     hotX = (origHotX * scaleCursor + 50) / 100;
1757     hotY = (origHotY * scaleCursor + 50) / 100;
1758     softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);
1759
1760     if (scaleCursor != 100) {
1761       softCursor = softCursor.getScaledInstance(cursorWidth, cursorHeight,
1762                                                 Image.SCALE_SMOOTH);
1763     }
1764
1765     if (showSoftCursor) {
1766       // Compute screen area to update.
1767       x = Math.min(x, cursorX - hotX);
1768       y = Math.min(y, cursorY - hotY);
1769       w = Math.max(w, cursorWidth);
1770       h = Math.max(h, cursorHeight);
1771
1772       repaint(viewer.deferCursorUpdates, x, y, w, h);
1773     }
1774   }
1775
1776   //
1777   // softCursorMove(). Moves soft cursor into a particular location.
1778   //
1779
1780   synchronized void softCursorMove(int x, int y) {
1781     int oldX = cursorX;
1782     int oldY = cursorY;
1783     cursorX = x;
1784     cursorY = y;
1785     if (showSoftCursor) {
1786       repaint(viewer.deferCursorUpdates,
1787               oldX - hotX, oldY - hotY, cursorWidth, cursorHeight);
1788       repaint(viewer.deferCursorUpdates,
1789               cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1790     }
1791   }
1792
1793   //
1794   // softCursorFree(). Remove soft cursor, dispose resources.
1795   //
1796
1797   synchronized void softCursorFree() {
1798     if (showSoftCursor) {
1799       showSoftCursor = false;
1800       softCursor = null;
1801       softCursorSource = null;
1802
1803       repaint(viewer.deferCursorUpdates,
1804               cursorX - hotX, cursorY - hotY, cursorWidth, cursorHeight);
1805     }
1806   }
1807 }