source: trunk/packages/sipb-xen-vnc-client/code/VncCanvas.java @ 1242

Last change on this file since 1242 was 128, checked in by quentin, 17 years ago

Added control and alt checkboxes, for ease of typing modifier sequences

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