source: trunk/vnc/vnc_javasrc/VncCanvas.java @ 74

Last change on this file since 74 was 66, checked in by quentin, 17 years ago

Initial checkin of modified Java VNC viewer for use as remote console

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.