source: trunk/packages/invirt-vnc-client/VncCanvas.java @ 2766

Last change on this file since 2766 was 1438, checked in by broder, 16 years ago

Isolate our patches to the VNC client from the upstream TightVNC
source

File size: 47.4 KB
Line 
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
24import java.awt.*;
25import java.awt.event.*;
26import java.awt.image.*;
27import java.io.*;
28import java.lang.*;
29import java.util.zip.*;
30
31
32//
33// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
34//
35
36class 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}
Note: See TracBrowser for help on using the repository browser.