--- /dev/null
+/* 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));
+ }
+
+}
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;
--- /dev/null
+/* 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;
+
+}
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;
}
--- /dev/null
+/* 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;
+}
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) {
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;
}
--- /dev/null
+/* 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());
+ }
+ }
+}
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");
}
--- /dev/null
+/* 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);
+ }
+
+}
/* 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_;
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) {
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) {
os.pad(3);
}
+ public final boolean isBigEndian() {
+ return bigEndian;
+ }
+
+ public final boolean isLittleEndian() {
+ return ! bigEndian;
+ }
+
public final boolean is888() {
if(!trueColour)
return false;
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);
}
}
- 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;
}
}
}
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;
public int redShift;
public int greenShift;
public int blueShift;
+
+ protected int redBits, greenBits, blueBits;
+ protected int maxBits, minBits;
+ protected boolean endianMismatch;
+
+ private ColorModel model;
}
/* 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);
+ }
+
}
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);
+ }
+
}
--- /dev/null
+/* 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());
+ }
+}
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();
}
// "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
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
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) {
}
}
- // 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");
}
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();
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;
}
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 {
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);
}
}
}
// 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 {
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) {
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;
}
+++ /dev/null
-/* 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");
-}
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;
public CConn(String vncServerName, Socket socket)
{
+ serverHost = null; serverPort = 0; desktop = null;
pendingPFChange = false;
currentEncoding = Encodings.encodingTight; lastServerEncoding = -1;
formatChange = false; encodingChange = false;
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;
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;
vlog.info("connected to host "+Hostname.getHost(name)+" port "+Hostname.getPort(name));
}
+ // See callback below
sock.inStream().setBlockCallback(this);
setStreams(sock.inStream(), sock.outStream());
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) {
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
// 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) &&
}
// 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);
}
// 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.
// 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.
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;
}
// 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())
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);
}
pf.read(memStream);
- desktop.setServerPF(pf);
cp.setPF(pf);
}
}
+ ////////////////////// Internal methods //////////////////////
private void resizeFramebuffer()
{
if (desktop == null)
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
// 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) {
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;
(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;
}
}
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
this.currentEncoding = encNum;
}
- this.cp.supportsLocalCursor = useLocalCursor.getValue();
+ this.cp.supportsLocalCursor = true;
if (customCompressLevel.getValue())
this.cp.compressLevel = compressLevel.getValue();
}
- 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)
return;
boolean down = (ev.getID() == KeyEvent.KEY_PRESSED);
-
+
int keySym, keyCode = ev.getKeyCode();
// If neither the keyCode or keyChar are defined, then there's
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;
}
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);
}
////////////////////////////////////////////////////////////////////
// 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
// 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.
private boolean supportsSyncFence;
- Viewport viewport;
private HashMap<Integer, Integer> downKeySym;
public ActionListener closeListener = null;
/* 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;
}
+
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);
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
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);
} 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"));
}
}
+ 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;
--- /dev/null
+/* 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);
+ }
+
+}
}
}
- private static Map<String, Object> callbacks = new HashMap<String, Object>();
+ private static Map<Object, String> callbacks = new HashMap<Object, String>();
/* Compression */
JCheckBox autoselectCheckbox;
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 */
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;
});
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()));
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() {
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);
rawButton.setSelected(true);
break;
}
-
+
if (fullColor.getValue())
fullcolorButton.setSelected(true);
else {
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()) {
break;
}
}
-
+
secTypesExt = security.GetEnabledExtSecTypes();
for (iterExt = secTypesExt.iterator(); iterExt.hasNext(); ) {
switch ((Integer)iterExt.next()) {
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;
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());
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 */
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());
} 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 */
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) {
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) {
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,
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,
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,
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,
desktopHeightInput.setEnabled(desktopSizeCheckbox.isSelected());
}
+ private void handleRemoteResize()
+ {
+ scalingFactorInput.setEnabled(!remoteResizeButton.isSelected());
+ }
+
private void handleTunnel()
{
viaCheckbox.setEnabled(tunnelCheckbox.isSelected());
desktopSizeCheckbox.setEnabled(false);
desktopWidthInput.setEnabled(false);
desktopHeightInput.setEnabled(false);
+ remoteResizeButton.setEnabled(false);
+ remoteScaleButton.setEnabled(false);
fullScreenCheckbox.setEnabled(false);
fullScreenAllMonitorsCheckbox.setEnabled(false);
scalingFactorInput.setEnabled(false);
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",
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,
compressLevel,
noJpeg,
qualityLevel,
+ maximize,
fullScreen,
fullScreenAllMonitors,
desktopSize,
+ remoteResize,
viewOnly,
shared,
acceptClipboard,
sendClipboard,
menuKey,
noLionFS,
- useLocalCursor,
sendLocalUsername,
maxCutText,
scalingFactor,
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()) {
} 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");
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()))
int lineNr = 0;
while (line != null) {
-
+
// Read the next line
try {
line = reader.readLine();
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
}
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) {
parameterArray[i].getName()));
}
}
-
+
return servername;
}
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");
}
/* 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();
+
+}
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.*;
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;
VncViewer.class.getResourceAsStream("timestamp");
public static final String os =
System.getProperty("os.name").toLowerCase();
+ private static VncViewer applet;
public static void setLookAndFeel() {
try {
}
public VncViewer() {
- //this(new String[0]);
- embed.setParam(true);
+ // Only called in applet mode
+ this(new String[0]);
}
public VncViewer(String[] argv) {
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.
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);
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);
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();
}
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();
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);
}
public static CConn cc;
- private static JScrollPane sp;
public static StringParameter config
= new StringParameter("Config",
"Specifies a configuration file to load.", null);