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