]> source.dussan.org Git - tigervnc.git/commitdiff
Complete rewrite of pixel buffer & decoder implementation. 390/head
authorBrian P. Hinz <bphinz@users.sf.net>
Sun, 11 Dec 2016 00:28:42 +0000 (19:28 -0500)
committerBrian P. Hinz <bphinz@users.sf.net>
Sun, 11 Dec 2016 00:28:42 +0000 (19:28 -0500)
Adds multi-threading, more robust support for different pixel formats,
and several new runtime options.

27 files changed:
java/com/tigervnc/rfb/CopyRectDecoder.java [new file with mode: 0644]
java/com/tigervnc/rfb/Cursor.java
java/com/tigervnc/rfb/DecodeManager.java [new file with mode: 0644]
java/com/tigervnc/rfb/Decoder.java
java/com/tigervnc/rfb/FullFramePixelBuffer.java [new file with mode: 0644]
java/com/tigervnc/rfb/HextileDecoder.java
java/com/tigervnc/rfb/JpegDecompressor.java [new file with mode: 0644]
java/com/tigervnc/rfb/ManagedPixelBuffer.java
java/com/tigervnc/rfb/ModifiablePixelBuffer.java [new file with mode: 0644]
java/com/tigervnc/rfb/PixelBuffer.java
java/com/tigervnc/rfb/PixelFormat.java
java/com/tigervnc/rfb/RREDecoder.java
java/com/tigervnc/rfb/RawDecoder.java
java/com/tigervnc/rfb/Region.java [new file with mode: 0644]
java/com/tigervnc/rfb/TightDecoder.java
java/com/tigervnc/rfb/ZRLEDecoder.java
java/com/tigervnc/vncviewer/BIPixelBuffer.java [deleted file]
java/com/tigervnc/vncviewer/CConn.java
java/com/tigervnc/vncviewer/DesktopWindow.java
java/com/tigervnc/vncviewer/Dialog.java
java/com/tigervnc/vncviewer/F8Menu.java
java/com/tigervnc/vncviewer/JavaPixelBuffer.java [new file with mode: 0644]
java/com/tigervnc/vncviewer/OptionsDialog.java
java/com/tigervnc/vncviewer/Parameters.java
java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
java/com/tigervnc/vncviewer/Viewport.java
java/com/tigervnc/vncviewer/VncViewer.java

diff --git a/java/com/tigervnc/rfb/CopyRectDecoder.java b/java/com/tigervnc/rfb/CopyRectDecoder.java
new file mode 100644 (file)
index 0000000..a4298fd
--- /dev/null
@@ -0,0 +1,44 @@
+/* Copyright 2014 Pierre Ossman <ossman@cendio.se> for Cendio AB
+ * Copyright 2016 Brian P. Hinz
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import com.tigervnc.rdr.*;
+
+public class CopyRectDecoder extends Decoder {
+
+  public CopyRectDecoder() { super(DecoderFlags.DecoderPlain); }
+
+  public void readRect(Rect r, InStream is,
+                       ConnParams cp, OutStream os)
+  {
+    os.copyBytes(is, 4);
+  }
+
+  public void decodeRect(Rect r, Object buffer,
+                         int buflen, ConnParams cp,
+                         ModifiablePixelBuffer pb)
+  {
+    MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+    int srcX = is.readU16();
+    int srcY = is.readU16();
+    pb.copyRect(r, new Point(r.tl.x-srcX, r.tl.y-srcY));
+  }
+
+}
index 78aa0fb215c6e4113797f390834f281ce96519d6..05122ae5066c6a215baf4a9f1956b9df9fd9c49a 100644 (file)
@@ -20,11 +20,18 @@ package com.tigervnc.rfb;
 
 public class Cursor extends ManagedPixelBuffer {
 
+  public Cursor(PixelFormat pf, int w, int h) {
+    super(pf, w, h);
+    hotspot = new Point(0, 0);
+  }
+
   public void setSize(int w, int h) {
+    int oldMaskLen = maskLen();
     super.setSize(w, h);
-    if (mask == null || mask.length < maskLen())
+    if (mask == null || maskLen() > oldMaskLen)
       mask = new byte[maskLen()];
   }
+
   public int maskLen() { return (width() + 7) / 8 * height(); }
 
   public Point hotspot;
diff --git a/java/com/tigervnc/rfb/DecodeManager.java b/java/com/tigervnc/rfb/DecodeManager.java
new file mode 100644 (file)
index 0000000..9e254ad
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright 2015 Pierre Ossman for Cendio AB
+ * Copyright 2016 Brian P. Hinz
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import java.lang.Runtime;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+
+import com.tigervnc.rdr.*;
+import com.tigervnc.rdr.Exception;
+
+import static com.tigervnc.rfb.Decoder.DecoderFlags.*;
+
+public class DecodeManager {
+
+  static LogWriter vlog = new LogWriter("DecodeManager");
+
+  public DecodeManager(CConnection conn) {
+    int cpuCount;
+
+    this.conn = conn; threadException = null;
+    decoders = new Decoder[Encodings.encodingMax+1];
+
+    queueMutex = new ReentrantLock();
+    producerCond = queueMutex.newCondition();
+    consumerCond = queueMutex.newCondition();
+
+    //cpuCount = 1;
+    cpuCount = Runtime.getRuntime().availableProcessors();
+    if (cpuCount == 0) {
+      vlog.error("Unable to determine the number of CPU cores on this system");
+      cpuCount = 1;
+    } else {
+      vlog.info("Detected "+cpuCount+" CPU core(s)");
+      // No point creating more threads than this, they'll just end up
+      // wasting CPU fighting for locks
+      if (cpuCount > 4)
+        cpuCount = 4;
+      // The overhead of threading is small, but not small enough to
+      // ignore on single CPU systems
+      if (cpuCount == 1)
+        vlog.info("Decoding data on main thread");
+      else
+        vlog.info("Creating "+cpuCount+" decoder thread(s)");
+    }
+
+    freeBuffers = new ArrayDeque<MemOutStream>(cpuCount*2);
+    workQueue = new ArrayDeque<QueueEntry>(cpuCount);
+    threads = new ArrayList<DecodeThread>(cpuCount);
+    while (cpuCount-- > 0) {
+      // Twice as many possible entries in the queue as there
+      // are worker threads to make sure they don't stall
+      try {
+      freeBuffers.addLast(new MemOutStream());
+      freeBuffers.addLast(new MemOutStream());
+
+      threads.add(new DecodeThread(this));
+      } catch (IllegalStateException e) { }
+    }
+
+  }
+
+  public void decodeRect(Rect r, int encoding,
+                         ModifiablePixelBuffer pb)
+  {
+    Decoder decoder;
+    MemOutStream bufferStream;
+
+    QueueEntry entry;
+
+    assert(pb != null);
+
+    if (!Decoder.supported(encoding)) {
+      vlog.error("Unknown encoding " + encoding);
+      throw new Exception("Unknown encoding");
+    }
+
+    if (decoders[encoding] == null) {
+      decoders[encoding] = Decoder.createDecoder(encoding);
+      if (decoders[encoding] == null) {
+        vlog.error("Unknown encoding " + encoding);
+        throw new Exception("Unknown encoding");
+      }
+    }
+
+    decoder = decoders[encoding];
+
+    // Fast path for single CPU machines to avoid the context
+    // switching overhead
+    if (threads.size() == 1) {
+      bufferStream = freeBuffers.getFirst();
+      bufferStream.clear();
+      decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream);
+      decoder.decodeRect(r, (Object)bufferStream.data(), bufferStream.length(),
+                         conn.cp, pb);
+      return;
+    }
+
+    // Wait for an available memory buffer
+    queueMutex.lock();
+
+    while (freeBuffers.isEmpty())
+      try {
+      producerCond.await();
+      } catch (InterruptedException e) { }
+
+    // Don't pop the buffer in case we throw an exception
+    // whilst reading
+    bufferStream = freeBuffers.getFirst();
+
+    queueMutex.unlock();
+
+    // First check if any thread has encountered a problem
+    throwThreadException();
+
+    // Read the rect
+    bufferStream.clear();
+    decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream);
+
+    // Then try to put it on the queue
+    entry = new QueueEntry();
+
+    entry.active = false;
+    entry.rect = r;
+    entry.encoding = encoding;
+    entry.decoder = decoder;
+    entry.cp = conn.cp;
+    entry.pb = pb;
+    entry.bufferStream = bufferStream;
+    entry.affectedRegion = new Region(r);
+
+    decoder.getAffectedRegion(r, bufferStream.data(),
+                              bufferStream.length(), conn.cp,
+                              entry.affectedRegion);
+
+    // The workers add buffers to the end so it's safe to assume
+    // the front is still the same buffer
+    freeBuffers.removeFirst();
+
+    queueMutex.lock();
+
+    workQueue.addLast(entry);
+
+    // We only put a single entry on the queue so waking a single
+    // thread is sufficient
+    consumerCond.signal();
+
+    queueMutex.unlock();
+  }
+
+  public void flush()
+  {
+    queueMutex.lock();
+
+    while (!workQueue.isEmpty())
+      try {
+      producerCond.await();
+      } catch (InterruptedException e) { }
+
+    queueMutex.unlock();
+
+    throwThreadException();
+  }
+
+  private void setThreadException(Exception e)
+  {
+    //os::AutoMutex a(queueMutex);
+    queueMutex.lock();
+
+    if (threadException == null)
+      return;
+
+    threadException =
+      new Exception("Exception on worker thread: "+e.getMessage());
+  }
+
+  private void throwThreadException()
+  {
+    //os::AutoMutex a(queueMutex);
+    queueMutex.lock();
+
+    if (threadException == null)
+      return;
+
+    Exception e = new Exception(threadException.getMessage());
+
+    threadException = null;
+
+    throw e;
+  }
+
+  private class QueueEntry {
+
+    public QueueEntry() {
+    }
+    public boolean active;
+    public Rect rect;
+    public int encoding;
+    public Decoder decoder;
+    public ConnParams cp;
+    public ModifiablePixelBuffer pb;
+    public MemOutStream bufferStream;
+    public Region affectedRegion;
+  }
+
+  private class DecodeThread implements Runnable {
+
+    public DecodeThread(DecodeManager manager)
+    {
+      this.manager = manager;
+
+      stopRequested = false;
+
+      (thread = new Thread(this)).start();
+    }
+
+    public void stop()
+    {
+      //os::AutoMutex a(manager.queueMutex);
+      manager.queueMutex.lock();
+
+      if (!thread.isAlive())
+        return;
+
+      stopRequested = true;
+
+      // We can't wake just this thread, so wake everyone
+      manager.consumerCond.signalAll();
+    }
+
+    public void run()
+    {
+      manager.queueMutex.lock();
+      while (!stopRequested) {
+        QueueEntry entry;
+
+        // Look for an available entry in the work queue
+        entry = findEntry();
+        if (entry == null) {
+          // Wait and try again
+          try {
+          manager.consumerCond.await();
+          } catch (InterruptedException e) { }
+          continue;
+        }
+
+        // This is ours now
+        entry.active = true;
+
+        manager.queueMutex.unlock();
+
+        // Do the actual decoding
+        try {
+          entry.decoder.decodeRect(entry.rect, entry.bufferStream.data(),
+                                   entry.bufferStream.length(),
+                                   entry.cp, entry.pb);
+        } catch (com.tigervnc.rdr.Exception e) {
+          manager.setThreadException(e);
+        } catch(java.lang.Exception e) {
+          assert(false);
+        }
+
+        manager.queueMutex.lock();
+
+        // Remove the entry from the queue and give back the memory buffer
+        manager.freeBuffers.add(entry.bufferStream);
+        manager.workQueue.remove(entry);
+        entry = null;
+
+        // Wake the main thread in case it is waiting for a memory buffer
+        manager.producerCond.signal();
+        // This rect might have been blocking multiple other rects, so
+        // wake up every worker thread
+        if (manager.workQueue.size() > 1)
+          manager.consumerCond.signalAll();
+      }
+
+      manager.queueMutex.unlock();
+    }
+
+    protected QueueEntry findEntry()
+    {
+      Iterator<QueueEntry> iter;
+      Region lockedRegion = new Region();
+
+      if (manager.workQueue.isEmpty())
+        return null;
+
+      if (!manager.workQueue.peek().active)
+        return manager.workQueue.peek();
+
+      for (iter = manager.workQueue.iterator(); iter.hasNext();) {
+        QueueEntry entry;
+
+        Iterator<QueueEntry> iter2;
+
+        entry = iter.next();
+
+        // Another thread working on this?
+        if (entry.active) {
+          lockedRegion.assign_union(entry.affectedRegion);
+          continue;
+        }
+
+        // If this is an ordered decoder then make sure this is the first
+        // rectangle in the queue for that decoder
+        if ((entry.decoder.flags & DecoderOrdered) != 0) {
+          for (iter2 = manager.workQueue.iterator(); iter2.hasNext() && iter2 != iter;) {
+            if (entry.encoding == (iter2.next()).encoding) {
+              lockedRegion.assign_union(entry.affectedRegion);
+              continue;
+            }
+          }
+        }
+
+        // For a partially ordered decoder we must ask the decoder for each
+        // pair of rectangles.
+        if ((entry.decoder.flags & DecoderPartiallyOrdered) != 0) {
+          for (iter2 = manager.workQueue.iterator(); iter2.hasNext() && iter2 != iter;) {
+            QueueEntry entry2 = iter2.next();
+            if (entry.encoding != entry2.encoding)
+              continue;
+            if (entry.decoder.doRectsConflict(entry.rect,
+                                              entry.bufferStream.data(),
+                                              entry.bufferStream.length(),
+                                              entry2.rect,
+                                              entry2.bufferStream.data(),
+                                              entry2.bufferStream.length(),
+                                              entry.cp))
+              lockedRegion.assign_union(entry.affectedRegion);
+              continue;
+          }
+        }
+
+        // Check overlap with earlier rectangles
+        if (!lockedRegion.intersect(entry.affectedRegion).is_empty()) {
+          lockedRegion.assign_union(entry.affectedRegion);
+          continue;
+        }
+
+        return entry;
+
+      }
+
+      return null;
+    }
+
+    private DecodeManager manager;
+    private boolean stopRequested;
+
+    private Thread thread;
+
+  }
+
+  private CConnection conn;
+  private Decoder[] decoders;
+
+  private ArrayDeque<MemOutStream> freeBuffers;
+  private ArrayDeque<QueueEntry> workQueue;
+
+  private ReentrantLock queueMutex;
+  private Condition producerCond;
+  private Condition consumerCond;
+
+  private List<DecodeThread> threads;
+  private com.tigervnc.rdr.Exception threadException;
+
+}
index f0ece0af121cd7d96e6144f3fb2dd49a562c5f23..6bbed85e832a867734a395b6b41478d030becc3e 100644 (file)
 
 package com.tigervnc.rfb;
 
+import com.tigervnc.rdr.*;
+
 abstract public class Decoder {
 
-  abstract public void readRect(Rect r, CMsgHandler handler);
+  public static class DecoderFlags {
+    // A constant for decoders that don't need anything special
+    public static int DecoderPlain = 0;
+    // All rects for this decoder must be handled in order
+    public static int DecoderOrdered = 1 << 0;
+    // Only some of the rects must be handled in order,
+    // see doesRectsConflict()
+    public static int DecoderPartiallyOrdered = 1 << 1;
+  };
+
+  public Decoder(int flags)
+  {
+    this.flags = flags;
+  }
+
+  abstract public void readRect(Rect r, InStream is,
+                                ConnParams cp, OutStream os);
+
+  abstract public void decodeRect(Rect r, Object buffer,
+                                  int buflen, ConnParams cp,
+                                  ModifiablePixelBuffer pb);
+
+  public void getAffectedRegion(Rect rect, Object buffer,
+                                int buflen, ConnParams cp,
+                                Region region)
+  {
+    region.reset(rect);
+  }
+
+  public boolean doRectsConflict(Rect rectA, Object bufferA,
+                                 int buflenA, Rect rectB,
+                                 Object bufferB, int buflenB,
+                                 ConnParams cp)
+  {
+    return false;
+  }
 
   static public boolean supported(int encoding)
   {
-/*
-    return encoding <= Encodings.encodingMax && createFns[encoding];
-*/
-    return (encoding == Encodings.encodingRaw ||
-            encoding == Encodings.encodingRRE ||
-            encoding == Encodings.encodingHextile ||
-            encoding == Encodings.encodingTight ||
-            encoding == Encodings.encodingZRLE);
+    switch(encoding) {
+    case Encodings.encodingRaw:
+    case Encodings.encodingCopyRect:
+    case Encodings.encodingRRE:
+    case Encodings.encodingHextile:
+    case Encodings.encodingZRLE:
+    case Encodings.encodingTight:
+      return true;
+    default:
+      return false;
+    }
   }
-  static public Decoder createDecoder(int encoding, CMsgReader reader) {
-/*
-    if (encoding <= Encodings.encodingMax && createFns[encoding])
-      return (createFns[encoding])(reader);
-    return 0;
-*/
+
+  static public Decoder createDecoder(int encoding) {
     switch(encoding) {
-    case Encodings.encodingRaw:     return new RawDecoder(reader);
-    case Encodings.encodingRRE:     return new RREDecoder(reader);
-    case Encodings.encodingHextile: return new HextileDecoder(reader);
-    case Encodings.encodingTight:   return new TightDecoder(reader);
-    case Encodings.encodingZRLE:    return new ZRLEDecoder(reader);
+    case Encodings.encodingRaw:
+      return new RawDecoder();
+    case Encodings.encodingCopyRect:
+      return new CopyRectDecoder();
+    case Encodings.encodingRRE:
+      return new RREDecoder();
+    case Encodings.encodingHextile:
+      return new HextileDecoder();
+    case Encodings.encodingZRLE:
+      return new ZRLEDecoder();
+    case Encodings.encodingTight:
+      return new TightDecoder();
+    default:
+      return null;
     }
-    return null;
   }
+
+  public final int flags;
 }
diff --git a/java/com/tigervnc/rfb/FullFramePixelBuffer.java b/java/com/tigervnc/rfb/FullFramePixelBuffer.java
new file mode 100644 (file)
index 0000000..1c3b095
--- /dev/null
@@ -0,0 +1,54 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import java.awt.image.*;
+
+public class FullFramePixelBuffer extends ModifiablePixelBuffer {
+
+  public FullFramePixelBuffer(PixelFormat pf, int w, int h,
+                              WritableRaster data_) {
+    super(pf, w, h);
+    data = data_;
+  }
+
+  protected FullFramePixelBuffer() {}
+
+  public WritableRaster getBufferRW(Rect r)
+  {
+    return data.createWritableChild(r.tl.x, r.tl.y, r.width(), r.height(),
+                                    0, 0, null);
+  }
+
+  public void commitBufferRW(Rect r)
+  {
+  }
+
+  public Raster getBuffer(Rect r)
+  {
+    Raster src =
+      data.createChild(r.tl.x, r.tl.y, r.width(), r.height(), 0, 0, null);
+    WritableRaster dst =
+      data.createCompatibleWritableRaster(r.width(), r.height());
+    dst.setDataElements(0, 0, src);
+    return dst;
+  }
+
+  protected WritableRaster data;
+}
index 94e91f7031eede83c1be8c3cfddbe344b5915205..b0744cad06961bba4ce7ad1ff4e1e3ee7b2d6a15 100644 (file)
 
 package com.tigervnc.rfb;
 
+import java.awt.image.*;
+import java.nio.*;
+import java.util.Arrays;
+
 import com.tigervnc.rdr.*;
 
 public class HextileDecoder extends Decoder {
 
-  public HextileDecoder(CMsgReader reader_) { reader = reader_; }
+  public static final int hextileRaw = (1 << 0);
+  public static final int hextileBgSpecified = (1 << 1);
+  public static final int hextileFgSpecified = (1 << 2);
+  public static final int hextileAnySubrects = (1 << 3);
+  public static final int hextileSubrectsColoured = (1 << 4);
+
+  public HextileDecoder() { super(DecoderFlags.DecoderPlain); }
+
+  public void readRect(Rect r, InStream is,
+                       ConnParams cp, OutStream os)
+  {
+    Rect t = new Rect();
+    int bytesPerPixel;
+
+    bytesPerPixel = cp.pf().bpp/8;
+
+    for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) {
+
+      t.br.y = Math.min(r.br.y, t.tl.y + 16);
+
+      for (t.tl.x = r.tl.x; t.tl.x < r.br.x; t.tl.x += 16) {
+        int tileType;
+
+        t.br.x = Math.min(r.br.x, t.tl.x + 16);
+
+        tileType = is.readU8() & 0xff;
+        os.writeU32(tileType);
+
+        if ((tileType & hextileRaw) != 0) {
+          os.copyBytes(is, t.area() * bytesPerPixel);
+          continue;
+        }
+
+        if ((tileType & hextileBgSpecified) != 0)
+          os.copyBytes(is, bytesPerPixel);
+
+        if ((tileType & hextileFgSpecified) != 0)
+          os.copyBytes(is, bytesPerPixel);
 
-  public void readRect(Rect r, CMsgHandler handler) {
-    InStream is = reader.getInStream();
-    int bytesPerPixel = handler.cp.pf().bpp / 8;
-    boolean bigEndian = handler.cp.pf().bigEndian;
+        if ((tileType & hextileAnySubrects) != 0) {
+          int nSubrects;
 
-    int[] buf = reader.getImageBuf(16 * 16 * 4);
+          nSubrects = is.readU8() & 0xff;
+          os.writeU32(nSubrects);
 
+          if ((tileType & hextileSubrectsColoured) != 0)
+            os.copyBytes(is, nSubrects * (bytesPerPixel + 2));
+          else
+            os.copyBytes(is, nSubrects * 2);
+        }
+      }
+    }
+  }
+
+  public void decodeRect(Rect r, Object buffer,
+                         int buflen, ConnParams cp,
+                         ModifiablePixelBuffer pb)
+  {
+    MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+    PixelFormat pf = cp.pf();
+    switch (pf.bpp) {
+    case 8:  hextileDecode8(r, is, pf, pb); break;
+    case 16: hextileDecode16(r, is, pf, pb); break;
+    case 32: hextileDecode32(r, is, pf, pb); break;
+    }
+  }
+
+  private void hextileDecode8(Rect r, InStream is,
+                              PixelFormat pf,
+                              ModifiablePixelBuffer pb)
+  {
+    HEXTILE_DECODE(r, is, pf, pb);
+  }
+
+  private void hextileDecode16(Rect r, InStream is,
+                               PixelFormat pf,
+                               ModifiablePixelBuffer pb)
+  {
+    HEXTILE_DECODE(r, is, pf, pb);
+  }
+
+  private void hextileDecode32(Rect r, InStream is,
+                               PixelFormat pf,
+                               ModifiablePixelBuffer pb)
+  {
+    HEXTILE_DECODE(r, is, pf, pb);
+  }
+
+  private static ByteBuffer READ_PIXEL(InStream is, PixelFormat pf) {
+    ByteBuffer b = ByteBuffer.allocate(4);
+    switch (pf.bpp) {
+    case 8:
+      b.putInt(is.readOpaque8());
+      return ByteBuffer.allocate(1).put(b.get(3));
+    case 16:
+      b.putInt(is.readOpaque16());
+      return ByteBuffer.allocate(2).put(b.array(), 2, 2);
+    case 32:
+    default:
+      b.putInt(is.readOpaque32());
+      return b;
+    }
+  }
+
+  private void HEXTILE_DECODE(Rect r, InStream is,
+                                     PixelFormat pf,
+                                     ModifiablePixelBuffer pb)
+  {
     Rect t = new Rect();
-    int bg = 0;
-    int fg = 0;
+    ByteBuffer bg = ByteBuffer.allocate(pf.bpp/8);
+    ByteBuffer fg = ByteBuffer.allocate(pf.bpp/8);
+    ByteBuffer buf = ByteBuffer.allocate(16 * 16 * 4);
 
     for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) {
 
@@ -43,59 +147,51 @@ public class HextileDecoder extends Decoder {
 
         t.br.x = Math.min(r.br.x, t.tl.x + 16);
 
-        int tileType = is.readU8();
+        int tileType = is.readU32();
 
-        if ((tileType & Hextile.raw) != 0) {
-          is.readPixels(buf, t.area(), bytesPerPixel, bigEndian);
-          handler.imageRect(t, buf);
+        if ((tileType & hextileRaw) != 0) {
+          is.readBytes(buf, t.area() * (pf.bpp/8));
+          pb.imageRect(pf, t, buf.array());
           continue;
         }
 
-        if ((tileType & Hextile.bgSpecified) != 0)
-          bg = is.readPixel(bytesPerPixel, bigEndian);
+        if ((tileType & hextileBgSpecified) != 0)
+          bg = READ_PIXEL(is, pf);
 
         int len = t.area();
-        int ptr = 0;
-        while (len-- > 0) buf[ptr++] = bg;
+        ByteBuffer ptr = buf.duplicate();
+        while (len-- > 0) ptr.put(bg.array());
 
-        if ((tileType & Hextile.fgSpecified) != 0)
-          fg = is.readPixel(bytesPerPixel, bigEndian);
+        if ((tileType & hextileFgSpecified) != 0)
+          fg = READ_PIXEL(is, pf);
 
-        if ((tileType & Hextile.anySubrects) != 0) {
-          int nSubrects = is.readU8();
+        if ((tileType & hextileAnySubrects) != 0) {
+          int nSubrects = is.readU32();
 
           for (int i = 0; i < nSubrects; i++) {
 
-            if ((tileType & Hextile.subrectsColoured) != 0)
-              fg = is.readPixel(bytesPerPixel, bigEndian);
+            if ((tileType & hextileSubrectsColoured) != 0)
+              fg = READ_PIXEL(is, pf);
 
             int xy = is.readU8();
             int wh = is.readU8();
 
-/*
-            Rect s = new Rect();
-            s.tl.x = t.tl.x + ((xy >> 4) & 15);
-            s.tl.y = t.tl.y + (xy & 15);
-            s.br.x = s.tl.x + ((wh >> 4) & 15) + 1;
-            s.br.y = s.tl.y + (wh & 15) + 1;
-*/
             int x = ((xy >> 4) & 15);
             int y = (xy & 15);
             int w = ((wh >> 4) & 15) + 1;
             int h = (wh & 15) + 1;
-            ptr = y * t.width() + x;
-            int rowAdd = t.width() - w;
+            ptr = buf.duplicate();
+            ptr.position((y * t.width() + x)*pf.bpp/8);
+            int rowAdd = (t.width() - w)*pf.bpp/8;
             while (h-- > 0) {
               len = w;
-              while (len-- > 0) buf[ptr++] = fg;
-              ptr += rowAdd;
+              while (len-- > 0) ptr.put(fg.array());
+              ptr.position(ptr.position()+Math.min(rowAdd,ptr.remaining()));
             }
           }
         }
-        handler.imageRect(t, buf);
+        pb.imageRect(pf, t, buf.array());
       }
     }
   }
-
-  CMsgReader reader;
 }
diff --git a/java/com/tigervnc/rfb/JpegDecompressor.java b/java/com/tigervnc/rfb/JpegDecompressor.java
new file mode 100644 (file)
index 0000000..9137847
--- /dev/null
@@ -0,0 +1,53 @@
+/* Copyright (C) 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+package com.tigervnc.rfb;
+
+import java.awt.image.*;
+import java.io.*;
+import java.nio.ByteBuffer;
+import javax.imageio.*;
+import javax.imageio.stream.*;
+
+public class JpegDecompressor {
+
+  public JpegDecompressor() {}
+
+  public void decompress(ByteBuffer jpegBuf, int jpegBufLen,
+    WritableRaster buf, Rect r, PixelFormat pf)
+  {
+
+    byte[] src = new byte[jpegBufLen];
+
+    jpegBuf.get(src);
+    try {
+      BufferedImage image =
+        ImageIO.read(new MemoryCacheImageInputStream(new ByteArrayInputStream(src)));
+      ColorModel cm = pf.getColorModel();
+      if (cm.isCompatibleRaster(image.getRaster()) &&
+          cm.isCompatibleSampleModel(image.getRaster().getSampleModel())) {
+        buf.setDataElements(0, 0, image.getRaster());
+      } else {
+        ColorConvertOp converter = pf.getColorConvertOp(cm.getColorSpace());
+        converter.filter(image.getRaster(), buf);
+      }
+      image.flush();
+    } catch (IOException e) {
+      throw new Exception(e.getMessage());
+    }
+  }
+}
index f947af71408254db40d63ba7e1be274d06550836..6e14b92eb2c00692ba28da9027171704fd0b267b 100644 (file)
 
 package com.tigervnc.rfb;
 
-public class ManagedPixelBuffer extends PixelBuffer {
-  public void setSize(int w, int h) {
-    width_ = w;
-    height_ = h;
+public class ManagedPixelBuffer extends FullFramePixelBuffer {
+
+  public ManagedPixelBuffer() {
+    datasize = 0;
     checkDataSize();
   }
-  public void setPF(PixelFormat pf) {
-    super.setPF(pf);
+
+  public ManagedPixelBuffer(PixelFormat pf, int w, int h)
+  {
+    super(pf, w, h, null);
+    datasize = 0;
     checkDataSize();
   }
 
-  public int dataLen() { return area(); }
+  public void setPF(PixelFormat pf) {
+    format = pf; checkDataSize();
+  }
+
+  public void setSize(int w, int h) {
+    width_ = w; height_ = h; checkDataSize();
+  }
 
   final void checkDataSize() {
-    if (data == null || data.length < dataLen())
-      data = new int[dataLen()];
+    int new_datasize = width_ * height_;
+    if (datasize < new_datasize) {
+      vlog.debug("reallocating managed buffer ("+width_+"x"+height_+")");
+      if (format != null)
+        data = PixelFormat.getColorModel(format).createCompatibleWritableRaster(width_, height_);
+    }
   }
+
+  protected int datasize;
+  static LogWriter vlog = new LogWriter("ManagedPixelBuffer");
 }
diff --git a/java/com/tigervnc/rfb/ModifiablePixelBuffer.java b/java/com/tigervnc/rfb/ModifiablePixelBuffer.java
new file mode 100644 (file)
index 0000000..bcc559d
--- /dev/null
@@ -0,0 +1,267 @@
+/* Copyright 2016 Brian P. Hinz
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+// -=- Modifiable generic pixel buffer class
+
+package com.tigervnc.rfb;
+
+import java.awt.image.*;
+import java.awt.Color;
+import java.awt.color.ColorSpace;
+import java.lang.*;
+import java.nio.*;
+import java.util.*;
+
+import static java.awt.image.DataBuffer.*;
+
+public abstract class ModifiablePixelBuffer extends PixelBuffer
+{
+
+  public ModifiablePixelBuffer(PixelFormat pf, int w, int h)
+  {
+    super(pf, w, h);
+  }
+
+  protected ModifiablePixelBuffer()
+  {
+  }
+
+  ///////////////////////////////////////////////
+  // Access to pixel data
+  //
+
+  // Get a writeable pointer into the buffer
+  //   Like getBuffer(), the pointer is to the top-left pixel of the
+  //   specified Rect.
+  public abstract WritableRaster getBufferRW(Rect r);
+  // Commit the modified contents
+  //   Ensures that the changes to the specified Rect is properly
+  //   stored away and any temporary buffers are freed. The Rect given
+  //   here needs to match the Rect given to the earlier call to
+  //   getBufferRW().
+  public abstract void commitBufferRW(Rect r);
+
+  static LogWriter vlog = new LogWriter("ModifiablePixelBuffer");
+  ///////////////////////////////////////////////
+  // Basic rendering operations
+  // These operations DO NOT clip to the pixelbuffer area, or trap overruns.
+
+  // Fill a rectangle
+  public synchronized void fillRect(Rect r, byte[] pix)
+  {
+    WritableRaster buf;
+    int w, h;
+
+    w = r.width();
+    h = r.height();
+
+    if (h == 0 || w ==0)
+      return;
+
+    buf = getBufferRW(r);
+
+    ByteBuffer src =
+      ByteBuffer.allocate(r.area()*format.bpp/8).order(format.getByteOrder());
+    for (int i=0; i < r.area(); i++)
+      src.put(pix);
+    Raster raster = format.rasterFromBuffer(r, (ByteBuffer)src.rewind());
+    buf.setDataElements(0, 0, raster);
+
+    commitBufferRW(r);
+  }
+
+  // Copy pixel data to the buffer
+  public synchronized void imageRect(Rect r, byte[] pixels)
+  {
+    WritableRaster dest = getBufferRW(r);
+
+    ByteBuffer src = ByteBuffer.wrap(pixels).order(format.getByteOrder());
+    Raster raster = format.rasterFromBuffer(r, src);
+    dest.setDataElements(0, 0, raster);
+
+    commitBufferRW(r);
+  }
+
+  // Copy pixel data from one PixelBuffer location to another
+  public synchronized void copyRect(Rect rect,
+                                    Point move_by_delta)
+  {
+    Raster srcData;
+    WritableRaster dstData;
+
+    Rect drect, srect;
+
+    drect = new Rect(rect.tl, rect.br);
+    if (!drect.enclosed_by(getRect())) {
+      String msg = "Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d";
+      vlog.error(String.format(msg, drect.width(), drect.height(),
+                               drect.tl.x, drect.tl.y, width_, height_));
+      drect = drect.intersect(getRect());
+    }
+
+    if (drect.is_empty())
+      return;
+
+    srect = drect.translate(move_by_delta.negate());
+    if (!srect.enclosed_by(getRect())) {
+      String msg = "Source rect %dx%d at %d,%d exceeds framebuffer %dx%d";
+      vlog.error(String.format(msg, srect.width(), srect.height(),
+                               srect.tl.x, srect.tl.y, width_, height_));
+      srect = srect.intersect(getRect());
+      // Need to readjust the destination now that the area has changed
+      drect = srect.translate(move_by_delta);
+    }
+
+    if (srect.is_empty())
+      return;
+
+    srcData = getBuffer(srect);
+    dstData = getBufferRW(drect);
+
+    dstData.setDataElements(0, 0, srcData);
+
+    commitBufferRW(rect);
+  }
+
+  // Copy pixel data to the buffer through a mask
+  //   pixels is a pointer to the pixel to be copied to r.tl.
+  //   maskPos specifies the pixel offset in the mask to start from.
+  //   mask_ is a pointer to the mask bits at (0,0).
+  //   pStride and mStride are the strides of the pixel and mask buffers.
+  public synchronized void maskRect(Rect r,
+                                    Object pixels, byte[] mask_)
+  {
+    Rect cr = getRect().intersect(r);
+    if (cr.is_empty()) return;
+    WritableRaster data = getBufferRW(cr);
+
+    // FIXME
+    ColorModel cm = format.getColorModel();
+    SampleModel sm =
+      cm.createCompatibleSampleModel(r.width(), r.height());
+    DataBuffer db = null;
+    ByteBuffer src =
+      ByteBuffer.wrap((byte[])pixels).order(format.getByteOrder());
+    Buffer dst;
+    switch (sm.getTransferType()) {
+    case TYPE_INT:
+      dst = IntBuffer.allocate(src.remaining()).put(src.asIntBuffer());
+      db = new DataBufferInt(((IntBuffer)dst).array(), r.area());
+      break;
+    case TYPE_BYTE:
+      db = new DataBufferByte(src.array(), r.area());
+      break;
+    case TYPE_SHORT:
+      dst = ShortBuffer.allocate(src.remaining()).put(src.asShortBuffer());
+      db = new DataBufferShort(((ShortBuffer)dst).array(), r.area());
+      break;
+    }
+    assert(db != null);
+    Raster raster =
+      Raster.createRaster(sm, db, new java.awt.Point(0, 0));
+    ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+    WritableRaster t = data.createCompatibleWritableRaster();
+    converter.filter(raster, t);
+
+    int w = cr.width();
+    int h = cr.height();
+
+    Point offset = new Point(cr.tl.x-r.tl.x, cr.tl.y-r.tl.y);
+
+    int maskBytesPerRow = (w + 7) / 8;
+
+    for (int y = 0; y < h; y++) {
+      int cy = offset.y + y;
+      for (int x = 0; x < w; x++) {
+        int cx = offset.x + x;
+        int byte_ = cy * maskBytesPerRow + y / 8;
+        int bit = 7 - cx % 8;
+
+        if ((mask_[byte_] & (1 << bit)) != 0)
+          data.setDataElements(x+cx, y+cy, t.getDataElements(x+cx, y+cy, null));
+      }
+    }
+
+    commitBufferRW(r);
+  }
+
+  //   pixel is the Pixel value to be used where mask_ is set
+  public synchronized void maskRect(Rect r, int pixel, byte[] mask)
+  {
+    // FIXME
+  }
+
+  // Render in a specific format
+  //   Does the exact same thing as the above methods, but the given
+  //   pixel values are defined by the given PixelFormat. 
+  public synchronized void fillRect(PixelFormat pf, Rect dest, byte[] pix)
+  {
+    WritableRaster dstBuffer = getBufferRW(dest);
+
+    ColorModel cm = pf.getColorModel();
+    if (cm.isCompatibleRaster(dstBuffer) &&
+        cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) {
+      fillRect(dest, pix);
+    } else {
+      ByteBuffer src =
+        ByteBuffer.allocate(dest.area()*pf.bpp/8).order(pf.getByteOrder());
+      for (int i=0; i < dest.area(); i++)
+        src.put(pix);
+      Raster raster = pf.rasterFromBuffer(dest, (ByteBuffer)src.rewind());
+      ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+      converter.filter(raster, dstBuffer);
+    }
+
+    commitBufferRW(dest);
+  }
+
+  public synchronized void imageRect(PixelFormat pf, Rect dest, byte[] pixels)
+  {
+    WritableRaster dstBuffer = getBufferRW(dest);
+
+    ColorModel cm = pf.getColorModel();
+    if (cm.isCompatibleRaster(dstBuffer) &&
+        cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) {
+      imageRect(dest, pixels);
+    } else {
+      ByteBuffer src = ByteBuffer.wrap(pixels).order(pf.getByteOrder());
+      Raster raster = pf.rasterFromBuffer(dest, src);
+      ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+      converter.filter(raster, dstBuffer);
+    }
+
+    commitBufferRW(dest);
+  }
+
+  public synchronized void imageRect(PixelFormat pf, Rect dest, Raster pixels)
+  {
+    WritableRaster dstBuffer = getBufferRW(dest);
+
+    ColorModel cm = pf.getColorModel();
+    if (cm.isCompatibleRaster(dstBuffer) &&
+        cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) {
+      dstBuffer.setDataElements(0, 0, pixels);
+    } else {
+      ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace());
+      converter.filter(pixels, dstBuffer);
+    }
+
+    commitBufferRW(dest);
+  }
+
+}
index a46667d38eb50315cbf1d253fc7e1b520cf1fc7c..1b7d2c1a3751996d6cb11da5707df863582b580d 100644 (file)
@@ -1,4 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2016 Brian P. Hinz
  *
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * USA.
  */
 
-//
-// PixelBuffer - note that this code is only written for the 8, 16, and 32 bpp cases at the
-// moment.
-//
+// -=- Generic pixel buffer class
 
 package com.tigervnc.rfb;
 
 import java.awt.image.*;
+import java.awt.Color;
+import java.nio.*;
+import java.util.concurrent.atomic.*;
 
-public class PixelBuffer {
+public abstract class PixelBuffer {
 
-  public PixelBuffer() {
-    setPF(new PixelFormat());
-  }
-
-  public void setPF(PixelFormat pf) {
-    if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8))
-      throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")");
+  public PixelBuffer(PixelFormat pf, int w, int h) {
     format = pf;
-    switch (pf.depth) {
-    case  3:
-      // Fall-through to depth 8
-    case  6:
-      // Fall-through to depth 8
-    case  8:
-      if (!pf.trueColour) {
-        if (cm == null)
-          cm = new IndexColorModel(8, 256, new byte[256], new byte[256], new byte[256]);
-        break;
-      }
-      int rmask = pf.redMax << pf.redShift;
-      int gmask = pf.greenMax << pf.greenShift;
-      int bmask = pf.blueMax << pf.blueShift;
-      cm = new DirectColorModel(8, rmask, gmask, bmask);
-      break;
-    case 16:
-      cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E);
-      break;
-    case 24:
-      cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff);
-      break;
-    case 32:
-      cm = new DirectColorModel(32, (0xff << pf.redShift),
-        (0xff << pf.greenShift), (0xff << pf.blueShift));
-      break;
-    default:
-      throw new Exception("Unsupported color depth ("+pf.depth+")");
-    }
+    width_ = w;
+    height_= h;
   }
-  public PixelFormat getPF() { return format; }
 
+  protected PixelBuffer() { width_ = 0; height_ = 0; }
+
+  // Get pixel format
+  public final PixelFormat getPF() { return format; }
+
+  // Get width, height and number of pixels
   public final int width() { return width_; }
   public final int height() { return height_; }
   public final int area() { return width_ * height_; }
 
-  public void fillRect(int x, int y, int w, int h, int pix) {
-    for (int ry = y; ry < y + h; ry++)
-      for (int rx = x; rx < x + w; rx++)
-        data[ry * width_ + rx] = pix;
-  }
-
-  public void imageRect(int x, int y, int w, int h, int[] pix) {
-    for (int j = 0; j < h; j++)
-      System.arraycopy(pix, (w * j), data, width_ * (y + j) + x, w);
-  }
-
-  public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
-    int dest = (width_ * y) + x;
-    int src = (width_ * srcY) + srcX;
-    int inc = width_;
-
-    if (y > srcY) {
-      src += (h-1) * inc;
-      dest += (h-1) * inc;
-      inc = -inc;
-    }
-    int destEnd = dest + h * inc;
-
-    while (dest != destEnd) {
-      System.arraycopy(data, src, data, dest, w);
-      src += inc;
-      dest += inc;
-    }
-  }
-
-  public void maskRect(int x, int y, int w, int h, int[] pix, byte[] mask) {
-    int maskBytesPerRow = (w + 7) / 8;
-
-    for (int j = 0; j < h; j++) {
-      int cy = y + j;
-
-      if (cy < 0 || cy >= height_)
-        continue;
-
-      for (int i = 0; i < w; i++) {
-        int cx = x + i;
-
-        if (cx < 0 || cx >= width_)
-          continue;
-
-        int byte_ = j * maskBytesPerRow + i / 8;
-        int bit = 7 - i % 8;
-
-        if ((mask[byte_] & (1 << bit)) != 0)
-         data[cy * width_ + cx] = pix[j * w + i];
-      }
-    }
+  // Get rectangle encompassing this buffer
+  //   Top-left of rectangle is either at (0,0), or the specified point.
+  public final Rect getRect() { return new Rect(0, 0, width_, height_); }
+  public final Rect getRect(Point pos) {
+    return new Rect(pos, pos.translate(new Point(width_, height_)));
   }
 
-  public int[] data;
-  public ColorModel cm;
+  ///////////////////////////////////////////////
+  // Access to pixel data
+  //
+
+  // Get a pointer into the buffer
+  //   The pointer is to the top-left pixel of the specified Rect.
+  public abstract Raster getBuffer(Rect r);
+
+  // Get pixel data for a given part of the buffer
+  //   Data is copied into the supplied buffer, with the specified
+  //   stride. Try to avoid using this though as getBuffer() will in
+  //   most cases avoid the extra memory copy.
+  //void getImage(void* imageBuf, const Rect& r, int stride=0) const;
+  // Get pixel data in a given format
+  //   Works just the same as getImage(), but guaranteed to be in a
+  //   specific format.
+  //void getImage(const PixelFormat& pf, void* imageBuf,
+  //                const Rect& r, int stride=0) const;
+
+  ///////////////////////////////////////////////
+  // Framebuffer update methods
+  //
+
+  // Ensure that the specified rectangle of buffer is up to date.
+  //   Overridden by derived classes implementing framebuffer access
+  //   to copy the required display data into place.
+  //public abstract void grabRegion(Region& region) {}
 
   protected PixelFormat format;
   protected int width_, height_;
index c4d687018028363d37f90b049608975f10612428..9a2699928ebefb672619e54496681c2cbada7a0e 100644 (file)
 
 package com.tigervnc.rfb;
 
+import java.awt.color.*;
+import java.awt.image.*;
+import java.nio.*;
+import java.util.*;
+
 import com.tigervnc.rdr.*;
-import java.awt.image.ColorModel;
 
 public class PixelFormat {
 
-  public PixelFormat(int b, int d, boolean e, boolean t) {
-    bpp = b;
-    depth = d;
-    bigEndian = e;
-    trueColour = t;
-  }
   public PixelFormat(int b, int d, boolean e, boolean t,
-                     int rm, int gm, int bm, int rs, int gs, int bs) {
-    this(b, d, e, t);
-    redMax = rm;
-    greenMax = gm;
-    blueMax = bm;
-    redShift = rs;
-    greenShift = gs;
-    blueShift = bs;
+                     int rm, int gm, int bm, int rs, int gs, int bs)
+  {
+    bpp = b; depth = d; trueColour = t; bigEndian = e;
+    redMax = rm; greenMax = gm; blueMax = bm;
+    redShift = rs; greenShift = gs; blueShift = bs;
+    converters = new HashMap<Integer, ColorConvertOp>();
+    assert(isSane());
+
+    updateState();
+  }
+
+  public PixelFormat()
+  {
+    this(8, 8, false, true, 7, 7, 3, 0, 3, 6);
+    updateState();
   }
-  public PixelFormat() { this(8,8,false,true,7,7,3,0,3,6); }
-
-  public boolean equal(PixelFormat x) {
-    return (bpp == x.bpp &&
-            depth == x.depth &&
-            (bigEndian == x.bigEndian || bpp == 8) &&
-            trueColour == x.trueColour &&
-            (!trueColour || (redMax == x.redMax &&
-                             greenMax == x.greenMax &&
-                             blueMax == x.blueMax &&
-                             redShift == x.redShift &&
-                             greenShift == x.greenShift &&
-                             blueShift == x.blueShift)));
+
+  public boolean equal(PixelFormat other)
+  {
+    if (bpp != other.bpp || depth != other.depth)
+      return false;
+
+    if (redMax != other.redMax)
+      return false;
+    if (greenMax != other.greenMax)
+      return false;
+    if (blueMax != other.blueMax)
+      return false;
+
+    // Endianness requires more care to determine compatibility
+    if (bigEndian == other.bigEndian || bpp == 8) {
+      if (redShift != other.redShift)
+        return false;
+      if (greenShift != other.greenShift)
+        return false;
+      if (blueShift != other.blueShift)
+        return false;
+    } else {
+      // Has to be the same byte for each channel
+      if (redShift/8 != (3 - other.redShift/8))
+        return false;
+      if (greenShift/8 != (3 - other.greenShift/8))
+        return false;
+      if (blueShift/8 != (3 - other.blueShift/8))
+        return false;
+
+      // And the same bit offset within the byte
+      if (redShift%8 != other.redShift%8)
+        return false;
+      if (greenShift%8 != other.greenShift%8)
+        return false;
+      if (blueShift%8 != other.blueShift%8)
+        return false;
+
+      // And not cross a byte boundary
+      if (redShift/8 != (redShift + redBits - 1)/8)
+        return false;
+      if (greenShift/8 != (greenShift + greenBits - 1)/8)
+        return false;
+      if (blueShift/8 != (blueShift + blueBits - 1)/8)
+        return false;
+    }
+
+    return true;
   }
 
   public void read(InStream is) {
@@ -73,6 +113,23 @@ public class PixelFormat {
     greenShift = is.readU8();
     blueShift = is.readU8();
     is.skip(3);
+
+    // We have no real support for colour maps. If the client
+    // wants one, then we force a 8-bit true colour format and
+    // pretend it's a colour map.
+    if (!trueColour) {
+      redMax = 7;
+      greenMax = 7;
+      blueMax = 3;
+      redShift = 0;
+      greenShift = 3;
+      blueShift = 6;
+    }
+
+    if (!isSane())
+      throw new Exception("invalid pixel format: "+print());
+
+    updateState();
   }
 
   public void write(OutStream os) {
@@ -89,6 +146,14 @@ public class PixelFormat {
     os.pad(3);
   }
 
+  public final boolean isBigEndian() {
+    return bigEndian;
+  }
+
+  public final boolean isLittleEndian() {
+    return ! bigEndian;
+  }
+
   public final boolean is888() {
     if(!trueColour)
       return false;
@@ -139,53 +204,140 @@ public class PixelFormat {
     return 0;
   }
 
-  public void bufferFromRGB(int[] dst, int dstPtr, byte[] src,
-                            int srcPtr, int pixels) {
+  public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, int pixels)
+  {
+    bufferFromRGB(dst, src, pixels, pixels, 1);
+  }
+
+  public void bufferFromRGB(ByteBuffer dst, ByteBuffer src,
+                            int w, int stride, int h)
+  {
     if (is888()) {
       // Optimised common case
-      int r, g, b;
+      int r, g, b, x;
+
+      if (bigEndian) {
+        r = dst.position() + (24 - redShift)/8;
+        g = dst.position() + (24 - greenShift)/8;
+        b = dst.position() + (24 - blueShift)/8;
+        x = dst.position() + (24 - (48 - redShift - greenShift - blueShift))/8;
+      } else {
+        r = dst.position() + redShift/8;
+        g = dst.position() + greenShift/8;
+        b = dst.position() + blueShift/8;
+        x = dst.position() + (48 - redShift - greenShift - blueShift)/8;
+      }
 
-      for (int i=srcPtr; i < pixels; i++) {
-        if (bigEndian) {
-          r = (src[3*i+0] & 0xff) << (24 - redShift);
-          g = (src[3*i+1] & 0xff) << (24 - greenShift);
-          b = (src[3*i+2] & 0xff) << (24 - blueShift);
-          dst[dstPtr+i] = r | g | b | 0xff;
-        } else {
-          r = (src[3*i+0] & 0xff) << redShift;
-          g = (src[3*i+1] & 0xff) << greenShift;
-          b = (src[3*i+2] & 0xff) << blueShift;
-          dst[dstPtr+i] = (0xff << 24) | r | g | b;
+      int dstPad = (stride - w) * 4;
+      while (h-- > 0) {
+        int w_ = w;
+        while (w_-- > 0) {
+          dst.put(r, src.get());
+          dst.put(g, src.get());
+          dst.put(b, src.get());
+          dst.put(x, (byte)0);
+          r += 4;
+          g += 4;
+          b += 4;
+          x += 4;
         }
+        r += dstPad;
+        g += dstPad;
+        b += dstPad;
+        x += dstPad;
       }
     } else {
       // Generic code
-      int p, r, g, b;
-      int[] rgb = new int[4];
+      int dstPad = (stride - w) * bpp/8;
+      while (h-- > 0) {
+        int w_ = w;
+        while (w_-- > 0) {
+          int p;
+          int r, g, b;
 
-      int i = srcPtr; int j = dstPtr;
-      while (i < pixels) {
-        r = src[i++] & 0xff;
-        g = src[i++] & 0xff;
-        b = src[i++] & 0xff;
+          r = src.get();
+          g = src.get();
+          b = src.get();
 
-        //p = pixelFromRGB(r, g, b, cm);
-        p = ColorModel.getRGBdefault().getDataElement(new int[] {0xff, r, g, b}, 0);
+          p = pixelFromRGB(r, g, b, model);
 
-        bufferFromPixel(dst, j, p);
-        j += bpp/8;
+          bufferFromPixel(dst, p);
+          dst.position(dst.position() + bpp/8);
+        }
+        dst.position(dst.position() + dstPad);
       }
     }
   }
 
-  public void rgbFromBuffer(byte[] dst, int dstPtr, byte[] src, int srcPtr, int pixels, ColorModel cm)
+  public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, int pixels)
+  {
+    rgbFromBuffer(dst, src, pixels, pixels, 1);
+  }
+
+  public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src,
+                            int w, int stride, int h)
+  {
+    if (is888()) {
+      // Optimised common case
+      int r, g, b;
+
+      if (bigEndian) {
+        r = src.position() + (24 - redShift)/8;
+        g = src.position() + (24 - greenShift)/8;
+        b = src.position() + (24 - blueShift)/8;
+      } else {
+        r = src.position() + redShift/8;
+        g = src.position() + greenShift/8;
+        b = src.position() + blueShift/8;
+      }
+
+      int srcPad = (stride - w) * 4;
+      while (h-- > 0) {
+        int w_ = w;
+        while (w_-- > 0) {
+          dst.put(src.get(r));
+          dst.put(src.get(g));
+          dst.put(src.get(b));
+          r += 4;
+          g += 4;
+          b += 4;
+        }
+        r += srcPad;
+        g += srcPad;
+        b += srcPad;
+      }
+    } else {
+      // Generic code
+      int srcPad = (stride - w) * bpp/8;
+      while (h-- > 0) {
+        int w_ = w;
+        while (w_-- > 0) {
+          int p;
+          byte r, g, b;
+
+          p = pixelFromBuffer(src.duplicate());
+
+          r = (byte)getColorModel().getRed(p);
+          g = (byte)getColorModel().getGreen(p);
+          b = (byte)getColorModel().getBlue(p);
+
+          dst.put(r);
+          dst.put(g);
+          dst.put(b);
+          src.position(src.position() + bpp/8);
+        }
+        src.reset().position(src.position() + srcPad).mark();
+      }
+    }
+  }
+
+  public void rgbFromPixels(byte[] dst, int dstPtr, int[] src, int srcPtr, int pixels, ColorModel cm)
   {
     int p;
     byte r, g, b;
 
     for (int i=0; i < pixels; i++) {
-      p = pixelFromBuffer(src, srcPtr);
-      srcPtr += bpp/8;
+      p = src[i];
 
       dst[dstPtr++] = (byte)cm.getRed(p);
       dst[dstPtr++] = (byte)cm.getGreen(p);
@@ -193,31 +345,29 @@ public class PixelFormat {
     }
   }
 
-  public int pixelFromBuffer(byte[] buffer, int bufferPtr)
+  public int pixelFromBuffer(ByteBuffer buffer)
   {
     int p;
 
-    p = 0;
+    p = 0xff000000;
 
-    if (bigEndian) {
+    if (!bigEndian) {
       switch (bpp) {
       case 32:
-        p = (buffer[0] & 0xff) << 24 | (buffer[1] & 0xff) << 16 | (buffer[2] & 0xff) << 8 | 0xff;
-        break;
+        p |= buffer.get() << 24;
+        p |= buffer.get() << 16;
       case 16:
-        p = (buffer[0] & 0xff) << 8 | (buffer[1] & 0xff);
-        break;
+        p |= buffer.get() << 8;
       case 8:
-        p = (buffer[0] & 0xff);
-        break;
+        p |= buffer.get();
       }
     } else {
-      p = (buffer[0] & 0xff);
+      p |= buffer.get(0);
       if (bpp >= 16) {
-        p |= (buffer[1] & 0xff) << 8;
+        p |= buffer.get(1) << 8;
         if (bpp == 32) {
-          p |= (buffer[2] & 0xff) << 16;
-          p |= (buffer[3] & 0xff) << 24;
+          p |= buffer.get(2) << 16;
+          p |= buffer.get(3) << 24;
         }
       }
     }
@@ -263,33 +413,212 @@ public class PixelFormat {
     return s.toString();
   }
 
-  public void bufferFromPixel(int[] buffer, int bufPtr, int p)
+  private static int bits(int value)
+  {
+    int bits;
+
+    bits = 16;
+
+    if ((value & 0xff00) == 0) {
+      bits -= 8;
+      value <<= 8;
+    }
+    if ((value & 0xf000) == 0) {
+      bits -= 4;
+      value <<= 4;
+    }
+    if ((value & 0xc000) == 0) {
+      bits -= 2;
+      value <<= 2;
+    }
+    if ((value & 0x8000) == 0) {
+      bits -= 1;
+      value <<= 1;
+    }
+
+    return bits;
+  }
+
+  private void updateState()
+  {
+    int endianTest = 1;
+
+    redBits = bits(redMax);
+    greenBits = bits(greenMax);
+    blueBits = bits(blueMax);
+
+    maxBits = redBits;
+    if (greenBits > maxBits)
+      maxBits = greenBits;
+    if (blueBits > maxBits)
+      maxBits = blueBits;
+
+    minBits = redBits;
+    if (greenBits < minBits)
+      minBits = greenBits;
+    if (blueBits < minBits)
+      minBits = blueBits;
+
+    if ((((char)endianTest) == 0) != bigEndian)
+      endianMismatch = true;
+    else
+      endianMismatch = false;
+
+    model = getColorModel(this);
+  }
+
+  private boolean isSane()
+  {
+    int totalBits;
+
+    if ((bpp != 8) && (bpp != 16) && (bpp != 32))
+      return false;
+    if (depth > bpp)
+      return false;
+
+    if (!trueColour && (depth != 8))
+      return false;
+
+    if ((redMax & (redMax + 1)) != 0)
+      return false;
+    if ((greenMax & (greenMax + 1)) != 0)
+      return false;
+    if ((blueMax & (blueMax + 1)) != 0)
+      return false;
+
+    /*
+     * We don't allow individual channels > 8 bits in order to keep our
+     * conversions simple.
+     */
+    if (redMax >= (1 << 8))
+      return false;
+    if (greenMax >= (1 << 8))
+      return false;
+    if (blueMax >= (1 << 8))
+      return false;
+
+    totalBits = bits(redMax) + bits(greenMax) + bits(blueMax);
+    if (totalBits > bpp)
+      return false;
+
+    if (((redMax << redShift) & (greenMax << greenShift)) != 0)
+      return false;
+    if (((redMax << redShift) & (blueMax << blueShift)) != 0)
+      return false;
+    if (((greenMax << greenShift) & (blueMax << blueShift)) != 0)
+      return false;
+
+    return true;
+  }
+
+  public void bufferFromPixel(ByteBuffer buffer, int p)
   {
     if (bigEndian) {
       switch (bpp) {
         case 32:
-          buffer[bufPtr++] = (p >> 24) & 0xff;
-          buffer[bufPtr++] = (p >> 16) & 0xff;
+          buffer.put((byte)((p >> 24) & 0xff));
+          buffer.put((byte)((p >> 16) & 0xff));
           break;
         case 16:
-          buffer[bufPtr++] = (p >> 8) & 0xff;
+          buffer.put((byte)((p >> 8) & 0xff));
           break;
         case 8:
-          buffer[bufPtr++] = (p >> 0) & 0xff;
+          buffer.put((byte)((p >> 0) & 0xff));
           break;
       }
     } else {
-      buffer[0] = (p >> 0) & 0xff;
+      buffer.put(0, (byte)((p >> 0) & 0xff));
       if (bpp >= 16) {
-        buffer[1] = (p >> 8) & 0xff;
+        buffer.put(1, (byte)((p >> 8) & 0xff));
         if (bpp == 32) {
-          buffer[2] = (p >> 16) & 0xff;
-          buffer[3] = (p >> 24) & 0xff;
+          buffer.put(2, (byte)((p >> 16) & 0xff));
+          buffer.put(3, (byte)((p >> 24) & 0xff));
         }
       }
     }
   }
 
+  public ColorModel getColorModel()
+  {
+    return model;
+  }
+
+  public static ColorModel getColorModel(PixelFormat pf) {
+    if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8))
+      throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")");
+    ColorModel cm;
+    switch (pf.depth) {
+    case  3:
+      // Fall-through to depth 8
+    case  6:
+      // Fall-through to depth 8
+    case  8:
+      int rmask = pf.redMax << pf.redShift;
+      int gmask = pf.greenMax << pf.greenShift;
+      int bmask = pf.blueMax << pf.blueShift;
+      cm = new DirectColorModel(8, rmask, gmask, bmask);
+      break;
+    case 16:
+      cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E);
+      break;
+    case 24:
+      cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff);
+      break;
+    case 32:
+      cm = new DirectColorModel(32, (0xff << pf.redShift),
+        (0xff << pf.greenShift), (0xff << pf.blueShift));
+      break;
+    default:
+      throw new Exception("Unsupported color depth ("+pf.depth+")");
+    }
+    assert(cm != null);
+    return cm;
+  }
+
+  public ColorConvertOp getColorConvertOp(ColorSpace src)
+  {
+    // The overhead associated with initializing ColorConvertOps is
+    // enough to justify maintaining a static lookup table.
+    if (converters.containsKey(src.getType()))
+      return converters.get(src.getType());
+    ColorSpace dst = model.getColorSpace();
+    converters.put(src.getType(), new ColorConvertOp(src, dst, null));
+    return converters.get(src.getType());
+  }
+
+  public ByteOrder getByteOrder()
+  {
+    if (isBigEndian())
+      return ByteOrder.BIG_ENDIAN;
+    else
+      return ByteOrder.LITTLE_ENDIAN;
+  }
+
+  public Raster rasterFromBuffer(Rect r, ByteBuffer buf)
+  {
+    Buffer dst;
+    DataBuffer db = null;
+
+    SampleModel sm =
+      model.createCompatibleSampleModel(r.width(), r.height());
+    switch (sm.getTransferType()) {
+    case DataBuffer.TYPE_INT:
+      dst = IntBuffer.allocate(r.area()).put(buf.asIntBuffer());
+      db = new DataBufferInt(((IntBuffer)dst).array(), r.area());
+      break;
+    case DataBuffer.TYPE_BYTE:
+      db = new DataBufferByte(buf.array(), r.area());
+      break;
+    case DataBuffer.TYPE_SHORT:
+      dst = ShortBuffer.allocate(r.area()).put(buf.asShortBuffer());
+      db = new DataBufferShort(((ShortBuffer)dst).array(), r.area());
+      break;
+    }
+    assert(db != null);
+    return Raster.createRaster(sm, db, new java.awt.Point(0, 0));
+  }
+
+  private static HashMap<Integer, ColorConvertOp> converters;
 
   public int bpp;
   public int depth;
@@ -301,4 +630,10 @@ public class PixelFormat {
   public int redShift;
   public int greenShift;
   public int blueShift;
+
+  protected int redBits, greenBits, blueBits;
+  protected int maxBits, minBits;
+  protected boolean endianMismatch;
+
+  private ColorModel model;
 }
index 487aa3d034f3bfdaf851ee351d288c4eeef1c796..c73c7a948937451b60b77af39e1cb056303db341 100644 (file)
@@ -1,4 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2016 Brian P. Hinz
  *
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package com.tigervnc.rfb;
 
+import java.nio.*;
+
 import com.tigervnc.rdr.*;
 
 public class RREDecoder extends Decoder {
 
-  public RREDecoder(CMsgReader reader_) { reader = reader_; }
+  public RREDecoder() { super(DecoderFlags.DecoderPlain); }
+
+  public void readRect(Rect r, InStream is,
+                       ConnParams cp, OutStream os)
+  {
+    int numRects;
+
+    numRects = is.readU32();
+    os.writeU32(numRects);
+
+    os.copyBytes(is, cp.pf().bpp/8 + numRects * (cp.pf().bpp/8 + 8));
+  }
 
-  public void readRect(Rect r, CMsgHandler handler) {
-    InStream is = reader.getInStream();
-    int bytesPerPixel = handler.cp.pf().bpp / 8;
-    boolean bigEndian = handler.cp.pf().bigEndian;
+  public void decodeRect(Rect r, Object buffer,
+                         int buflen, ConnParams cp,
+                         ModifiablePixelBuffer pb)
+  {
+    MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+    PixelFormat pf = cp.pf();
+    switch (pf.bpp) {
+    case 8:  rreDecode8 (r, is, pf, pb); break;
+    case 16: rreDecode16(r, is, pf, pb); break;
+    case 32: rreDecode32(r, is, pf, pb); break;
+    }
+  }
+
+  private static ByteBuffer READ_PIXEL(InStream is, PixelFormat pf) {
+    ByteBuffer b = ByteBuffer.allocate(4);
+    switch (pf.bpp) {
+    case 8:
+      b.putInt(is.readOpaque8());
+      return ByteBuffer.allocate(1).put(b.get(3));
+    case 16:
+      b.putInt(is.readOpaque16());
+      return ByteBuffer.allocate(2).put(b.array(), 2, 2);
+    case 32:
+    default:
+      b.putInt(is.readOpaque32());
+      return b;
+    }
+  }
+
+  private void RRE_DECODE(Rect r, InStream is,
+                          PixelFormat pf, ModifiablePixelBuffer pb)
+  {
     int nSubrects = is.readU32();
-    int bg = is.readPixel(bytesPerPixel, bigEndian);
-    handler.fillRect(r, bg);
+    byte[] bg = READ_PIXEL(is, pf).array();
+    pb.fillRect(pf, r, bg);
 
     for (int i = 0; i < nSubrects; i++) {
-      int pix = is.readPixel(bytesPerPixel, bigEndian);
+      byte[] pix = READ_PIXEL(is, pf).array();
       int x = is.readU16();
       int y = is.readU16();
       int w = is.readU16();
       int h = is.readU16();
-      handler.fillRect(new Rect(r.tl.x + x, r.tl.y + y,
-                                r.tl.x + x + w, r.tl.y + y + h), pix);
+      pb.fillRect(pf, new Rect(r.tl.x+x, r.tl.y+y, r.tl.x+x+w, r.tl.y+y+h), pix);
     }
   }
 
-  CMsgReader reader;
+  private void rreDecode8(Rect r, InStream is,
+                          PixelFormat pf,
+                          ModifiablePixelBuffer pb)
+  {
+    RRE_DECODE(r, is, pf, pb);
+  }
+
+  private void rreDecode16(Rect r, InStream is,
+                           PixelFormat pf,
+                           ModifiablePixelBuffer pb)
+  {
+    RRE_DECODE(r, is, pf, pb);
+  }
+
+  private void rreDecode32(Rect r, InStream is,
+                           PixelFormat pf,
+                           ModifiablePixelBuffer pb)
+  {
+    RRE_DECODE(r, is, pf, pb);
+  }
+
 }
index b2219a242c493c83502081f171a7a57f61cd592d..71b79607917a712773ee0336ef988dea4f1044eb 100644 (file)
 
 package com.tigervnc.rfb;
 
+import com.tigervnc.rdr.*;
+
 public class RawDecoder extends Decoder {
 
-  public RawDecoder(CMsgReader reader_) { reader = reader_; }
+  public RawDecoder() { super(DecoderFlags.DecoderPlain); }
 
-  public void readRect(Rect r, CMsgHandler handler) {
-    int x = r.tl.x;
-    int y = r.tl.y;
-    int w = r.width();
-    int h = r.height();
-    int[] imageBuf = new int[w*h];
-    int nPixels = imageBuf.length;
-    int bytesPerRow = w * (reader.bpp() / 8);
-    while (h > 0) {
-      int nRows = nPixels / w;
-      if (nRows > h) nRows = h;
-      reader.getInStream().readPixels(imageBuf, nPixels, (reader.bpp() / 8), handler.cp.pf().bigEndian);
-      handler.imageRect(new Rect(x, y, x+w, y+nRows), imageBuf);
-      h -= nRows;
-      y += nRows;
-    }
+  public void readRect(Rect r, InStream is,
+                       ConnParams cp, OutStream os)
+  {
+    os.copyBytes(is, r.area() * cp.pf().bpp/8);
   }
 
-  CMsgReader reader;
   static LogWriter vlog = new LogWriter("RawDecoder");
+  public void decodeRect(Rect r, Object buffer,
+                         int buflen, ConnParams cp,
+                         ModifiablePixelBuffer pb)
+  {
+    assert(buflen >= r.area() * cp.pf().bpp/8);
+    pb.imageRect(cp.pf(), r, (byte[])buffer);
+  }
+
 }
diff --git a/java/com/tigervnc/rfb/Region.java b/java/com/tigervnc/rfb/Region.java
new file mode 100644 (file)
index 0000000..f7da91d
--- /dev/null
@@ -0,0 +1,102 @@
+/* Copyright (C) 2016 Brian P. Hinz.  All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.rfb;
+
+import java.awt.*;
+import java.awt.geom.*;
+
+public class Region extends Area {
+
+  // Create an empty region
+  public Region() {
+    super();
+  }
+
+  // Create a rectangular region
+  public Region(Rect r) {
+    super(new Rectangle(r.tl.x, r.tl.y, r.width(), r.height()));
+  }
+
+  public Region(Region r) {
+    super(r);
+    //intersect(r);
+  }
+
+  public void clear() { reset(); }
+
+  public void reset(Rect r) {
+    if (r.is_empty()) {
+      clear();
+    } else {
+      clear();
+      assign_union(new Region(r));
+      /*
+      xrgn.numRects = 1;
+      xrgn.rects[0].x1 = xrgn.extents.x1 = r.tl.x;
+      xrgn.rects[0].y1 = xrgn.extents.y1 = r.tl.y;
+      xrgn.rects[0].x2 = xrgn.extents.x2 = r.br.x;
+      xrgn.rects[0].y2 = xrgn.extents.y2 = r.br.y;
+      */
+    }
+  }
+
+  public void translate(Point delta) {
+    AffineTransform t = 
+      AffineTransform.getTranslateInstance((double)delta.x, (double)delta.y);
+    transform(t);
+  }
+
+  public void assign_intersect(Region r) {
+    intersect(r);
+  }
+
+  public void assign_union(Region r) {
+    add(r);
+  }
+
+  public void assign_subtract(Region r) {
+    subtract(r);
+  }
+
+  public Region intersect(Region r) {
+    Region ret = new Region(this);
+    ((Area)ret).intersect(this);
+    return ret;
+  }
+
+  public Region union(Region r) {
+    Region ret = new Region(r);
+    ((Area)ret).add(this);
+    return ret;
+  }
+
+  public Region subtract(Region r) {
+    Region ret = new Region(this);
+    ((Area)ret).subtract(r);
+    return ret;
+  }
+
+  public boolean is_empty() { return isEmpty(); }
+
+  public Rect get_bounding_rect() {
+    Rectangle b = getBounds();
+    return new Rect((int)b.getX(), (int)b.getY(),
+                    (int)b.getWidth(), (int)b.getHeight());
+  }
+}
index b644cdb480ca06b68dcf4cb709134b80e9515d46..aa468eb8b3f12803055e41fd71352001667bdfc7 100644 (file)
 package com.tigervnc.rfb;
 
 import com.tigervnc.rdr.InStream;
+import com.tigervnc.rdr.MemInStream;
+import com.tigervnc.rdr.OutStream;
 import com.tigervnc.rdr.ZlibInStream;
 import java.util.ArrayList;
 import java.io.InputStream;
 import java.awt.image.*;
 import java.awt.*;
+import java.math.BigInteger;
+import java.io.*;
+import java.nio.*;
+import javax.imageio.*;
+import javax.imageio.stream.*;
 
 public class TightDecoder extends Decoder {
 
   final static int TIGHT_MAX_WIDTH = 2048;
+  final static int TIGHT_MIN_TO_COMPRESS = 12;
 
   // Compression control
-  final static int rfbTightExplicitFilter = 0x04;
-  final static int rfbTightFill = 0x08;
-  final static int rfbTightJpeg = 0x09;
-  final static int rfbTightMaxSubencoding = 0x09;
+  final static int tightExplicitFilter = 0x04;
+  final static int tightFill = 0x08;
+  final static int tightJpeg = 0x09;
+  final static int tightMaxSubencoding = 0x09;
 
   // Filters to improve compression efficiency
-  final static int rfbTightFilterCopy = 0x00;
-  final static int rfbTightFilterPalette = 0x01;
-  final static int rfbTightFilterGradient = 0x02;
-  final static int rfbTightMinToCompress = 12;
+  final static int tightFilterCopy = 0x00;
+  final static int tightFilterPalette = 0x01;
+  final static int tightFilterGradient = 0x02;
 
-  final static Toolkit tk = Toolkit.getDefaultToolkit();
-
-  public TightDecoder(CMsgReader reader_) {
-    reader = reader_;
+  public TightDecoder() {
+    super(DecoderFlags.DecoderPartiallyOrdered);
     zis = new ZlibInStream[4];
     for (int i = 0; i < 4; i++)
       zis[i] = new ZlibInStream();
   }
 
-  public void readRect(Rect r, CMsgHandler handler)
+  public void readRect(Rect r, InStream is,
+                       ConnParams cp, OutStream os)
   {
-    InStream is = reader.getInStream();
-    boolean cutZeros = false;
-    clientpf = handler.getPreferredPF();
-    serverpf = handler.cp.pf();
-    int bpp = serverpf.bpp;
-    cutZeros = false;
-    if (bpp == 32) {
-      if (serverpf.is888()) {
-        cutZeros = true;
+    int comp_ctl;
+
+    comp_ctl = is.readU8();
+    os.writeU8(comp_ctl);
+
+    comp_ctl >>= 4;
+
+    // "Fill" compression type.
+    if (comp_ctl == tightFill) {
+      if (cp.pf().is888())
+        os.copyBytes(is, 3);
+      else
+        os.copyBytes(is, cp.pf().bpp/8);
+      return;
+    }
+
+    // "JPEG" compression type.
+    if (comp_ctl == tightJpeg) {
+      int len;
+
+      len = readCompact(is);
+      os.writeOpaque32(len);
+      os.copyBytes(is, len);
+      return;
+    }
+
+    // Quit on unsupported compression type.
+    if (comp_ctl > tightMaxSubencoding)
+      throw new Exception("TightDecoder: bad subencoding value received");
+
+    // "Basic" compression type.
+
+    int palSize = 0;
+
+    if (r.width() > TIGHT_MAX_WIDTH)
+      throw new Exception("TightDecoder: too large rectangle ("+r.width()+" pixels)");
+
+    // Possible palette
+    if ((comp_ctl & tightExplicitFilter) != 0) {
+      int filterId;
+
+      filterId = is.readU8() & 0xff;
+      os.writeU8(filterId);
+
+      switch (filterId) {
+      case tightFilterPalette:
+        palSize = is.readU8() + 1;
+        os.writeU32(palSize - 1);
+
+        if (cp.pf().is888())
+          os.copyBytes(is, palSize * 3);
+        else
+          os.copyBytes(is, palSize * cp.pf().bpp/8);
+        break;
+      case tightFilterGradient:
+        if (cp.pf().bpp == 8)
+          throw new Exception("TightDecoder: invalid BPP for gradient filter");
+        break;
+      case tightFilterCopy:
+        break;
+      default:
+        throw new Exception("TightDecoder: unknown filter code received");
       }
     }
 
-    int comp_ctl = is.readU8();
+    int rowSize, dataSize;
+
+    if (palSize != 0) {
+      if (palSize <= 2)
+        rowSize = (r.width() + 7) / 8;
+      else
+        rowSize = r.width();
+    } else if (cp.pf().is888()) {
+      rowSize = r.width() * 3;
+    } else {
+      rowSize = r.width() * cp.pf().bpp/8;
+    }
 
-    boolean bigEndian = handler.cp.pf().bigEndian;
+    dataSize = r.height() * rowSize;
 
-    // Flush zlib streams if we are told by the server to do so.
+    if (dataSize < TIGHT_MIN_TO_COMPRESS) {
+      os.copyBytes(is, dataSize);
+    } else {
+      int len;
+
+      len = readCompact(is);
+      os.writeOpaque32(len);
+      os.copyBytes(is, len);
+    }
+  }
+
+  public boolean doRectsConflict(Rect rectA,
+                                 Object bufferA,
+                                 int buflenA,
+                                 Rect rectB,
+                                 Object bufferB,
+                                 int buflenB,
+                                 ConnParams cp)
+  {
+    byte comp_ctl_a, comp_ctl_b;
+
+    assert(buflenA >= 1);
+    assert(buflenB >= 1);
+
+    comp_ctl_a = ((byte[])bufferA)[0];
+    comp_ctl_b = ((byte[])bufferB)[0];
+
+    // Resets or use of zlib pose the same problem, so merge them
+    if ((comp_ctl_a & 0x80) == 0x00)
+      comp_ctl_a |= 1 << ((comp_ctl_a >> 4) & 0x03);
+    if ((comp_ctl_b & 0x80) == 0x00)
+      comp_ctl_b |= 1 << ((comp_ctl_b >> 4) & 0x03);
+
+    if (((comp_ctl_a & 0x0f) & (comp_ctl_b & 0x0f)) != 0)
+      return true;
+
+    return false;
+  }
+
+  public void decodeRect(Rect r, Object buffer,
+                         int buflen, ConnParams cp,
+                         ModifiablePixelBuffer pb)
+  {
+    ByteBuffer bufptr;
+    PixelFormat pf = cp.pf();
+
+    int comp_ctl;
+
+    bufptr = ByteBuffer.wrap((byte[])buffer);
+
+    assert(buflen >= 1);
+
+    comp_ctl = bufptr.get() & 0xff;
+    buflen -= 1;
+
+    // Reset zlib streams if we are told by the server to do so.
     for (int i = 0; i < 4; i++) {
       if ((comp_ctl & 1) != 0) {
         zis[i].reset();
@@ -80,190 +205,216 @@ public class TightDecoder extends Decoder {
     }
 
     // "Fill" compression type.
-    if (comp_ctl == rfbTightFill) {
-      int[] pix = new int[1];
-      if (cutZeros) {
-        byte[] bytebuf = new byte[3];
-        is.readBytes(bytebuf, 0, 3);
-        serverpf.bufferFromRGB(pix, 0, bytebuf, 0, 1);
+    if (comp_ctl == tightFill) {
+      if (pf.is888()) {
+        ByteBuffer pix = ByteBuffer.allocate(4);
+
+        assert(buflen >= 3);
+
+        pf.bufferFromRGB(pix, bufptr, 1);
+        pb.fillRect(pf, r, pix.array());
       } else {
-        pix[0] = is.readPixel(serverpf.bpp/8, serverpf.bigEndian);
+        assert(buflen >= pf.bpp/8);
+        byte[] pix = new byte[pf.bpp/8];
+        bufptr.get(pix);
+        pb.fillRect(pf, r, pix);
       }
-      handler.fillRect(r, pix[0]);
       return;
     }
 
     // "JPEG" compression type.
-    if (comp_ctl == rfbTightJpeg) {
-      DECOMPRESS_JPEG_RECT(r, is, handler);
+    if (comp_ctl == tightJpeg) {
+      int len;
+
+      WritableRaster buf;
+
+      JpegDecompressor jd = new JpegDecompressor();
+
+      assert(buflen >= 4);
+
+      len = bufptr.getInt();
+      buflen -= 4;
+
+      // We always use direct decoding with JPEG images
+      buf = pb.getBufferRW(r);
+      jd.decompress(bufptr, len, buf, r, pb.getPF());
+      pb.commitBufferRW(r);
       return;
     }
 
     // Quit on unsupported compression type.
-    if (comp_ctl > rfbTightMaxSubencoding) {
+    if (comp_ctl > tightMaxSubencoding)
       throw new Exception("TightDecoder: bad subencoding value received");
-    }
 
     // "Basic" compression type.
     int palSize = 0;
-    int[] palette = new int[256];
+    ByteBuffer palette = ByteBuffer.allocate(256 * 4);
     boolean useGradient = false;
 
-    if ((comp_ctl & rfbTightExplicitFilter) != 0) {
-      int filterId = is.readU8();
+    if ((comp_ctl & tightExplicitFilter) != 0) {
+      int filterId;
+
+      assert(buflen >= 1);
+
+      filterId = bufptr.get();
 
       switch (filterId) {
-      case rfbTightFilterPalette:
-        palSize = is.readU8() + 1;
-        byte[] tightPalette;
-        if (cutZeros) {
-          tightPalette = new byte[256 * 3];
-          is.readBytes(tightPalette, 0, palSize * 3);
-          serverpf.bufferFromRGB(palette, 0, tightPalette, 0, palSize);
+      case tightFilterPalette:
+        assert(buflen >= 1);
+
+        palSize = bufptr.getInt() + 1;
+        buflen -= 4;
+
+        if (pf.is888()) {
+          ByteBuffer tightPalette = ByteBuffer.allocate(palSize * 3);
+
+          assert(buflen >= tightPalette.capacity());
+
+          bufptr.get(tightPalette.array(), 0, tightPalette.capacity());
+          buflen -= tightPalette.capacity();
+
+          pf.bufferFromRGB(palette.duplicate(), tightPalette, palSize);
         } else {
-          is.readPixels(palette, palSize, serverpf.bpp/8, serverpf.bigEndian);
+          int len;
+
+          len = palSize * pf.bpp/8;
+
+          assert(buflen >= len);
+
+          bufptr.get(palette.array(), 0, len);
+          buflen -= len;
         }
         break;
-      case rfbTightFilterGradient:
+      case tightFilterGradient:
         useGradient = true;
         break;
-      case rfbTightFilterCopy:
+      case tightFilterCopy:
         break;
       default:
-        throw new Exception("TightDecoder: unknown filter code recieved");
+        assert(false);
       }
     }
 
-    int bppp = bpp;
+    // Determine if the data should be decompressed or just copied.
+    int rowSize, dataSize;
+    byte[] netbuf;
+
     if (palSize != 0) {
-      bppp = (palSize <= 2) ? 1 : 8;
-    } else if (cutZeros) {
-      bppp = 24;
+      if (palSize <= 2)
+        rowSize = (r.width() + 7) / 8;
+      else
+        rowSize = r.width();
+    } else if (pf.is888()) {
+      rowSize = r.width() * 3;
+    } else {
+      rowSize = r.width() * pf.bpp/8;
     }
 
-    // Determine if the data should be decompressed or just copied.
-    int rowSize = (r.width() * bppp + 7) / 8;
-    int dataSize = r.height() * rowSize;
-    int streamId = -1;
-    InStream input;
-    if (dataSize < rfbTightMinToCompress) {
-      input = is;
+    dataSize = r.height() * rowSize;
+
+    if (dataSize < TIGHT_MIN_TO_COMPRESS) {
+      assert(buflen >= dataSize);
     } else {
-      int length = is.readCompactLength();
+      int len;
+      int streamId;
+      MemInStream ms;
+
+      assert(buflen >= 4);
+
+      len = bufptr.getInt();
+      buflen -= 4;
+
+      assert(buflen >= len);
+
       streamId = comp_ctl & 0x03;
-      zis[streamId].setUnderlying(is, length);
-      input = (ZlibInStream)zis[streamId];
-    }
+      ms = new MemInStream(bufptr.array(), bufptr.position(), len);
+      zis[streamId].setUnderlying(ms, len);
+
+      // Allocate netbuf and read in data
+      netbuf = new byte[dataSize];
 
-    // Allocate netbuf and read in data
-    byte[] netbuf = new byte[dataSize];
-    input.readBytes(netbuf, 0, dataSize);
+      zis[streamId].readBytes(netbuf, 0, dataSize);
 
+      zis[streamId].removeUnderlying();
+      ms = null;
+
+      bufptr = ByteBuffer.wrap(netbuf);
+      buflen = dataSize;
+    }
+
+    ByteBuffer outbuf = ByteBuffer.allocate(r.area() * pf.bpp/8);
     int stride = r.width();
-    int[] buf = reader.getImageBuf(r.area());
 
     if (palSize == 0) {
       // Truecolor data.
       if (useGradient) {
-        if (bpp == 32 && cutZeros) {
-          FilterGradient24(netbuf, buf, stride, r);
+        if (pf.is888()) {
+          FilterGradient24(bufptr, pf, outbuf, stride, r);
         } else {
-          FilterGradient(netbuf, buf, stride, r);
+          switch (pf.bpp) {
+          case 8:
+            assert(false);
+            break;
+          case 16:
+            FilterGradient(bufptr, pf, outbuf, stride, r);
+            break;
+          case 32:
+            FilterGradient(bufptr, pf, outbuf, stride, r);
+            break;
+          }
         }
       } else {
         // Copy
-        int h = r.height();
-        int ptr = 0;
-        int srcPtr = 0;
+        ByteBuffer ptr = (ByteBuffer)outbuf.duplicate().mark();
+        ByteBuffer srcPtr = bufptr.duplicate();
         int w = r.width();
-        if (cutZeros) {
-          serverpf.bufferFromRGB(buf, ptr, netbuf, srcPtr, w*h);
+        int h = r.height();
+        if (pf.is888()) {
+          while (h > 0) {
+            pf.bufferFromRGB(ptr.duplicate(), srcPtr.duplicate(), w);
+            ptr.position(ptr.position() + stride * pf.bpp/8);
+            srcPtr.position(srcPtr.position() + w * 3);
+            h--;
+          }
         } else {
-          int pixelSize = (bpp >= 24) ? 3 : bpp/8;
           while (h > 0) {
-            for (int i = 0; i < w; i++) {
-              if (bpp == 8) {
-                buf[ptr+i] = netbuf[srcPtr+i] & 0xff;
-              } else {
-                for (int j = pixelSize-1; j >= 0; j--)
-                  buf[ptr+i] |= ((netbuf[srcPtr+i+j] & 0xff) << j*8);
-              }
-            }
-            ptr += stride;
-            srcPtr += w * pixelSize;
+            ptr.put(srcPtr.array(), srcPtr.position(), w * pf.bpp/8);
+            ptr.reset().position(ptr.position() + stride * pf.bpp/8).mark();
+            srcPtr.position(srcPtr.position() + w * pf.bpp/8);
             h--;
           }
         }
       }
     } else {
       // Indexed color
-      int x, h = r.height(), w = r.width(), b, pad = stride - w;
-      int ptr = 0;
-      int srcPtr = 0, bits;
-      if (palSize <= 2) {
-        // 2-color palette
-        while (h > 0) {
-          for (x = 0; x < w / 8; x++) {
-            bits = netbuf[srcPtr++];
-            for(b = 7; b >= 0; b--) {
-              buf[ptr++] = palette[bits >> b & 1];
-            }
-          }
-          if (w % 8 != 0) {
-            bits = netbuf[srcPtr++];
-            for (b = 7; b >= 8 - w % 8; b--) {
-              buf[ptr++] = palette[bits >> b & 1];
-            }
-          }
-          ptr += pad;
-          h--;
-        }
-      } else {
-        // 256-color palette
-        while (h > 0) {
-          int endOfRow = ptr + w;
-          while (ptr < endOfRow) {
-            buf[ptr++] = palette[netbuf[srcPtr++] & 0xff];
-          }
-          ptr += pad;
-          h--;
-        }
+      switch (pf.bpp) {
+      case 8:
+        FilterPalette8(palette, palSize,
+                       bufptr, outbuf, stride, r);
+        break;
+      case 16:
+        FilterPalette16(palette.asShortBuffer(), palSize,
+                        bufptr, outbuf.asShortBuffer(), stride, r);
+        break;
+      case 32:
+        FilterPalette32(palette.asIntBuffer(), palSize,
+                        bufptr, outbuf.asIntBuffer(), stride, r);
+        break;
       }
     }
 
-    handler.imageRect(r, buf);
-
-    if (streamId != -1) {
-      zis[streamId].reset();
-    }
-  }
+    pb.imageRect(pf, r, outbuf.array());
 
-  final private void DECOMPRESS_JPEG_RECT(Rect r, InStream is, CMsgHandler handler)
-  {
-    // Read length
-    int compressedLen = is.readCompactLength();
-    if (compressedLen <= 0)
-      vlog.info("Incorrect data received from the server.");
-
-    // Allocate netbuf and read in data
-    byte[] netbuf = new byte[compressedLen];
-    is.readBytes(netbuf, 0, compressedLen);
-
-    // Create an Image object from the JPEG data.
-    Image jpeg = tk.createImage(netbuf);
-    jpeg.setAccelerationPriority(1);
-    handler.imageRect(r, jpeg);
-    jpeg.flush();
   }
 
-  final private void FilterGradient24(byte[] netbuf, int[] buf, int stride,
-                                      Rect r)
+  final private void FilterGradient24(ByteBuffer inbuf,
+                                      PixelFormat pf, ByteBuffer outbuf,
+                                      int stride, Rect r)
   {
-
     int x, y, c;
     byte[] prevRow = new byte[TIGHT_MAX_WIDTH*3];
     byte[] thisRow = new byte[TIGHT_MAX_WIDTH*3];
-    byte[] pix = new byte[3];
+    ByteBuffer pix = ByteBuffer.allocate(3);
     int[] est = new int[3];
 
     // Set up shortcut variables
@@ -273,38 +424,38 @@ public class TightDecoder extends Decoder {
     for (y = 0; y < rectHeight; y++) {
       /* First pixel in a row */
       for (c = 0; c < 3; c++) {
-        pix[c] = (byte)(netbuf[y*rectWidth*3+c] + prevRow[c]);
-        thisRow[c] = pix[c];
+        pix.put(c, (byte)(inbuf.get(y*rectWidth*3+c) + prevRow[c]));
+        thisRow[c] = pix.get(c);
       }
-      serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1);
+      pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1);
 
       /* Remaining pixels of a row */
       for (x = 1; x < rectWidth; x++) {
         for (c = 0; c < 3; c++) {
-          est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]);
-          if (est[c] > 0xFF) {
-            est[c] = 0xFF;
+          est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c];
+          if (est[c] > 0xff) {
+            est[c] = 0xff;
           } else if (est[c] < 0) {
             est[c] = 0;
           }
-          pix[c] = (byte)(netbuf[(y*rectWidth+x)*3+c] + est[c]);
-          thisRow[x*3+c] = pix[c];
+          pix.put(c, (byte)(inbuf.get((y*rectWidth+x)*3+c) + est[c]));
+          thisRow[x*3+c] = pix.get(c);
         }
-        serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1);
+        pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1);
       }
 
       System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length);
     }
   }
 
-  final private void FilterGradient(byte[] netbuf, int[] buf, int stride,
-                                    Rect r)
+  final private void FilterGradient(ByteBuffer inbuf,
+                                    PixelFormat pf, ByteBuffer outbuf,
+                                    int stride, Rect r)
   {
-
     int x, y, c;
     byte[] prevRow = new byte[TIGHT_MAX_WIDTH];
     byte[] thisRow = new byte[TIGHT_MAX_WIDTH];
-    byte[] pix = new byte[3];
+    ByteBuffer pix = ByteBuffer.allocate(3);
     int[] est = new int[3];
 
     // Set up shortcut variables
@@ -313,19 +464,18 @@ public class TightDecoder extends Decoder {
 
     for (y = 0; y < rectHeight; y++) {
       /* First pixel in a row */
-      // FIXME
-      //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth, 1, cm);
+      pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth), 1);
       for (c = 0; c < 3; c++)
-        pix[c] += prevRow[c];
+        pix.put(c, (byte)(pix.get(c) + prevRow[c]));
 
-      System.arraycopy(pix, 0, thisRow, 0, pix.length);
+      System.arraycopy(pix.array(), 0, thisRow, 0, pix.capacity());
 
-      serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1);
+      pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1);
 
       /* Remaining pixels of a row */
       for (x = 1; x < rectWidth; x++) {
         for (c = 0; c < 3; c++) {
-          est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]);
+          est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c];
           if (est[c] > 0xff) {
             est[c] = 0xff;
           } else if (est[c] < 0) {
@@ -333,24 +483,156 @@ public class TightDecoder extends Decoder {
           }
         }
 
-        // FIXME
-        //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth+x, 1, cm);
+        pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth+x), 1);
         for (c = 0; c < 3; c++)
-          pix[c] += est[c];
+          pix.put(c, (byte)(pix.get(c) + est[c]));
 
-        System.arraycopy(pix, 0, thisRow, x*3, pix.length);
+        System.arraycopy(pix.array(), 0, thisRow, x*3, pix.capacity());
 
-        serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1);
+        pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1);
       }
 
       System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length);
     }
   }
 
-  private CMsgReader reader;
+  private void FilterPalette8(ByteBuffer palette, int palSize,
+                              ByteBuffer inbuf, ByteBuffer outbuf,
+                              int stride, Rect r)
+  {
+    // Indexed color
+    int x, h = r.height(), w = r.width(), b, pad = stride - w;
+    ByteBuffer ptr = outbuf.duplicate();
+    byte bits;
+    ByteBuffer srcPtr = inbuf.duplicate();
+    if (palSize <= 2) {
+      // 2-color palette
+      while (h > 0) {
+        for (x = 0; x < w / 8; x++) {
+          bits = srcPtr.get();
+          for (b = 7; b >= 0; b--) {
+            ptr.put(palette.get(bits >> b & 1));
+          }
+        }
+        if (w % 8 != 0) {
+          bits = srcPtr.get();
+          for (b = 7; b >= 8 - w % 8; b--) {
+            ptr.put(palette.get(bits >> b & 1));
+          }
+        }
+        ptr.position(ptr.position() + pad);
+        h--;
+      }
+    } else {
+      // 256-color palette
+      while (h > 0) {
+        int endOfRow = ptr.position() + w;
+        while (ptr.position() < endOfRow) {
+          ptr.put(palette.get(srcPtr.get()));
+        }
+        ptr.position(ptr.position() + pad);
+        h--;
+      }
+    }
+  }
+
+  private void FilterPalette16(ShortBuffer palette, int palSize,
+                               ByteBuffer inbuf, ShortBuffer outbuf,
+                               int stride, Rect r)
+  {
+    // Indexed color
+    int x, h = r.height(), w = r.width(), b, pad = stride - w;
+    ShortBuffer ptr = outbuf.duplicate();
+    byte bits;
+    ByteBuffer srcPtr = inbuf.duplicate();
+    if (palSize <= 2) {
+      // 2-color palette
+      while (h > 0) {
+        for (x = 0; x < w / 8; x++) {
+          bits = srcPtr.get();
+          for (b = 7; b >= 0; b--) {
+            ptr.put(palette.get(bits >> b & 1));
+          }
+        }
+        if (w % 8 != 0) {
+          bits = srcPtr.get();
+          for (b = 7; b >= 8 - w % 8; b--) {
+            ptr.put(palette.get(bits >> b & 1));
+          }
+        }
+        ptr.position(ptr.position() + pad);
+        h--;
+      }
+    } else {
+      // 256-color palette
+      while (h > 0) {
+        int endOfRow = ptr.position() + w;
+        while (ptr.position() < endOfRow) {
+          ptr.put(palette.get(srcPtr.get()));
+        }
+        ptr.position(ptr.position() + pad);
+        h--;
+      }
+    }
+  }
+
+  private void FilterPalette32(IntBuffer palette, int palSize,
+                               ByteBuffer inbuf, IntBuffer outbuf,
+                               int stride, Rect r)
+  {
+    // Indexed color
+    int x, h = r.height(), w = r.width(), b, pad = stride - w;
+    IntBuffer ptr = outbuf.duplicate();
+    byte bits;
+    ByteBuffer srcPtr = inbuf.duplicate();
+    if (palSize <= 2) {
+      // 2-color palette
+      while (h > 0) {
+        for (x = 0; x < w / 8; x++) {
+          bits = srcPtr.get();
+          for (b = 7; b >= 0; b--) {
+            ptr.put(palette.get(bits >> b & 1));
+          }
+        }
+        if (w % 8 != 0) {
+          bits = srcPtr.get();
+          for (b = 7; b >= 8 - w % 8; b--) {
+            ptr.put(palette.get(bits >> b & 1));
+          }
+        }
+        ptr.position(ptr.position() + pad);
+        h--;
+      }
+    } else {
+      // 256-color palette
+      while (h > 0) {
+        int endOfRow = ptr.position() + w;
+        while (ptr.position() < endOfRow) {
+          ptr.put(palette.get(srcPtr.get() & 0xff));
+        }
+        ptr.position(ptr.position() + pad);
+        h--;
+      }
+    }
+  }
+
+  public final int readCompact(InStream is) {
+    byte b;
+    int result;
+
+    b = (byte)is.readU8();
+    result = (int)b & 0x7F;
+    if ((b & 0x80) != 0) {
+      b = (byte)is.readU8();
+      result |= ((int)b & 0x7F) << 7;
+      if ((b & 0x80) != 0) {
+        b = (byte)is.readU8();
+        result |= ((int)b & 0xFF) << 14;
+      }
+    }
+    return result;
+  }
+
   private ZlibInStream[] zis;
-  private PixelFormat serverpf;
-  private PixelFormat clientpf;
-  static LogWriter vlog = new LogWriter("TightDecoder");
 
 }
index e706510f7424fcf2125a962f6e4f2fb38ec3b8be..c1f908abaa354cb88e9a40b25ddfc59ae00ba9dc 100644 (file)
 
 package com.tigervnc.rfb;
 
+import java.awt.image.*;
+import java.nio.*;
+import java.util.*;
+
 import com.tigervnc.rdr.*;
 
 public class ZRLEDecoder extends Decoder {
 
-  public ZRLEDecoder(CMsgReader reader_) {
-    reader = reader_;
+  private static int readOpaque24A(InStream is)
+  {
+    is.check(3);
+    ByteBuffer r = ByteBuffer.allocate(4);
+    r.put(0, (byte)is.readU8());
+    r.put(1, (byte)is.readU8());
+    r.put(2, (byte)is.readU8());
+    return ((ByteBuffer)r.rewind()).getInt();
+  }
+
+  private static int readOpaque24B(InStream is)
+  {
+    is.check(3);
+    ByteBuffer r = ByteBuffer.allocate(4);
+    r.put(2, (byte)is.readU8());
+    r.put(1, (byte)is.readU8());
+    r.put(0, (byte)is.readU8());
+    return ((ByteBuffer)r.rewind()).getInt();
+  }
+
+  public ZRLEDecoder() {
+    super(DecoderFlags.DecoderOrdered);
     zis = new ZlibInStream();
   }
 
-  public void readRect(Rect r, CMsgHandler handler) {
-    InStream is = reader.getInStream();
-    int[] buf = reader.getImageBuf(64 * 64 * 4);
-    int bpp = handler.cp.pf().bpp;
-    int bytesPerPixel = (bpp > 24 ? 3 : bpp / 8);
-    boolean bigEndian = handler.cp.pf().bigEndian;
+  public void readRect(Rect r, InStream is,
+                      ConnParams cp, OutStream os)
+  {
+    int len;
 
+    len = is.readU32();
+    os.writeU32(len);
+    os.copyBytes(is, len);
+  }
+
+  public void decodeRect(Rect r, Object buffer,
+                         int buflen, ConnParams cp,
+                         ModifiablePixelBuffer pb)
+  {
+    MemInStream is = new MemInStream((byte[])buffer, 0, buflen);
+    PixelFormat pf = cp.pf();
+    ByteBuffer buf = ByteBuffer.allocate(64 * 64 * 4);
+    switch (pf.bpp) {
+    case 8:  zrleDecode8(r, is, zis, buf, pf, pb); break;
+    case 16: zrleDecode16(r, is, zis, buf, pf, pb); break;
+    case 32:
+        int maxPixel = pf.pixelFromRGB(-1, -1, -1, pf.getColorModel());
+        boolean fitsInLS3Bytes = maxPixel < (1<<24);
+        boolean fitsInMS3Bytes = (maxPixel & 0xff) == 0;
+
+        if ((fitsInLS3Bytes && pf.isLittleEndian()) ||
+            (fitsInMS3Bytes && pf.isBigEndian()))
+        {
+          zrleDecode24A(r, is, zis, buf, pf, pb);
+        }
+        else if ((fitsInLS3Bytes && pf.isBigEndian()) ||
+                (fitsInMS3Bytes && pf.isLittleEndian()))
+        {
+          zrleDecode24B(r, is, zis, buf, pf, pb);
+        }
+        else
+        {
+          zrleDecode32(r, is, zis, buf, pf, pb);
+        }
+        break;
+    }
+  }
+
+  private static enum PIXEL_T { U8, U16, U24A, U24B, U32 };
+
+  private static ByteBuffer READ_PIXEL(InStream is, PIXEL_T type) {
+    ByteBuffer b = ByteBuffer.allocate(4);
+    switch (type) {
+    case U8:
+      b.putInt(is.readOpaque8());
+      return (ByteBuffer)ByteBuffer.allocate(1).put(b.get(3)).rewind();
+    case U16:
+      b.putInt(is.readOpaque16());
+      return (ByteBuffer)ByteBuffer.allocate(2).put(b.array(), 2, 2).rewind();
+    case U24A:
+      return (ByteBuffer)b.putInt(readOpaque24A(is)).rewind();
+    case U24B:
+      return (ByteBuffer)b.putInt(readOpaque24B(is)).rewind();
+    case U32:
+    default:
+      return (ByteBuffer)b.putInt(is.readOpaque32()).rewind();
+    }
+  }
+
+  private void zrleDecode8(Rect r, InStream is,
+                           ZlibInStream zis, ByteBuffer buf,
+                           PixelFormat pf, ModifiablePixelBuffer pb)
+  {
+    ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U8);
+  }
+
+  private void zrleDecode16(Rect r, InStream is,
+                            ZlibInStream zis, ByteBuffer buf,
+                            PixelFormat pf, ModifiablePixelBuffer pb)
+  {
+    ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U16);
+  }
+
+  private void zrleDecode24A(Rect r, InStream is,
+                             ZlibInStream zis, ByteBuffer buf,
+                             PixelFormat pf, ModifiablePixelBuffer pb)
+  {
+    ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U24A);
+  }
+
+  private void zrleDecode24B(Rect r, InStream is,
+                             ZlibInStream zis, ByteBuffer buf,
+                             PixelFormat pf, ModifiablePixelBuffer pb)
+  {
+    ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U24B);
+  }
+
+  private void zrleDecode32(Rect r, InStream is,
+                            ZlibInStream zis, ByteBuffer buf,
+                            PixelFormat pf, ModifiablePixelBuffer pb)
+  {
+    ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U32);
+  }
+
+  private void ZRLE_DECODE(Rect r, InStream is,
+                           ZlibInStream zis, ByteBuffer buf,
+                           PixelFormat pf, ModifiablePixelBuffer pb,
+                           PIXEL_T pix_t)
+  {
     int length = is.readU32();
     zis.setUnderlying(is, length);
     Rect t = new Rect();
@@ -49,13 +170,16 @@ public class ZRLEDecoder extends Decoder {
         int mode = zis.readU8();
         boolean rle = (mode & 128) != 0;
         int palSize = mode & 127;
-        int[] palette = new int[128];
+        ByteBuffer palette = ByteBuffer.allocate(128 * pf.bpp/8);
 
-        zis.readPixels(palette, palSize, bytesPerPixel, bigEndian);
+        for (int i = 0; i < palSize; i++) {
+          palette.put(READ_PIXEL(zis, pix_t));
+        }
 
         if (palSize == 1) {
-          int pix = palette[0];
-          handler.fillRect(t, pix);
+          ByteBuffer pix = 
+            ByteBuffer.allocate(pf.bpp/8).put(palette.array(), 0, pf.bpp/8);
+          pb.fillRect(pf, t, pix.array());
           continue;
         }
 
@@ -63,8 +187,17 @@ public class ZRLEDecoder extends Decoder {
           if (palSize == 0) {
 
             // raw
-
-            zis.readPixels(buf, t.area(), bytesPerPixel, bigEndian);
+            switch (pix_t) {
+            case U24A:
+            case U24B:
+              ByteBuffer ptr = buf.duplicate();
+              for (int iptr=0; iptr < t.area(); iptr++) {
+                ptr.put(READ_PIXEL(zis, pix_t));
+              }
+              break;
+            default:
+              zis.readBytes(buf, t.area() * (pf.bpp/8));
+            }
 
           } else {
 
@@ -72,21 +205,21 @@ public class ZRLEDecoder extends Decoder {
             int bppp = ((palSize > 16) ? 8 :
                         ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
 
-            int ptr = 0;
+            ByteBuffer ptr = buf.duplicate();
 
             for (int i = 0; i < t.height(); i++) {
-              int eol = ptr + t.width();
+              int eol = ptr.position() + t.width()*pf.bpp/8;
               int b = 0;
               int nbits = 0;
 
-              while (ptr < eol) {
+              while (ptr.position() < eol) {
                 if (nbits == 0) {
                   b = zis.readU8();
                   nbits = 8;
                 }
                 nbits -= bppp;
                 int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
-                buf[ptr++] = palette[index];
+                ptr.put(palette.array(), index*pf.bpp/8, pf.bpp/8);
               }
             }
           }
@@ -97,10 +230,10 @@ public class ZRLEDecoder extends Decoder {
 
             // plain RLE
 
-            int ptr = 0;
-            int end = ptr + t.area();
-            while (ptr < end) {
-              int pix = zis.readPixel(bytesPerPixel, bigEndian);
+            ByteBuffer ptr = buf.duplicate();
+            int end = ptr.position() + t.area()*pf.bpp/8;
+            while (ptr.position() < end) {
+              ByteBuffer pix = READ_PIXEL(zis, pix_t);
               int len = 1;
               int b;
               do {
@@ -108,19 +241,21 @@ public class ZRLEDecoder extends Decoder {
                 len += b;
               } while (b == 255);
 
-              if (!(len <= end - ptr))
-                throw new Exception("ZRLEDecoder: assertion (len <= end - ptr)"
-                                    +" failed");
+              if (end - ptr.position() < len*(pf.bpp/8)) {
+                System.err.println("ZRLE decode error\n");
+                throw new Exception("ZRLE decode error");
+              }
+
+              while (len-- > 0) ptr.put(pix);
 
-              while (len-- > 0) buf[ptr++] = pix;
             }
           } else {
 
             // palette RLE
 
-            int ptr = 0;
-            int end = ptr + t.area();
-            while (ptr < end) {
+            ByteBuffer ptr = buf.duplicate();
+            int end = ptr.position() + t.area()*pf.bpp/8;
+            while (ptr.position() < end) {
               int index = zis.readU8();
               int len = 1;
               if ((index & 128) != 0) {
@@ -130,27 +265,26 @@ public class ZRLEDecoder extends Decoder {
                   len += b;
                 } while (b == 255);
 
-                if (!(len <= end - ptr))
-                  throw new Exception("ZRLEDecoder: assertion "
-                                      +"(len <= end - ptr) failed");
+                if (end - ptr.position() < len*(pf.bpp/8)) {
+                  System.err.println("ZRLE decode error\n");
+                  throw new Exception("ZRLE decode error");
+                }
               }
 
               index &= 127;
 
-              int pix = palette[index];
+              while (len-- > 0) ptr.put(palette.array(), index*pf.bpp/8, pf.bpp/8);
 
-              while (len-- > 0) buf[ptr++] = pix;
             }
           }
         }
 
-        handler.imageRect(t, buf);
+        pb.imageRect(pf, t, buf.array());
       }
     }
 
-    zis.reset();
+    zis.removeUnderlying();
   }
 
-  CMsgReader reader;
-  ZlibInStream zis;
+  private ZlibInStream zis;
 }
diff --git a/java/com/tigervnc/vncviewer/BIPixelBuffer.java b/java/com/tigervnc/vncviewer/BIPixelBuffer.java
deleted file mode 100644 (file)
index 1634ebd..0000000
+++ /dev/null
@@ -1,141 +0,0 @@
-/* Copyright (C) 2012 Brian P. Hinz
- * Copyright (C) 2012 D. R. Commander.  All Rights Reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
- */
-
-package com.tigervnc.vncviewer;
-
-import java.awt.*;
-import java.awt.image.*;
-
-import com.tigervnc.rfb.*;
-import com.tigervnc.rfb.Exception;
-
-public class BIPixelBuffer extends PlatformPixelBuffer implements ImageObserver
-{
-  public BIPixelBuffer(PixelFormat pf, int w, int h, DesktopWindow desktop_) {
-    super(pf, w, h, desktop_);
-    clip = new Rectangle();
-  }
-
-  public void setPF(PixelFormat pf) {
-    super.setPF(pf);
-    createImage(width(), height());
-  }
-
-  public void updateColourMap() {
-    super.updateColourMap();
-    createImage(width_, height_);
-  }
-
-  // resize() resizes the image, preserving the image data where possible.
-  public void resize(int w, int h) {
-    if (w == width() && h == height())
-      return;
-
-    width_ = w;
-    height_ = h;
-    createImage(w, h);
-  }
-
-  private void createImage(int w, int h) {
-    if (w == 0 || h == 0) return;
-    WritableRaster wr;
-    if (cm instanceof IndexColorModel)
-      wr = ((IndexColorModel)cm).createCompatibleWritableRaster(w, h);
-    else
-      wr = ((DirectColorModel)cm).createCompatibleWritableRaster(w, h);
-    image = new BufferedImage(cm, wr, true, null);
-    db = wr.getDataBuffer();
-  }
-
-  public void fillRect(int x, int y, int w, int h, int pix) {
-    Graphics2D graphics = (Graphics2D)image.getGraphics();
-    switch (format.depth) {
-    case 24:
-      graphics.setColor(new Color(pix));
-      graphics.fillRect(x, y, w, h);
-      break;
-    default:
-      Color color = new Color((0xff << 24) | (cm.getRed(pix) << 16) |
-                              (cm.getGreen(pix) << 8) | (cm.getBlue(pix)));
-      graphics.setColor(color);
-      graphics.fillRect(x, y, w, h);
-      break;
-    }
-    graphics.dispose();
-  }
-
-  public void imageRect(int x, int y, int w, int h, Object pix) {
-    if (pix instanceof Image) {
-      Image img = (Image)pix;
-      clip = new Rectangle(x, y, w, h);
-      synchronized(clip) {
-        tk.prepareImage(img, -1, -1, this);
-        try {
-          clip.wait(1000);
-        } catch (InterruptedException e) {
-          throw new Exception("Error decoding JPEG data");
-        }
-      }
-      clip = null;
-      img.flush();
-    } else {
-      if (image.getSampleModel().getTransferType() == DataBuffer.TYPE_BYTE) {
-        byte[] bytes = new byte[((int[])pix).length];
-        for (int i = 0; i < bytes.length; i++)
-          bytes[i] = (byte)((int[])pix)[i];
-        pix = bytes;
-      }
-      image.getSampleModel().setDataElements(x, y, w, h, pix, db);
-    }
-  }
-
-  public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
-    Graphics2D graphics = (Graphics2D)image.getGraphics();
-    graphics.copyArea(srcX, srcY, w, h, x - srcX, y - srcY);
-    graphics.dispose();
-  }
-
-  public Image getImage() {
-    return (Image)image;
-  }
-
-  public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
-    if ((infoflags & (ALLBITS | ABORT)) == 0) {
-      return true;
-    } else {
-      if ((infoflags & ALLBITS) != 0) {
-        if (clip != null) {
-          synchronized(clip) {
-            Graphics2D graphics = (Graphics2D)image.getGraphics();
-            graphics.drawImage(img, clip.x, clip.y, clip.width, clip.height, null);
-            graphics.dispose();
-            clip.notify();
-          }
-        }
-      }
-      return false;
-    }
-  }
-
-  BufferedImage image;
-  DataBuffer db;
-  Rectangle clip;
-
-  static LogWriter vlog = new LogWriter("BIPixelBuffer");
-}
index d7134344f984e295df6d5e0fe598eb85575d8566..8a2303b0b9f458caa588f039135ba7bf79863a90 100644 (file)
@@ -63,16 +63,20 @@ import com.tigervnc.network.TcpSocket;
 import static com.tigervnc.vncviewer.Parameters.*;
 
 public class CConn extends CConnection implements 
-  UserPasswdGetter, UserMsgBox,
-  FdInStreamBlockCallback, ActionListener {
+  UserPasswdGetter, FdInStreamBlockCallback, ActionListener {
 
-  public final PixelFormat getPreferredPF() { return fullColorPF; }
+  // 8 colours (1 bit per component)
   static final PixelFormat verylowColorPF =
     new PixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0);
+
+  // 64 colours (2 bits per component)
   static final PixelFormat lowColorPF =
     new PixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0);
+
+  // 256 colours (2-3 bits per component)
   static final PixelFormat mediumColorPF =
-    new PixelFormat(8, 8, false, false, 7, 7, 3, 0, 3, 6);
+    new PixelFormat(8, 8, false, true, 7, 7, 3, 5, 2, 0);
+
   static final int KEY_LOC_SHIFT_R = 0;
   static final int KEY_LOC_SHIFT_L = 16;
   static final int SUPER_MASK = 1<<15;
@@ -82,6 +86,7 @@ public class CConn extends CConnection implements
 
   public CConn(String vncServerName, Socket socket)
   {
+    serverHost = null; serverPort = 0; desktop = null;
     pendingPFChange = false;
     currentEncoding = Encodings.encodingTight; lastServerEncoding = -1;
     formatChange = false; encodingChange = false;
@@ -93,13 +98,12 @@ public class CConn extends CConnection implements
     downKeySym = new HashMap<Integer, Integer>();
 
     upg = this;
-    msg = this;
 
     int encNum = Encodings.encodingNum(preferredEncoding.getValue());
     if (encNum != -1)
       currentEncoding = encNum;
 
-    cp.supportsLocalCursor = useLocalCursor.getValue();
+    cp.supportsLocalCursor = true;
 
     cp.supportsDesktopResize = true;
     cp.supportsExtendedDesktopSize = true;
@@ -107,12 +111,13 @@ public class CConn extends CConnection implements
 
     cp.supportsSetDesktopSize = false;
     cp.supportsClientRedirect = true;
+
     if (customCompressLevel.getValue())
       cp.compressLevel = compressLevel.getValue();
     else
       cp.compressLevel = -1;
 
-    if (noJpeg.getValue())
+    if (!noJpeg.getValue())
       cp.qualityLevel = qualityLevel.getValue();
     else
       cp.qualityLevel = -1;
@@ -142,6 +147,7 @@ public class CConn extends CConnection implements
         vlog.info("connected to host "+Hostname.getHost(name)+" port "+Hostname.getPort(name));
     }
 
+    // See callback below
     sock.inStream().setBlockCallback(this);
 
     setStreams(sock.inStream(), sock.outStream());
@@ -161,21 +167,37 @@ public class CConn extends CConnection implements
       requestNewUpdate();
   }
 
-  public boolean showMsgBox(int flags, String title, String text)
-  {
-    //StringBuffer titleText = new StringBuffer("VNC Viewer: "+title);
-    return true;
-  }
-
-  // deleteWindow() is called when the user closes the desktop or menu windows.
+  public String connectionInfo() {
+    String info = new String("Desktop name: %s%n"+
+                             "Host: %s:%d%n"+
+                             "Size: %dx%d%n"+
+                             "Pixel format: %s%n"+
+                             "  (server default: %s)%n"+
+                             "Requested encoding: %s%n"+
+                             "Last used encoding: %s%n"+
+                             "Line speed estimate: %d kbit/s%n"+
+                             "Protocol version: %d.%d%n"+
+                             "Security method: %s [%s]%n");
+    String infoText =
+      String.format(info, cp.name(),
+                    sock.getPeerName(), sock.getPeerPort(),
+                    cp.width, cp.height,
+                    cp.pf().print(),
+                    serverPF.print(),
+                    Encodings.encodingName(currentEncoding),
+                    Encodings.encodingName(lastServerEncoding),
+                    sock.inStream().kbitsPerSecond(),
+                    cp.majorVersion, cp.minorVersion,
+                    Security.secTypeName(csecurity.getType()),
+                    csecurity.description());
 
-  void deleteWindow() {
-    if (viewport != null)
-      viewport.dispose();
-    viewport = null;
+    return infoText;
   }
 
-  // blockCallback() is called when reading from the socket would block.
+  // The RFB core is not properly asynchronous, so it calls this callback
+  // whenever it needs to block to wait for more data. Since FLTK is
+  // monitoring the socket, we just make sure FLTK gets to run.
+
   public void blockCallback() {
     try {
       synchronized(this) {
@@ -238,12 +260,13 @@ public class CConn extends CConnection implements
     return true;
   }
 
-  // CConnection callback methods
+  ////////////////////// CConnection callback methods //////////////////////
 
   // serverInit() is called when the serverInit message has been received.  At
   // this point we create the desktop window and display it.  We also tell the
   // server the pixel format and encodings to use and request the first update.
-  public void serverInit() {
+  public void serverInit()
+  {
     super.serverInit();
 
     // If using AutoSelect with old servers, start in FullColor
@@ -265,23 +288,22 @@ public class CConn extends CConnection implements
     // This initial update request is a bit of a corner case, so we need
     // to help out setting the correct format here.
     assert(pendingPFChange);
-    desktop.setServerPF(pendingPF);
     cp.setPF(pendingPF);
     pendingPFChange = false;
-
-    recreateViewport();
   }
 
   // setDesktopSize() is called when the desktop size changes (including when
   // it is set initially).
-  public void setDesktopSize(int w, int h) {
+  public void setDesktopSize(int w, int h)
+  {
     super.setDesktopSize(w, h);
     resizeFramebuffer();
   }
 
   // setExtendedDesktopSize() is a more advanced version of setDesktopSize()
   public void setExtendedDesktopSize(int reason, int result, int w, int h,
-                                     ScreenSet layout) {
+                                     ScreenSet layout)
+  {
     super.setExtendedDesktopSize(reason, result, w, h, layout);
 
     if ((reason == screenTypes.reasonClient) &&
@@ -294,8 +316,8 @@ public class CConn extends CConnection implements
   }
 
   // clientRedirect() migrates the client to another host/port
-  public void clientRedirect(int port, String host,
-                             String x509subject) {
+  public void clientRedirect(int port, String host, String x509subject)
+  {
     try {
       sock.close();
       sock = new TcpSocket(host, port);
@@ -311,10 +333,11 @@ public class CConn extends CConnection implements
   }
 
   // setName() is called when the desktop name changes
-  public void setName(String name) {
+  public void setName(String name)
+  {
     super.setName(name);
-    if (viewport != null)
-      viewport.setTitle(name+" - TigerVNC");
+    if (desktop != null)
+      desktop.setName(name);
   }
 
   // framebufferUpdateStart() is called at the beginning of an update.
@@ -323,12 +346,23 @@ public class CConn extends CConnection implements
   // one.
   public void framebufferUpdateStart()
   {
+    ModifiablePixelBuffer pb;
+    PlatformPixelBuffer ppb;
+
     super.framebufferUpdateStart();
 
     // Note: This might not be true if sync fences are supported
     pendingUpdate = false;
 
     requestNewUpdate();
+
+    // We might still be rendering the previous update
+    pb = getFramebuffer();
+    assert(pb != null);
+    ppb = (PlatformPixelBuffer)pb;
+    assert(ppb != null);
+
+    //FIXME
   }
 
   // framebufferUpdateEnd() is called at the end of an update.
@@ -342,53 +376,17 @@ public class CConn extends CConnection implements
     desktop.updateWindow();
 
     if (firstUpdate) {
-      int width, height;
-
       // We need fences to make extra update requests and continuous
       // updates "safe". See fence() for the next step.
       if (cp.supportsFence)
         writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext, 0, null);
 
-      if (cp.supportsSetDesktopSize &&
-          !desktopSize.getValue().isEmpty() &&
-          desktopSize.getValue().split("x").length == 2) {
-        width = Integer.parseInt(desktopSize.getValue().split("x")[0]);
-        height = Integer.parseInt(desktopSize.getValue().split("x")[1]);
-        ScreenSet layout;
-
-        layout = cp.screenLayout;
-
-        if (layout.num_screens() == 0)
-          layout.add_screen(new Screen());
-        else if (layout.num_screens() != 1) {
-
-          while (true) {
-            Iterator<Screen> iter = layout.screens.iterator();
-            Screen screen = (Screen)iter.next();
-
-            if (!iter.hasNext())
-              break;
-
-            layout.remove_screen(screen.id);
-          }
-        }
-
-        Screen screen0 = (Screen)layout.screens.iterator().next();
-        screen0.dimensions.tl.x = 0;
-        screen0.dimensions.tl.y = 0;
-        screen0.dimensions.br.x = width;
-        screen0.dimensions.br.y = height;
-
-        writer().writeSetDesktopSize(width, height, layout);
-      }
-
       firstUpdate = false;
     }
 
     // A format change has been scheduled and we are now past the update
     // with the old format. Time to active the new one.
     if (pendingPFChange) {
-      desktop.setServerPF(pendingPF);
       cp.setPF(pendingPF);
       pendingPFChange = false;
     }
@@ -400,16 +398,19 @@ public class CConn extends CConnection implements
 
   // The rest of the callbacks are fairly self-explanatory...
 
-  public void setColourMapEntries(int firstColor, int nColors, int[] rgbs) {
-    desktop.setColourMapEntries(firstColor, nColors, rgbs);
+  public void setColourMapEntries(int firstColor, int nColors, int[] rgbs)
+  {
+    vlog.error("Invalid SetColourMapEntries from server!");
   }
 
-  public void bell() {
+  public void bell()
+  {
     if (acceptBell.getValue())
       desktop.getToolkit().beep();
   }
 
-  public void serverCutText(String str, int len) {
+  public void serverCutText(String str, int len)
+  {
     StringSelection buffer;
 
     if (!acceptClipboard.getValue())
@@ -418,34 +419,21 @@ public class CConn extends CConnection implements
     ClipboardDialog.serverCutText(str);
   }
 
-  // We start timing on beginRect and stop timing on endRect, to
-  // avoid skewing the bandwidth estimation as a result of the server
-  // being slow or the network having high latency
-  public void beginRect(Rect r, int encoding) {
+  public void dataRect(Rect r, int encoding)
+  {
     sock.inStream().startTiming();
-    if (encoding != Encodings.encodingCopyRect) {
-      lastServerEncoding = encoding;
-    }
-  }
 
-  public void endRect(Rect r, int encoding) {
-    sock.inStream().stopTiming();
-  }
-
-  public void fillRect(Rect r, int p) {
-    desktop.fillRect(r.tl.x, r.tl.y, r.width(), r.height(), p);
-  }
+    if (encoding != Encodings.encodingCopyRect)
+      lastServerEncoding = encoding;
 
-  public void imageRect(Rect r, Object p) {
-    desktop.imageRect(r.tl.x, r.tl.y, r.width(), r.height(), p);
-  }
+    super.dataRect(r, encoding);
 
-  public void copyRect(Rect r, int sx, int sy) {
-    desktop.copyRect(r.tl.x, r.tl.y, r.width(), r.height(), sx, sy);
+    sock.inStream().stopTiming();
   }
 
   public void setCursor(int width, int height, Point hotspot,
-                        int[] data, byte[] mask) {
+                        byte[] data, byte[] mask)
+  {
     desktop.setCursor(width, height, hotspot, data, mask);
   }
 
@@ -480,11 +468,11 @@ public class CConn extends CConnection implements
 
       pf.read(memStream);
 
-      desktop.setServerPF(pf);
       cp.setPF(pf);
     }
   }
 
+  ////////////////////// Internal methods //////////////////////
   private void resizeFramebuffer()
   {
     if (desktop == null)
@@ -493,82 +481,7 @@ public class CConn extends CConnection implements
     if (continuousUpdates)
       writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height);
 
-    if ((cp.width == 0) && (cp.height == 0))
-      return;
-    if ((desktop.width() == cp.width) && (desktop.height() == cp.height))
-      return;
-
-    desktop.resize();
-    if (!firstUpdate)
-      recreateViewport();
-  }
-  
-  // recreateViewport() recreates our top-level window.  This seems to be
-  // better than attempting to resize the existing window, at least with
-  // various X window managers.
-
-  public void recreateViewport() {
-    if (embed.getValue()) {
-      desktop.setViewport(VncViewer.getViewport());
-      Container viewer =
-        SwingUtilities.getAncestorOfClass(JApplet.class, desktop);
-      viewer.addFocusListener(new FocusAdapter() {
-        public void focusGained(FocusEvent e) {
-          Container c =
-            SwingUtilities.getAncestorOfClass(JApplet.class, desktop);
-          if (c != null && desktop.isAncestorOf(c))
-            desktop.requestFocus();
-        }
-        public void focusLost(FocusEvent e) {
-          releaseDownKeys();
-        }
-      });
-      viewer.validate();
-      desktop.requestFocus();
-    } else {
-      if (viewport != null)
-        viewport.dispose();
-      viewport = new Viewport(cp.name(), this);
-      viewport.setUndecorated(fullScreen.getValue());
-      desktop.setViewport(viewport.getViewport());
-      reconfigureViewport();
-      if ((cp.width > 0) && (cp.height > 0))
-        viewport.setVisible(true);
-      desktop.requestFocusInWindow();
-    }
-  }
-
-  private void reconfigureViewport() {
-    Dimension dpySize = viewport.getScreenSize();
-    int w = desktop.scaledWidth;
-    int h = desktop.scaledHeight;
-    if (fullScreen.getValue()) {
-      if (!fullScreenAllMonitors.getValue())
-        viewport.setExtendedState(JFrame.MAXIMIZED_BOTH);
-      viewport.setBounds(viewport.getScreenBounds());
-      if (!fullScreenAllMonitors.getValue())
-        Viewport.setFullScreenWindow(viewport);
-    } else {
-      int wmDecorationWidth = viewport.getInsets().left + viewport.getInsets().right;
-      int wmDecorationHeight = viewport.getInsets().top + viewport.getInsets().bottom;
-      if (w + wmDecorationWidth >= dpySize.width)
-        w = dpySize.width - wmDecorationWidth;
-      if (h + wmDecorationHeight >= dpySize.height)
-        h = dpySize.height - wmDecorationHeight;
-      if (viewport.getExtendedState() == JFrame.MAXIMIZED_BOTH) {
-        w = viewport.getSize().width;
-        h = viewport.getSize().height;
-        int x = viewport.getLocation().x;
-        int y = viewport.getLocation().y;
-        viewport.setGeometry(x, y, w, h);
-      } else {
-        int x = (dpySize.width - w - wmDecorationWidth) / 2;
-        int y = (dpySize.height - h - wmDecorationHeight)/2;
-        viewport.setExtendedState(JFrame.NORMAL);
-        viewport.setGeometry(x, y, w, h);
-      }
-      Viewport.setFullScreenWindow(null);
-    }
+    desktop.resizeFramebuffer(cp.width, cp.height);
   }
 
   // autoSelectFormatAndEncoding() chooses the format and encoding appropriate
@@ -586,11 +499,12 @@ public class CConn extends CConnection implements
   //   Note: The system here is fairly arbitrary and should be replaced
   //         with something more intelligent at the server end.
   //
-  private void autoSelectFormatAndEncoding() {
+  private void autoSelectFormatAndEncoding()
+  {
     long kbitsPerSecond = sock.inStream().kbitsPerSecond();
     long timeWaited = sock.inStream().timeWaited();
     boolean newFullColor = fullColor.getValue();
-    int newQualityLevel = cp.qualityLevel;
+    int newQualityLevel = qualityLevel.getValue();
 
     // Always use Tight
     if (currentEncoding != Encodings.encodingTight) {
@@ -603,13 +517,13 @@ public class CConn extends CConnection implements
       return;
 
     // Select appropriate quality level
-    if (!cp.noJpeg) {
+    if (!noJpeg.getValue()) {
       if (kbitsPerSecond > 16000)
         newQualityLevel = 8;
       else
         newQualityLevel = 6;
 
-      if (newQualityLevel != cp.qualityLevel) {
+      if (newQualityLevel != qualityLevel.getValue()) {
         vlog.info("Throughput "+kbitsPerSecond+
                   " kbit/s - changing to quality "+newQualityLevel);
         cp.qualityLevel = newQualityLevel;
@@ -637,7 +551,17 @@ public class CConn extends CConnection implements
                 (newFullColor ? "enabled" : "disabled"));
       fullColor.setParam(newFullColor);
       formatChange = true;
-      forceNonincremental = true;
+    }
+  }
+
+  // checkEncodings() sends a setEncodings message if one is needed.
+  private void checkEncodings()
+  {
+    if (encodingChange && (writer() != null)) {
+      vlog.info("Using " + Encodings.encodingName(currentEncoding) +
+        " encoding");
+      writer().writeSetEncodings(currentEncoding, true);
+      encodingChange = false;
     }
   }
 
@@ -699,84 +623,9 @@ public class CConn extends CConnection implements
     forceNonincremental = false;
   }
 
-
-  ////////////////////////////////////////////////////////////////////
-  // The following methods are all called from the GUI thread
-
-  // close() shuts down the socket, thus waking up the RFB thread.
-  public void close() {
-    if (closeListener != null) {
-      embed.setParam(true);
-      JFrame f =
-        (JFrame)SwingUtilities.getAncestorOfClass(JFrame.class, desktop);
-      if (f != null)
-        f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
-    }
-    deleteWindow();
-    shuttingDown = true;
-    try {
-      if (sock != null)
-        sock.shutdown();
-    } catch (java.lang.Exception e) {
-      throw new Exception(e.getMessage());
-    }
-  }
-
-  void showInfo() {
-    Window fullScreenWindow = Viewport.getFullScreenWindow();
-    if (fullScreenWindow != null)
-      Viewport.setFullScreenWindow(null);
-    String info = new String("Desktop name: %s%n"+
-                             "Host: %s:%d%n"+
-                             "Size: %dx%d%n"+
-                             "Pixel format: %s%n"+
-                             "  (server default: %s)%n"+
-                             "Requested encoding: %s%n"+
-                             "Last used encoding: %s%n"+
-                             "Line speed estimate: %d kbit/s%n"+
-                             "Protocol version: %d.%d%n"+
-                             "Security method: %s [%s]%n");
-    String msg =
-      String.format(info, cp.name(),
-                    sock.getPeerName(), sock.getPeerPort(),
-                    cp.width, cp.height,
-                    desktop.getPF().print(),
-                    serverPF.print(),
-                    Encodings.encodingName(currentEncoding),
-                    Encodings.encodingName(lastServerEncoding),
-                    sock.inStream().kbitsPerSecond(),
-                    cp.majorVersion, cp.minorVersion,
-                    Security.secTypeName(csecurity.getType()),
-                    csecurity.description());
-    Object[] options = {"Close  \u21B5"};
-    JOptionPane op =
-      new JOptionPane(msg, JOptionPane.PLAIN_MESSAGE,
-                      JOptionPane.DEFAULT_OPTION, null, options);
-    JDialog dlg = op.createDialog(desktop, "VNC connection info");
-    dlg.setIconImage(VncViewer.frameIcon);
-    dlg.setAlwaysOnTop(true);
-    dlg.setVisible(true);
-    if (fullScreenWindow != null)
-      Viewport.setFullScreenWindow(fullScreenWindow);
-  }
-
-  public void refresh() {
-    writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height), false);
-    pendingUpdate = true;
-  }
-
-  public synchronized int currentEncoding() {
-    return currentEncoding;
-  }
-
   public void handleOptions()
   {
 
-    if (viewport != null && viewport.isVisible()) {
-      viewport.toFront();
-      viewport.requestFocus();
-    }
-
     // Checking all the details of the current set of encodings is just
     // a pain. Assume something has changed, as resending the encoding
     // list is cheap. Avoid overriding what the auto logic has selected
@@ -788,7 +637,7 @@ public class CConn extends CConnection implements
         this.currentEncoding = encNum;
     }
 
-    this.cp.supportsLocalCursor = useLocalCursor.getValue();
+    this.cp.supportsLocalCursor = true;
 
     if (customCompressLevel.getValue())
       this.cp.compressLevel = compressLevel.getValue();
@@ -829,19 +678,68 @@ public class CConn extends CConnection implements
 
   }
 
-  public void toggleFullScreen() {
-    if (embed.getValue())
-      return;
-    fullScreen.setParam(!fullScreen.getValue());
-    if (viewport != null) {
-      if (!viewport.lionFSSupported()) {
-        recreateViewport();
-      } else {
-        viewport.toggleLionFS();
-      }
+  ////////////////////////////////////////////////////////////////////
+  // The following methods are all called from the GUI thread
+
+  // close() shuts down the socket, thus waking up the RFB thread.
+  public void close() {
+    if (closeListener != null) {
+      embed.setParam(true);
+      JFrame f =
+        (JFrame)SwingUtilities.getAncestorOfClass(JFrame.class, desktop);
+      if (f != null)
+        f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
+    }
+    shuttingDown = true;
+    try {
+      if (sock != null)
+        sock.shutdown();
+    } catch (java.lang.Exception e) {
+      throw new Exception(e.getMessage());
     }
   }
 
+  void showInfo() {
+    Window fullScreenWindow = DesktopWindow.getFullScreenWindow();
+    if (fullScreenWindow != null)
+      DesktopWindow.setFullScreenWindow(null);
+    String info = new String("Desktop name: %s%n"+
+                             "Host: %s:%d%n"+
+                             "Size: %dx%d%n"+
+                             "Pixel format: %s%n"+
+                             "  (server default: %s)%n"+
+                             "Requested encoding: %s%n"+
+                             "Last used encoding: %s%n"+
+                             "Line speed estimate: %d kbit/s%n"+
+                             "Protocol version: %d.%d%n"+
+                             "Security method: %s [%s]%n");
+    String msg =
+      String.format(info, cp.name(),
+                    sock.getPeerName(), sock.getPeerPort(),
+                    cp.width, cp.height,
+                    cp.pf().print(),
+                    serverPF.print(),
+                    Encodings.encodingName(currentEncoding),
+                    Encodings.encodingName(lastServerEncoding),
+                    sock.inStream().kbitsPerSecond(),
+                    cp.majorVersion, cp.minorVersion,
+                    Security.secTypeName(csecurity.getType()),
+                    csecurity.description());
+    JOptionPane op = new JOptionPane(msg, JOptionPane.PLAIN_MESSAGE,
+                                     JOptionPane.DEFAULT_OPTION);
+    JDialog dlg = op.createDialog(desktop, "VNC connection info");
+    dlg.setIconImage(VncViewer.frameIcon);
+    dlg.setAlwaysOnTop(true);
+    dlg.setVisible(true);
+    if (fullScreenWindow != null)
+      DesktopWindow.setFullScreenWindow(fullScreenWindow);
+  }
+
+  public void refresh() {
+    writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height), false);
+    pendingUpdate = true;
+  }
+
   // writeClientCutText() is called from the clipboard dialog
   public void writeClientCutText(String str, int len) {
     if (state() != RFBSTATE_NORMAL || shuttingDown)
@@ -860,7 +758,7 @@ public class CConn extends CConnection implements
       return;
 
     boolean down = (ev.getID() == KeyEvent.KEY_PRESSED);
-   
+
     int keySym, keyCode = ev.getKeyCode();
 
     // If neither the keyCode or keyChar are defined, then there's
@@ -875,9 +773,9 @@ public class CConn extends CConnection implements
       if (iter == null) {
         // Note that dead keys will raise this sort of error falsely
         // See https://bugs.openjdk.java.net/browse/JDK-6534883 
-        vlog.error("Unexpected key release of keyCode "+keyCode);
+        vlog.debug("Unexpected key release of keyCode "+keyCode);
         String fmt = ev.paramString().replaceAll("%","%%");
-        vlog.error(String.format(fmt.replaceAll(",","%n       ")));
+        vlog.debug(String.format(fmt.replaceAll(",","%n       ")));
 
         return;
       }
@@ -961,15 +859,6 @@ public class CConn extends CConnection implements
       break;
     }
 
-    if (cp.width != desktop.scaledWidth ||
-        cp.height != desktop.scaledHeight) {
-      int sx = (desktop.scaleWidthRatio == 1.00) ?
-        ev.getX() : (int)Math.floor(ev.getX() / desktop.scaleWidthRatio);
-      int sy = (desktop.scaleHeightRatio == 1.00) ?
-        ev.getY() : (int)Math.floor(ev.getY() / desktop.scaleHeightRatio);
-      ev.translatePoint(sx - ev.getX(), sy - ev.getY());
-    }
-
     writer().writePointerEvent(new Point(ev.getX(), ev.getY()), buttonMask);
   }
 
@@ -1014,16 +903,6 @@ public class CConn extends CConnection implements
   ////////////////////////////////////////////////////////////////////
   // The following methods are called from both RFB and GUI threads
 
-  // checkEncodings() sends a setEncodings message if one is needed.
-  private void checkEncodings() {
-    if (encodingChange && (writer() != null)) {
-      vlog.info("Requesting " + Encodings.encodingName(currentEncoding) +
-        " encoding");
-      writer().writeSetEncodings(currentEncoding, true);
-      encodingChange = false;
-    }
-  }
-
   // the following never change so need no synchronization:
 
   // access to desktop by different threads is specified in DesktopWindow
@@ -1031,7 +910,6 @@ public class CConn extends CConnection implements
   // the following need no synchronization:
 
   public static UserPasswdGetter upg;
-  public UserMsgBox msg;
 
   // shuttingDown is set by the GUI thread and only ever tested by the RFB
   // thread after the window has been destroyed.
@@ -1072,7 +950,6 @@ public class CConn extends CConnection implements
 
   private boolean supportsSyncFence;
 
-  Viewport viewport;
   private HashMap<Integer, Integer> downKeySym;
   public ActionListener closeListener = null;
 
index de2d2cd51487654b80fd09859a0ef87520a4157b..e76a01567583af2087a2e59000cc9ab888b58f3d 100644 (file)
@@ -1,8 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright (C) 2006 Constantin Kaplinsky.  All Rights Reserved.
- * Copyright (C) 2009 Paul Donohue.  All Rights Reserved.
- * Copyright (C) 2010, 2012-2013 D. R. Commander.  All Rights Reserved.
- * Copyright (C) 2011-2014 Brian P. Hinz
+ * Copyright (C) 2011-2016 Brian P. Hinz
+ * Copyright (C) 2012-2013 D. R. Commander.  All Rights Reserved.
  *
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * USA.
  */
 
-//
-// DesktopWindow is an AWT Canvas representing a VNC desktop.
-//
-// Methods on DesktopWindow are called from both the GUI thread and the thread
-// which processes incoming RFB messages ("the RFB thread").  This means we
-// need to be careful with synchronization here.
-//
-
 package com.tigervnc.vncviewer;
+
 import java.awt.*;
 import java.awt.event.*;
-import java.awt.image.*;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.Transferable;
-import java.awt.datatransfer.Clipboard;
-import java.io.BufferedReader;
-import java.nio.CharBuffer;
+import java.lang.reflect.*;
+import java.util.*;
 import javax.swing.*;
+import javax.swing.Timer;
+import javax.swing.border.*;
 
 import com.tigervnc.rfb.*;
-import com.tigervnc.rfb.Cursor;
 import com.tigervnc.rfb.Point;
+import java.lang.Exception;
+
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
+import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
 
 import static com.tigervnc.vncviewer.Parameters.*;
 
-class DesktopWindow extends JPanel implements Runnable, MouseListener,
-  MouseMotionListener, MouseWheelListener, KeyListener {
+public class DesktopWindow extends JFrame
+{
 
-  ////////////////////////////////////////////////////////////////////
-  // The following methods are all called from the RFB thread
+  static LogWriter vlog = new LogWriter("DesktopWindow");
 
-  public DesktopWindow(int width, int height, String name, PixelFormat serverPF,
-                       CConn cc_) {
+  public DesktopWindow(int w, int h, String name,
+                       PixelFormat serverPF, CConn cc_)
+  {
     cc = cc_;
-    setSize(width, height);
-    setScaledSize();
-    setOpaque(false);
-    if (cc.viewport != null)
-      cc.viewport.setName(name);
-    GraphicsEnvironment ge =
-      GraphicsEnvironment.getLocalGraphicsEnvironment();
-    GraphicsDevice gd = ge.getDefaultScreenDevice();
-    GraphicsConfiguration gc = gd.getDefaultConfiguration();
-    BufferCapabilities bufCaps = gc.getBufferCapabilities();
-    ImageCapabilities imgCaps = gc.getImageCapabilities();
-    if (bufCaps.isPageFlipping() || bufCaps.isMultiBufferAvailable() ||
-        imgCaps.isAccelerated()) {
-      vlog.debug("GraphicsDevice supports HW acceleration.");
-    } else {
-      vlog.debug("GraphicsDevice does not support HW acceleration.");
-    }
-    im = new BIPixelBuffer(serverPF, width, height, this);
-
-    cursor = new Cursor();
-    cursorBacking = new ManagedPixelBuffer();
-    Dimension bestSize = tk.getBestCursorSize(16, 16);
-    BufferedImage cursorImage;
-    cursorImage = new BufferedImage(bestSize.width, bestSize.height,
-                                    BufferedImage.TYPE_INT_ARGB);
-    java.awt.Point hotspot = new java.awt.Point(0,0);
-    nullCursor = tk.createCustomCursor(cursorImage, hotspot, "nullCursor");
-    cursorImage.flush();
-    if (!cc.cp.supportsLocalCursor && !bestSize.equals(new Dimension(0,0)))
-      setCursor(nullCursor);
-    addMouseListener(this);
-    addMouseWheelListener(this);
-    addMouseMotionListener(this);
-    addKeyListener(this);
-    addFocusListener(new FocusAdapter() {
-      public void focusGained(FocusEvent e) {
-        ClipboardDialog.clientCutText();
+    firstUpdate = true;
+    delayedFullscreen = false; delayedDesktopSize = false;
+
+    setFocusable(false);
+    setFocusTraversalKeysEnabled(false);
+    getToolkit().setDynamicLayout(false);
+    if (!VncViewer.os.startsWith("mac os x"))
+      setIconImage(VncViewer.frameIcon);
+    UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
+      new UIDefaults.LazyInputMap(new Object[]{}));
+    scroll = new JScrollPane(new Viewport(w, h, serverPF, cc));
+    viewport = (Viewport)scroll.getViewport().getView();
+    scroll.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
+    getContentPane().add(scroll);
+
+    setName(name);
+
+    lastScaleFactor = scalingFactor.getValue();
+    if (VncViewer.os.startsWith("mac os x"))
+      if (!noLionFS.getValue())
+        enableLionFS();
+
+    OptionsDialog.addCallback("handleOptions", this);
+
+    addWindowFocusListener(new WindowAdapter() {
+      public void windowGainedFocus(WindowEvent e) {
+        if (isVisible())
+          if (scroll.getViewport() != null)
+            scroll.getViewport().getView().requestFocusInWindow();
       }
-      public void focusLost(FocusEvent e) {
+      public void windowLostFocus(WindowEvent e) {
         cc.releaseDownKeys();
       }
     });
-    setFocusTraversalKeysEnabled(false);
-    setFocusable(true);
-    OptionsDialog.addCallback("handleOptions", this);
-  }
 
-  public int width() {
-    return getWidth();
-  }
+    addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        cc.close();
+      }
+      public void windowDeiconified(WindowEvent e) {
+        // ViewportBorder sometimes lost when window is shaded or de-iconified
+        repositionViewport();
+      }
+    });
 
-  public int height() {
-    return getHeight();
-  }
+    addWindowStateListener(new WindowAdapter() {
+      public void windowStateChanged(WindowEvent e) {
+        int state = e.getNewState();
+        if ((state & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
+          Rectangle b = getGraphicsConfiguration().getBounds();
+          if (!b.contains(getLocationOnScreen()))
+            setLocation((int)b.getX(), (int)b.getY());
+        }
+        // ViewportBorder sometimes lost when restoring on Windows
+        repositionViewport();
+      }
+    });
 
-  public final PixelFormat getPF() { return im.getPF(); }
+    // Window resize events
+    timer = new Timer(500, new AbstractAction() {
+      public void actionPerformed(ActionEvent e) {
+        handleResizeTimeout();
+      }
+    });
+    timer.setRepeats(false);
+    addComponentListener(new ComponentAdapter() {
+      public void componentResized(ComponentEvent e) {
+        if (remoteResize.getValue()) {
+          if (timer.isRunning())
+            timer.restart();
+          else
+            // Try to get the remote size to match our window size, provided
+            // the following conditions are true:
+            //
+            // a) The user has this feature turned on
+            // b) The server supports it
+            // c) We're not still waiting for a chance to handle DesktopSize
+            // d) We're not still waiting for startup fullscreen to kick in
+            if (!firstUpdate && !delayedFullscreen &&
+                remoteResize.getValue() && cc.cp.supportsSetDesktopSize)
+              timer.start();
+        } else {
+          String scaleString = scalingFactor.getValue();
+          if (!scaleString.matches("^[0-9]+$")) {
+            Dimension maxSize = getContentPane().getSize();
+            if ((maxSize.width != viewport.scaledWidth) ||
+                (maxSize.height != viewport.scaledHeight))
+              viewport.setScaledSize(maxSize.width, maxSize.height);
+            if (!scaleString.equals("Auto")) {
+              if (!isMaximized() && !fullscreen_active()) {
+                int dx = getInsets().left + getInsets().right;
+                int dy = getInsets().top + getInsets().bottom;
+                setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
+              }
+            }
+          }
+          repositionViewport();
+        }
+      }
+    });
 
-  public void setViewport(JViewport viewport) {
-    setScaledSize();
-    viewport.setView(this);
-    // pack() must be called on a JFrame before getInsets()
-    // will return anything other than 0.
-    if (viewport.getRootPane() != null)
-      if (getRootPane().getParent() instanceof JFrame)
-        ((JFrame)getRootPane().getParent()).pack();
-  }
+    pack();
+    if (embed.getValue()) {
+      scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+      scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+      VncViewer.setupEmbeddedFrame(scroll);
+    } else {
+      if (fullScreen.getValue())
+        fullscreen_on();
+      else
+        setVisible(true);
 
-  // Methods called from the RFB thread - these need to be synchronized
-  // wherever they access data shared with the GUI thread.
+      if (maximize.getValue())
+        setExtendedState(JFrame.MAXIMIZED_BOTH);
+    }
 
-  public void setCursor(int w, int h, Point hotspot,
-                        int[] data, byte[] mask) {
-    // strictly we should use a mutex around this test since useLocalCursor
-    // might be being altered by the GUI thread.  However it's only a single
-    // boolean and it doesn't matter if we get the wrong value anyway.
+  }
 
-    if (!useLocalCursor.getValue())
-      return;
+  // Remove resize listener in order to prevent recursion when resizing
+  @Override
+  public void setSize(Dimension d)
+  {
+    ComponentListener[] listeners = getListeners(ComponentListener.class);
+    for (ComponentListener l : listeners)
+      removeComponentListener(l);
+    super.setSize(d);
+    for (ComponentListener l : listeners)
+      addComponentListener(l);
+  }
 
-    hideLocalCursor();
+  @Override
+  public void setSize(int width, int height)
+  {
+    ComponentListener[] listeners = getListeners(ComponentListener.class);
+    for (ComponentListener l : listeners)
+      removeComponentListener(l);
+    super.setSize(width, height);
+    for (ComponentListener l : listeners)
+      addComponentListener(l);
+  }
 
-    cursor.hotspot = (hotspot != null) ? hotspot : new Point(0, 0);
-    cursor.setSize(w, h);
-    cursor.setPF(getPF());
+  @Override
+  public void setBounds(Rectangle r)
+  {
+    ComponentListener[] listeners = getListeners(ComponentListener.class);
+    for (ComponentListener l : listeners)
+      removeComponentListener(l);
+    super.setBounds(r);
+    for (ComponentListener l : listeners)
+      addComponentListener(l);
+  }
 
-    cursorBacking.setSize(cursor.width(), cursor.height());
-    cursorBacking.setPF(getPF());
+  private void repositionViewport()
+  {
+    scroll.revalidate();
+    Rectangle r = scroll.getViewportBorderBounds();
+    int dx = r.width - viewport.scaledWidth;
+    int dy = r.height - viewport.scaledHeight;
+    int top = (int)Math.max(Math.floor(dy/2), 0);
+    int left = (int)Math.max(Math.floor(dx/2), 0);
+    int bottom = (int)Math.max(dy - top, 0);
+    int right = (int)Math.max(dx - left, 0);
+    Insets insets = new Insets(top, left, bottom, right);
+    scroll.setViewportBorder(new MatteBorder(insets, Color.BLACK));
+    scroll.revalidate();
+  }
+
+  public PixelFormat getPreferredPF()
+  {
+    return viewport.getPreferredPF();
+  }
 
-    cursor.data = new int[cursor.width() * cursor.height()];
-    cursor.mask = new byte[cursor.maskLen()];
+  public void setName(String name)
+  {
+    setTitle(name);
+  }
+
+       // Copy the areas of the framebuffer that have been changed (damaged)
+       // to the displayed window.
+
+       public void updateWindow()
+       {
+         if (firstUpdate) {
+           if (cc.cp.supportsSetDesktopSize && !desktopSize.getValue().equals("")) {
+             // Hack: Wait until we're in the proper mode and position until
+             // resizing things, otherwise we might send the wrong thing.
+             if (delayedFullscreen)
+               delayedDesktopSize = true;
+             else
+               handleDesktopSize();
+           }
+           firstUpdate = false;
+         }
+
+         viewport.updateWindow();
+       }
+
+  public void resizeFramebuffer(int new_w, int new_h)
+  {
+    if ((new_w == viewport.scaledWidth) && (new_h == viewport.scaledHeight))
+      return;
 
-    int maskBytesPerRow = (w + 7) / 8;
-    for (int y = 0; y < h; y++) {
-      for (int x = 0; x < w; x++) {
-        int byte_ = y * maskBytesPerRow + x / 8;
-        int bit = 7 - x % 8;
-        if ((mask[byte_] & (1 << bit)) > 0) {
-          cursor.data[y * cursor.width() + x] = (0xff << 24) |
-            (im.cm.getRed(data[y * w + x]) << 16) |
-            (im.cm.getGreen(data[y * w + x]) << 8) |
-            (im.cm.getBlue(data[y * w + x]));
-        }
+    // If we're letting the viewport match the window perfectly, then
+    // keep things that way for the new size, otherwise just keep things
+    // like they are.
+    int dx = getInsets().left + getInsets().right;
+    int dy = getInsets().top + getInsets().bottom;
+    if (!fullscreen_active()) {
+      if ((w() == viewport.scaledWidth) && (h() == viewport.scaledHeight))
+        setSize(new_w+dx, new_h+dy);
+      else {
+        // Make sure the window isn't too big. We do this manually because
+        // we have to disable the window size restriction (and it isn't
+        // entirely trustworthy to begin with).
+        if ((w() > new_w) || (h() > new_h))
+          setSize(Math.min(w(), new_w)+dx, Math.min(h(), new_h)+dy);
       }
-      System.arraycopy(mask, y * maskBytesPerRow, cursor.mask,
-        y * ((cursor.width() + 7) / 8), maskBytesPerRow);
     }
 
-    int cw = (int)Math.floor((float)cursor.width() * scaleWidthRatio);
-    int ch = (int)Math.floor((float)cursor.height() * scaleHeightRatio);
-    Dimension bestSize = tk.getBestCursorSize(cw, ch);
-    MemoryImageSource cursorSrc;
-    cursorSrc = new MemoryImageSource(cursor.width(), cursor.height(),
-                                      ColorModel.getRGBdefault(),
-                                      cursor.data, 0, cursor.width());
-    Image srcImage = tk.createImage(cursorSrc);
-    BufferedImage cursorImage;
-    cursorImage = new BufferedImage(bestSize.width, bestSize.height,
-                                    BufferedImage.TYPE_INT_ARGB);
-    Graphics2D g2 = cursorImage.createGraphics();
-    g2.setRenderingHint(RenderingHints.KEY_RENDERING,
-                        RenderingHints.VALUE_RENDER_SPEED);
-    g2.drawImage(srcImage, 0, 0, (int)Math.min(cw, bestSize.width),
-                 (int)Math.min(ch, bestSize.height), 0, 0, cursor.width(),
-                 cursor.height(), null);
-    g2.dispose();
-    srcImage.flush();
-
-    int x = (int)Math.floor((float)cursor.hotspot.x * scaleWidthRatio);
-    int y = (int)Math.floor((float)cursor.hotspot.y * scaleHeightRatio);
-    x = (int)Math.min(x, Math.max(bestSize.width - 1, 0));
-    y = (int)Math.min(y, Math.max(bestSize.height - 1, 0));
-    java.awt.Point hs = new java.awt.Point(x, y);
-    if (!bestSize.equals(new Dimension(0, 0)))
-      softCursor = tk.createCustomCursor(cursorImage, hs, "softCursor");
-    cursorImage.flush();
-
-    if (softCursor != null) {
-      setCursor(softCursor);
-      cursorAvailable = false;
-      return;
-    }
+    viewport.resize(0, 0, new_w, new_h);
 
-    if (!cursorAvailable) {
-      cursorAvailable = true;
-    }
+    // We might not resize the main window, so we need to manually call this
+    // to make sure the viewport is centered.
+    repositionViewport();
 
-    showLocalCursor();
+    // repositionViewport() makes sure the scroll widget notices any changes
+    // in position, but it might be just the size that changes so we also
+    // need a poke here as well.
+    validate();
   }
 
-  public void setServerPF(PixelFormat pf) {
-    im.setPF(pf);
+  public void setCursor(int width, int height, Point hotspot,
+                        byte[] data, byte[] mask)
+  {
+    viewport.setCursor(width, height, hotspot, data, mask);
   }
 
-  public PixelFormat getPreferredPF() {
-    return im.getNativePF();
+  public void fullscreen_on()
+  {
+    fullScreen.setParam(true);
+    lastState = getExtendedState();
+    lastBounds = getBounds();
+    dispose();
+    // Screen bounds calculation affected by maximized window?
+    setExtendedState(JFrame.NORMAL);
+    setUndecorated(true);
+    setVisible(true);
+    setBounds(getScreenBounds());
+  }
+
+  public void fullscreen_off()
+  {
+    fullScreen.setParam(false);
+    dispose();
+    setUndecorated(false);
+    setExtendedState(lastState);
+    setBounds(lastBounds);
+    setVisible(true);
   }
 
-  // setColourMapEntries() changes some of the entries in the colourmap.
-  // Unfortunately these messages are often sent one at a time, so we delay the
-  // settings taking effect unless the whole colourmap has changed.  This is
-  // because getting java to recalculate its internal translation table and
-  // redraw the screen is expensive.
-
-  public synchronized void setColourMapEntries(int firstColour, int nColours,
-                                               int[] rgbs) {
-    im.setColourMapEntries(firstColour, nColours, rgbs);
-    if (nColours <= 256) {
-      im.updateColourMap();
-    } else {
-      if (setColourMapEntriesTimerThread == null) {
-        setColourMapEntriesTimerThread = new Thread(this);
-        setColourMapEntriesTimerThread.start();
-      }
-    }
+  public boolean fullscreen_active()
+  {
+    return isUndecorated();
   }
 
-  // Update the actual window with the changed parts of the framebuffer.
-  public void updateWindow() {
-    Rect r = damage;
-    if (!r.is_empty()) {
-      if (cc.cp.width != scaledWidth || cc.cp.height != scaledHeight) {
-        int x = (int)Math.floor(r.tl.x * scaleWidthRatio);
-        int y = (int)Math.floor(r.tl.y * scaleHeightRatio);
-        // Need one extra pixel to account for rounding.
-        int width = (int)Math.ceil(r.width() * scaleWidthRatio) + 1;
-        int height = (int)Math.ceil(r.height() * scaleHeightRatio) + 1;
-        paintImmediately(x, y, width, height);
-      } else {
-        paintImmediately(r.tl.x, r.tl.y, r.width(), r.height());
-      }
-      damage.clear();
-    }
-  }
+  private void handleDesktopSize()
+  {
+    if (!desktopSize.getValue().equals("")) {
+      int width, height;
 
-  // resize() is called when the desktop has changed size
-  public void resize() {
-    int w = cc.cp.width;
-    int h = cc.cp.height;
-    hideLocalCursor();
-    setSize(w, h);
-    setScaledSize();
-    im.resize(w, h);
-  }
+      // An explicit size has been requested
 
-  public final void fillRect(int x, int y, int w, int h, int pix) {
-    if (overlapsCursor(x, y, w, h)) hideLocalCursor();
-    im.fillRect(x, y, w, h, pix);
-    damageRect(new Rect(x, y, x+w, y+h));
-    showLocalCursor();
-  }
+      if (desktopSize.getValue().split("x").length != 2)
+        return;
 
-  public final void imageRect(int x, int y, int w, int h,
-                              Object pix) {
-    if (overlapsCursor(x, y, w, h)) hideLocalCursor();
-    im.imageRect(x, y, w, h, pix);
-    damageRect(new Rect(x, y, x+w, y+h));
-    showLocalCursor();
+      width = Integer.parseInt(desktopSize.getValue().split("x")[0]);
+      height = Integer.parseInt(desktopSize.getValue().split("x")[1]);
+      remoteResize(width, height);
+    } else if (remoteResize.getValue()) {
+      // No explicit size, but remote resizing is on so make sure it
+      // matches whatever size the window ended up being
+      remoteResize(w(), h());
+    }
   }
 
-  public final void copyRect(int x, int y, int w, int h,
-                             int srcX, int srcY) {
-    if (overlapsCursor(x, y, w, h) || overlapsCursor(srcX, srcY, w, h))
-      hideLocalCursor();
-    im.copyRect(x, y, w, h, srcX, srcY);
-    damageRect(new Rect(x, y, x+w, y+h));
-    showLocalCursor();
-  }
+  public void handleResizeTimeout()
+  {
+    DesktopWindow self = (DesktopWindow)this;
 
+    assert(self != null);
 
-  // mutex MUST be held when overlapsCursor() is called
-  final boolean overlapsCursor(int x, int y, int w, int h) {
-    return (x < cursorBackingX + cursorBacking.width() &&
-            y < cursorBackingY + cursorBacking.height() &&
-            x + w > cursorBackingX && y + h > cursorBackingY);
+    self.remoteResize(self.w(), self.h());
   }
 
+  private void remoteResize(int width, int height)
+  {
+    ScreenSet layout;
+    ListIterator<Screen> iter;
 
-  ////////////////////////////////////////////////////////////////////
-  // The following methods are all called from the GUI thread
+    if (!fullscreen_active() || (width > w()) || (height > h())) {
+      // In windowed mode (or the framebuffer is so large that we need
+      // to scroll) we just report a single virtual screen that covers
+      // the entire framebuffer.
 
-  void resetLocalCursor() {
-    if (cc.cp.supportsLocalCursor) {
-      if (softCursor != null)
-        setCursor(softCursor);
-    } else {
-      setCursor(nullCursor);
-    }
-    hideLocalCursor();
-    cursorAvailable = false;
-  }
+      layout = cc.cp.screenLayout;
 
-  //
-  // Callback methods to determine geometry of our Component.
-  //
+      // Not sure why we have no screens, but adding a new one should be
+      // safe as there is nothing to conflict with...
+      if (layout.num_screens() == 0)
+        layout.add_screen(new Screen());
+      else if (layout.num_screens() != 1) {
+        // More than one screen. Remove all but the first (which we
+        // assume is the "primary").
 
-  public Dimension getPreferredSize() {
-    return new Dimension(scaledWidth, scaledHeight);
-  }
+        while (true) {
+          iter = layout.begin();
+          Screen screen = iter.next();
 
-  public Dimension getMinimumSize() {
-    return new Dimension(scaledWidth, scaledHeight);
-  }
+          if (iter == layout.end())
+            break;
 
-  public Dimension getMaximumSize() {
-    return new Dimension(scaledWidth, scaledHeight);
-  }
+          layout.remove_screen(screen.id);
+        }
+      }
 
-  public void setScaledSize() {
-    String scaleString = scalingFactor.getValue();
-    if (!scaleString.equalsIgnoreCase("Auto") &&
-        !scaleString.equalsIgnoreCase("FixedRatio")) {
-      int scalingFactor = Integer.parseInt(scaleString);
-      scaledWidth =
-        (int)Math.floor((float)cc.cp.width * (float)scalingFactor/100.0);
-      scaledHeight =
-        (int)Math.floor((float)cc.cp.height * (float)scalingFactor/100.0);
+      // Resize the remaining single screen to the complete framebuffer
+      ((Screen)layout.begin().next()).dimensions.tl.x = 0;
+      ((Screen)layout.begin().next()).dimensions.tl.y = 0;
+      ((Screen)layout.begin().next()).dimensions.br.x = width;
+      ((Screen)layout.begin().next()).dimensions.br.y = height;
     } else {
-      if (cc.viewport == null) {
-        scaledWidth = cc.cp.width;
-        scaledHeight = cc.cp.height;
-      } else {
-        Dimension vpSize = cc.viewport.getSize();
-        Insets vpInsets = cc.viewport.getInsets();
-        Dimension availableSize =
-          new Dimension(vpSize.width - vpInsets.left - vpInsets.right,
-                        vpSize.height - vpInsets.top - vpInsets.bottom);
-        if (availableSize.width == 0 || availableSize.height == 0)
-          availableSize = new Dimension(cc.cp.width, cc.cp.height);
-        if (scaleString.equalsIgnoreCase("FixedRatio")) {
-          float widthRatio = (float)availableSize.width / (float)cc.cp.width;
-          float heightRatio = (float)availableSize.height / (float)cc.cp.height;
-          float ratio = Math.min(widthRatio, heightRatio);
-          scaledWidth = (int)Math.floor(cc.cp.width * ratio);
-          scaledHeight = (int)Math.floor(cc.cp.height * ratio);
-        } else {
-          scaledWidth = availableSize.width;
-          scaledHeight = availableSize.height;
+      layout = new ScreenSet();
+      int id;
+      int sx, sy, sw, sh;
+      Rect viewport_rect = new Rect();
+      Rect screen_rect = new Rect();
+
+      // In full screen we report all screens that are fully covered.
+
+      viewport_rect.setXYWH(x() + (w() - width)/2, y() + (h() - height)/2,
+                            width, height);
+
+      // If we can find a matching screen in the existing set, we use
+      // that, otherwise we create a brand new screen.
+      //
+      // FIXME: We should really track screens better so we can handle
+      //        a resized one.
+      //
+      GraphicsEnvironment ge =
+        GraphicsEnvironment.getLocalGraphicsEnvironment();
+      for (GraphicsDevice gd : ge.getScreenDevices()) {
+        for (GraphicsConfiguration gc : gd.getConfigurations()) {
+          Rectangle bounds = gc.getBounds();
+          sx = bounds.x;
+          sy = bounds.y;
+          sw = bounds.width;
+          sh = bounds.height;
+
+          // Check that the screen is fully inside the framebuffer
+          screen_rect.setXYWH(sx, sy, sw, sh);
+          if (!screen_rect.enclosed_by(viewport_rect))
+            continue;
+
+          // Adjust the coordinates so they are relative to our viewport
+          sx -= viewport_rect.tl.x;
+          sy -= viewport_rect.tl.y;
+
+          // Look for perfectly matching existing screen...
+          for (iter = cc.cp.screenLayout.begin();
+              iter != cc.cp.screenLayout.end(); iter.next()) {
+            Screen screen = iter.next(); iter.previous();
+            if ((screen.dimensions.tl.x == sx) &&
+                (screen.dimensions.tl.y == sy) &&
+                (screen.dimensions.width() == sw) &&
+                (screen.dimensions.height() == sh))
+              break;
+          }
+
+          // Found it?
+          if (iter != cc.cp.screenLayout.end()) {
+            layout.add_screen(iter.next());
+            continue;
+          }
+
+          // Need to add a new one, which means we need to find an unused id
+          Random rng = new Random();
+          while (true) {
+            id = rng.nextInt();
+            for (iter = cc.cp.screenLayout.begin();
+                iter != cc.cp.screenLayout.end(); iter.next()) {
+              Screen screen = iter.next(); iter.previous();
+              if (screen.id == id)
+                break;
+            }
+
+            if (iter == cc.cp.screenLayout.end())
+              break;
+          }
+
+          layout.add_screen(new Screen(id, sx, sy, sw, sh, 0));
         }
+
+        // If the viewport doesn't match a physical screen, then we might
+        // end up with no screens in the layout. Add a fake one...
+        if (layout.num_screens() == 0)
+          layout.add_screen(new Screen(0, 0, 0, width, height, 0));
       }
     }
-    scaleWidthRatio = (float)scaledWidth / (float)cc.cp.width;
-    scaleHeightRatio = (float)scaledHeight / (float)cc.cp.height;
-  }
 
-  public void paintComponent(Graphics g) {
-    Graphics2D g2 = (Graphics2D) g;
-    if (cc.cp.width != scaledWidth || cc.cp.height != scaledHeight) {
-      g2.setRenderingHint(RenderingHints.KEY_RENDERING,
-                          RenderingHints.VALUE_RENDER_QUALITY);
-      g2.drawImage(im.getImage(), 0, 0, scaledWidth, scaledHeight, null);
-    } else {
-      g2.drawImage(im.getImage(), 0, 0, null);
+    // Do we actually change anything?
+    if ((width == cc.cp.width) &&
+        (height == cc.cp.height) &&
+        (layout == cc.cp.screenLayout))
+      return;
+
+    String buffer;
+    vlog.debug(String.format("Requesting framebuffer resize from %dx%d to %dx%d",
+               cc.cp.width, cc.cp.height, width, height));
+    layout.debug_print();
+
+    if (!layout.validate(width, height)) {
+      vlog.error("Invalid screen layout computed for resize request!");
+      return;
     }
-    g2.dispose();
+
+    cc.writer().writeSetDesktopSize(width, height, layout);
   }
 
-  // Mouse-Motion callback function
-  private void mouseMotionCB(MouseEvent e) {
-    if (!viewOnly.getValue() &&
-        e.getX() >= 0 && e.getX() <= scaledWidth &&
-        e.getY() >= 0 && e.getY() <= scaledHeight)
-      cc.writePointerEvent(e);
-    // - If local cursor rendering is enabled then use it
-    if (cursorAvailable) {
-      // - Render the cursor!
-      if (e.getX() != cursorPosX || e.getY() != cursorPosY) {
-        hideLocalCursor();
-        if (e.getX() >= 0 && e.getX() < im.width() &&
-            e.getY() >= 0 && e.getY() < im.height()) {
-          cursorPosX = e.getX();
-          cursorPosY = e.getY();
-          showLocalCursor();
-        }
+  boolean lionFSSupported() { return canDoLionFS; }
+
+  private int x() { return getContentPane().getX(); }
+  private int y() { return getContentPane().getY(); }
+  private int w() { return getContentPane().getWidth(); }
+  private int h() { return getContentPane().getHeight(); }
+
+  void enableLionFS() {
+    try {
+      String version = System.getProperty("os.version");
+      int firstDot = version.indexOf('.');
+      int lastDot = version.lastIndexOf('.');
+      if (lastDot > firstDot && lastDot >= 0) {
+        version = version.substring(0, version.indexOf('.', firstDot + 1));
       }
+      double v = Double.parseDouble(version);
+      if (v < 10.7)
+        throw new Exception("Operating system version is " + v);
+
+      Class fsuClass = Class.forName("com.apple.eawt.FullScreenUtilities");
+      Class argClasses[] = new Class[]{Window.class, Boolean.TYPE};
+      Method setWindowCanFullScreen =
+        fsuClass.getMethod("setWindowCanFullScreen", argClasses);
+      setWindowCanFullScreen.invoke(fsuClass, this, true);
+
+      canDoLionFS = true;
+    } catch (Exception e) {
+      vlog.debug("Could not enable OS X 10.7+ full-screen mode: " +
+                 e.getMessage());
     }
-    lastX = e.getX();
-    lastY = e.getY();
   }
-  public void mouseDragged(MouseEvent e) { mouseMotionCB(e); }
-  public void mouseMoved(MouseEvent e) { mouseMotionCB(e); }
-
-  // Mouse callback function
-  private void mouseCB(MouseEvent e) {
-    if (!viewOnly.getValue()) {
-      if ((e.getID() == MouseEvent.MOUSE_RELEASED) ||
-          (e.getX() >= 0 && e.getX() <= scaledWidth &&
-           e.getY() >= 0 && e.getY() <= scaledHeight))
-        cc.writePointerEvent(e);
+
+  public void toggleLionFS() {
+    try {
+      Class appClass = Class.forName("com.apple.eawt.Application");
+      Method getApplication = appClass.getMethod("getApplication",
+                                                 (Class[])null);
+      Object app = getApplication.invoke(appClass);
+      Method requestToggleFullScreen =
+        appClass.getMethod("requestToggleFullScreen", Window.class);
+      requestToggleFullScreen.invoke(app, this);
+    } catch (Exception e) {
+      vlog.debug("Could not toggle OS X 10.7+ full-screen mode: " +
+                 e.getMessage());
     }
-    lastX = e.getX();
-    lastY = e.getY();
-  }
-  public void mouseReleased(MouseEvent e) { mouseCB(e); }
-  public void mousePressed(MouseEvent e) { mouseCB(e); }
-  public void mouseClicked(MouseEvent e) {}
-  public void mouseEntered(MouseEvent e) {
-    if (embed.getValue())
-      requestFocus();
   }
-  public void mouseExited(MouseEvent e) {}
 
-  // MouseWheel callback function
-  private void mouseWheelCB(MouseWheelEvent e) {
-    if (!viewOnly.getValue())
-      cc.writeWheelEvent(e);
-  }
 
-  public void mouseWheelMoved(MouseWheelEvent e) {
-    mouseWheelCB(e);
+  public boolean isMaximized()
+  {
+    int state = getExtendedState();
+    return ((state & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
   }
 
-  private static final Integer keyEventLock = 0; 
-
-  // Handle the key-typed event.
-  public void keyTyped(KeyEvent e) { }
+  public Dimension getScreenSize() {
+    return getScreenBounds().getSize();
+  }
 
-  // Handle the key-released event.
-  public void keyReleased(KeyEvent e) {
-    synchronized(keyEventLock) {
-      cc.writeKeyEvent(e);
+  public Rectangle getScreenBounds() {
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+    Rectangle r = new Rectangle();
+    if (fullScreenAllMonitors.getValue()) {
+      for (GraphicsDevice gd : ge.getScreenDevices())
+        for (GraphicsConfiguration gc : gd.getConfigurations())
+          r = r.union(gc.getBounds());
+    } else {
+      GraphicsConfiguration gc = getGraphicsConfiguration();
+      r = gc.getBounds();
     }
+    return r;
   }
 
-  // Handle the key-pressed event.
-  public void keyPressed(KeyEvent e) {
-    if (e.getKeyCode() == MenuKey.getMenuKeyCode()) {
-      int sx = (scaleWidthRatio == 1.00) ?
-        lastX : (int)Math.floor(lastX * scaleWidthRatio);
-      int sy = (scaleHeightRatio == 1.00) ?
-        lastY : (int)Math.floor(lastY * scaleHeightRatio);
-      java.awt.Point ev = new java.awt.Point(lastX, lastY);
-      ev.translate(sx - lastX, sy - lastY);
-      F8Menu menu = new F8Menu(cc);
-      menu.show(this, (int)ev.getX(), (int)ev.getY());
-      return;
-    }
-    int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
-    if ((e.getModifiers() & ctrlAltShiftMask) == ctrlAltShiftMask) {
-      switch (e.getKeyCode()) {
-        case KeyEvent.VK_A:
-          VncViewer.showAbout(this);
-          return;
-        case KeyEvent.VK_F:
-          cc.toggleFullScreen();
-          return;
-        case KeyEvent.VK_H:
-          cc.refresh();
-          return;
-        case KeyEvent.VK_I:
-          cc.showInfo();
-          return;
-        case KeyEvent.VK_O:
-            OptionsDialog.showDialog(cc.viewport);
-          return;
-        case KeyEvent.VK_W:
-          VncViewer.newViewer();
-          return;
-        case KeyEvent.VK_LEFT:
-        case KeyEvent.VK_RIGHT:
-        case KeyEvent.VK_UP:
-        case KeyEvent.VK_DOWN:
-          return;
-      }
-    }
-    if ((e.getModifiers() & Event.META_MASK) == Event.META_MASK) {
-      switch (e.getKeyCode()) {
-        case KeyEvent.VK_COMMA:
-        case KeyEvent.VK_N:
-        case KeyEvent.VK_W:
-        case KeyEvent.VK_I:
-        case KeyEvent.VK_R:
-        case KeyEvent.VK_L:
-        case KeyEvent.VK_F:
-        case KeyEvent.VK_Z:
-        case KeyEvent.VK_T:
-          return;
-      }
-    }
-    synchronized(keyEventLock) {
-      cc.writeKeyEvent(e);
+  public static Window getFullScreenWindow() {
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+    for (GraphicsDevice gd : ge.getScreenDevices()) {
+      Window fullScreenWindow = gd.getFullScreenWindow();
+      if (fullScreenWindow != null)
+        return fullScreenWindow;
     }
+    return null;
   }
 
-  ////////////////////////////////////////////////////////////////////
-  // The following methods are called from both RFB and GUI threads
-
-  // Note that mutex MUST be held when hideLocalCursor() and showLocalCursor()
-  // are called.
-
-  private synchronized void hideLocalCursor() {
-    // - Blit the cursor backing store over the cursor
-    if (cursorVisible) {
-      cursorVisible = false;
-      im.imageRect(cursorBackingX, cursorBackingY, cursorBacking.width(),
-                   cursorBacking.height(), cursorBacking.data);
-      damageRect(new Rect(cursorBackingX, cursorBackingY,
-                          cursorBackingX+cursorBacking.width(),
-                          cursorBackingY+cursorBacking.height()));
+  public static void setFullScreenWindow(Window fullScreenWindow) {
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+    if (fullScreenAllMonitors.getValue()) {
+      for (GraphicsDevice gd : ge.getScreenDevices())
+        gd.setFullScreenWindow(fullScreenWindow);
+    } else {
+      GraphicsDevice gd = ge.getDefaultScreenDevice();
+      gd.setFullScreenWindow(fullScreenWindow);
     }
   }
 
-  private synchronized void showLocalCursor() {
-    if (cursorAvailable && !cursorVisible) {
-      if (!im.getPF().equal(cursor.getPF()) ||
-          cursor.width() == 0 || cursor.height() == 0) {
-        vlog.debug("attempting to render invalid local cursor");
-        cursorAvailable = false;
-        return;
-      }
-      cursorVisible = true;
-
-      int cursorLeft = cursor.hotspot.x;
-      int cursorTop = cursor.hotspot.y;
-      int cursorRight = cursorLeft + cursor.width();
-      int cursorBottom = cursorTop + cursor.height();
+  public void handleOptions()
+  {
 
-      int x = (cursorLeft >= 0 ? cursorLeft : 0);
-      int y = (cursorTop >= 0 ? cursorTop : 0);
-      int w = ((cursorRight < im.width() ? cursorRight : im.width()) - x);
-      int h = ((cursorBottom < im.height() ? cursorBottom : im.height()) - y);
+    if (fullScreen.getValue() && !fullscreen_active())
+      fullscreen_on();
+    else if (!fullScreen.getValue() && fullscreen_active())
+      fullscreen_off();
 
-      cursorBackingX = x;
-      cursorBackingY = y;
-      cursorBacking.setSize(w, h);
+    if (remoteResize.getValue()) {
+      scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+      scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+      remoteResize(w(), h());
+    } else {
+      String scaleString = scalingFactor.getValue();
+      if (!scaleString.equals(lastScaleFactor)) {
+        if (scaleString.matches("^[0-9]+$")) {
+          scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
+          scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
+          viewport.setScaledSize(cc.cp.width, cc.cp.height);
+        } else {
+          scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
+          scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
+          viewport.setScaledSize(w(), h());
+        }
 
-      for (int j = 0; j < h; j++)
-        System.arraycopy(im.data, (y + j) * im.width() + x,
-                         cursorBacking.data, j * w, w);
+        if (isMaximized() || fullscreen_active()) {
+          repositionViewport();
+        } else {
+          int dx = getInsets().left + getInsets().right;
+          int dy = getInsets().top + getInsets().bottom;
+          setSize(viewport.scaledWidth+dx, viewport.scaledHeight+dy);
+        }
 
-      im.maskRect(cursorLeft, cursorTop, cursor.width(), cursor.height(),
-                  cursor.data, cursor.mask);
-      damageRect(new Rect(x, y, x+w, y+h));
+        repositionViewport();
+        lastScaleFactor = scaleString;
+      }
     }
-  }
 
-  void damageRect(Rect r) {
-    if (damage.is_empty()) {
-      damage.setXYWH(r.tl.x, r.tl.y, r.width(), r.height());
-    } else {
-      r = damage.union_boundary(r);
-      damage.setXYWH(r.tl.x, r.tl.y, r.width(), r.height());
+    if (isVisible()) {
+      toFront();
+      requestFocus();
     }
   }
 
-  // run() is executed by the setColourMapEntriesTimerThread - it sleeps for
-  // 100ms before actually updating the colourmap.
-  public synchronized void run() {
-    try {
-      Thread.sleep(100);
-    } catch(InterruptedException e) {}
-    im.updateColourMap();
-    setColourMapEntriesTimerThread = null;
-  }
-
-  public void handleOptions()
+  public void handleFullscreenTimeout()
   {
-    if (fullScreen.getValue() && Viewport.getFullScreenWindow() == null)
-      cc.toggleFullScreen();
-    else if (!fullScreen.getValue() && Viewport.getFullScreenWindow() != null)
-      cc.toggleFullScreen();
-  }
-
-  // access to cc by different threads is specified in CConn
-  CConn cc;
+    DesktopWindow self = (DesktopWindow)this;
 
-  // access to the following must be synchronized:
-  PlatformPixelBuffer im;
-  Thread setColourMapEntriesTimerThread;
+    assert(self != null);
 
-  Cursor cursor;
-  boolean cursorVisible = false;     // Is cursor currently rendered?
-  boolean cursorAvailable = false;   // Is cursor available for rendering?
-  int cursorPosX, cursorPosY;
-  ManagedPixelBuffer cursorBacking;
-  int cursorBackingX, cursorBackingY;
-  java.awt.Cursor softCursor, nullCursor;
-  static Toolkit tk = Toolkit.getDefaultToolkit();
+    self.delayedFullscreen = false;
 
-  public int scaledWidth = 0, scaledHeight = 0;
-  float scaleWidthRatio, scaleHeightRatio;
+    if (self.delayedDesktopSize) {
+      self.handleDesktopSize();
+      self.delayedDesktopSize = false;
+    }
+  }
 
-  // the following are only ever accessed by the GUI thread:
-  int lastX, lastY;
-  Rect damage = new Rect();
+  private CConn cc;
+  private JScrollPane scroll;
+  public Viewport viewport;
 
-  static LogWriter vlog = new LogWriter("DesktopWindow");
+  private boolean firstUpdate;
+  private boolean delayedFullscreen;
+  private boolean delayedDesktopSize;
+  private boolean canDoLionFS;
+  private String lastScaleFactor;
+  private Rectangle lastBounds;
+  private int lastState;
+  private Timer timer;
 }
+
index 6204ba10ac884ddc73138cb08434bbbdc3339cb9..a2fb04fe6acae59e6208bc57f6f75f138af5cc0f 100644 (file)
@@ -65,9 +65,6 @@ class Dialog extends JDialog implements ActionListener,
       int y = (dpySize.height - mySize.height) / 2;
       setLocation(x, y);
     }
-    fullScreenWindow = Viewport.getFullScreenWindow();
-    if (fullScreenWindow != null)
-      Viewport.setFullScreenWindow(null);
 
     if (getModalityType() == ModalityType.APPLICATION_MODAL)
       setAlwaysOnTop(true);
@@ -81,9 +78,6 @@ class Dialog extends JDialog implements ActionListener,
   public void endDialog() {
     setVisible(false);
     setAlwaysOnTop(false);
-    fullScreenWindow = Viewport.getFullScreenWindow();
-    if (fullScreenWindow != null)
-      Viewport.setFullScreenWindow(fullScreenWindow);
   }
 
   // initDialog() can be overridden in a derived class.  Typically it is used
index d7f9e482ceb6862742063ab248e59234e3bbc21e..0c67305ad2b8c2b775ad811043d934d8a2aed440 100644 (file)
@@ -106,19 +106,24 @@ public class F8Menu extends JPopupMenu implements ActionListener {
     if (actionMatch(ev, exit)) {
       cc.close();
     } else if (actionMatch(ev, fullScreenCheckbox)) {
-      cc.toggleFullScreen();
+      if (fullScreenCheckbox.isSelected())
+        cc.desktop.fullscreen_on();
+      else
+        cc.desktop.fullscreen_off();
     } else if (actionMatch(ev, restore)) {
-      if (fullScreen.getValue()) cc.toggleFullScreen();
-      cc.viewport.setExtendedState(JFrame.NORMAL);
+      if (cc.desktop.fullscreen_active())
+        cc.desktop.fullscreen_off();
+      cc.desktop.setExtendedState(JFrame.NORMAL);
     } else if (actionMatch(ev, minimize)) {
-      if (fullScreen.getValue()) cc.toggleFullScreen();
-      cc.viewport.setExtendedState(JFrame.ICONIFIED);
+      if (cc.desktop.fullscreen_active())
+        cc.desktop.fullscreen_off();
+      cc.desktop.setExtendedState(JFrame.ICONIFIED);
     } else if (actionMatch(ev, maximize)) {
-      if (fullScreen.getValue()) cc.toggleFullScreen();
-      cc.viewport.setExtendedState(JFrame.MAXIMIZED_BOTH);
+      if (cc.desktop.fullscreen_active())
+        cc.desktop.fullscreen_off();
+      cc.desktop.setExtendedState(JFrame.MAXIMIZED_BOTH);
     } else if (actionMatch(ev, clipboard)) {
-      //ClipboardDialog dlg = new ClipboardDialog(cc);
-      ClipboardDialog.showDialog(cc.viewport);
+      ClipboardDialog.showDialog(cc.desktop);
     } else if (actionMatch(ev, f8)) {
       cc.writeKeyEvent(MenuKey.getMenuKeySym(), true);
       cc.writeKeyEvent(MenuKey.getMenuKeySym(), false);
@@ -134,7 +139,7 @@ public class F8Menu extends JPopupMenu implements ActionListener {
     } else if (actionMatch(ev, newConn)) {
       VncViewer.newViewer();
     } else if (actionMatch(ev, options)) {
-      OptionsDialog.showDialog(cc.viewport);
+      OptionsDialog.showDialog(cc.desktop);
     } else if (actionMatch(ev, save)) {
            String title = "Save the TigerVNC configuration to file";
            File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc"));
@@ -170,6 +175,24 @@ public class F8Menu extends JPopupMenu implements ActionListener {
     }
   }
 
+  public void show(Component invoker, int x, int y) {
+    // lightweight components can't show in FullScreen Exclusive mode
+    /*
+    Window fsw = DesktopWindow.getFullScreenWindow();
+    GraphicsDevice gd = null;
+    if (fsw != null) {
+      gd = fsw.getGraphicsConfiguration().getDevice();
+      if (gd.isFullScreenSupported())
+        DesktopWindow.setFullScreenWindow(null);
+    }
+    */
+    super.show(invoker, x, y);
+    /*
+    if (fsw != null && gd.isFullScreenSupported())
+      DesktopWindow.setFullScreenWindow(fsw);
+      */
+  }
+
   CConn cc;
   JMenuItem restore, move, size, minimize, maximize;
   JMenuItem exit, clipboard, ctrlAltDel, refresh;
diff --git a/java/com/tigervnc/vncviewer/JavaPixelBuffer.java b/java/com/tigervnc/vncviewer/JavaPixelBuffer.java
new file mode 100644 (file)
index 0000000..b639673
--- /dev/null
@@ -0,0 +1,59 @@
+/* Copyright (C) 2012-2016 Brian P. Hinz
+ * Copyright (C) 2012 D. R. Commander.  All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.awt.*;
+import java.awt.image.*;
+import java.nio.ByteOrder;
+
+import com.tigervnc.rfb.*;
+
+public class JavaPixelBuffer extends PlatformPixelBuffer 
+{
+
+  public JavaPixelBuffer(int w, int h) {
+    super(getPreferredPF(), w, h,
+          getPreferredPF().getColorModel().createCompatibleWritableRaster(w,h));
+  }
+
+  private static PixelFormat getPreferredPF()
+  {
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+    GraphicsConfiguration gc = gd.getDefaultConfiguration();
+    ColorModel cm = gc.getColorModel();
+    int depth = ((cm.getPixelSize() > 24) ? 24 : cm.getPixelSize());
+    int bpp = (depth > 16 ? 32 : (depth > 8 ? 16 : 8));
+    ByteOrder byteOrder = ByteOrder.nativeOrder();
+    boolean bigEndian = (byteOrder == ByteOrder.BIG_ENDIAN ? true : false);
+    boolean trueColour = true;
+    int redShift    = cm.getComponentSize()[0] + cm.getComponentSize()[1];
+    int greenShift  = cm.getComponentSize()[0];
+    int blueShift   = 0;
+    int redMask   = ((int)Math.pow(2, cm.getComponentSize()[2]) - 1);
+    int greenMask = ((int)Math.pow(2, cm.getComponentSize()[1]) - 1);
+    int blueMmask = ((int)Math.pow(2, cm.getComponentSize()[0]) - 1);
+    return new PixelFormat(bpp, depth, bigEndian, trueColour,
+                           redMask, greenMask, blueMmask,
+                           redShift, greenShift, blueShift);
+  }
+
+}
index db2749119f99a2999864b418004c84209021aa3e..a7c8778478a6e86337cebcfdf3271496050002dc 100644 (file)
@@ -94,7 +94,7 @@ class OptionsDialog extends Dialog {
     }
   }
 
-  private static Map<String, Object> callbacks = new HashMap<String, Object>();
+  private static Map<Object, String> callbacks = new HashMap<Object, String>();
   /* Compression */
   JCheckBox autoselectCheckbox;
 
@@ -140,13 +140,18 @@ class OptionsDialog extends Dialog {
   JCheckBox desktopSizeCheckbox;
   JTextField desktopWidthInput;
   JTextField desktopHeightInput;
+
+  ButtonGroup sizingGroup;
+  JRadioButton remoteResizeButton;
+  JRadioButton remoteScaleButton;
+  JComboBox scalingFactorInput;
+
   JCheckBox fullScreenCheckbox;
   JCheckBox fullScreenAllMonitorsCheckbox;
-  JComboBox scalingFactorInput;
 
   /* Misc. */
   JCheckBox sharedCheckbox;
-  JCheckBox localCursorCheckbox;
+  JCheckBox dotWhenNoCursorCheckbox;
   JCheckBox acceptBellCheckbox;
 
   /* SSH */
@@ -190,9 +195,10 @@ class OptionsDialog extends Dialog {
     tabPane.addTab("SSH", createSshPanel());
     tabPane.setBorder(BorderFactory.createEmptyBorder());
     // Resize the tabPane if necessary to prevent scrolling
-    Insets tpi =
-      (Insets)UIManager.get("TabbedPane:TabbedPaneTabArea.contentMargins");
-    int minWidth = tpi.left + tpi.right;
+    int minWidth = 0;
+    Object tpi = UIManager.get("TabbedPane:TabbedPaneTabArea.contentMargins");
+    if (tpi != null)
+      minWidth += ((Insets)tpi).left + ((Insets)tpi).right;
     for (int i = 0; i < tabPane.getTabCount(); i++)
       minWidth += tabPane.getBoundsAt(i).width;
     int minHeight = tabPane.getPreferredSize().height;
@@ -215,7 +221,7 @@ class OptionsDialog extends Dialog {
     });
 
     JPanel buttonPane = new JPanel(new GridLayout(1, 5, 10, 10));
-    buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
+    buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 5, 5, 5));
     buttonPane.add(Box.createRigidArea(new Dimension()));
     buttonPane.add(Box.createRigidArea(new Dimension()));
     buttonPane.add(Box.createRigidArea(new Dimension()));
@@ -240,12 +246,12 @@ class OptionsDialog extends Dialog {
 
   public static void addCallback(String cb, Object obj)
   {
-    callbacks.put(cb, obj);
+    callbacks.put(obj, cb);
   }
 
-  public static void removeCallback(String cb)
+  public static void removeCallback(Object obj)
   {
-    callbacks.remove(cb);
+    callbacks.remove(obj);
   }
 
   public void endDialog() {
@@ -258,15 +264,18 @@ class OptionsDialog extends Dialog {
     fullScreenCheckbox.setEnabled(s);
     fullScreenAllMonitorsCheckbox.setEnabled(s);
     scalingFactorInput.setEnabled(s);
+    Enumeration<AbstractButton> e = sizingGroup.getElements();
+    while (e.hasMoreElements())
+      e.nextElement().setEnabled(s);
   }
 
   private void loadOptions()
   {
     /* Compression */
     autoselectCheckbox.setSelected(autoSelect.getValue());
-  
+
     int encNum = Encodings.encodingNum(preferredEncoding.getValueStr());
-  
+
     switch (encNum) {
     case Encodings.encodingTight:
       tightButton.setSelected(true);
@@ -281,7 +290,7 @@ class OptionsDialog extends Dialog {
       rawButton.setSelected(true);
       break;
     }
-  
+
     if (fullColor.getValue())
       fullcolorButton.setSelected(true);
     else {
@@ -323,13 +332,13 @@ class OptionsDialog extends Dialog {
     encNoneCheckbox.setSelected(false);
     encTLSCheckbox.setSelected(false);
     encX509Checkbox.setSelected(false);
-  
+
     authNoneCheckbox.setSelected(false);
     authVncCheckbox.setSelected(false);
     authPlainCheckbox.setSelected(false);
     authIdentCheckbox.setSelected(false);
     sendLocalUsernameCheckbox.setSelected(sendLocalUsername.getValue());
-  
+
     secTypes = security.GetEnabledSecTypes();
     for (iter = secTypes.iterator(); iter.hasNext(); ) {
       switch ((Integer)iter.next()) {
@@ -343,7 +352,7 @@ class OptionsDialog extends Dialog {
         break;
       }
     }
-  
+
     secTypesExt = security.GetEnabledExtSecTypes();
     for (iterExt = secTypesExt.iterator(); iterExt.hasNext(); ) {
       switch ((Integer)iterExt.next()) {
@@ -404,14 +413,14 @@ class OptionsDialog extends Dialog {
     viewOnlyCheckbox.setSelected(viewOnly.getValue());
     acceptClipboardCheckbox.setSelected(acceptClipboard.getValue());
     sendClipboardCheckbox.setSelected(sendClipboard.getValue());
-  
+
     menuKeyChoice.setSelectedIndex(0);
-  
+
     String menuKeyStr = menuKey.getValueStr();
     for (int i = 0; i < menuKeyChoice.getItemCount(); i++)
       if (menuKeyStr.equals(menuKeyChoice.getItemAt(i)))
         menuKeyChoice.setSelectedIndex(i);
-  
+
     /* Screen */
     String width, height;
 
@@ -427,6 +436,10 @@ class OptionsDialog extends Dialog {
       height = desktopSize.getValueStr().split("x")[1];
       desktopHeightInput.setText(height);
     }
+    if (remoteResize.getValue())
+      remoteResizeButton.setSelected(true);
+    else
+      remoteScaleButton.setSelected(true);
     fullScreenCheckbox.setSelected(fullScreen.getValue());
     fullScreenAllMonitorsCheckbox.setSelected(fullScreenAllMonitors.getValue());
 
@@ -434,15 +447,17 @@ class OptionsDialog extends Dialog {
     String scaleStr = scalingFactor.getValueStr();
     if (scaleStr.matches("^[0-9]+$"))
       scaleStr = scaleStr.concat("%");
+    if (scaleStr.matches("^FixedRatio$"))
+      scaleStr = new String("Fixed Aspect Ratio");
     for (int i = 0; i < scalingFactorInput.getItemCount(); i++)
       if (scaleStr.equals(scalingFactorInput.getItemAt(i)))
         scalingFactorInput.setSelectedIndex(i);
 
     handleDesktopSize();
-  
+
     /* Misc. */
     sharedCheckbox.setSelected(shared.getValue());
-    localCursorCheckbox.setSelected(useLocalCursor.getValue());
+    dotWhenNoCursorCheckbox.setSelected(dotWhenNoCursor.getValue());
     acceptBellCheckbox.setSelected(acceptBell.getValue());
 
     /* SSH */
@@ -556,7 +571,7 @@ class OptionsDialog extends Dialog {
     File crlFile = new File(crlInput.getText());
     if (crlFile.exists() && crlFile.canRead())
       CSecurityTLS.X509CRL.setParam(crlFile.getAbsolutePath());
+
     /* Input */
     viewOnly.setParam(viewOnlyCheckbox.isSelected());
     acceptClipboard.setParam(acceptClipboardCheckbox.isSelected());
@@ -576,18 +591,18 @@ class OptionsDialog extends Dialog {
     } else {
       desktopSize.setParam("");
     }
+    remoteResize.setParam(remoteResizeButton.isSelected());
     fullScreen.setParam(fullScreenCheckbox.isSelected());
     fullScreenAllMonitors.setParam(fullScreenAllMonitorsCheckbox.isSelected());
 
     String scaleStr =
       ((String)scalingFactorInput.getSelectedItem()).replace("%", "");
-    if (scaleStr.equals("Fixed Aspect Ratio"))
-      scaleStr = "FixedRatio";
+    scaleStr.replace("Fixed Aspect Ratio", "FixedRatio");
     scalingFactor.setParam(scaleStr);
 
     /* Misc. */
     shared.setParam(sharedCheckbox.isSelected());
-    useLocalCursor.setParam(localCursorCheckbox.isSelected());
+    dotWhenNoCursor.setParam(dotWhenNoCursorCheckbox.isSelected());
     acceptBell.setParam(acceptBellCheckbox.isSelected());
 
     /* SSH */
@@ -614,9 +629,11 @@ class OptionsDialog extends Dialog {
       sshKeyFile.setParam(sshKeyFileInput.getText());
 
     try {
-      for (Map.Entry<String, Object> iter : callbacks.entrySet()) {
-        Object obj = iter.getValue();
-        Method cb = obj.getClass().getMethod(iter.getKey(), new Class[]{});
+      for (Map.Entry<Object, String> iter : callbacks.entrySet()) {
+        Object obj = iter.getKey();
+        Method cb = obj.getClass().getMethod(iter.getValue(), new Class[]{});
+        if (cb == null)
+          vlog.info(obj.getClass().getName());
         cb.invoke(obj);
       }
     } catch (NoSuchMethodException e) {
@@ -1015,6 +1032,9 @@ class OptionsDialog extends Dialog {
   private JPanel createScreenPanel() {
     JPanel ScreenPanel = new JPanel(new GridBagLayout());
     ScreenPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
+
+    JPanel SizingPanel = new JPanel(new GridBagLayout());
+    SizingPanel.setBorder(BorderFactory.createTitledBorder("Desktop Sizing"));
     desktopSizeCheckbox = new JCheckBox("Resize remote session on connect");
     desktopSizeCheckbox.addItemListener(new ItemListener() {
       public void itemStateChanged(ItemEvent e) {
@@ -1028,16 +1048,28 @@ class OptionsDialog extends Dialog {
     desktopSizePanel.add(desktopWidthInput);
     desktopSizePanel.add(new JLabel(" x "));
     desktopSizePanel.add(desktopHeightInput);
-    fullScreenCheckbox = new JCheckBox("Full-screen mode");
-    fullScreenAllMonitorsCheckbox =
-      new JCheckBox("Enable full-screen mode over all monitors");
+    sizingGroup = new ButtonGroup();
+    remoteResizeButton =
+      new JRadioButton("Resize remote session to the local window");
+    sizingGroup.add(remoteResizeButton);
+    remoteScaleButton =
+      new JRadioButton("Scale remote session to the local window");
+    sizingGroup.add(remoteScaleButton);
+    remoteResizeButton.addItemListener(new ItemListener() {
+      public void itemStateChanged(ItemEvent e) {
+        handleRemoteResize();
+      }
+    });
     JLabel scalingFactorLabel = new JLabel("Scaling Factor");
     Object[] scalingFactors = {
       "Auto", "Fixed Aspect Ratio", "50%", "75%", "95%", "100%", "105%",
       "125%", "150%", "175%", "200%", "250%", "300%", "350%", "400%" };
     scalingFactorInput = new MyJComboBox(scalingFactors);
     scalingFactorInput.setEditable(true);
-    ScreenPanel.add(desktopSizeCheckbox,
+    fullScreenCheckbox = new JCheckBox("Full-screen mode");
+    fullScreenAllMonitorsCheckbox =
+      new JCheckBox("Enable full-screen mode over all monitors");
+    SizingPanel.add(desktopSizeCheckbox,
                     new GridBagConstraints(0, 0,
                                            REMAINDER, 1,
                                            LIGHT, LIGHT,
@@ -1045,44 +1077,66 @@ class OptionsDialog extends Dialog {
                                            new Insets(0, 0, 0, 0),
                                            NONE, NONE));
     int indent = getButtonLabelInset(desktopSizeCheckbox);
-    ScreenPanel.add(desktopSizePanel,
+    SizingPanel.add(desktopSizePanel,
                     new GridBagConstraints(0, 1,
                                            REMAINDER, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, NONE,
                                            new Insets(0, indent, 0, 0),
                                            NONE, NONE));
-    ScreenPanel.add(fullScreenCheckbox,
+    SizingPanel.add(remoteResizeButton,
                     new GridBagConstraints(0, 2,
                                            REMAINDER, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, NONE,
                                            new Insets(0, 0, 4, 0),
                                            NONE, NONE));
-    indent = getButtonLabelInset(fullScreenCheckbox);
-    ScreenPanel.add(fullScreenAllMonitorsCheckbox,
+    SizingPanel.add(remoteScaleButton,
                     new GridBagConstraints(0, 3,
                                            REMAINDER, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, NONE,
-                                           new Insets(0, indent, 4, 0),
+                                           new Insets(0, 0, 4, 0),
                                            NONE, NONE));
-    ScreenPanel.add(scalingFactorLabel,
+    indent = getButtonLabelInset(remoteScaleButton);
+    SizingPanel.add(scalingFactorLabel,
                     new GridBagConstraints(0, 4,
                                            1, 1,
                                            LIGHT, LIGHT,
                                            LINE_START, NONE,
-                                           new Insets(0, 0, 4, 0),
+                                           new Insets(0, indent, 4, 0),
                                            NONE, NONE));
-    ScreenPanel.add(scalingFactorInput,
+    SizingPanel.add(scalingFactorInput,
                     new GridBagConstraints(1, 4,
                                            1, 1,
                                            HEAVY, LIGHT,
                                            LINE_START, NONE,
                                            new Insets(0, 5, 4, 0),
                                            NONE, NONE));
+    ScreenPanel.add(SizingPanel,
+                    new GridBagConstraints(0, 0,
+                                           REMAINDER, 1,
+                                           LIGHT, LIGHT,
+                                           LINE_START, HORIZONTAL,
+                                           new Insets(0, 0, 4, 0),
+                                           NONE, NONE));
+    ScreenPanel.add(fullScreenCheckbox,
+                    new GridBagConstraints(0, 1,
+                                           REMAINDER, 1,
+                                           LIGHT, LIGHT,
+                                           LINE_START, NONE,
+                                           new Insets(0, 0, 4, 0),
+                                           NONE, NONE));
+    indent = getButtonLabelInset(fullScreenCheckbox);
+    ScreenPanel.add(fullScreenAllMonitorsCheckbox,
+                    new GridBagConstraints(0, 2,
+                                           REMAINDER, 1,
+                                           LIGHT, LIGHT,
+                                           LINE_START, NONE,
+                                           new Insets(0, indent, 4, 0),
+                                           NONE, NONE));
     ScreenPanel.add(Box.createRigidArea(new Dimension(5, 0)),
-                    new GridBagConstraints(0, 5,
+                    new GridBagConstraints(0, 3,
                                            REMAINDER, REMAINDER,
                                            HEAVY, HEAVY,
                                            LINE_START, BOTH,
@@ -1096,7 +1150,7 @@ class OptionsDialog extends Dialog {
     MiscPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
     sharedCheckbox =
       new JCheckBox("Shared (don't disconnect other viewers)");
-    localCursorCheckbox = new JCheckBox("Render cursor locally");
+    dotWhenNoCursorCheckbox = new JCheckBox("Show dot when no cursor");
     acceptBellCheckbox = new JCheckBox("Beep when requested by the server");
     MiscPanel.add(sharedCheckbox,
                   new GridBagConstraints(0, 0,
@@ -1105,7 +1159,7 @@ class OptionsDialog extends Dialog {
                                          LINE_START, NONE,
                                          new Insets(0, 0, 4, 0),
                                          NONE, NONE));
-    MiscPanel.add(localCursorCheckbox,
+    MiscPanel.add(dotWhenNoCursorCheckbox,
                   new GridBagConstraints(0, 1,
                                          1, 1,
                                          LIGHT, LIGHT,
@@ -1472,6 +1526,11 @@ class OptionsDialog extends Dialog {
     desktopHeightInput.setEnabled(desktopSizeCheckbox.isSelected());
   }
 
+  private void handleRemoteResize()
+  {
+    scalingFactorInput.setEnabled(!remoteResizeButton.isSelected());
+  }
+
   private void handleTunnel()
   {
     viaCheckbox.setEnabled(tunnelCheckbox.isSelected());
@@ -1533,6 +1592,8 @@ class OptionsDialog extends Dialog {
       desktopSizeCheckbox.setEnabled(false);
       desktopWidthInput.setEnabled(false);
       desktopHeightInput.setEnabled(false);
+      remoteResizeButton.setEnabled(false);
+      remoteScaleButton.setEnabled(false);
       fullScreenCheckbox.setEnabled(false);
       fullScreenAllMonitorsCheckbox.setEnabled(false);
       scalingFactorInput.setEnabled(false);
index e6b91c339aa1c4249c7cdd514795f490db2517d8..50e26cba0e7e1fc27f9010aa4a0f6127ac5780ee 100644 (file)
@@ -31,168 +31,167 @@ import com.tigervnc.rfb.Exception;
 
 public class Parameters {
 
-
   public static BoolParameter noLionFS
   = new BoolParameter("NoLionFS",
-  "On Mac systems, setting this parameter will force the use of the old "+
-  "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+
-  "Lion or later.",
-  false);
+    "On Mac systems, setting this parameter will force the use of the old "+
+    "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+
+    "Lion or later.",
+    false);
 
   public static BoolParameter embed
   = new BoolParameter("Embed",
-  "If the viewer is being run as an applet, display its output to " +
-  "an embedded frame in the browser window rather than to a dedicated " +
-  "window. Embed=1 implies FullScreen=0 and Scale=100.",
-  false);
+    "If the viewer is being run as an applet, display its output to " +
+    "an embedded frame in the browser window rather than to a dedicated " +
+    "window. Embed=1 implies FullScreen=0 and Scale=100.",
+    false);
 
-  public static BoolParameter useLocalCursor
-  = new BoolParameter("UseLocalCursor",
-                      "Render the mouse cursor locally",
-                      true);
+  public static BoolParameter dotWhenNoCursor
+  = new BoolParameter("DotWhenNoCursor",
+    "Show the dot cursor when the server sends an invisible cursor",
+    false);
 
   public static BoolParameter sendLocalUsername
   = new BoolParameter("SendLocalUsername",
-                      "Send the local username for SecurityTypes "+
-                      "such as Plain rather than prompting",
-                      true);
+    "Send the local username for SecurityTypes "+
+    "such as Plain rather than prompting",
+    true);
 
   public static StringParameter passwordFile
   = new StringParameter("PasswordFile",
-                        "Password file for VNC authentication",
-                        "");
+    "Password file for VNC authentication",
+    "");
 
   public static AliasParameter passwd
   = new AliasParameter("passwd",
-                       "Alias for PasswordFile",
-                       passwordFile);
+    "Alias for PasswordFile",
+    passwordFile);
 
   public static BoolParameter autoSelect
   = new BoolParameter("AutoSelect",
-                      "Auto select pixel format and encoding",
-                      true);
+    "Auto select pixel format and encoding",
+    true);
 
   public static BoolParameter fullColor
   = new BoolParameter("FullColor",
-                      "Use full color - otherwise 6-bit colour is "+
-                      "used until AutoSelect decides the link is "+
-                      "fast enough",
-                      true);
+    "Use full color - otherwise 6-bit colour is used "+
+    "until AutoSelect decides the link is fast enough",
+    true);
 
   public static AliasParameter fullColorAlias
   = new AliasParameter("FullColour",
-                       "Alias for FullColor",
-                       Parameters.fullColor);
+    "Alias for FullColor",
+    Parameters.fullColor);
 
   public static IntParameter lowColorLevel
   = new IntParameter("LowColorLevel",
-                     "Color level to use on slow connections. "+
-                     "0 = Very Low (8 colors), 1 = Low (64 colors), "+
-                     "2 = Medium (256 colors)",
-                     2);
+    "Color level to use on slow connections. "+
+    "0 = Very Low (8 colors), 1 = Low (64 colors), "+
+    "2 = Medium (256 colors)",
+    2);
 
   public static AliasParameter lowColorLevelAlias
   = new AliasParameter("LowColourLevel",
-                       "Alias for LowColorLevel",
-                       lowColorLevel);
+    "Alias for LowColorLevel",
+    lowColorLevel);
 
   public static StringParameter preferredEncoding
   = new StringParameter("PreferredEncoding",
-                        "Preferred encoding to use (Tight, ZRLE, "+
-                        "hextile or raw) - implies AutoSelect=0",
-                        "Tight");
+    "Preferred encoding to use (Tight, ZRLE, "+
+    "hextile or raw) - implies AutoSelect=0",
+    "Tight");
+
+  public static BoolParameter remoteResize
+  = new BoolParameter("RemoteResize",
+    "Dynamically resize the remote desktop size as "+
+    "the size of the local client window changes. "+
+    "(Does not work with all servers)",
+    true);
 
   public static BoolParameter viewOnly
   = new BoolParameter("ViewOnly",
-                      "Don't send any mouse or keyboard events to "+
-                      "the server",
-                      false);
+    "Don't send any mouse or keyboard events to the server",
+    false);
 
   public static BoolParameter shared
   = new BoolParameter("Shared",
-                      "Don't disconnect other viewers upon "+
-                      "connection - share the desktop instead",
-                      false);
+    "Don't disconnect other viewers upon "+
+    "connection - share the desktop instead",
+    false);
+
+  public static BoolParameter maximize
+  = new BoolParameter("Maximize",
+    "Maximize viewer window",
+    false);
 
   public static BoolParameter fullScreen
   = new BoolParameter("FullScreen",
-                      "Full Screen Mode",
-                      false);
+    "Full Screen Mode",
+    false);
 
   public static BoolParameter fullScreenAllMonitors
   = new BoolParameter("FullScreenAllMonitors",
-                      "Enable full screen over all monitors",
-                      true);
+    "Enable full screen over all monitors",
+    true);
 
   public static BoolParameter acceptClipboard
   = new BoolParameter("AcceptClipboard",
-                      "Accept clipboard changes from the server",
-                      true);
+    "Accept clipboard changes from the server",
+    true);
 
   public static BoolParameter sendClipboard
   = new BoolParameter("SendClipboard",
-                      "Send clipboard changes to the server",
-                      true);
+    "Send clipboard changes to the server",
+    true);
 
   public static IntParameter maxCutText
   = new IntParameter("MaxCutText",
-                     "Maximum permitted length of an outgoing clipboard update",
-                     262144);
+    "Maximum permitted length of an outgoing clipboard update",
+    262144);
 
   public static StringParameter menuKey
   = new StringParameter("MenuKey",
-                        "The key which brings up the popup menu",
-                        "F8");
+    "The key which brings up the popup menu",
+    "F8");
 
   public static StringParameter desktopSize
   = new StringParameter("DesktopSize",
-                        "Reconfigure desktop size on the server on "+
-                        "connect (if possible)", "");
+    "Reconfigure desktop size on the server on connect (if possible)",
+    "");
 
   public static BoolParameter listenMode
   = new BoolParameter("listen",
-                      "Listen for connections from VNC servers",
-                      false);
+    "Listen for connections from VNC servers",
+    false);
 
   public static StringParameter scalingFactor
   = new StringParameter("ScalingFactor",
-                        "Reduce or enlarge the remote desktop image. "+
-                        "The value is interpreted as a scaling factor "+
-                        "in percent. If the parameter is set to "+
-                        "\"Auto\", then automatic scaling is "+
-                        "performed. Auto-scaling tries to choose a "+
-                        "scaling factor in such a way that the whole "+
-                        "remote desktop will fit on the local screen. "+
-                        "If the parameter is set to \"FixedRatio\", "+
-                        "then automatic scaling is performed, but the "+
-                        "original aspect ratio is preserved.",
-                        "100");
+    "Reduce or enlarge the remote desktop image. "+
+    "The value is interpreted as a scaling factor "+
+    "in percent. If the parameter is set to "+
+    "\"Auto\", then automatic scaling is "+
+    "performed. Auto-scaling tries to choose a "+
+    "scaling factor in such a way that the whole "+
+    "remote desktop will fit on the local screen. "+
+    "If the parameter is set to \"FixedRatio\", "+
+    "then automatic scaling is performed, but the "+
+    "original aspect ratio is preserved.",
+    "100");
 
   public static BoolParameter alwaysShowServerDialog
   = new BoolParameter("AlwaysShowServerDialog",
-                      "Always show the server dialog even if a server "+
-                      "has been specified in an applet parameter or on "+
-                      "the command line",
-                      false);
+    "Always show the server dialog even if a server has been "+
+    "specified in an applet parameter or on the command line",
+    false);
 
   public static StringParameter vncServerName
   = new StringParameter("Server",
-                        "The VNC server <host>[:<dpyNum>] or "+
-                        "<host>::<port>",
-                        "");
-
-  /*
-  public static IntParameter vncServerPort
-  = new IntParameter("Port",
-                     "The VNC server's port number, assuming it is on "+
-                     "the host from which the applet was downloaded",
-                     0);
-  */
+    "The VNC server <host>[:<dpyNum>] or <host>::<port>",
+    "");
 
   public static BoolParameter acceptBell
   = new BoolParameter("AcceptBell",
-                      "Produce a system beep when requested to by the server.",
-                      true);
+    "Produce a system beep when requested to by the server.",
+    true);
 
   public static StringParameter via
   = new StringParameter("Via",
@@ -271,28 +270,26 @@ public class Parameters {
 
   public static BoolParameter customCompressLevel
   = new BoolParameter("CustomCompressLevel",
-                      "Use custom compression level. "+
-                      "Default if CompressLevel is specified.",
-                      false);
+    "Use custom compression level. Default if CompressLevel is specified.",
+    false);
 
   public static IntParameter compressLevel
   = new IntParameter("CompressLevel",
-                     "Use specified compression level "+
-                     "0 = Low, 6 = High",
-                     1);
+    "Use specified compression level. 0 = Low, 6 = High",
+    1);
 
   public static BoolParameter noJpeg
   = new BoolParameter("NoJPEG",
-                      "Disable lossy JPEG compression in Tight encoding.",
-                      false);
+    "Disable lossy JPEG compression in Tight encoding.",
+    false);
 
   public static IntParameter qualityLevel
   = new IntParameter("QualityLevel",
-                     "JPEG quality level. "+
-                     "0 = Low, 9 = High",
-                     8);
+    "JPEG quality level. 0 = Low, 9 = High",
+    8);
 
-  private static final String IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0";
+  private static final String IDENTIFIER_STRING
+  = "TigerVNC Configuration file Version 1.0";
 
   static VoidParameter[] parameterArray = {
     CSecurityTLS.X509CA,
@@ -306,16 +303,17 @@ public class Parameters {
     compressLevel,
     noJpeg,
     qualityLevel,
+    maximize,
     fullScreen,
     fullScreenAllMonitors,
     desktopSize,
+    remoteResize,
     viewOnly,
     shared,
     acceptClipboard,
     sendClipboard,
     menuKey,
     noLionFS,
-    useLocalCursor,
     sendLocalUsername,
     maxCutText,
     scalingFactor,
@@ -333,7 +331,7 @@ public class Parameters {
   static LogWriter vlog = new LogWriter("Parameters");
 
        public static void saveViewerParameters(String filename, String servername) {
-       
+
          // Write to the registry or a predefined file if no filename was specified.
     String filepath;
     if (filename == null || filename.isEmpty()) {
@@ -349,13 +347,13 @@ public class Parameters {
     } else {
       filepath = filename;
     }
-       
+
          /* Write parameters to file */
     File f = new File(filepath);
     if (f.exists() && !f.canWrite())
            throw new Exception(String.format("Failed to write configuration file,"+
                                         "can't open %s", filepath)); 
-         
+
     PrintWriter pw = null;
     try {
       pw = new PrintWriter(f, "UTF-8");
@@ -365,12 +363,12 @@ public class Parameters {
 
     pw.println(IDENTIFIER_STRING);
     pw.println("");
-       
+
          if (servername != null && !servername.isEmpty()) {
            pw.println(String.format("ServerName=%s\n", servername));
       updateConnHistory(servername);
     }
-         
+
     for (int i = 0; i < parameterArray.length; i++) {
       if (parameterArray[i] instanceof StringParameter) {
         //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName()))
@@ -432,7 +430,7 @@ public class Parameters {
 
     int lineNr = 0;
     while (line != null) {
-      
+
       // Read the next line
       try {
         line = reader.readLine();
@@ -449,7 +447,7 @@ public class Parameters {
         if(line.equals(IDENTIFIER_STRING))
           continue;
         else
-          throw new Exception(String.format(new String("Configuration file %s is in an invalid format"), filename));
+          throw new Exception(String.format("Configuration file %s is in an invalid format", filename));
       }
 
       // Skip empty lines and comments
@@ -551,13 +549,13 @@ public class Parameters {
   }
 
   public static String loadFromReg() {
-  
+
     String hKey = "global";
-  
+
     String servername = UserPreferences.get(hKey, "ServerName");
     if (servername == null)
       servername = "";
-    
+
     for (int i = 0; i < parameterArray.length; i++) {
       if (parameterArray[i] instanceof StringParameter) {
         if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) {
@@ -582,7 +580,7 @@ public class Parameters {
                    parameterArray[i].getName()));
       }
     }
-  
+
     return servername;
   }
 
index 8fc2760b33845ce095bba7005888a16d953e0b17..564eb8eb475a77733c61fefd3c41585222d77a7a 100644 (file)
@@ -24,84 +24,38 @@ import java.awt.image.*;
 import java.nio.ByteOrder;
 
 import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Point;
 
-abstract public class PlatformPixelBuffer extends PixelBuffer
+public class PlatformPixelBuffer extends FullFramePixelBuffer
 {
-  public PlatformPixelBuffer(PixelFormat pf, int w, int h, DesktopWindow desktop_) {
-    desktop = desktop_;
-    PixelFormat nativePF = getNativePF();
-    if (nativePF.depth > pf.depth) {
-      setPF(pf);
-    } else {
-      setPF(nativePF);
-    }
-    resize(w, h);
+  public PlatformPixelBuffer(PixelFormat pf,
+                             int w, int h,
+                             WritableRaster data)
+  {
+    super(pf, w, h, data);
+    damage = new Rect(0, 0, w, h);
   }
 
-  // resize() resizes the image, preserving the image data where possible.
-  abstract public void resize(int w, int h);
-
-  public PixelFormat getNativePF() {
-    PixelFormat pf;
-    cm = tk.getColorModel();
-    if (cm.getColorSpace().getType() == java.awt.color.ColorSpace.TYPE_RGB) {
-      int depth = ((cm.getPixelSize() > 24) ? 24 : cm.getPixelSize());
-      int bpp = (depth > 16 ? 32 : (depth > 8 ? 16 : 8));
-      ByteOrder byteOrder = ByteOrder.nativeOrder();
-      boolean bigEndian = (byteOrder == ByteOrder.BIG_ENDIAN ? true : false);
-      boolean trueColour = (depth > 8 ? true : false);
-      int redShift    = cm.getComponentSize()[0] + cm.getComponentSize()[1];
-      int greenShift  = cm.getComponentSize()[0];
-      int blueShift   = 0;
-      pf = new PixelFormat(bpp, depth, bigEndian, trueColour,
-        (depth > 8 ? 0xff : 0),
-        (depth > 8 ? 0xff : 0),
-        (depth > 8 ? 0xff : 0),
-        (depth > 8 ? redShift : 0),
-        (depth > 8 ? greenShift : 0),
-        (depth > 8 ? blueShift : 0));
-    } else {
-      pf = new PixelFormat(8, 8, false, false, 7, 7, 3, 0, 3, 6);
+  public void commitBufferRW(Rect r)
+  {
+    super.commitBufferRW(r);
+    synchronized(damage) {
+      Rect n = damage.union_boundary(r);
+      damage.setXYWH(n.tl.x, n.tl.y, n.width(), n.height());
     }
-    vlog.debug("Native pixel format is "+pf.print());
-    return pf;
   }
 
-  abstract public void imageRect(int x, int y, int w, int h, Object pix);
+  public Rect getDamage() {
+    Rect r = new Rect();
 
-  // setColourMapEntries() changes some of the entries in the colourmap.
-  // However these settings won't take effect until updateColourMap() is
-  // called.  This is because getting java to recalculate its internal
-  // translation table and redraw the screen is expensive.
-
-  public void setColourMapEntries(int firstColour, int nColours_,
-                                               int[] rgbs) {
-    nColours = nColours_;
-    reds = new byte[nColours];
-    blues = new byte[nColours];
-    greens = new byte[nColours];
-    for (int i = 0; i < nColours; i++) {
-      reds[firstColour+i] = (byte)(rgbs[i*3]   >> 8);
-      greens[firstColour+i] = (byte)(rgbs[i*3+1] >> 8);
-      blues[firstColour+i] = (byte)(rgbs[i*3+2] >> 8);
+    synchronized(damage) {
+      r.setXYWH(damage.tl.x, damage.tl.y, damage.width(), damage.height());
+      damage.clear();
     }
-  }
 
-  public void updateColourMap() {
-    cm = new IndexColorModel(8, nColours, reds, greens, blues);
+    return r;
   }
 
-  protected static Toolkit tk = Toolkit.getDefaultToolkit();
-
-  abstract public Image getImage();
-
-  protected Image image;
-
-  int nColours;
-  byte[] reds;
-  byte[] greens;
-  byte[] blues;
+  protected Rect damage;
 
-  DesktopWindow desktop;
-  static LogWriter vlog = new LogWriter("PlatformPixelBuffer");
 }
index 3a5fb54ca445ac1eb647c2667606d42f1da57cc7..bf07d2d5837404609b177389956a5e648cbffc72 100644 (file)
@@ -1,6 +1,8 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright (C) 2011-2015 Brian P. Hinz
- * Copyright (C) 2012-2013 D. R. Commander.  All Rights Reserved.
+ * Copyright (C) 2006 Constantin Kaplinsky.  All Rights Reserved.
+ * Copyright (C) 2009 Paul Donohue.  All Rights Reserved.
+ * Copyright (C) 2010, 2012-2013 D. R. Commander.  All Rights Reserved.
+ * Copyright (C) 2011-2014 Brian P. Hinz
  *
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * USA.
  */
 
-package com.tigervnc.vncviewer;
+//
+// DesktopWindow is an AWT Canvas representing a VNC desktop.
+//
+// Methods on DesktopWindow are called from both the GUI thread and the thread
+// which processes incoming RFB messages ("the RFB thread").  This means we
+// need to be careful with synchronization here.
+//
 
+package com.tigervnc.vncviewer;
+import java.awt.*;
 import java.awt.Color;
+import java.awt.color.ColorSpace;
 import java.awt.event.*;
-import java.awt.Dimension;
-import java.awt.Event;
-import java.awt.GraphicsConfiguration;
-import java.awt.GraphicsDevice;
-import java.awt.GraphicsEnvironment;
-import java.awt.Image;
-import java.awt.Insets;
-import java.awt.Window;
-import java.lang.reflect.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.*;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.Clipboard;
+import java.io.BufferedReader;
+import java.nio.*;
 import javax.swing.*;
 
-import com.tigervnc.rfb.*;
-import java.lang.Exception;
-import java.awt.Rectangle;
+import javax.imageio.*;
+import java.io.*;
 
-import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
-import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER;
-import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED;
-import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
+import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Cursor;
+import com.tigervnc.rfb.Point;
 
 import static com.tigervnc.vncviewer.Parameters.*;
 
-public class Viewport extends JFrame
-{
-  public Viewport(String name, CConn cc_) {
+class Viewport extends JPanel implements MouseListener,
+  MouseMotionListener, MouseWheelListener, KeyListener {
+
+  static LogWriter vlog = new LogWriter("Viewport");
+
+  public Viewport(int w, int h, PixelFormat serverPF, CConn cc_)
+  {
     cc = cc_;
-    setTitle(name+" - TigerVNC");
-    setFocusable(false);
-    setFocusTraversalKeysEnabled(false);
-    if (!VncViewer.os.startsWith("mac os x"))
-      setIconImage(VncViewer.frameIcon);
-    UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
-      new UIDefaults.LazyInputMap(new Object[]{}));
-    sp = new JScrollPane();
-    sp.getViewport().setBackground(Color.BLACK);
-    sp.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
-    getContentPane().add(sp);
-    if (VncViewer.os.startsWith("mac os x")) {
-      if (!noLionFS.getValue())
-        enableLionFS();
-    }
-    addWindowFocusListener(new WindowAdapter() {
-      public void windowGainedFocus(WindowEvent e) {
-        if (isVisible())
-          sp.getViewport().getView().requestFocusInWindow();
+    setScaledSize(cc.cp.width, cc.cp.height);
+    frameBuffer = createFramebuffer(serverPF, w, h);
+    assert(frameBuffer != null);
+    setBackground(Color.BLACK);
+
+    cc.setFramebuffer(frameBuffer);
+    OptionsDialog.addCallback("handleOptions", this);
+
+    addMouseListener(this);
+    addMouseWheelListener(this);
+    addMouseMotionListener(this);
+    addKeyListener(this);
+    addFocusListener(new FocusAdapter() {
+      public void focusGained(FocusEvent e) {
+        ClipboardDialog.clientCutText();
       }
-      public void windowLostFocus(WindowEvent e) {
+      public void focusLost(FocusEvent e) {
         cc.releaseDownKeys();
       }
     });
-    addWindowListener(new WindowAdapter() {
-      public void windowClosing(WindowEvent e) {
-        cc.close();
+    setFocusTraversalKeysEnabled(false);
+    setFocusable(true);
+
+    // Send a fake pointer event so that the server will stop rendering
+    // a server-side cursor. Ideally we'd like to send the actual pointer
+    // position, but we can't really tell when the window manager is done
+    // placing us so we don't have a good time for that.
+    cc.writer().writePointerEvent(new Point(w/2, h/2), 0);
+  }
+
+  // Most efficient format (from Viewport's point of view)
+  public PixelFormat getPreferredPF()
+  {
+    return frameBuffer.getPF();
+  }
+
+  // Copy the areas of the framebuffer that have been changed (damaged)
+  // to the displayed window.
+  public void updateWindow() {
+    Rect r = frameBuffer.getDamage();
+    if (!r.is_empty()) {
+      if (image == null)
+        image = (BufferedImage)createImage(frameBuffer.width(), frameBuffer.height());
+      image.getRaster().setDataElements(r.tl.x, r.tl.y, frameBuffer.getBuffer(r));
+      if (cc.cp.width != scaledWidth ||
+          cc.cp.height != scaledHeight) {
+        AffineTransform t = new AffineTransform(); 
+        t.scale((double)scaleRatioX, (double)scaleRatioY);
+        Rectangle s = new Rectangle(r.tl.x, r.tl.y, r.width(), r.height());
+        s = t.createTransformedShape(s).getBounds();
+        paintImmediately(s.x, s.y, s.width, s.height);
+      } else {
+        paintImmediately(r.tl.x, r.tl.y, r.width(), r.height());
       }
-    });
-    addComponentListener(new ComponentAdapter() {
-      public void componentResized(ComponentEvent e) {
-        String scaleString = scalingFactor.getValue();
-        if (scaleString.equalsIgnoreCase("Auto") ||
-            scaleString.equalsIgnoreCase("FixedRatio")) {
-          if ((sp.getSize().width != cc.desktop.scaledWidth) ||
-              (sp.getSize().height != cc.desktop.scaledHeight)) {
-            cc.desktop.setScaledSize();
-            sp.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
-            sp.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER);
-            sp.validate();
-            if (getExtendedState() != JFrame.MAXIMIZED_BOTH &&
-                !fullScreen.getValue()) {
-              sp.setSize(new Dimension(cc.desktop.scaledWidth,
-                                       cc.desktop.scaledHeight));
-              int w = cc.desktop.scaledWidth + getInsets().left +
-                      getInsets().right;
-              int h = cc.desktop.scaledHeight + getInsets().top +
-                      getInsets().bottom;
-              if (scaleString.equalsIgnoreCase("FixedRatio"))
-                setSize(w, h);
-            }
+    }
+  }
+
+  static final int[] dotcursor_xpm = {
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+    0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
+    0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
+    0x00000000, 0xff000000, 0xff000000, 0xff000000, 0x00000000,
+    0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+  };
+
+  public void setCursor(int width, int height, Point hotspot,
+                        byte[] data, byte[] mask)
+  {
+
+    int mask_len = ((width+7)/8) * height;
+    int i;
+
+    for (i = 0; i < mask_len; i++)
+      if ((mask[i] & 0xff) != 0) break;
+
+    if ((i == mask_len) && dotWhenNoCursor.getValue()) {
+      vlog.debug("cursor is empty - using dot");
+      cursor = new BufferedImage(5, 5, BufferedImage.TYPE_INT_ARGB_PRE);
+      cursor.setRGB(0, 0, 5, 5, dotcursor_xpm, 0, 5);
+      cursorHotspot.x = cursorHotspot.y = 3;
+    } else {
+      if ((width == 0) || (height == 0)) {
+        cursor = new BufferedImage(tk.getBestCursorSize(0, 0).width,
+                                   tk.getBestCursorSize(0, 0).height,
+                                   BufferedImage.TYPE_INT_ARGB_PRE);
+        cursorHotspot.x = cursorHotspot.y = 0;
+      } else {
+        ByteBuffer buffer = ByteBuffer.allocate(width*height*4);
+        ByteBuffer in, o, m;
+        int m_width;
+
+        PixelFormat pf;
+
+        pf = cc.cp.pf();
+
+        in = (ByteBuffer)ByteBuffer.wrap(data).mark();
+        o = (ByteBuffer)buffer.duplicate().mark();
+        m = ByteBuffer.wrap(mask);
+        m_width = (width+7)/8;
+
+        for (int y = 0; y < height; y++) {
+          for (int x = 0; x < width; x++) {
+            // NOTE: BufferedImage needs ARGB, rather than RGBA
+            if ((m.get((m_width*y)+(x/8)) & 0x80>>(x%8)) != 0)
+              o.put((byte)255);
+            else
+              o.put((byte)0);
+
+            pf.rgbFromBuffer(o, in.duplicate(), 1);
+
+            o.position(o.reset().position() + 4).mark();
+            in.position(in.position() + pf.bpp/8);
           }
-        } else {
-          sp.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
-          sp.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
-          sp.validate();
-        }
-        if (cc.desktop.cursor != null) {
-          Cursor cursor = cc.desktop.cursor;
-          cc.setCursor(cursor.width(),cursor.height(),cursor.hotspot,
-                       cursor.data, cursor.mask);
         }
+
+        IntBuffer rgb =
+          IntBuffer.allocate(width*height).put(buffer.asIntBuffer());
+        cursor = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
+        cursor.setRGB(0, 0, width, height, rgb.array(), 0, width);
+
+        cursorHotspot = hotspot;
+
       }
-    });
+    }
+
+    int cw = (int)Math.floor((float)cursor.getWidth() * scaleRatioX);
+    int ch = (int)Math.floor((float)cursor.getHeight() * scaleRatioY);
+
+    int x = (int)Math.floor((float)cursorHotspot.x * scaleRatioX);
+    int y = (int)Math.floor((float)cursorHotspot.y * scaleRatioY);
+
+    java.awt.Cursor softCursor;
+
+    Dimension cs = tk.getBestCursorSize(cw, ch);
+    if (cs.width != cw && cs.height != ch) {
+      cw = Math.min(cw, cs.width);
+      ch = Math.min(ch, cs.height);
+      x = (int)Math.min(x, Math.max(cs.width - 1, 0));
+      y = (int)Math.min(y, Math.max(cs.height - 1, 0));
+      BufferedImage scaledImage = 
+        new BufferedImage(cs.width, cs.height, BufferedImage.TYPE_INT_ARGB);
+      Graphics2D g2 = scaledImage.createGraphics();
+      g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+                          RenderingHints.VALUE_RENDER_QUALITY);
+      g2.drawImage(cursor,
+                   0, 0, cw, ch,
+                   0, 0, cursor.getWidth(), cursor.getHeight(), null);
+      g2.dispose();
+      java.awt.Point hs = new java.awt.Point(x, y);
+      softCursor = tk.createCustomCursor(scaledImage, hs, "softCursor");
+      scaledImage.flush();
+    } else {
+      java.awt.Point hs = new java.awt.Point(x, y);
+      softCursor = tk.createCustomCursor(cursor, hs, "softCursor");
+    }
+
+    cursor.flush();
+
+    setCursor(softCursor);
+
   }
 
-  public void setName(String name) {
-    setTitle(name + "- TigerVNC");
+  public void resize(int x, int y, int w, int h) {
+    if ((w != frameBuffer.width()) || (h != frameBuffer.height())) {
+      vlog.debug("Resizing framebuffer from "+frameBuffer.width()+"x"+
+                 frameBuffer.height()+" to "+w+"x"+h);
+      frameBuffer = createFramebuffer(frameBuffer.getPF(), w, h);
+      assert(frameBuffer != null);
+      cc.setFramebuffer(frameBuffer);
+      image = null;
+    }
+    setScaledSize(w, h);
   }
 
-  boolean lionFSSupported() { return canDoLionFS; }
+  private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h)
+  {
+    PlatformPixelBuffer fb;
 
-  void enableLionFS() {
-    try {
-      String version = System.getProperty("os.version");
-      int firstDot = version.indexOf('.');
-      int lastDot = version.lastIndexOf('.');
-      if (lastDot > firstDot && lastDot >= 0) {
-        version = version.substring(0, version.indexOf('.', firstDot + 1));
-      }
-      double v = Double.parseDouble(version);
-      if (v < 10.7)
-        throw new Exception("Operating system version is " + v);
-
-      Class fsuClass = Class.forName("com.apple.eawt.FullScreenUtilities");
-      Class argClasses[] = new Class[]{Window.class, Boolean.TYPE};
-      Method setWindowCanFullScreen =
-        fsuClass.getMethod("setWindowCanFullScreen", argClasses);
-      setWindowCanFullScreen.invoke(fsuClass, this, true);
-
-      canDoLionFS = true;
-    } catch (Exception e) {
-      vlog.debug("Could not enable OS X 10.7+ full-screen mode: " +
-                 e.getMessage());
-    }
+    fb = new JavaPixelBuffer(w, h);
+
+    return fb;
+  }
+
+  //
+  // Callback methods to determine geometry of our Component.
+  //
+
+  public Dimension getPreferredSize() {
+    return new Dimension(scaledWidth, scaledHeight);
   }
 
-  public void toggleLionFS() {
-    try {
-      Class appClass = Class.forName("com.apple.eawt.Application");
-      Method getApplication = appClass.getMethod("getApplication",
-                                                 (Class[])null);
-      Object app = getApplication.invoke(appClass);
-      Method requestToggleFullScreen =
-        appClass.getMethod("requestToggleFullScreen", Window.class);
-      requestToggleFullScreen.invoke(app, this);
-    } catch (Exception e) {
-      vlog.debug("Could not toggle OS X 10.7+ full-screen mode: " +
-                 e.getMessage());
+  public Dimension getMinimumSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  public Dimension getMaximumSize() {
+    return new Dimension(scaledWidth, scaledHeight);
+  }
+
+  public void paintComponent(Graphics g) {
+    Graphics2D g2 = (Graphics2D)g;
+    if (cc.cp.width != scaledWidth ||
+        cc.cp.height != scaledHeight) {
+      g2.setRenderingHint(RenderingHints.KEY_RENDERING,
+                          RenderingHints.VALUE_RENDER_QUALITY);
+      g2.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);
+    } else {
+      g2.drawImage(image, 0, 0, null);
     }
+    g2.dispose();
   }
 
-  public JViewport getViewport() {
-    return sp.getViewport();
+  // Mouse-Motion callback function
+  private void mouseMotionCB(MouseEvent e) {
+    if (!viewOnly.getValue() &&
+        e.getX() >= 0 && e.getX() <= scaledWidth &&
+        e.getY() >= 0 && e.getY() <= scaledHeight)
+      cc.writePointerEvent(translateMouseEvent(e));
   }
+  public void mouseDragged(MouseEvent e) { mouseMotionCB(e); }
+  public void mouseMoved(MouseEvent e) { mouseMotionCB(e); }
 
-  public void setGeometry(int x, int y, int w, int h) {
-    pack();
-    if (!fullScreen.getValue())
-      setLocation(x, y);
+  // Mouse callback function
+  private void mouseCB(MouseEvent e) {
+    if (!viewOnly.getValue())
+      if ((e.getID() == MouseEvent.MOUSE_RELEASED) ||
+          (e.getX() >= 0 && e.getX() <= scaledWidth &&
+           e.getY() >= 0 && e.getY() <= scaledHeight))
+        cc.writePointerEvent(translateMouseEvent(e));
+  }
+  public void mouseReleased(MouseEvent e) { mouseCB(e); }
+  public void mousePressed(MouseEvent e) { mouseCB(e); }
+  public void mouseClicked(MouseEvent e) {}
+  public void mouseEntered(MouseEvent e) {
+    if (embed.getValue())
+      requestFocus();
   }
+  public void mouseExited(MouseEvent e) {}
 
-  public Dimension getScreenSize() {
-    return getScreenBounds().getSize();
+  // MouseWheel callback function
+  private void mouseWheelCB(MouseWheelEvent e) {
+    if (!viewOnly.getValue())
+      cc.writeWheelEvent(e);
   }
 
-  public Rectangle getScreenBounds() {
-    GraphicsEnvironment ge =
-      GraphicsEnvironment.getLocalGraphicsEnvironment();
-    Rectangle r = new Rectangle();
-    setMaximizedBounds(null);
-    if (fullScreenAllMonitors.getValue()) {
-      for (GraphicsDevice gd : ge.getScreenDevices())
-        for (GraphicsConfiguration gc : gd.getConfigurations())
-          r = r.union(gc.getBounds());
-      Rectangle mb = new Rectangle(r);
-      mb.grow(getInsets().left, getInsets().bottom);
-      setMaximizedBounds(mb);
+  public void mouseWheelMoved(MouseWheelEvent e) {
+    mouseWheelCB(e);
+  }
+
+  private static final Integer keyEventLock = 0; 
+
+  // Handle the key-typed event.
+  public void keyTyped(KeyEvent e) { }
+
+  // Handle the key-released event.
+  public void keyReleased(KeyEvent e) {
+    synchronized(keyEventLock) {
+      cc.writeKeyEvent(e);
+    }
+  }
+
+  // Handle the key-pressed event.
+  public void keyPressed(KeyEvent e)
+  {
+    if (e.getKeyCode() == MenuKey.getMenuKeyCode()) {
+      java.awt.Point pt = e.getComponent().getMousePosition();
+      if (pt != null) {
+        F8Menu menu = new F8Menu(cc);
+        menu.show(e.getComponent(), (int)pt.getX(), (int)pt.getY());
+      }
+      return;
+    }
+    int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
+    if ((e.getModifiers() & ctrlAltShiftMask) == ctrlAltShiftMask) {
+      switch (e.getKeyCode()) {
+        case KeyEvent.VK_A:
+          VncViewer.showAbout(this);
+          return;
+        case KeyEvent.VK_F:
+          if (cc.desktop.fullscreen_active())
+            cc.desktop.fullscreen_on();
+          else
+            cc.desktop.fullscreen_off();
+          return;
+        case KeyEvent.VK_H:
+          cc.refresh();
+          return;
+        case KeyEvent.VK_I:
+          cc.showInfo();
+          return;
+        case KeyEvent.VK_O:
+            OptionsDialog.showDialog(this);
+          return;
+        case KeyEvent.VK_W:
+          VncViewer.newViewer();
+          return;
+        case KeyEvent.VK_LEFT:
+        case KeyEvent.VK_RIGHT:
+        case KeyEvent.VK_UP:
+        case KeyEvent.VK_DOWN:
+          return;
+      }
+    }
+    if ((e.getModifiers() & Event.META_MASK) == Event.META_MASK) {
+      switch (e.getKeyCode()) {
+        case KeyEvent.VK_COMMA:
+        case KeyEvent.VK_N:
+        case KeyEvent.VK_W:
+        case KeyEvent.VK_I:
+        case KeyEvent.VK_R:
+        case KeyEvent.VK_L:
+        case KeyEvent.VK_F:
+        case KeyEvent.VK_Z:
+        case KeyEvent.VK_T:
+          return;
+      }
+    }
+    synchronized(keyEventLock) {
+      cc.writeKeyEvent(e);
+    }
+  }
+
+  public void setScaledSize(int width, int height)
+  {
+    assert(width != 0 && height != 0);
+    String scaleString = scalingFactor.getValue();
+    if (remoteResize.getValue()) {
+      scaledWidth = width;
+      scaledHeight = height;
+      scaleRatioX = 1.00f;
+      scaleRatioY = 1.00f;
     } else {
-      GraphicsDevice gd = ge.getDefaultScreenDevice();
-      GraphicsConfiguration gc = gd.getDefaultConfiguration();
-      r = gc.getBounds();
+      if (scaleString.matches("^[0-9]+$")) {
+        int scalingFactor = Integer.parseInt(scaleString);
+        scaledWidth =
+          (int)Math.floor((float)width * (float)scalingFactor/100.0);
+        scaledHeight =
+          (int)Math.floor((float)height * (float)scalingFactor/100.0);
+      } else if (scaleString.equalsIgnoreCase("Auto")) {
+        scaledWidth = width;
+        scaledHeight = height;
+      } else {
+        float widthRatio = (float)width / (float)cc.cp.width;
+        float heightRatio = (float)height / (float)cc.cp.height;
+        float ratio = Math.min(widthRatio, heightRatio);
+        scaledWidth = (int)Math.floor(cc.cp.width * ratio);
+        scaledHeight = (int)Math.floor(cc.cp.height * ratio);
+      }
+      scaleRatioX = (float)scaledWidth / (float)cc.cp.width;
+      scaleRatioY = (float)scaledHeight / (float)cc.cp.height;
     }
-    return r;
+    if (scaledWidth != getWidth() || scaledHeight != getHeight())
+      setSize(new Dimension(scaledWidth, scaledHeight));
   }
 
-  public static Window getFullScreenWindow() {
-    GraphicsEnvironment ge =
-      GraphicsEnvironment.getLocalGraphicsEnvironment();
-    GraphicsDevice gd = ge.getDefaultScreenDevice();
-    Window fullScreenWindow = gd.getFullScreenWindow();
-    return fullScreenWindow;
+  private MouseEvent translateMouseEvent(MouseEvent e)
+  {
+    if (cc.cp.width != scaledWidth ||
+        cc.cp.height != scaledHeight) {
+      int sx = (scaleRatioX == 1.00) ?
+        e.getX() : (int)Math.floor(e.getX() / scaleRatioX);
+      int sy = (scaleRatioY == 1.00) ?
+        e.getY() : (int)Math.floor(e.getY() / scaleRatioY);
+      e.translatePoint(sx - e.getX(), sy - e.getY());
+    }
+    return e;
   }
 
-  public static void setFullScreenWindow(Window fullScreenWindow) {
-    GraphicsEnvironment ge =
-      GraphicsEnvironment.getLocalGraphicsEnvironment();
-    GraphicsDevice gd = ge.getDefaultScreenDevice();
-    if (gd.isFullScreenSupported())
-      gd.setFullScreenWindow(fullScreenWindow);
+  public void handleOptions()
+  {
+    /*
+    setScaledSize(cc.cp.width, cc.cp.height);
+    if (!oldSize.equals(new Dimension(scaledWidth, scaledHeight))) {
+    // Re-layout the DesktopWindow when the scaled size changes.
+    // Ideally we'd do this with a ComponentListener, but unfortunately
+    // sometimes a spurious resize event is triggered on the viewport
+    // when the DesktopWindow is manually resized via the drag handles.
+    if (cc.desktop != null && cc.desktop.isVisible()) {
+      JScrollPane scroll = (JScrollPane)((JViewport)getParent()).getParent();
+      scroll.setViewportBorder(BorderFactory.createEmptyBorder(0,0,0,0));
+      cc.desktop.pack();
+    }
+    */
   }
 
-  CConn cc;
-  JScrollPane sp;
-  boolean canDoLionFS;
-  static LogWriter vlog = new LogWriter("Viewport");
-}
+  // access to cc by different threads is specified in CConn
+  private CConn cc;
+  private BufferedImage image;
 
+  // access to the following must be synchronized:
+  public PlatformPixelBuffer frameBuffer;
+
+  static Toolkit tk = Toolkit.getDefaultToolkit();
+
+  public int scaledWidth = 0, scaledHeight = 0;
+  float scaleRatioX, scaleRatioY;
+
+  BufferedImage cursor;
+  Point cursorHotspot = new Point();
+
+}
index a3daef31ffcc134356db55356dda14ed60369b94..f5b317753b16c16c56b90e51171fa220c8603d1b 100644 (file)
@@ -47,6 +47,7 @@ import java.util.jar.Attributes;
 import java.util.jar.Manifest;
 import java.util.*;
 import javax.swing.*;
+import javax.swing.border.*;
 import javax.swing.plaf.FontUIResource;
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager.*;
@@ -60,10 +61,11 @@ import static com.tigervnc.vncviewer.Parameters.*;
 public class VncViewer extends javax.swing.JApplet 
   implements Runnable, ActionListener {
 
-  public static final String aboutText = new String("TigerVNC Java Viewer v%s (%s)%n"+
-                                                    "Built on %s at %s%n"+
-                                                    "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+
-                                                    "See http://www.tigervnc.org for information on TigerVNC.");
+  public static final String aboutText =
+    new String("TigerVNC Java Viewer v%s (%s)%n"+
+               "Built on %s at %s%n"+
+               "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+
+               "See http://www.tigervnc.org for information on TigerVNC.");
 
   public static String version = null;
   public static String build = null;
@@ -79,6 +81,7 @@ public class VncViewer extends javax.swing.JApplet
     VncViewer.class.getResourceAsStream("timestamp");
   public static final String os = 
     System.getProperty("os.name").toLowerCase();
+  private static VncViewer applet;
 
   public static void setLookAndFeel() {
     try {
@@ -140,8 +143,8 @@ public class VncViewer extends javax.swing.JApplet
   }
 
   public VncViewer() {
-    //this(new String[0]);
-    embed.setParam(true);
+    // Only called in applet mode
+    this(new String[0]);
   }
 
   public VncViewer(String[] argv) {
@@ -311,7 +314,7 @@ public class VncViewer extends javax.swing.JApplet
 
   public void appletDragStarted() {
     embed.setParam(false);
-    cc.recreateViewport();
+    //cc.recreateViewport();
     JFrame f = (JFrame)JOptionPane.getFrameForComponent(this);
     // The default JFrame created by the drag event will be
     // visible briefly between appletDragStarted and Finished.
@@ -333,20 +336,69 @@ public class VncViewer extends javax.swing.JApplet
     cc.setCloseListener(null);
   }
 
-  public void init() {
-    vlog.debug("init called");
-    Container parent = getParent();
-    while (!parent.isFocusCycleRoot()) {
-      parent = parent.getParent();
+  public static void setupEmbeddedFrame(JScrollPane sp) {
+    InputMap im = sp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
+    int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
+    if (im != null) {
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, ctrlAltShiftMask),
+             "unitScrollUp");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, ctrlAltShiftMask),
+             "unitScrollDown");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, ctrlAltShiftMask),
+             "unitScrollLeft");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, ctrlAltShiftMask),
+             "unitScrollRight");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, ctrlAltShiftMask),
+             "scrollUp");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, ctrlAltShiftMask),
+             "scrollDown");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, ctrlAltShiftMask),
+             "scrollLeft");
+      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, ctrlAltShiftMask),
+             "scrollRight");
     }
-    ((Frame)parent).setModalExclusionType(null);
-    parent.setFocusable(false);
-    parent.setFocusTraversalKeysEnabled(false);
+    applet.getContentPane().removeAll();
+    applet.getContentPane().add(sp);
+    applet.validate();
+  }
+
+  public void init() {
+    // Called right after zero-arg constructor in applet mode
     setLookAndFeel();
     setBackground(Color.white);
+    applet = this;
+    String servername = loadAppletParameters(applet);
+    vncServerName.setParam(servername);
+    alwaysShowServerDialog.setParam(false);
+    if (embed.getValue()) {
+      fullScreen.setParam(false);
+      remoteResize.setParam(false);
+      maximize.setParam(false);
+      scalingFactor.setParam("100");
+    }
+    setFocusTraversalKeysEnabled(false);
+    addFocusListener(new FocusAdapter() {
+      public void focusGained(FocusEvent e) {
+        if (cc != null && cc.desktop != null)
+          cc.desktop.viewport.requestFocusInWindow();
+      }
+    });
+    Frame frame = (Frame)getFocusCycleRootAncestor();
+    frame.setFocusTraversalKeysEnabled(false);
+    frame.addWindowListener(new WindowAdapter() {
+      // Transfer focus to scrollpane when browser receives it
+      public void windowActivated(WindowEvent e) {
+        if (cc != null && cc.desktop != null)
+          cc.desktop.viewport.requestFocusInWindow();
+      }
+      public void windowDeactivated(WindowEvent e) {
+        if (cc != null)
+          cc.releaseDownKeys();
+      }
+    });
   }
 
-  private void getTimestamp() {
+  private static void getTimestamp() {
     if (version == null || build == null) {
       try {
         Manifest manifest = new Manifest(timestamp);
@@ -369,9 +421,9 @@ public class VncViewer extends javax.swing.JApplet
       pkgTime = attributes.getValue("Package-Time");
     } catch (java.lang.Exception e) { }
 
-    Window fullScreenWindow = Viewport.getFullScreenWindow();
+    Window fullScreenWindow = DesktopWindow.getFullScreenWindow();
     if (fullScreenWindow != null)
-      Viewport.setFullScreenWindow(null);
+      DesktopWindow.setFullScreenWindow(null);
     String msg =
       String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build,
                     VncViewer.buildDate, VncViewer.buildTime);
@@ -384,20 +436,10 @@ public class VncViewer extends javax.swing.JApplet
     dlg.setAlwaysOnTop(true);
     dlg.setVisible(true);
     if (fullScreenWindow != null)
-      Viewport.setFullScreenWindow(fullScreenWindow);
+      DesktopWindow.setFullScreenWindow(fullScreenWindow);
   }
 
   public void start() {
-    vlog.debug("start called");
-    getTimestamp();
-    if (embed.getValue()) {
-      setupEmbeddedFrame();
-      alwaysShowServerDialog.setParam(false);
-      String servername = loadAppletParameters(this);
-      vncServerName.setParam(servername);
-      fullScreen.setParam(false);
-      scalingFactor.setParam("100");
-    }
     thread = new Thread(this);
     thread.start();
   }
@@ -409,41 +451,6 @@ public class VncViewer extends javax.swing.JApplet
       System.exit(n);
   }
 
-  private void setupEmbeddedFrame() {
-    UIManager.getDefaults().put("ScrollPane.ancestorInputMap",
-      new UIDefaults.LazyInputMap(new Object[]{}));
-    sp = new JScrollPane();
-    sp.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
-    sp.getViewport().setBackground(Color.BLACK);
-    InputMap im = sp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
-    int ctrlAltShiftMask = Event.SHIFT_MASK | Event.CTRL_MASK | Event.ALT_MASK;
-    if (im != null) {
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, ctrlAltShiftMask),
-             "unitScrollUp");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, ctrlAltShiftMask),
-             "unitScrollDown");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, ctrlAltShiftMask),
-             "unitScrollLeft");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, ctrlAltShiftMask),
-             "unitScrollRight");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, ctrlAltShiftMask),
-             "scrollUp");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, ctrlAltShiftMask),
-             "scrollDown");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, ctrlAltShiftMask),
-             "scrollLeft");
-      im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, ctrlAltShiftMask),
-             "scrollRight");
-    }
-    sp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
-    sp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
-    add(sp);
-  }
-
-  public static JViewport getViewport() {
-    return sp.getViewport();
-  }
-
   // If "Reconnect" button is pressed
   public void actionPerformed(ActionEvent e) {
     getContentPane().removeAll();
@@ -524,7 +531,7 @@ public class VncViewer extends javax.swing.JApplet
       if (cc == null || !cc.shuttingDown) {
         reportException(e);
         if (cc != null)
-          cc.deleteWindow();
+          cc.close();
       } else if (embed.getValue()) {
         reportException(new java.lang.Exception("Connection closed"));
         exit(0);
@@ -534,7 +541,6 @@ public class VncViewer extends javax.swing.JApplet
   }
 
   public static CConn cc;
-  private static JScrollPane sp;
   public static StringParameter config
   = new StringParameter("Config",
   "Specifies a configuration file to load.", null);