diff options
author | Brian Hinz <bphinz@users.sourceforge.net> | 2016-12-10 19:38:29 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-12-10 19:38:29 -0500 |
commit | 8b5a7d90172d4699dcaa355a05535e5ef07b3cac (patch) | |
tree | ec4f2376c83ad032be7ba915ceec3202b3b52591 | |
parent | 7e8b8b10cd290d80954f6e2c57112f2eb3e2a251 (diff) | |
parent | 985d0eb0657351c7bf01db3d1d30012f35c153de (diff) | |
download | tigervnc-8b5a7d90172d4699dcaa355a05535e5ef07b3cac.tar.gz tigervnc-8b5a7d90172d4699dcaa355a05535e5ef07b3cac.zip |
Merge pull request #390 from bphinz/static-viewer
Java viewer refresh
51 files changed, 6544 insertions, 4279 deletions
diff --git a/java/com/tigervnc/network/SSLEngineManager.java b/java/com/tigervnc/network/SSLEngineManager.java index c0110995..e6abe3e9 100644 --- a/java/com/tigervnc/network/SSLEngineManager.java +++ b/java/com/tigervnc/network/SSLEngineManager.java @@ -74,7 +74,7 @@ public class SSLEngineManager { hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { switch (hs) { - + case NEED_UNWRAP: // Receive handshaking data from peer peerNetData.flip(); @@ -92,26 +92,26 @@ public class SSLEngineManager { peerNetData.flip(); peerNetData.compact(); break; - + case OK: // Process incoming handshaking data break; - + case CLOSED: engine.closeInbound(); break; - + } break; - + case NEED_WRAP: // Empty the local network packet buffer. myNetData.clear(); - + // Generate handshaking data res = engine.wrap(myAppData, myNetData); hs = res.getHandshakeStatus(); - + // Check status switch (res.getStatus()) { case OK: @@ -121,15 +121,15 @@ public class SSLEngineManager { os.flush(); myNetData.clear(); break; - + case BUFFER_OVERFLOW: // FIXME: How much larger should the buffer be? break; - + case CLOSED: engine.closeOutbound(); break; - + } break; diff --git a/java/com/tigervnc/rdr/InStream.java b/java/com/tigervnc/rdr/InStream.java index 3938a327..0906d326 100644 --- a/java/com/tigervnc/rdr/InStream.java +++ b/java/com/tigervnc/rdr/InStream.java @@ -23,6 +23,8 @@ package com.tigervnc.rdr; +import java.nio.*; + import com.tigervnc.network.*; abstract public class InStream { @@ -98,6 +100,16 @@ abstract public class InStream { // readBytes() reads an exact number of bytes into an array at an offset. + public void readBytes(ByteBuffer data, int length) { + int dataEnd = data.mark().position() + length; + while (data.position() < dataEnd) { + int n = check(1, dataEnd - data.position()); + data.put(b, ptr, n); + ptr += n; + } + data.reset(); + } + public void readBytes(byte[] data, int dataPtr, int length) { int dataEnd = dataPtr + length; while (dataPtr < dataEnd) { @@ -147,20 +159,6 @@ abstract public class InStream { } } - public final int readCompactLength() { - int b = readU8(); - int result = b & 0x7F; - if ((b & 0x80) != 0) { - b = readU8(); - result |= (b & 0x7F) << 7; - if ((b & 0x80) != 0) { - b = readU8(); - result |= (b & 0xFF) << 14; - } - } - return result; - } - // pos() returns the position in the stream. abstract public int pos(); diff --git a/java/com/tigervnc/rdr/OutStream.java b/java/com/tigervnc/rdr/OutStream.java index 46fe7346..a3b1a6c0 100644 --- a/java/com/tigervnc/rdr/OutStream.java +++ b/java/com/tigervnc/rdr/OutStream.java @@ -116,6 +116,17 @@ abstract public class OutStream { } } + // copyBytes() efficiently transfers data between streams + + public void copyBytes(InStream is, int length) { + while (length > 0) { + int n = check(1, length); + is.readBytes(b, ptr, n); + ptr += n; + length -= n; + } + } + // writeOpaqueN() writes a quantity without byte-swapping. Because java has // no byte-ordering, we just use big-endian. diff --git a/java/com/tigervnc/rdr/ZlibInStream.java b/java/com/tigervnc/rdr/ZlibInStream.java index 103bb646..151633ec 100644 --- a/java/com/tigervnc/rdr/ZlibInStream.java +++ b/java/com/tigervnc/rdr/ZlibInStream.java @@ -62,6 +62,18 @@ public class ZlibInStream extends InStream { ptr = end = start; } + public void removeUnderlying() + { + ptr = end = start; + if (underlying == null) return; + + while (bytesIn > 0) { + decompress(true); + end = start; // throw away any data + } + underlying = null; + } + public int pos() { return offset + ptr - start; diff --git a/java/com/tigervnc/rfb/AliasParameter.java b/java/com/tigervnc/rfb/AliasParameter.java index a1ae838c..3f20ae46 100644 --- a/java/com/tigervnc/rfb/AliasParameter.java +++ b/java/com/tigervnc/rfb/AliasParameter.java @@ -44,5 +44,13 @@ public class AliasParameter extends VoidParameter { param.setImmutable(); } + public void setHasBeenSet() { + param.setHasBeenSet(); + } + + public boolean hasBeenSet() { + return param.hasBeenSet(); + } + protected VoidParameter param; } diff --git a/java/com/tigervnc/rfb/CConnection.java b/java/com/tigervnc/rfb/CConnection.java index c354868b..0b38aeaf 100644 --- a/java/com/tigervnc/rfb/CConnection.java +++ b/java/com/tigervnc/rfb/CConnection.java @@ -19,7 +19,11 @@ package com.tigervnc.rfb; +import java.awt.color.*; +import java.awt.image.*; +import java.nio.*; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import com.tigervnc.network.*; import com.tigervnc.rdr.*; @@ -28,21 +32,86 @@ abstract public class CConnection extends CMsgHandler { public CConnection() { - csecurity = null; is = null; os = null; reader_ = null; - writer_ = null; shared = false; + super(); + csecurity = null; is = null; os = null; reader_ = null; writer_ = null; + shared = false; state_ = RFBSTATE_UNINITIALISED; useProtocol3_3 = false; + framebuffer = null; decoder = new DecodeManager(this); security = new SecurityClient(); } - // deleteReaderAndWriter() deletes the reader and writer associated with - // this connection. This may be useful if you want to delete the streams - // before deleting the SConnection to make sure that no attempt by the - // SConnection is made to read or write. - // XXX Do we really need this at all??? - public void deleteReaderAndWriter() + // Methods to initialise the connection + + // setServerName() is used to provide a unique(ish) name for the server to + // which we are connected. This might be the result of getPeerEndpoint on + // a TcpSocket, for example, or a host specified by DNS name & port. + // The serverName is used when verifying the Identity of a host (see RA2). + public final void setServerName(String name) { + serverName = name; + } + + // setShared sets the value of the shared flag which will be sent to the + // server upon initialisation. + public final void setShared(boolean s) { shared = s; } + + // setProtocol3_3 configures whether or not the CConnection should + // only ever support protocol version 3.3 + public final void setProtocol3_3(boolean s) { useProtocol3_3 = s; } + + // setStreams() sets the streams to be used for the connection. These must + // be set before initialiseProtocol() and processMsg() are called. The + // CSecurity object may call setStreams() again to provide alternative + // streams over which the RFB protocol is sent (i.e. encrypting/decrypting + // streams). Ownership of the streams remains with the caller + // (i.e. SConnection will not delete them). + public final void setStreams(InStream is_, OutStream os_) { - reader_ = null; - writer_ = null; + is = is_; + os = os_; + } + + // setFramebuffer configures the PixelBuffer that the CConnection + // should render all pixel data in to. Note that the CConnection + // takes ownership of the PixelBuffer and it must not be deleted by + // anyone else. Call setFramebuffer again with NULL or a different + // PixelBuffer to delete the previous one. + public void setFramebuffer(ModifiablePixelBuffer fb) + { + decoder.flush(); + + if ((framebuffer != null) && (fb != null)) { + Rect rect = new Rect(); + + Raster data; + + byte[] black = new byte[4]; + + // Copy still valid area + + rect.setXYWH(0, 0, + Math.min(fb.width(), framebuffer.width()), + Math.min(fb.height(), framebuffer.height())); + data = framebuffer.getBuffer(rect); + fb.imageRect(framebuffer.getPF(), rect, data); + + // Black out any new areas + + if (fb.width() > framebuffer.width()) { + rect.setXYWH(framebuffer.width(), 0, + fb.width() - framebuffer.width(), + fb.height()); + fb.fillRect(rect, black); + } + + if (fb.height() > framebuffer.height()) { + rect.setXYWH(0, framebuffer.height(), + fb.width(), + fb.height() - framebuffer.height()); + fb.fillRect(rect, black); + } + } + + framebuffer = fb; } // initialiseProtocol() should be called once the streams and security @@ -75,11 +144,12 @@ abstract public class CConnection extends CMsgHandler { private void processVersionMsg() { vlog.debug("reading protocol version"); - if (!cp.readVersion(is)) { + AtomicBoolean done = new AtomicBoolean(); + if (!cp.readVersion(is, done)) { state_ = RFBSTATE_INVALID; throw new Exception("reading version failed: not an RFB server?"); } - if (!cp.done) return; + if (!done.get()) return; vlog.info("Server supports RFB protocol version " +cp.majorVersion+"."+ cp.minorVersion); @@ -111,7 +181,7 @@ abstract public class CConnection extends CMsgHandler { int secType = Security.secTypeInvalid; List<Integer> secTypes = new ArrayList<Integer>(); - secTypes = Security.GetEnabledSecTypes(); + secTypes = security.GetEnabledSecTypes(); if (cp.isVersion(3,3)) { @@ -241,59 +311,56 @@ abstract public class CConnection extends CMsgHandler { private void securityCompleted() { state_ = RFBSTATE_INITIALISATION; - reader_ = new CMsgReaderV3(this, is); - writer_ = new CMsgWriterV3(cp, os); + reader_ = new CMsgReader(this, is); + writer_ = new CMsgWriter(cp, os); vlog.debug("Authentication success!"); authSuccess(); writer_.writeClientInit(shared); } - // Methods to initialise the connection - - // setServerName() is used to provide a unique(ish) name for the server to - // which we are connected. This might be the result of getPeerEndpoint on - // a TcpSocket, for example, or a host specified by DNS name & port. - // The serverName is used when verifying the Identity of a host (see RA2). - public final void setServerName(String name) { - serverName = name; - } + // Methods to be overridden in a derived class - // setStreams() sets the streams to be used for the connection. These must - // be set before initialiseProtocol() and processMsg() are called. The - // CSecurity object may call setStreams() again to provide alternative - // streams over which the RFB protocol is sent (i.e. encrypting/decrypting - // streams). Ownership of the streams remains with the caller - // (i.e. SConnection will not delete them). - public final void setStreams(InStream is_, OutStream os_) - { - is = is_; - os = os_; - } + // Note: These must be called by any deriving classes - // setShared sets the value of the shared flag which will be sent to the - // server upon initialisation. - public final void setShared(boolean s) { shared = s; } - - // setProtocol3_3 configures whether or not the CConnection should - // only ever support protocol version 3.3 - public final void setProtocol3_3(boolean s) { useProtocol3_3 = s; } + public void setDesktopSize(int w, int h) { + decoder.flush(); - public void setServerPort(int port) { - serverPort = port; + super.setDesktopSize(w,h); } - public void initSecTypes() { - nSecTypes = 0; - } + public void setExtendedDesktopSize(int reason, + int result, int w, int h, + ScreenSet layout) { + decoder.flush(); - // Methods to be overridden in a derived class + super.setExtendedDesktopSize(reason, result, w, h, layout); + } // getIdVerifier() returns the identity verifier associated with the connection. // Ownership of the IdentityVerifier is retained by the CConnection instance. //public IdentityVerifier getIdentityVerifier() { return 0; } + public void framebufferUpdateStart() + { + super.framebufferUpdateStart(); + } + + public void framebufferUpdateEnd() + { + decoder.flush(); + + super.framebufferUpdateEnd(); + } + + public void dataRect(Rect r, int encoding) + { + decoder.decodeRect(r, encoding, framebuffer); + } + // authSuccess() is called when authentication has succeeded. - public void authSuccess() {} + public void authSuccess() + { + } // serverInit() is called when the ServerInit message is received. The // derived class must call on to CConnection::serverInit(). @@ -303,34 +370,17 @@ abstract public class CConnection extends CMsgHandler { vlog.debug("initialisation done"); } - // getCSecurity() gets the CSecurity object for the given type. The type - // is guaranteed to be one of the secTypes passed in to addSecType(). The - // CSecurity object's destroy() method will be called by the CConnection - // from its destructor. - //abstract public CSecurity getCSecurity(int secType); - - // getCurrentCSecurity() gets the CSecurity instance used for this - // connection. - //public CSecurity getCurrentCSecurity() { return security; } - - // setClientSecTypeOrder() determines whether the client should obey the - // server's security type preference, by picking the first server security - // type that the client supports, or whether it should pick the first type - // that the server supports, from the client-supported list of types. - public void setClientSecTypeOrder( boolean csto ) { - clientSecTypeOrder = csto; - } - // Other methods - public CMsgReaderV3 reader() { return reader_; } - public CMsgWriterV3 writer() { return writer_; } + public CMsgReader reader() { return reader_; } + public CMsgWriter writer() { return writer_; } public InStream getInStream() { return is; } public OutStream getOutStream() { return os; } + // Access method used by SSecurity implementations that can verify servers' + // Identities, to determine the unique(ish) name of the server. public String getServerName() { return serverName; } - public int getServerPort() { return serverPort; } public static final int RFBSTATE_UNINITIALISED = 0; public static final int RFBSTATE_PROTOCOL_VERSION = 1; @@ -343,7 +393,17 @@ abstract public class CConnection extends CMsgHandler { public int state() { return state_; } - protected final void setState(int s) { state_ = s; } + public int getServerPort() { return serverPort; } + public void setServerPort(int port) { + serverPort = port; + } + + protected void setState(int s) { state_ = s; } + + protected void setReader(CMsgReader r) { reader_ = r; } + protected void setWriter(CMsgWriter w) { writer_ = w; } + + protected ModifiablePixelBuffer getFramebuffer() { return framebuffer; } public void fence(int flags, int len, byte[] data) { @@ -373,20 +433,27 @@ abstract public class CConnection extends CMsgHandler { throw new AuthFailureException(reason); } - InStream is; - OutStream os; - CMsgReaderV3 reader_; - CMsgWriterV3 writer_; - boolean shared; + private InStream is; + private OutStream os; + private CMsgReader reader_; + private CMsgWriter writer_; + private boolean deleteStreamsWhenDone; + private boolean shared; + private int state_ = RFBSTATE_UNINITIALISED; + + private String serverName; + + private boolean useProtocol3_3; + + protected ModifiablePixelBuffer framebuffer; + private DecodeManager decoder; + public CSecurity csecurity; public SecurityClient security; public static final int maxSecTypes = 8; int nSecTypes; int[] secTypes; - int state_ = RFBSTATE_UNINITIALISED; - String serverName; int serverPort; - boolean useProtocol3_3; boolean clientSecTypeOrder; static LogWriter vlog = new LogWriter("CConnection"); diff --git a/java/com/tigervnc/rfb/CMsgHandler.java b/java/com/tigervnc/rfb/CMsgHandler.java index dd9767e1..99405983 100644 --- a/java/com/tigervnc/rfb/CMsgHandler.java +++ b/java/com/tigervnc/rfb/CMsgHandler.java @@ -78,25 +78,18 @@ abstract public class CMsgHandler { String x509subject) {} public void setCursor(int width, int height, Point hotspot, - int[] data, byte[] mask) {} + byte[] data, byte[] mask) {} public void serverInit() {} public void framebufferUpdateStart() {} public void framebufferUpdateEnd() {} - public void beginRect(Rect r, int encoding) {} - public void endRect(Rect r, int encoding) {} + public void dataRect(Rect r, int encoding) {} public void setColourMapEntries(int firstColour, int nColours, int[] rgbs) { } public void bell() {} public void serverCutText(String str, int len) {} - public void fillRect(Rect r, int pix) {} - public void imageRect(Rect r, Object pixels) {} - public void copyRect(Rect r, int srcX, int srcY) {} - - abstract public PixelFormat getPreferredPF(); - public ConnParams cp; static LogWriter vlog = new LogWriter("CMsgHandler"); diff --git a/java/com/tigervnc/rfb/CMsgReader.java b/java/com/tigervnc/rfb/CMsgReader.java index a93324ca..e7b4deb8 100644 --- a/java/com/tigervnc/rfb/CMsgReader.java +++ b/java/com/tigervnc/rfb/CMsgReader.java @@ -28,7 +28,7 @@ import java.nio.CharBuffer; import java.nio.charset.Charset; import com.tigervnc.rdr.*; -abstract public class CMsgReader { +public class CMsgReader { protected CMsgReader(CMsgHandler handler_, InStream is_) { @@ -37,7 +37,82 @@ abstract public class CMsgReader { is = is_; imageBuf = null; imageBufSize = 0; - decoders = new Decoder[Encodings.encodingMax+1]; + nUpdateRectsLeft = 0; + } + + public void readServerInit() + { + int width = is.readU16(); + int height = is.readU16(); + handler.setDesktopSize(width, height); + PixelFormat pf = new PixelFormat(); + pf.read(is); + handler.setPixelFormat(pf); + String name = is.readString(); + handler.setName(name); + handler.serverInit(); + } + + public void readMsg() + { + if (nUpdateRectsLeft == 0) { + int type = is.readU8(); + + switch (type) { + case MsgTypes.msgTypeSetColourMapEntries: + readSetColourMapEntries(); + break; + case MsgTypes.msgTypeBell: + readBell(); + break; + case MsgTypes.msgTypeServerCutText: + readServerCutText(); + break; + case MsgTypes.msgTypeFramebufferUpdate: + readFramebufferUpdate(); + break; + case MsgTypes.msgTypeServerFence: + readFence(); + break; + case MsgTypes.msgTypeEndOfContinuousUpdates: + readEndOfContinuousUpdates(); + break; + default: + //fprintf(stderr, "unknown message type %d\n", type); + throw new Exception("unknown message type"); + } + } else { + int x = is.readU16(); + int y = is.readU16(); + int w = is.readU16(); + int h = is.readU16(); + int encoding = is.readS32(); + + switch (encoding) { + case Encodings.pseudoEncodingLastRect: + nUpdateRectsLeft = 1; // this rectangle is the last one + break; + case Encodings.pseudoEncodingCursor: + readSetCursor(w, h, new Point(x,y)); + break; + case Encodings.pseudoEncodingDesktopName: + readSetDesktopName(x, y, w, h); + break; + case Encodings.pseudoEncodingDesktopSize: + handler.setDesktopSize(w, h); + break; + case Encodings.pseudoEncodingExtendedDesktopSize: + readExtendedDesktopSize(x, y, w, h); + break; + default: + readRect(new Rect(x, y, x+w, y+h), encoding); + break; + }; + + nUpdateRectsLeft--; + if (nUpdateRectsLeft == 0) + handler.framebufferUpdateEnd(); + } } protected void readSetColourMapEntries() @@ -72,6 +147,43 @@ abstract public class CMsgReader { handler.serverCutText(chars.toString(), len); } + protected void readFence() + { + int flags; + int len; + byte[] data = new byte[64]; + + is.skip(3); + + flags = is.readU32(); + + len = is.readU8(); + if (len > data.length) { + System.out.println("Ignoring fence with too large payload\n"); + is.skip(len); + return; + } + + is.readBytes(data, 0, len); + + handler.fence(flags, len, data); + } + + protected void readEndOfContinuousUpdates() + { + handler.endOfContinuousUpdates(); + } + + protected void readFramebufferUpdate() + { + is.skip(1); + nUpdateRectsLeft = is.readU16(); + handler.framebufferUpdateStart(); + } + + + + /* protected void readFramebufferUpdateStart() { handler.framebufferUpdateStart(); @@ -81,6 +193,7 @@ abstract public class CMsgReader { { handler.framebufferUpdateEnd(); } + */ protected void readRect(Rect r, int encoding) { @@ -94,43 +207,56 @@ abstract public class CMsgReader { if (r.is_empty()) vlog.error("Ignoring zero size rect"); - handler.beginRect(r, encoding); + handler.dataRect(r, encoding); + } - if (encoding == Encodings.encodingCopyRect) { - readCopyRect(r); - } else { + protected void readSetCursor(int width, int height, Point hotspot) + { + int data_len = width * height * (handler.cp.pf().bpp/8); + int mask_len = ((width+7)/8) * height; + byte[] data = new byte[data_len]; + byte[] mask = new byte[mask_len]; - if (decoders[encoding] == null) { - decoders[encoding] = Decoder.createDecoder(encoding, this); - if (decoders[encoding] == null) { - vlog.error("Unknown rect encoding "+encoding); - throw new Exception("Unknown rect encoding"); - } - } - decoders[encoding].readRect(r, handler); - } + is.readBytes(data, 0, data_len); + is.readBytes(mask, 0, mask_len); - handler.endRect(r, encoding); + handler.setCursor(width, height, hotspot, data, mask); } - protected void readCopyRect(Rect r) + protected void readSetDesktopName(int x, int y, int w, int h) { - int srcX = is.readU16(); - int srcY = is.readU16(); - handler.copyRect(r, srcX, srcY); + String name = is.readString(); + + if (x != 0 || y != 0 || w != 0 || h != 0) { + vlog.error("Ignoring DesktopName rect with non-zero position/size"); + } else { + handler.setName(name); + } + } - protected void readSetCursor(int width, int height, Point hotspot) + protected void readExtendedDesktopSize(int x, int y, int w, int h) { - int data_len = width * height; - int mask_len = ((width+7)/8) * height; - int[] data = new int[data_len]; - byte[] mask = new byte[mask_len]; + int screens, i; + int id, flags; + int sx, sy, sw, sh; + ScreenSet layout = new ScreenSet(); - is.readPixels(data, data_len, (handler.cp.pf().bpp/8), handler.cp.pf().bigEndian); - is.readBytes(mask, 0, mask_len); + screens = is.readU8(); + is.skip(3); - handler.setCursor(width, height, hotspot, data, mask); + for (i = 0;i < screens;i++) { + id = is.readU32(); + sx = is.readU16(); + sy = is.readU16(); + sw = is.readU16(); + sh = is.readU16(); + flags = is.readU32(); + + layout.add_screen(new Screen(id, sx, sy, sw, sh, flags)); + } + + handler.setExtendedDesktopSize(x, y, w, h, layout); } public int[] getImageBuf(int required) { return getImageBuf(required, 0, 0); } @@ -154,23 +280,13 @@ abstract public class CMsgReader { return imageBuf; } - public final int bpp() - { - return handler.cp.pf().bpp; - } - - abstract public void readServerInit(); - - // readMsg() reads a message, calling the handler as appropriate. - abstract public void readMsg(); - public InStream getInStream() { return is; } public int imageBufIdealSize; protected CMsgHandler handler; protected InStream is; - protected Decoder[] decoders; + protected int nUpdateRectsLeft; protected int[] imageBuf; protected int imageBufSize; diff --git a/java/com/tigervnc/rfb/CMsgReaderV3.java b/java/com/tigervnc/rfb/CMsgReaderV3.java deleted file mode 100644 index e09d3bb0..00000000 --- a/java/com/tigervnc/rfb/CMsgReaderV3.java +++ /dev/null @@ -1,186 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB - * Copyright (C) 2011 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 com.tigervnc.rdr.*; - -public class CMsgReaderV3 extends CMsgReader { - - public CMsgReaderV3(CMsgHandler handler_, InStream is_) - { - super(handler_, is_); - nUpdateRectsLeft = 0; - } - - public void readServerInit() - { - int width = is.readU16(); - int height = is.readU16(); - handler.setDesktopSize(width, height); - PixelFormat pf = new PixelFormat(); - pf.read(is); - handler.setPixelFormat(pf); - String name = is.readString(); - handler.setName(name); - handler.serverInit(); - } - - public void readMsg() - { - if (nUpdateRectsLeft == 0) { - - int type = is.readU8(); - switch (type) { - case MsgTypes.msgTypeFramebufferUpdate: readFramebufferUpdate(); break; - case MsgTypes.msgTypeSetColourMapEntries: readSetColourMapEntries(); break; - case MsgTypes.msgTypeBell: readBell(); break; - case MsgTypes.msgTypeServerCutText: readServerCutText(); break; - case MsgTypes.msgTypeServerFence: readFence(); break; - case MsgTypes.msgTypeEndOfContinuousUpdates: readEndOfContinuousUpdates(); break; - default: - vlog.error("unknown message type "+type); - throw new Exception("unknown message type"); - } - - } else { - - int x = is.readU16(); - int y = is.readU16(); - int w = is.readU16(); - int h = is.readU16(); - int encoding = is.readS32(); - - switch (encoding) { - case Encodings.pseudoEncodingDesktopSize: - handler.setDesktopSize(w, h); - break; - case Encodings.pseudoEncodingExtendedDesktopSize: - readExtendedDesktopSize(x, y, w, h); - break; - case Encodings.pseudoEncodingDesktopName: - readSetDesktopName(x, y, w, h); - break; - case Encodings.pseudoEncodingCursor: - readSetCursor(w, h, new Point(x,y)); - break; - case Encodings.pseudoEncodingLastRect: - nUpdateRectsLeft = 1; // this rectangle is the last one - break; - case Encodings.pseudoEncodingClientRedirect: - readClientRedirect(x, y, w, h); - break; - default: - readRect(new Rect(x, y, x+w, y+h), encoding); - break; - } - - nUpdateRectsLeft--; - if (nUpdateRectsLeft == 0) handler.framebufferUpdateEnd(); - } - } - - void readFramebufferUpdate() - { - is.skip(1); - nUpdateRectsLeft = is.readU16(); - handler.framebufferUpdateStart(); - } - - void readSetDesktopName(int x, int y, int w, int h) - { - String name = is.readString(); - - if (x != 0 || y != 0 || w != 0 || h != 0) { - vlog.error("Ignoring DesktopName rect with non-zero position/size"); - } else { - handler.setName(name); - } - - } - - void readExtendedDesktopSize(int x, int y, int w, int h) - { - int screens, i; - int id, flags; - int sx, sy, sw, sh; - ScreenSet layout = new ScreenSet(); - - screens = is.readU8(); - is.skip(3); - - for (i = 0;i < screens;i++) { - id = is.readU32(); - sx = is.readU16(); - sy = is.readU16(); - sw = is.readU16(); - sh = is.readU16(); - flags = is.readU32(); - - layout.add_screen(new Screen(id, sx, sy, sw, sh, flags)); - } - - handler.setExtendedDesktopSize(x, y, w, h, layout); - } - - void readFence() - { - int flags; - int len; - byte[] data = new byte[64]; - - is.skip(3); - - flags = is.readU32(); - - len = is.readU8(); - if (len > data.length) { - System.out.println("Ignoring fence with too large payload\n"); - is.skip(len); - return; - } - - is.readBytes(data, 0, len); - - handler.fence(flags, len, data); - } - - void readEndOfContinuousUpdates() - { - handler.endOfContinuousUpdates(); - } - - void readClientRedirect(int x, int y, int w, int h) - { - int port = is.readU16(); - String host = is.readString(); - String x509subject = is.readString(); - - if (x != 0 || y != 0 || w != 0 || h != 0) { - vlog.error("Ignoring ClientRedirect rect with non-zero position/size"); - } else { - handler.clientRedirect(port, host, x509subject); - } - } - - int nUpdateRectsLeft; - - static LogWriter vlog = new LogWriter("CMsgReaderV3"); -} diff --git a/java/com/tigervnc/rfb/CMsgWriter.java b/java/com/tigervnc/rfb/CMsgWriter.java index 0d3dccf9..bdf50c28 100644 --- a/java/com/tigervnc/rfb/CMsgWriter.java +++ b/java/com/tigervnc/rfb/CMsgWriter.java @@ -20,13 +20,23 @@ package com.tigervnc.rfb; +import com.tigervnc.rdr.*; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import com.tigervnc.rdr.*; +import java.util.Iterator; -abstract public class CMsgWriter { +public class CMsgWriter { - abstract public void writeClientInit(boolean shared); + protected CMsgWriter(ConnParams cp_, OutStream os_) + { + cp = cp_; + os = os_; + } + + synchronized public void writeClientInit(boolean shared) { + os.writeU8(shared?1:0); + endMsg(); + } synchronized public void writeSetPixelFormat(PixelFormat pf) { @@ -53,6 +63,7 @@ abstract public class CMsgWriter { { int nEncodings = 0; int[] encodings = new int[Encodings.encodingMax+3]; + if (cp.supportsLocalCursor) encodings[nEncodings++] = Encodings.pseudoEncodingCursor; if (cp.supportsDesktopResize) @@ -68,7 +79,6 @@ abstract public class CMsgWriter { encodings[nEncodings++] = Encodings.pseudoEncodingContinuousUpdates; encodings[nEncodings++] = Encodings.pseudoEncodingFence; - if (Decoder.supported(preferredEncoding)) { encodings[nEncodings++] = preferredEncoding; } @@ -98,9 +108,11 @@ abstract public class CMsgWriter { // Remaining encodings for (int i = Encodings.encodingMax; i >= 0; i--) { switch (i) { + case Encodings.encodingCopyRect: case Encodings.encodingTight: case Encodings.encodingZRLE: case Encodings.encodingHextile: + /* These have already been sent earlier */ break; default: if ((i != preferredEncoding) && Decoder.supported(i)) @@ -108,15 +120,44 @@ abstract public class CMsgWriter { } } - encodings[nEncodings++] = Encodings.pseudoEncodingLastRect; - if (cp.customCompressLevel && cp.compressLevel >= 0 && cp.compressLevel <= 9) - encodings[nEncodings++] = Encodings.pseudoEncodingCompressLevel0 + cp.compressLevel; - if (!cp.noJpeg && cp.qualityLevel >= 0 && cp.qualityLevel <= 9) - encodings[nEncodings++] = Encodings.pseudoEncodingQualityLevel0 + cp.qualityLevel; + if (cp.compressLevel >= 0 && cp.compressLevel <= 9) + encodings[nEncodings++] = + Encodings.pseudoEncodingCompressLevel0 + cp.compressLevel; + if (cp.qualityLevel >= 0 && cp.qualityLevel <= 9) + encodings[nEncodings++] = + Encodings.pseudoEncodingQualityLevel0 + cp.qualityLevel; writeSetEncodings(nEncodings, encodings); } + synchronized public void writeSetDesktopSize(int width, int height, + ScreenSet layout) + { + if (!cp.supportsSetDesktopSize) + throw new Exception("Server does not support SetDesktopSize"); + + startMsg(MsgTypes.msgTypeSetDesktopSize); + os.pad(1); + + os.writeU16(width); + os.writeU16(height); + + os.writeU8(layout.num_screens()); + os.pad(1); + + for (Iterator<Screen> iter = layout.screens.iterator(); iter.hasNext(); ) { + Screen refScreen = (Screen)iter.next(); + os.writeU32(refScreen.id); + os.writeU16(refScreen.dimensions.tl.x); + os.writeU16(refScreen.dimensions.tl.y); + os.writeU16(refScreen.dimensions.width()); + os.writeU16(refScreen.dimensions.height()); + os.writeU32(refScreen.flags); + } + + endMsg(); + } + synchronized public void writeFramebufferUpdateRequest(Rect r, boolean incremental) { startMsg(MsgTypes.msgTypeFramebufferUpdateRequest); @@ -128,6 +169,44 @@ abstract public class CMsgWriter { endMsg(); } + synchronized public void writeEnableContinuousUpdates(boolean enable, + int x, int y, int w, int h) + { + if (!cp.supportsContinuousUpdates) + throw new Exception("Server does not support continuous updates"); + + startMsg(MsgTypes.msgTypeEnableContinuousUpdates); + + os.writeU8((enable?1:0)); + + os.writeU16(x); + os.writeU16(y); + os.writeU16(w); + os.writeU16(h); + + endMsg(); + } + + synchronized public void writeFence(int flags, int len, byte[] data) + { + if (!cp.supportsFence) + throw new Exception("Server does not support fences"); + if (len > 64) + throw new Exception("Too large fence payload"); + if ((flags & ~fenceTypes.fenceFlagsSupported) != 0) + throw new Exception("Unknown fence flags"); + + startMsg(MsgTypes.msgTypeClientFence); + os.pad(3); + + os.writeU32(flags); + + os.writeU8(len); + os.writeBytes(data, 0, len); + + endMsg(); + } + synchronized public void writeKeyEvent(int key, boolean down) { startMsg(MsgTypes.msgTypeKeyEvent); @@ -163,17 +242,14 @@ abstract public class CMsgWriter { endMsg(); } - abstract public void startMsg(int type); - abstract public void endMsg(); - - synchronized public void setOutStream(OutStream os_) { os = os_; } - - ConnParams getConnParams() { return cp; } - OutStream getOutStream() { return os; } + synchronized public void startMsg(int type) { + os.writeU8(type); + } - protected CMsgWriter(ConnParams cp_, OutStream os_) {cp = cp_; os = os_;} + synchronized public void endMsg() { + os.flush(); + } ConnParams cp; OutStream os; - static LogWriter vlog = new LogWriter("CMsgWriter"); } diff --git a/java/com/tigervnc/rfb/CMsgWriterV3.java b/java/com/tigervnc/rfb/CMsgWriterV3.java deleted file mode 100644 index 10c377a7..00000000 --- a/java/com/tigervnc/rfb/CMsgWriterV3.java +++ /dev/null @@ -1,108 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2009-2011 Pierre Ossman for Cendio AB - * Copyright (C) 2011 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 com.tigervnc.rdr.*; -import java.util.*; - -public class CMsgWriterV3 extends CMsgWriter { - - public CMsgWriterV3(ConnParams cp_, OutStream os_) { super(cp_, os_); } - - synchronized public void writeClientInit(boolean shared) { - os.writeU8(shared?1:0); - endMsg(); - } - - synchronized public void startMsg(int type) { - os.writeU8(type); - } - - synchronized public void endMsg() { - os.flush(); - } - - synchronized public void writeSetDesktopSize(int width, int height, - ScreenSet layout) - { - if (!cp.supportsSetDesktopSize) - throw new Exception("Server does not support SetDesktopSize"); - - startMsg(MsgTypes.msgTypeSetDesktopSize); - os.pad(1); - - os.writeU16(width); - os.writeU16(height); - - os.writeU8(layout.num_screens()); - os.pad(1); - - for (Iterator<Screen> iter = layout.screens.iterator(); iter.hasNext(); ) { - Screen refScreen = (Screen)iter.next(); - os.writeU32(refScreen.id); - os.writeU16(refScreen.dimensions.tl.x); - os.writeU16(refScreen.dimensions.tl.y); - os.writeU16(refScreen.dimensions.width()); - os.writeU16(refScreen.dimensions.height()); - os.writeU32(refScreen.flags); - } - - endMsg(); - } - - synchronized public void writeFence(int flags, int len, byte[] data) - { - if (!cp.supportsFence) - throw new Exception("Server does not support fences"); - if (len > 64) - throw new Exception("Too large fence payload"); - if ((flags & ~fenceTypes.fenceFlagsSupported) != 0) - throw new Exception("Unknown fence flags"); - - startMsg(MsgTypes.msgTypeClientFence); - os.pad(3); - - os.writeU32(flags); - - os.writeU8(len); - os.writeBytes(data, 0, len); - - endMsg(); - } - - synchronized public void writeEnableContinuousUpdates(boolean enable, - int x, int y, int w, int h) - { - if (!cp.supportsContinuousUpdates) - throw new Exception("Server does not support continuous updates"); - - startMsg(MsgTypes.msgTypeEnableContinuousUpdates); - - os.writeU8((enable?1:0)); - - os.writeU16(x); - os.writeU16(y); - os.writeU16(w); - os.writeU16(h); - - endMsg(); - } -} diff --git a/java/com/tigervnc/rfb/CSecurityTLS.java b/java/com/tigervnc/rfb/CSecurityTLS.java index a8f6df35..4b20e0bf 100644 --- a/java/com/tigervnc/rfb/CSecurityTLS.java +++ b/java/com/tigervnc/rfb/CSecurityTLS.java @@ -58,11 +58,11 @@ import com.tigervnc.vncviewer.*; public class CSecurityTLS extends CSecurity { - public static StringParameter x509ca - = new StringParameter("x509ca", + public static StringParameter X509CA + = new StringParameter("X509CA", "X509 CA certificate", "", Configuration.ConfigurationObject.ConfViewer); - public static StringParameter x509crl - = new StringParameter("x509crl", + public static StringParameter X509CRL + = new StringParameter("X509CRL", "X509 CRL file", "", Configuration.ConfigurationObject.ConfViewer); private void initGlobal() @@ -80,8 +80,8 @@ public class CSecurityTLS extends CSecurity { manager = null; setDefaults(); - cafile = x509ca.getData(); - crlfile = x509crl.getData(); + cafile = X509CA.getData(); + crlfile = X509CRL.getData(); } public static String getDefaultCA() { @@ -99,9 +99,9 @@ public class CSecurityTLS extends CSecurity { public static void setDefaults() { if (new File(getDefaultCA()).exists()) - x509ca.setDefaultStr(getDefaultCA()); + X509CA.setDefaultStr(getDefaultCA()); if (new File(getDefaultCRL()).exists()) - x509crl.setDefaultStr(getDefaultCRL()); + X509CRL.setDefaultStr(getDefaultCRL()); } // FIXME: diff --git a/java/com/tigervnc/rfb/CSecurityVeNCrypt.java b/java/com/tigervnc/rfb/CSecurityVeNCrypt.java index f353874c..179900a4 100644 --- a/java/com/tigervnc/rfb/CSecurityVeNCrypt.java +++ b/java/com/tigervnc/rfb/CSecurityVeNCrypt.java @@ -134,7 +134,7 @@ public class CSecurityVeNCrypt extends CSecurity { Iterator<Integer> j; List<Integer> secTypes = new ArrayList<Integer>(); - secTypes = Security.GetEnabledExtSecTypes(); + secTypes = security.GetEnabledExtSecTypes(); /* Honor server's security type order */ for (i = 0; i < nAvailableTypes; i++) { diff --git a/java/com/tigervnc/rfb/Configuration.java b/java/com/tigervnc/rfb/Configuration.java index 5d140d95..11fc89a4 100644 --- a/java/com/tigervnc/rfb/Configuration.java +++ b/java/com/tigervnc/rfb/Configuration.java @@ -26,14 +26,19 @@ package com.tigervnc.rfb; import java.io.FileInputStream; import java.io.PrintWriter; +import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; +import com.tigervnc.vncviewer.VncViewer; + public class Configuration { static LogWriter vlog = new LogWriter("Configuration"); + private static final String IDENTIFIER_STRING = "TigerVNC Configuration file Version 1.0"; + public enum ConfigurationObject { ConfGlobal, ConfServer, ConfViewer }; // -=- The Global/server/viewer Configuration objects @@ -259,77 +264,6 @@ public class Configuration { list(79, 10); } - public void readAppletParams(java.applet.Applet applet) { - VoidParameter current = head; - while (current != null) { - String str = applet.getParameter(current.getName()); - if (str != null) - current.setParam(str); - current = current._next; - } - } - - public static void load(String filename) { - if (filename == null) - return; - - /* Read parameters from file */ - Properties props = new Properties(); - try { - props.load(new FileInputStream(filename)); - } catch(java.security.AccessControlException e) { - vlog.error("Cannot access system properties:"+e.getMessage()); - return; - } catch (java.lang.Exception e) { - vlog.error("Error opening config file:"+e.getMessage()); - return; - } - - for (Iterator<String> i = props.stringPropertyNames().iterator(); i.hasNext();) { - String name = (String)i.next(); - if (name.startsWith("[")) { - // skip the section delimiters - continue; - } else if (name.equals("host")) { - setParam("Server", props.getProperty(name)); - } else if (name.equals("disableclipboard")) { - setParam("RecvClipboard", props.getProperty(name)); - setParam("SendClipboard", props.getProperty(name)); - } else if (name.equals("localcursor")) { - setParam("UseLocalCursor", props.getProperty(name)); - } else { - if (!setParam(name, props.getProperty(name))) - vlog.debug("Cannot set parameter: "+name); - } - } - } - - public static void save(String filename) { - PrintWriter pw = null; - try { - pw = new PrintWriter(filename, "UTF-8"); - } catch (java.lang.Exception e) { - vlog.error("Error opening config file:"+e.getMessage()); - return; - } - - pw.println("# TigerVNC viewer configuration"); - DateFormat dateFormat = new SimpleDateFormat("E MMM d k:m:s z yyyy"); - Date date = new Date(); - pw.println("# "+dateFormat.format(date)); - VoidParameter current = Configuration.global().head; - while (current != null) { - String name = current.getName(); - String value = current.getValueStr(); - if (!name.equals("Server") && !name.equals("Port") && - value != null && value != current.getDefaultStr()) - pw.println(name+"="+current.getValueStr()); - current = current._next; - } - pw.flush(); - pw.close(); - } - // Name for this Configuration private String name; diff --git a/java/com/tigervnc/rfb/ConnParams.java b/java/com/tigervnc/rfb/ConnParams.java index f1f53958..fe52770f 100644 --- a/java/com/tigervnc/rfb/ConnParams.java +++ b/java/com/tigervnc/rfb/ConnParams.java @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright (C) 2012 Brian P. Hinz + * Copyright (C) 2012-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 @@ -21,11 +21,21 @@ package com.tigervnc.rfb; import com.tigervnc.rdr.*; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; public class ConnParams { - static LogWriter vlog = new LogWriter("ConnParams"); - public ConnParams() { + private static final int subsampleUndefined = -1; + private static final int subsampleNone = 0; + private static final int subsampleGray = 1; + private static final int subsample2X = 2; + private static final int subsample4X = 3; + private static final int subsample8X = 4; + private static final int subsample16X = 5; + + public ConnParams() + { majorVersion = 0; minorVersion = 0; width = 0; height = 0; useCopyRect = false; supportsLocalCursor = false; supportsLocalXCursor = false; @@ -34,19 +44,17 @@ public class ConnParams { supportsSetDesktopSize = false; supportsFence = false; supportsContinuousUpdates = false; supportsClientRedirect = false; - customCompressLevel = false; compressLevel = 6; - noJpeg = false; qualityLevel = -1; fineQualityLevel = -1; - subsampling = "SUBSAMP_UNDEFINED"; - name_ = null; nEncodings_ = 0; encodings_ = null; - currentEncoding_ = Encodings.encodingRaw; verStrPos = 0; + compressLevel = 6; qualityLevel = -1; fineQualityLevel = -1; + subsampling = subsampleUndefined; name_ = null; verStrPos = 0; + + encodings_ = new ArrayList(); screenLayout = new ScreenSet(); setName(""); } - public boolean readVersion(InStream is) + public boolean readVersion(InStream is, AtomicBoolean done) { - done = false; if (verStrPos >= 12) return false; verStr = new StringBuilder(13); while (is.checkNoWait(1) && verStrPos < 12) { @@ -54,10 +62,10 @@ public class ConnParams { } if (verStrPos < 12) { - done = false; + done.set(false); return true; } - done = true; + done.set(true); verStr.insert(12,'0'); verStrPos = 0; if (verStr.toString().matches("RFB \\d{3}\\.\\d{3}\\n0")) { @@ -68,7 +76,8 @@ public class ConnParams { return false; } - public void writeVersion(OutStream os) { + public void writeVersion(OutStream os) + { String str = String.format("RFB %03d.%03d\n", majorVersion, minorVersion); os.writeBytes(str.getBytes(), 0, 12); os.flush(); @@ -97,10 +106,11 @@ public class ConnParams { public PixelFormat pf() { return pf_; } public void setPF(PixelFormat pf) { + pf_ = pf; - if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32) { + + if (pf.bpp != 8 && pf.bpp != 16 && pf.bpp != 32) throw new Exception("setPF: not 8, 16 or 32 bpp?"); - } } public String name() { return name_; } @@ -109,80 +119,96 @@ public class ConnParams { name_ = name; } - public int currentEncoding() { return currentEncoding_; } - public int nEncodings() { return nEncodings_; } - public int[] encodings() { return encodings_; } + public boolean supportsEncoding(int encoding) + { + return encodings_.indexOf(encoding) != -1; + } + public void setEncodings(int nEncodings, int[] encodings) { - if (nEncodings > nEncodings_) { - encodings_ = new int[nEncodings]; - } - nEncodings_ = nEncodings; useCopyRect = false; supportsLocalCursor = false; supportsDesktopResize = false; supportsExtendedDesktopSize = false; supportsLocalXCursor = false; supportsLastRect = false; - customCompressLevel = false; compressLevel = -1; - noJpeg = true; qualityLevel = -1; fineQualityLevel = -1; - subsampling = "SUBSAMP_UNDEFINED"; - currentEncoding_ = Encodings.encodingRaw; + subsampling = subsampleUndefined; - for (int i = nEncodings-1; i >= 0; i--) { - encodings_[i] = encodings[i]; + encodings_.clear(); + encodings_.add(Encodings.encodingRaw); - if (encodings[i] == Encodings.encodingCopyRect) + for (int i = nEncodings-1; i >= 0; i--) { + switch (encodings[i]) { + case Encodings.encodingCopyRect: useCopyRect = true; - else if (encodings[i] == Encodings.pseudoEncodingCursor) + break; + case Encodings.pseudoEncodingCursor: supportsLocalCursor = true; - else if (encodings[i] == Encodings.pseudoEncodingXCursor) + break; + case Encodings.pseudoEncodingXCursor: supportsLocalXCursor = true; - else if (encodings[i] == Encodings.pseudoEncodingDesktopSize) + break; + case Encodings.pseudoEncodingDesktopSize: supportsDesktopResize = true; - else if (encodings[i] == Encodings.pseudoEncodingExtendedDesktopSize) + break; + case Encodings.pseudoEncodingExtendedDesktopSize: supportsExtendedDesktopSize = true; - else if (encodings[i] == Encodings.pseudoEncodingDesktopName) + break; + case Encodings.pseudoEncodingDesktopName: supportsDesktopRename = true; - else if (encodings[i] == Encodings.pseudoEncodingLastRect) + break; + case Encodings.pseudoEncodingLastRect: supportsLastRect = true; - else if (encodings[i] == Encodings.pseudoEncodingFence) + break; + case Encodings.pseudoEncodingFence: supportsFence = true; - else if (encodings[i] == Encodings.pseudoEncodingContinuousUpdates) + break; + case Encodings.pseudoEncodingContinuousUpdates: supportsContinuousUpdates = true; - else if (encodings[i] == Encodings.pseudoEncodingClientRedirect) + break; + case Encodings.pseudoEncodingClientRedirect: supportsClientRedirect = true; - else if (encodings[i] >= Encodings.pseudoEncodingCompressLevel0 && - encodings[i] <= Encodings.pseudoEncodingCompressLevel9) { - customCompressLevel = true; + break; + case Encodings.pseudoEncodingSubsamp1X: + subsampling = subsampleNone; + break; + case Encodings.pseudoEncodingSubsampGray: + subsampling = subsampleGray; + break; + case Encodings.pseudoEncodingSubsamp2X: + subsampling = subsample2X; + break; + case Encodings.pseudoEncodingSubsamp4X: + subsampling = subsample4X; + break; + case Encodings.pseudoEncodingSubsamp8X: + subsampling = subsample8X; + break; + case Encodings.pseudoEncodingSubsamp16X: + subsampling = subsample16X; + break; + } + + if (encodings[i] >= Encodings.pseudoEncodingCompressLevel0 && + encodings[i] <= Encodings.pseudoEncodingCompressLevel9) compressLevel = encodings[i] - Encodings.pseudoEncodingCompressLevel0; - } else if (encodings[i] >= Encodings.pseudoEncodingQualityLevel0 && - encodings[i] <= Encodings.pseudoEncodingQualityLevel9) { - noJpeg = false; + + if (encodings[i] >= Encodings.pseudoEncodingQualityLevel0 && + encodings[i] <= Encodings.pseudoEncodingQualityLevel9) qualityLevel = encodings[i] - Encodings.pseudoEncodingQualityLevel0; - } else if (encodings[i] <= Encodings.encodingMax && - Encoder.supported(encodings[i])) - currentEncoding_ = encodings[i]; - } - // If the TurboVNC fine quality/subsampling encodings exist, let them - // override the coarse TightVNC quality level - for (int i = nEncodings-1; i >= 0; i--) { - if (encodings[i] >= Encodings.pseudoEncodingFineQualityLevel0 + 1 && - encodings[i] <= Encodings.pseudoEncodingFineQualityLevel100) { - noJpeg = false; + if (encodings[i] >= Encodings.pseudoEncodingFineQualityLevel0 && + encodings[i] <= Encodings.pseudoEncodingFineQualityLevel100) fineQualityLevel = encodings[i] - Encodings.pseudoEncodingFineQualityLevel0; - } else if (encodings[i] >= Encodings.pseudoEncodingSubsamp1X && - encodings[i] <= Encodings.pseudoEncodingSubsampGray) { - noJpeg = false; - subsampling = JpegCompressor.subsamplingName(encodings[i] - Encodings.pseudoEncodingSubsamp1X); - } + + if (encodings[i] > 0) + encodings_.add(encodings[i]); } } - public boolean done; + public boolean useCopyRect; public boolean supportsLocalCursor; @@ -190,25 +216,22 @@ public class ConnParams { public boolean supportsDesktopResize; public boolean supportsExtendedDesktopSize; public boolean supportsDesktopRename; - public boolean supportsClientRedirect; - public boolean supportsFence; - public boolean supportsContinuousUpdates; public boolean supportsLastRect; + public boolean supportsClientRedirect; public boolean supportsSetDesktopSize; + public boolean supportsFence; + public boolean supportsContinuousUpdates; - public boolean customCompressLevel; public int compressLevel; - public boolean noJpeg; public int qualityLevel; public int fineQualityLevel; - public String subsampling; + public int subsampling; private PixelFormat pf_; private String name_; - private int nEncodings_; - private int[] encodings_; - private int currentEncoding_; + private Cursor cursor_; + private ArrayList encodings_; private StringBuilder verStr; private int verStrPos; } diff --git a/java/com/tigervnc/rfb/CopyRectDecoder.java b/java/com/tigervnc/rfb/CopyRectDecoder.java new file mode 100644 index 00000000..a4298fd5 --- /dev/null +++ b/java/com/tigervnc/rfb/CopyRectDecoder.java @@ -0,0 +1,44 @@ +/* Copyright 2014 Pierre Ossman <ossman@cendio.se> for Cendio AB + * Copyright 2016 Brian P. Hinz + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +package com.tigervnc.rfb; + +import com.tigervnc.rdr.*; + +public class CopyRectDecoder extends Decoder { + + public CopyRectDecoder() { super(DecoderFlags.DecoderPlain); } + + public void readRect(Rect r, InStream is, + ConnParams cp, OutStream os) + { + os.copyBytes(is, 4); + } + + public void decodeRect(Rect r, Object buffer, + int buflen, ConnParams cp, + ModifiablePixelBuffer pb) + { + MemInStream is = new MemInStream((byte[])buffer, 0, buflen); + int srcX = is.readU16(); + int srcY = is.readU16(); + pb.copyRect(r, new Point(r.tl.x-srcX, r.tl.y-srcY)); + } + +} diff --git a/java/com/tigervnc/rfb/Cursor.java b/java/com/tigervnc/rfb/Cursor.java index 78aa0fb2..05122ae5 100644 --- a/java/com/tigervnc/rfb/Cursor.java +++ b/java/com/tigervnc/rfb/Cursor.java @@ -20,11 +20,18 @@ package com.tigervnc.rfb; public class Cursor extends ManagedPixelBuffer { + public Cursor(PixelFormat pf, int w, int h) { + super(pf, w, h); + hotspot = new Point(0, 0); + } + public void setSize(int w, int h) { + int oldMaskLen = maskLen(); super.setSize(w, h); - if (mask == null || mask.length < maskLen()) + if (mask == null || maskLen() > oldMaskLen) mask = new byte[maskLen()]; } + public int maskLen() { return (width() + 7) / 8 * height(); } public Point hotspot; diff --git a/java/com/tigervnc/rfb/DecodeManager.java b/java/com/tigervnc/rfb/DecodeManager.java new file mode 100644 index 00000000..9e254ad2 --- /dev/null +++ b/java/com/tigervnc/rfb/DecodeManager.java @@ -0,0 +1,386 @@ +/* Copyright 2015 Pierre Ossman for Cendio AB + * Copyright 2016 Brian P. Hinz + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +package com.tigervnc.rfb; + +import java.lang.Runtime; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.*; + +import com.tigervnc.rdr.*; +import com.tigervnc.rdr.Exception; + +import static com.tigervnc.rfb.Decoder.DecoderFlags.*; + +public class DecodeManager { + + static LogWriter vlog = new LogWriter("DecodeManager"); + + public DecodeManager(CConnection conn) { + int cpuCount; + + this.conn = conn; threadException = null; + decoders = new Decoder[Encodings.encodingMax+1]; + + queueMutex = new ReentrantLock(); + producerCond = queueMutex.newCondition(); + consumerCond = queueMutex.newCondition(); + + //cpuCount = 1; + cpuCount = Runtime.getRuntime().availableProcessors(); + if (cpuCount == 0) { + vlog.error("Unable to determine the number of CPU cores on this system"); + cpuCount = 1; + } else { + vlog.info("Detected "+cpuCount+" CPU core(s)"); + // No point creating more threads than this, they'll just end up + // wasting CPU fighting for locks + if (cpuCount > 4) + cpuCount = 4; + // The overhead of threading is small, but not small enough to + // ignore on single CPU systems + if (cpuCount == 1) + vlog.info("Decoding data on main thread"); + else + vlog.info("Creating "+cpuCount+" decoder thread(s)"); + } + + freeBuffers = new ArrayDeque<MemOutStream>(cpuCount*2); + workQueue = new ArrayDeque<QueueEntry>(cpuCount); + threads = new ArrayList<DecodeThread>(cpuCount); + while (cpuCount-- > 0) { + // Twice as many possible entries in the queue as there + // are worker threads to make sure they don't stall + try { + freeBuffers.addLast(new MemOutStream()); + freeBuffers.addLast(new MemOutStream()); + + threads.add(new DecodeThread(this)); + } catch (IllegalStateException e) { } + } + + } + + public void decodeRect(Rect r, int encoding, + ModifiablePixelBuffer pb) + { + Decoder decoder; + MemOutStream bufferStream; + + QueueEntry entry; + + assert(pb != null); + + if (!Decoder.supported(encoding)) { + vlog.error("Unknown encoding " + encoding); + throw new Exception("Unknown encoding"); + } + + if (decoders[encoding] == null) { + decoders[encoding] = Decoder.createDecoder(encoding); + if (decoders[encoding] == null) { + vlog.error("Unknown encoding " + encoding); + throw new Exception("Unknown encoding"); + } + } + + decoder = decoders[encoding]; + + // Fast path for single CPU machines to avoid the context + // switching overhead + if (threads.size() == 1) { + bufferStream = freeBuffers.getFirst(); + bufferStream.clear(); + decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream); + decoder.decodeRect(r, (Object)bufferStream.data(), bufferStream.length(), + conn.cp, pb); + return; + } + + // Wait for an available memory buffer + queueMutex.lock(); + + while (freeBuffers.isEmpty()) + try { + producerCond.await(); + } catch (InterruptedException e) { } + + // Don't pop the buffer in case we throw an exception + // whilst reading + bufferStream = freeBuffers.getFirst(); + + queueMutex.unlock(); + + // First check if any thread has encountered a problem + throwThreadException(); + + // Read the rect + bufferStream.clear(); + decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream); + + // Then try to put it on the queue + entry = new QueueEntry(); + + entry.active = false; + entry.rect = r; + entry.encoding = encoding; + entry.decoder = decoder; + entry.cp = conn.cp; + entry.pb = pb; + entry.bufferStream = bufferStream; + entry.affectedRegion = new Region(r); + + decoder.getAffectedRegion(r, bufferStream.data(), + bufferStream.length(), conn.cp, + entry.affectedRegion); + + // The workers add buffers to the end so it's safe to assume + // the front is still the same buffer + freeBuffers.removeFirst(); + + queueMutex.lock(); + + workQueue.addLast(entry); + + // We only put a single entry on the queue so waking a single + // thread is sufficient + consumerCond.signal(); + + queueMutex.unlock(); + } + + public void flush() + { + queueMutex.lock(); + + while (!workQueue.isEmpty()) + try { + producerCond.await(); + } catch (InterruptedException e) { } + + queueMutex.unlock(); + + throwThreadException(); + } + + private void setThreadException(Exception e) + { + //os::AutoMutex a(queueMutex); + queueMutex.lock(); + + if (threadException == null) + return; + + threadException = + new Exception("Exception on worker thread: "+e.getMessage()); + } + + private void throwThreadException() + { + //os::AutoMutex a(queueMutex); + queueMutex.lock(); + + if (threadException == null) + return; + + Exception e = new Exception(threadException.getMessage()); + + threadException = null; + + throw e; + } + + private class QueueEntry { + + public QueueEntry() { + } + public boolean active; + public Rect rect; + public int encoding; + public Decoder decoder; + public ConnParams cp; + public ModifiablePixelBuffer pb; + public MemOutStream bufferStream; + public Region affectedRegion; + } + + private class DecodeThread implements Runnable { + + public DecodeThread(DecodeManager manager) + { + this.manager = manager; + + stopRequested = false; + + (thread = new Thread(this)).start(); + } + + public void stop() + { + //os::AutoMutex a(manager.queueMutex); + manager.queueMutex.lock(); + + if (!thread.isAlive()) + return; + + stopRequested = true; + + // We can't wake just this thread, so wake everyone + manager.consumerCond.signalAll(); + } + + public void run() + { + manager.queueMutex.lock(); + while (!stopRequested) { + QueueEntry entry; + + // Look for an available entry in the work queue + entry = findEntry(); + if (entry == null) { + // Wait and try again + try { + manager.consumerCond.await(); + } catch (InterruptedException e) { } + continue; + } + + // This is ours now + entry.active = true; + + manager.queueMutex.unlock(); + + // Do the actual decoding + try { + entry.decoder.decodeRect(entry.rect, entry.bufferStream.data(), + entry.bufferStream.length(), + entry.cp, entry.pb); + } catch (com.tigervnc.rdr.Exception e) { + manager.setThreadException(e); + } catch(java.lang.Exception e) { + assert(false); + } + + manager.queueMutex.lock(); + + // Remove the entry from the queue and give back the memory buffer + manager.freeBuffers.add(entry.bufferStream); + manager.workQueue.remove(entry); + entry = null; + + // Wake the main thread in case it is waiting for a memory buffer + manager.producerCond.signal(); + // This rect might have been blocking multiple other rects, so + // wake up every worker thread + if (manager.workQueue.size() > 1) + manager.consumerCond.signalAll(); + } + + manager.queueMutex.unlock(); + } + + protected QueueEntry findEntry() + { + Iterator<QueueEntry> iter; + Region lockedRegion = new Region(); + + if (manager.workQueue.isEmpty()) + return null; + + if (!manager.workQueue.peek().active) + return manager.workQueue.peek(); + + for (iter = manager.workQueue.iterator(); iter.hasNext();) { + QueueEntry entry; + + Iterator<QueueEntry> iter2; + + entry = iter.next(); + + // Another thread working on this? + if (entry.active) { + lockedRegion.assign_union(entry.affectedRegion); + continue; + } + + // If this is an ordered decoder then make sure this is the first + // rectangle in the queue for that decoder + if ((entry.decoder.flags & DecoderOrdered) != 0) { + for (iter2 = manager.workQueue.iterator(); iter2.hasNext() && iter2 != iter;) { + if (entry.encoding == (iter2.next()).encoding) { + lockedRegion.assign_union(entry.affectedRegion); + continue; + } + } + } + + // For a partially ordered decoder we must ask the decoder for each + // pair of rectangles. + if ((entry.decoder.flags & DecoderPartiallyOrdered) != 0) { + for (iter2 = manager.workQueue.iterator(); iter2.hasNext() && iter2 != iter;) { + QueueEntry entry2 = iter2.next(); + if (entry.encoding != entry2.encoding) + continue; + if (entry.decoder.doRectsConflict(entry.rect, + entry.bufferStream.data(), + entry.bufferStream.length(), + entry2.rect, + entry2.bufferStream.data(), + entry2.bufferStream.length(), + entry.cp)) + lockedRegion.assign_union(entry.affectedRegion); + continue; + } + } + + // Check overlap with earlier rectangles + if (!lockedRegion.intersect(entry.affectedRegion).is_empty()) { + lockedRegion.assign_union(entry.affectedRegion); + continue; + } + + return entry; + + } + + return null; + } + + private DecodeManager manager; + private boolean stopRequested; + + private Thread thread; + + } + + private CConnection conn; + private Decoder[] decoders; + + private ArrayDeque<MemOutStream> freeBuffers; + private ArrayDeque<QueueEntry> workQueue; + + private ReentrantLock queueMutex; + private Condition producerCond; + private Condition consumerCond; + + private List<DecodeThread> threads; + private com.tigervnc.rdr.Exception threadException; + +} diff --git a/java/com/tigervnc/rfb/Decoder.java b/java/com/tigervnc/rfb/Decoder.java index f0ece0af..6bbed85e 100644 --- a/java/com/tigervnc/rfb/Decoder.java +++ b/java/com/tigervnc/rfb/Decoder.java @@ -18,34 +18,80 @@ package com.tigervnc.rfb; +import com.tigervnc.rdr.*; + abstract public class Decoder { - abstract public void readRect(Rect r, CMsgHandler handler); + public static class DecoderFlags { + // A constant for decoders that don't need anything special + public static int DecoderPlain = 0; + // All rects for this decoder must be handled in order + public static int DecoderOrdered = 1 << 0; + // Only some of the rects must be handled in order, + // see doesRectsConflict() + public static int DecoderPartiallyOrdered = 1 << 1; + }; + + public Decoder(int flags) + { + this.flags = flags; + } + + abstract public void readRect(Rect r, InStream is, + ConnParams cp, OutStream os); + + abstract public void decodeRect(Rect r, Object buffer, + int buflen, ConnParams cp, + ModifiablePixelBuffer pb); + + public void getAffectedRegion(Rect rect, Object buffer, + int buflen, ConnParams cp, + Region region) + { + region.reset(rect); + } + + public boolean doRectsConflict(Rect rectA, Object bufferA, + int buflenA, Rect rectB, + Object bufferB, int buflenB, + ConnParams cp) + { + return false; + } static public boolean supported(int encoding) { -/* - return encoding <= Encodings.encodingMax && createFns[encoding]; -*/ - return (encoding == Encodings.encodingRaw || - encoding == Encodings.encodingRRE || - encoding == Encodings.encodingHextile || - encoding == Encodings.encodingTight || - encoding == Encodings.encodingZRLE); + switch(encoding) { + case Encodings.encodingRaw: + case Encodings.encodingCopyRect: + case Encodings.encodingRRE: + case Encodings.encodingHextile: + case Encodings.encodingZRLE: + case Encodings.encodingTight: + return true; + default: + return false; + } } - static public Decoder createDecoder(int encoding, CMsgReader reader) { -/* - if (encoding <= Encodings.encodingMax && createFns[encoding]) - return (createFns[encoding])(reader); - return 0; -*/ + + static public Decoder createDecoder(int encoding) { switch(encoding) { - case Encodings.encodingRaw: return new RawDecoder(reader); - case Encodings.encodingRRE: return new RREDecoder(reader); - case Encodings.encodingHextile: return new HextileDecoder(reader); - case Encodings.encodingTight: return new TightDecoder(reader); - case Encodings.encodingZRLE: return new ZRLEDecoder(reader); + case Encodings.encodingRaw: + return new RawDecoder(); + case Encodings.encodingCopyRect: + return new CopyRectDecoder(); + case Encodings.encodingRRE: + return new RREDecoder(); + case Encodings.encodingHextile: + return new HextileDecoder(); + case Encodings.encodingZRLE: + return new ZRLEDecoder(); + case Encodings.encodingTight: + return new TightDecoder(); + default: + return null; } - return null; } + + public final int flags; } diff --git a/java/com/tigervnc/rfb/FullFramePixelBuffer.java b/java/com/tigervnc/rfb/FullFramePixelBuffer.java new file mode 100644 index 00000000..1c3b0958 --- /dev/null +++ b/java/com/tigervnc/rfb/FullFramePixelBuffer.java @@ -0,0 +1,54 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +package com.tigervnc.rfb; + +import java.awt.image.*; + +public class FullFramePixelBuffer extends ModifiablePixelBuffer { + + public FullFramePixelBuffer(PixelFormat pf, int w, int h, + WritableRaster data_) { + super(pf, w, h); + data = data_; + } + + protected FullFramePixelBuffer() {} + + public WritableRaster getBufferRW(Rect r) + { + return data.createWritableChild(r.tl.x, r.tl.y, r.width(), r.height(), + 0, 0, null); + } + + public void commitBufferRW(Rect r) + { + } + + public Raster getBuffer(Rect r) + { + Raster src = + data.createChild(r.tl.x, r.tl.y, r.width(), r.height(), 0, 0, null); + WritableRaster dst = + data.createCompatibleWritableRaster(r.width(), r.height()); + dst.setDataElements(0, 0, src); + return dst; + } + + protected WritableRaster data; +} diff --git a/java/com/tigervnc/rfb/HextileDecoder.java b/java/com/tigervnc/rfb/HextileDecoder.java index 94e91f70..b0744cad 100644 --- a/java/com/tigervnc/rfb/HextileDecoder.java +++ b/java/com/tigervnc/rfb/HextileDecoder.java @@ -18,22 +18,126 @@ package com.tigervnc.rfb; +import java.awt.image.*; +import java.nio.*; +import java.util.Arrays; + import com.tigervnc.rdr.*; public class HextileDecoder extends Decoder { - public HextileDecoder(CMsgReader reader_) { reader = reader_; } + public static final int hextileRaw = (1 << 0); + public static final int hextileBgSpecified = (1 << 1); + public static final int hextileFgSpecified = (1 << 2); + public static final int hextileAnySubrects = (1 << 3); + public static final int hextileSubrectsColoured = (1 << 4); + + public HextileDecoder() { super(DecoderFlags.DecoderPlain); } + + public void readRect(Rect r, InStream is, + ConnParams cp, OutStream os) + { + Rect t = new Rect(); + int bytesPerPixel; + + bytesPerPixel = cp.pf().bpp/8; + + for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { + + t.br.y = Math.min(r.br.y, t.tl.y + 16); + + for (t.tl.x = r.tl.x; t.tl.x < r.br.x; t.tl.x += 16) { + int tileType; + + t.br.x = Math.min(r.br.x, t.tl.x + 16); + + tileType = is.readU8() & 0xff; + os.writeU32(tileType); + + if ((tileType & hextileRaw) != 0) { + os.copyBytes(is, t.area() * bytesPerPixel); + continue; + } + + if ((tileType & hextileBgSpecified) != 0) + os.copyBytes(is, bytesPerPixel); + + if ((tileType & hextileFgSpecified) != 0) + os.copyBytes(is, bytesPerPixel); - public void readRect(Rect r, CMsgHandler handler) { - InStream is = reader.getInStream(); - int bytesPerPixel = handler.cp.pf().bpp / 8; - boolean bigEndian = handler.cp.pf().bigEndian; + if ((tileType & hextileAnySubrects) != 0) { + int nSubrects; - int[] buf = reader.getImageBuf(16 * 16 * 4); + nSubrects = is.readU8() & 0xff; + os.writeU32(nSubrects); + if ((tileType & hextileSubrectsColoured) != 0) + os.copyBytes(is, nSubrects * (bytesPerPixel + 2)); + else + os.copyBytes(is, nSubrects * 2); + } + } + } + } + + public void decodeRect(Rect r, Object buffer, + int buflen, ConnParams cp, + ModifiablePixelBuffer pb) + { + MemInStream is = new MemInStream((byte[])buffer, 0, buflen); + PixelFormat pf = cp.pf(); + switch (pf.bpp) { + case 8: hextileDecode8(r, is, pf, pb); break; + case 16: hextileDecode16(r, is, pf, pb); break; + case 32: hextileDecode32(r, is, pf, pb); break; + } + } + + private void hextileDecode8(Rect r, InStream is, + PixelFormat pf, + ModifiablePixelBuffer pb) + { + HEXTILE_DECODE(r, is, pf, pb); + } + + private void hextileDecode16(Rect r, InStream is, + PixelFormat pf, + ModifiablePixelBuffer pb) + { + HEXTILE_DECODE(r, is, pf, pb); + } + + private void hextileDecode32(Rect r, InStream is, + PixelFormat pf, + ModifiablePixelBuffer pb) + { + HEXTILE_DECODE(r, is, pf, pb); + } + + private static ByteBuffer READ_PIXEL(InStream is, PixelFormat pf) { + ByteBuffer b = ByteBuffer.allocate(4); + switch (pf.bpp) { + case 8: + b.putInt(is.readOpaque8()); + return ByteBuffer.allocate(1).put(b.get(3)); + case 16: + b.putInt(is.readOpaque16()); + return ByteBuffer.allocate(2).put(b.array(), 2, 2); + case 32: + default: + b.putInt(is.readOpaque32()); + return b; + } + } + + private void HEXTILE_DECODE(Rect r, InStream is, + PixelFormat pf, + ModifiablePixelBuffer pb) + { Rect t = new Rect(); - int bg = 0; - int fg = 0; + ByteBuffer bg = ByteBuffer.allocate(pf.bpp/8); + ByteBuffer fg = ByteBuffer.allocate(pf.bpp/8); + ByteBuffer buf = ByteBuffer.allocate(16 * 16 * 4); for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { @@ -43,59 +147,51 @@ public class HextileDecoder extends Decoder { t.br.x = Math.min(r.br.x, t.tl.x + 16); - int tileType = is.readU8(); + int tileType = is.readU32(); - if ((tileType & Hextile.raw) != 0) { - is.readPixels(buf, t.area(), bytesPerPixel, bigEndian); - handler.imageRect(t, buf); + if ((tileType & hextileRaw) != 0) { + is.readBytes(buf, t.area() * (pf.bpp/8)); + pb.imageRect(pf, t, buf.array()); continue; } - if ((tileType & Hextile.bgSpecified) != 0) - bg = is.readPixel(bytesPerPixel, bigEndian); + if ((tileType & hextileBgSpecified) != 0) + bg = READ_PIXEL(is, pf); int len = t.area(); - int ptr = 0; - while (len-- > 0) buf[ptr++] = bg; + ByteBuffer ptr = buf.duplicate(); + while (len-- > 0) ptr.put(bg.array()); - if ((tileType & Hextile.fgSpecified) != 0) - fg = is.readPixel(bytesPerPixel, bigEndian); + if ((tileType & hextileFgSpecified) != 0) + fg = READ_PIXEL(is, pf); - if ((tileType & Hextile.anySubrects) != 0) { - int nSubrects = is.readU8(); + if ((tileType & hextileAnySubrects) != 0) { + int nSubrects = is.readU32(); for (int i = 0; i < nSubrects; i++) { - if ((tileType & Hextile.subrectsColoured) != 0) - fg = is.readPixel(bytesPerPixel, bigEndian); + if ((tileType & hextileSubrectsColoured) != 0) + fg = READ_PIXEL(is, pf); int xy = is.readU8(); int wh = is.readU8(); -/* - Rect s = new Rect(); - s.tl.x = t.tl.x + ((xy >> 4) & 15); - s.tl.y = t.tl.y + (xy & 15); - s.br.x = s.tl.x + ((wh >> 4) & 15) + 1; - s.br.y = s.tl.y + (wh & 15) + 1; -*/ int x = ((xy >> 4) & 15); int y = (xy & 15); int w = ((wh >> 4) & 15) + 1; int h = (wh & 15) + 1; - ptr = y * t.width() + x; - int rowAdd = t.width() - w; + ptr = buf.duplicate(); + ptr.position((y * t.width() + x)*pf.bpp/8); + int rowAdd = (t.width() - w)*pf.bpp/8; while (h-- > 0) { len = w; - while (len-- > 0) buf[ptr++] = fg; - ptr += rowAdd; + while (len-- > 0) ptr.put(fg.array()); + ptr.position(ptr.position()+Math.min(rowAdd,ptr.remaining())); } } } - handler.imageRect(t, buf); + pb.imageRect(pf, t, buf.array()); } } } - - CMsgReader reader; } diff --git a/java/com/tigervnc/rfb/JpegDecompressor.java b/java/com/tigervnc/rfb/JpegDecompressor.java new file mode 100644 index 00000000..9137847c --- /dev/null +++ b/java/com/tigervnc/rfb/JpegDecompressor.java @@ -0,0 +1,53 @@ +/* Copyright (C) 2016 Brian P. Hinz + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +package com.tigervnc.rfb; + +import java.awt.image.*; +import java.io.*; +import java.nio.ByteBuffer; +import javax.imageio.*; +import javax.imageio.stream.*; + +public class JpegDecompressor { + + public JpegDecompressor() {} + + public void decompress(ByteBuffer jpegBuf, int jpegBufLen, + WritableRaster buf, Rect r, PixelFormat pf) + { + + byte[] src = new byte[jpegBufLen]; + + jpegBuf.get(src); + try { + BufferedImage image = + ImageIO.read(new MemoryCacheImageInputStream(new ByteArrayInputStream(src))); + ColorModel cm = pf.getColorModel(); + if (cm.isCompatibleRaster(image.getRaster()) && + cm.isCompatibleSampleModel(image.getRaster().getSampleModel())) { + buf.setDataElements(0, 0, image.getRaster()); + } else { + ColorConvertOp converter = pf.getColorConvertOp(cm.getColorSpace()); + converter.filter(image.getRaster(), buf); + } + image.flush(); + } catch (IOException e) { + throw new Exception(e.getMessage()); + } + } +} diff --git a/java/com/tigervnc/rfb/ManagedPixelBuffer.java b/java/com/tigervnc/rfb/ManagedPixelBuffer.java index f947af71..6e14b92e 100644 --- a/java/com/tigervnc/rfb/ManagedPixelBuffer.java +++ b/java/com/tigervnc/rfb/ManagedPixelBuffer.java @@ -18,21 +18,37 @@ package com.tigervnc.rfb; -public class ManagedPixelBuffer extends PixelBuffer { - public void setSize(int w, int h) { - width_ = w; - height_ = h; +public class ManagedPixelBuffer extends FullFramePixelBuffer { + + public ManagedPixelBuffer() { + datasize = 0; checkDataSize(); } - public void setPF(PixelFormat pf) { - super.setPF(pf); + + public ManagedPixelBuffer(PixelFormat pf, int w, int h) + { + super(pf, w, h, null); + datasize = 0; checkDataSize(); } - public int dataLen() { return area(); } + public void setPF(PixelFormat pf) { + format = pf; checkDataSize(); + } + + public void setSize(int w, int h) { + width_ = w; height_ = h; checkDataSize(); + } final void checkDataSize() { - if (data == null || data.length < dataLen()) - data = new int[dataLen()]; + int new_datasize = width_ * height_; + if (datasize < new_datasize) { + vlog.debug("reallocating managed buffer ("+width_+"x"+height_+")"); + if (format != null) + data = PixelFormat.getColorModel(format).createCompatibleWritableRaster(width_, height_); + } } + + protected int datasize; + static LogWriter vlog = new LogWriter("ManagedPixelBuffer"); } diff --git a/java/com/tigervnc/rfb/ModifiablePixelBuffer.java b/java/com/tigervnc/rfb/ModifiablePixelBuffer.java new file mode 100644 index 00000000..bcc559d5 --- /dev/null +++ b/java/com/tigervnc/rfb/ModifiablePixelBuffer.java @@ -0,0 +1,267 @@ +/* Copyright 2016 Brian P. Hinz + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +// -=- Modifiable generic pixel buffer class + +package com.tigervnc.rfb; + +import java.awt.image.*; +import java.awt.Color; +import java.awt.color.ColorSpace; +import java.lang.*; +import java.nio.*; +import java.util.*; + +import static java.awt.image.DataBuffer.*; + +public abstract class ModifiablePixelBuffer extends PixelBuffer +{ + + public ModifiablePixelBuffer(PixelFormat pf, int w, int h) + { + super(pf, w, h); + } + + protected ModifiablePixelBuffer() + { + } + + /////////////////////////////////////////////// + // Access to pixel data + // + + // Get a writeable pointer into the buffer + // Like getBuffer(), the pointer is to the top-left pixel of the + // specified Rect. + public abstract WritableRaster getBufferRW(Rect r); + // Commit the modified contents + // Ensures that the changes to the specified Rect is properly + // stored away and any temporary buffers are freed. The Rect given + // here needs to match the Rect given to the earlier call to + // getBufferRW(). + public abstract void commitBufferRW(Rect r); + + static LogWriter vlog = new LogWriter("ModifiablePixelBuffer"); + /////////////////////////////////////////////// + // Basic rendering operations + // These operations DO NOT clip to the pixelbuffer area, or trap overruns. + + // Fill a rectangle + public synchronized void fillRect(Rect r, byte[] pix) + { + WritableRaster buf; + int w, h; + + w = r.width(); + h = r.height(); + + if (h == 0 || w ==0) + return; + + buf = getBufferRW(r); + + ByteBuffer src = + ByteBuffer.allocate(r.area()*format.bpp/8).order(format.getByteOrder()); + for (int i=0; i < r.area(); i++) + src.put(pix); + Raster raster = format.rasterFromBuffer(r, (ByteBuffer)src.rewind()); + buf.setDataElements(0, 0, raster); + + commitBufferRW(r); + } + + // Copy pixel data to the buffer + public synchronized void imageRect(Rect r, byte[] pixels) + { + WritableRaster dest = getBufferRW(r); + + ByteBuffer src = ByteBuffer.wrap(pixels).order(format.getByteOrder()); + Raster raster = format.rasterFromBuffer(r, src); + dest.setDataElements(0, 0, raster); + + commitBufferRW(r); + } + + // Copy pixel data from one PixelBuffer location to another + public synchronized void copyRect(Rect rect, + Point move_by_delta) + { + Raster srcData; + WritableRaster dstData; + + Rect drect, srect; + + drect = new Rect(rect.tl, rect.br); + if (!drect.enclosed_by(getRect())) { + String msg = "Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d"; + vlog.error(String.format(msg, drect.width(), drect.height(), + drect.tl.x, drect.tl.y, width_, height_)); + drect = drect.intersect(getRect()); + } + + if (drect.is_empty()) + return; + + srect = drect.translate(move_by_delta.negate()); + if (!srect.enclosed_by(getRect())) { + String msg = "Source rect %dx%d at %d,%d exceeds framebuffer %dx%d"; + vlog.error(String.format(msg, srect.width(), srect.height(), + srect.tl.x, srect.tl.y, width_, height_)); + srect = srect.intersect(getRect()); + // Need to readjust the destination now that the area has changed + drect = srect.translate(move_by_delta); + } + + if (srect.is_empty()) + return; + + srcData = getBuffer(srect); + dstData = getBufferRW(drect); + + dstData.setDataElements(0, 0, srcData); + + commitBufferRW(rect); + } + + // Copy pixel data to the buffer through a mask + // pixels is a pointer to the pixel to be copied to r.tl. + // maskPos specifies the pixel offset in the mask to start from. + // mask_ is a pointer to the mask bits at (0,0). + // pStride and mStride are the strides of the pixel and mask buffers. + public synchronized void maskRect(Rect r, + Object pixels, byte[] mask_) + { + Rect cr = getRect().intersect(r); + if (cr.is_empty()) return; + WritableRaster data = getBufferRW(cr); + + // FIXME + ColorModel cm = format.getColorModel(); + SampleModel sm = + cm.createCompatibleSampleModel(r.width(), r.height()); + DataBuffer db = null; + ByteBuffer src = + ByteBuffer.wrap((byte[])pixels).order(format.getByteOrder()); + Buffer dst; + switch (sm.getTransferType()) { + case TYPE_INT: + dst = IntBuffer.allocate(src.remaining()).put(src.asIntBuffer()); + db = new DataBufferInt(((IntBuffer)dst).array(), r.area()); + break; + case TYPE_BYTE: + db = new DataBufferByte(src.array(), r.area()); + break; + case TYPE_SHORT: + dst = ShortBuffer.allocate(src.remaining()).put(src.asShortBuffer()); + db = new DataBufferShort(((ShortBuffer)dst).array(), r.area()); + break; + } + assert(db != null); + Raster raster = + Raster.createRaster(sm, db, new java.awt.Point(0, 0)); + ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace()); + WritableRaster t = data.createCompatibleWritableRaster(); + converter.filter(raster, t); + + int w = cr.width(); + int h = cr.height(); + + Point offset = new Point(cr.tl.x-r.tl.x, cr.tl.y-r.tl.y); + + int maskBytesPerRow = (w + 7) / 8; + + for (int y = 0; y < h; y++) { + int cy = offset.y + y; + for (int x = 0; x < w; x++) { + int cx = offset.x + x; + int byte_ = cy * maskBytesPerRow + y / 8; + int bit = 7 - cx % 8; + + if ((mask_[byte_] & (1 << bit)) != 0) + data.setDataElements(x+cx, y+cy, t.getDataElements(x+cx, y+cy, null)); + } + } + + commitBufferRW(r); + } + + // pixel is the Pixel value to be used where mask_ is set + public synchronized void maskRect(Rect r, int pixel, byte[] mask) + { + // FIXME + } + + // Render in a specific format + // Does the exact same thing as the above methods, but the given + // pixel values are defined by the given PixelFormat. + public synchronized void fillRect(PixelFormat pf, Rect dest, byte[] pix) + { + WritableRaster dstBuffer = getBufferRW(dest); + + ColorModel cm = pf.getColorModel(); + if (cm.isCompatibleRaster(dstBuffer) && + cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) { + fillRect(dest, pix); + } else { + ByteBuffer src = + ByteBuffer.allocate(dest.area()*pf.bpp/8).order(pf.getByteOrder()); + for (int i=0; i < dest.area(); i++) + src.put(pix); + Raster raster = pf.rasterFromBuffer(dest, (ByteBuffer)src.rewind()); + ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace()); + converter.filter(raster, dstBuffer); + } + + commitBufferRW(dest); + } + + public synchronized void imageRect(PixelFormat pf, Rect dest, byte[] pixels) + { + WritableRaster dstBuffer = getBufferRW(dest); + + ColorModel cm = pf.getColorModel(); + if (cm.isCompatibleRaster(dstBuffer) && + cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) { + imageRect(dest, pixels); + } else { + ByteBuffer src = ByteBuffer.wrap(pixels).order(pf.getByteOrder()); + Raster raster = pf.rasterFromBuffer(dest, src); + ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace()); + converter.filter(raster, dstBuffer); + } + + commitBufferRW(dest); + } + + public synchronized void imageRect(PixelFormat pf, Rect dest, Raster pixels) + { + WritableRaster dstBuffer = getBufferRW(dest); + + ColorModel cm = pf.getColorModel(); + if (cm.isCompatibleRaster(dstBuffer) && + cm.isCompatibleSampleModel(dstBuffer.getSampleModel())) { + dstBuffer.setDataElements(0, 0, pixels); + } else { + ColorConvertOp converter = format.getColorConvertOp(cm.getColorSpace()); + converter.filter(pixels, dstBuffer); + } + + commitBufferRW(dest); + } + +} diff --git a/java/com/tigervnc/rfb/PixelBuffer.java b/java/com/tigervnc/rfb/PixelBuffer.java index a46667d3..1b7d2c1a 100644 --- a/java/com/tigervnc/rfb/PixelBuffer.java +++ b/java/com/tigervnc/rfb/PixelBuffer.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2016 Brian P. Hinz * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,117 +17,67 @@ * 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_; diff --git a/java/com/tigervnc/rfb/PixelFormat.java b/java/com/tigervnc/rfb/PixelFormat.java index c4d68701..9a269992 100644 --- a/java/com/tigervnc/rfb/PixelFormat.java +++ b/java/com/tigervnc/rfb/PixelFormat.java @@ -25,40 +25,80 @@ package com.tigervnc.rfb; +import java.awt.color.*; +import java.awt.image.*; +import java.nio.*; +import java.util.*; + import com.tigervnc.rdr.*; -import java.awt.image.ColorModel; public class PixelFormat { - public PixelFormat(int b, int d, boolean e, boolean t) { - bpp = b; - depth = d; - bigEndian = e; - trueColour = t; - } public PixelFormat(int b, int d, boolean e, boolean t, - int rm, int gm, int bm, int rs, int gs, int bs) { - this(b, d, e, t); - redMax = rm; - greenMax = gm; - blueMax = bm; - redShift = rs; - greenShift = gs; - blueShift = bs; + int rm, int gm, int bm, int rs, int gs, int bs) + { + bpp = b; depth = d; trueColour = t; bigEndian = e; + redMax = rm; greenMax = gm; blueMax = bm; + redShift = rs; greenShift = gs; blueShift = bs; + converters = new HashMap<Integer, ColorConvertOp>(); + assert(isSane()); + + updateState(); + } + + public PixelFormat() + { + this(8, 8, false, true, 7, 7, 3, 0, 3, 6); + updateState(); } - public PixelFormat() { this(8,8,false,true,7,7,3,0,3,6); } - - public boolean equal(PixelFormat x) { - return (bpp == x.bpp && - depth == x.depth && - (bigEndian == x.bigEndian || bpp == 8) && - trueColour == x.trueColour && - (!trueColour || (redMax == x.redMax && - greenMax == x.greenMax && - blueMax == x.blueMax && - redShift == x.redShift && - greenShift == x.greenShift && - blueShift == x.blueShift))); + + public boolean equal(PixelFormat other) + { + if (bpp != other.bpp || depth != other.depth) + return false; + + if (redMax != other.redMax) + return false; + if (greenMax != other.greenMax) + return false; + if (blueMax != other.blueMax) + return false; + + // Endianness requires more care to determine compatibility + if (bigEndian == other.bigEndian || bpp == 8) { + if (redShift != other.redShift) + return false; + if (greenShift != other.greenShift) + return false; + if (blueShift != other.blueShift) + return false; + } else { + // Has to be the same byte for each channel + if (redShift/8 != (3 - other.redShift/8)) + return false; + if (greenShift/8 != (3 - other.greenShift/8)) + return false; + if (blueShift/8 != (3 - other.blueShift/8)) + return false; + + // And the same bit offset within the byte + if (redShift%8 != other.redShift%8) + return false; + if (greenShift%8 != other.greenShift%8) + return false; + if (blueShift%8 != other.blueShift%8) + return false; + + // And not cross a byte boundary + if (redShift/8 != (redShift + redBits - 1)/8) + return false; + if (greenShift/8 != (greenShift + greenBits - 1)/8) + return false; + if (blueShift/8 != (blueShift + blueBits - 1)/8) + return false; + } + + return true; } public void read(InStream is) { @@ -73,6 +113,23 @@ public class PixelFormat { greenShift = is.readU8(); blueShift = is.readU8(); is.skip(3); + + // We have no real support for colour maps. If the client + // wants one, then we force a 8-bit true colour format and + // pretend it's a colour map. + if (!trueColour) { + redMax = 7; + greenMax = 7; + blueMax = 3; + redShift = 0; + greenShift = 3; + blueShift = 6; + } + + if (!isSane()) + throw new Exception("invalid pixel format: "+print()); + + updateState(); } public void write(OutStream os) { @@ -89,6 +146,14 @@ public class PixelFormat { os.pad(3); } + public final boolean isBigEndian() { + return bigEndian; + } + + public final boolean isLittleEndian() { + return ! bigEndian; + } + public final boolean is888() { if(!trueColour) return false; @@ -139,53 +204,140 @@ public class PixelFormat { return 0; } - public void bufferFromRGB(int[] dst, int dstPtr, byte[] src, - int srcPtr, int pixels) { + public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, int pixels) + { + bufferFromRGB(dst, src, pixels, pixels, 1); + } + + public void bufferFromRGB(ByteBuffer dst, ByteBuffer src, + int w, int stride, int h) + { if (is888()) { // Optimised common case - int r, g, b; + int r, g, b, x; + + if (bigEndian) { + r = dst.position() + (24 - redShift)/8; + g = dst.position() + (24 - greenShift)/8; + b = dst.position() + (24 - blueShift)/8; + x = dst.position() + (24 - (48 - redShift - greenShift - blueShift))/8; + } else { + r = dst.position() + redShift/8; + g = dst.position() + greenShift/8; + b = dst.position() + blueShift/8; + x = dst.position() + (48 - redShift - greenShift - blueShift)/8; + } - for (int i=srcPtr; i < pixels; i++) { - if (bigEndian) { - r = (src[3*i+0] & 0xff) << (24 - redShift); - g = (src[3*i+1] & 0xff) << (24 - greenShift); - b = (src[3*i+2] & 0xff) << (24 - blueShift); - dst[dstPtr+i] = r | g | b | 0xff; - } else { - r = (src[3*i+0] & 0xff) << redShift; - g = (src[3*i+1] & 0xff) << greenShift; - b = (src[3*i+2] & 0xff) << blueShift; - dst[dstPtr+i] = (0xff << 24) | r | g | b; + int dstPad = (stride - w) * 4; + while (h-- > 0) { + int w_ = w; + while (w_-- > 0) { + dst.put(r, src.get()); + dst.put(g, src.get()); + dst.put(b, src.get()); + dst.put(x, (byte)0); + r += 4; + g += 4; + b += 4; + x += 4; } + r += dstPad; + g += dstPad; + b += dstPad; + x += dstPad; } } else { // Generic code - int p, r, g, b; - int[] rgb = new int[4]; + int dstPad = (stride - w) * bpp/8; + while (h-- > 0) { + int w_ = w; + while (w_-- > 0) { + int p; + int r, g, b; - int i = srcPtr; int j = dstPtr; - while (i < pixels) { - r = src[i++] & 0xff; - g = src[i++] & 0xff; - b = src[i++] & 0xff; + r = src.get(); + g = src.get(); + b = src.get(); - //p = pixelFromRGB(r, g, b, cm); - p = ColorModel.getRGBdefault().getDataElement(new int[] {0xff, r, g, b}, 0); + p = pixelFromRGB(r, g, b, model); - bufferFromPixel(dst, j, p); - j += bpp/8; + bufferFromPixel(dst, p); + dst.position(dst.position() + bpp/8); + } + dst.position(dst.position() + dstPad); } } } - public void rgbFromBuffer(byte[] dst, int dstPtr, byte[] src, int srcPtr, int pixels, ColorModel cm) + public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, int pixels) + { + rgbFromBuffer(dst, src, pixels, pixels, 1); + } + + public void rgbFromBuffer(ByteBuffer dst, ByteBuffer src, + int w, int stride, int h) + { + if (is888()) { + // Optimised common case + int r, g, b; + + if (bigEndian) { + r = src.position() + (24 - redShift)/8; + g = src.position() + (24 - greenShift)/8; + b = src.position() + (24 - blueShift)/8; + } else { + r = src.position() + redShift/8; + g = src.position() + greenShift/8; + b = src.position() + blueShift/8; + } + + int srcPad = (stride - w) * 4; + while (h-- > 0) { + int w_ = w; + while (w_-- > 0) { + dst.put(src.get(r)); + dst.put(src.get(g)); + dst.put(src.get(b)); + r += 4; + g += 4; + b += 4; + } + r += srcPad; + g += srcPad; + b += srcPad; + } + } else { + // Generic code + int srcPad = (stride - w) * bpp/8; + while (h-- > 0) { + int w_ = w; + while (w_-- > 0) { + int p; + byte r, g, b; + + p = pixelFromBuffer(src.duplicate()); + + r = (byte)getColorModel().getRed(p); + g = (byte)getColorModel().getGreen(p); + b = (byte)getColorModel().getBlue(p); + + dst.put(r); + dst.put(g); + dst.put(b); + src.position(src.position() + bpp/8); + } + src.reset().position(src.position() + srcPad).mark(); + } + } + } + + public void rgbFromPixels(byte[] dst, int dstPtr, int[] src, int srcPtr, int pixels, ColorModel cm) { int p; byte r, g, b; for (int i=0; i < pixels; i++) { - p = pixelFromBuffer(src, srcPtr); - srcPtr += bpp/8; + p = src[i]; dst[dstPtr++] = (byte)cm.getRed(p); dst[dstPtr++] = (byte)cm.getGreen(p); @@ -193,31 +345,29 @@ public class PixelFormat { } } - public int pixelFromBuffer(byte[] buffer, int bufferPtr) + public int pixelFromBuffer(ByteBuffer buffer) { int p; - p = 0; + p = 0xff000000; - if (bigEndian) { + if (!bigEndian) { switch (bpp) { case 32: - p = (buffer[0] & 0xff) << 24 | (buffer[1] & 0xff) << 16 | (buffer[2] & 0xff) << 8 | 0xff; - break; + p |= buffer.get() << 24; + p |= buffer.get() << 16; case 16: - p = (buffer[0] & 0xff) << 8 | (buffer[1] & 0xff); - break; + p |= buffer.get() << 8; case 8: - p = (buffer[0] & 0xff); - break; + p |= buffer.get(); } } else { - p = (buffer[0] & 0xff); + p |= buffer.get(0); if (bpp >= 16) { - p |= (buffer[1] & 0xff) << 8; + p |= buffer.get(1) << 8; if (bpp == 32) { - p |= (buffer[2] & 0xff) << 16; - p |= (buffer[3] & 0xff) << 24; + p |= buffer.get(2) << 16; + p |= buffer.get(3) << 24; } } } @@ -263,33 +413,212 @@ public class PixelFormat { return s.toString(); } - public void bufferFromPixel(int[] buffer, int bufPtr, int p) + private static int bits(int value) + { + int bits; + + bits = 16; + + if ((value & 0xff00) == 0) { + bits -= 8; + value <<= 8; + } + if ((value & 0xf000) == 0) { + bits -= 4; + value <<= 4; + } + if ((value & 0xc000) == 0) { + bits -= 2; + value <<= 2; + } + if ((value & 0x8000) == 0) { + bits -= 1; + value <<= 1; + } + + return bits; + } + + private void updateState() + { + int endianTest = 1; + + redBits = bits(redMax); + greenBits = bits(greenMax); + blueBits = bits(blueMax); + + maxBits = redBits; + if (greenBits > maxBits) + maxBits = greenBits; + if (blueBits > maxBits) + maxBits = blueBits; + + minBits = redBits; + if (greenBits < minBits) + minBits = greenBits; + if (blueBits < minBits) + minBits = blueBits; + + if ((((char)endianTest) == 0) != bigEndian) + endianMismatch = true; + else + endianMismatch = false; + + model = getColorModel(this); + } + + private boolean isSane() + { + int totalBits; + + if ((bpp != 8) && (bpp != 16) && (bpp != 32)) + return false; + if (depth > bpp) + return false; + + if (!trueColour && (depth != 8)) + return false; + + if ((redMax & (redMax + 1)) != 0) + return false; + if ((greenMax & (greenMax + 1)) != 0) + return false; + if ((blueMax & (blueMax + 1)) != 0) + return false; + + /* + * We don't allow individual channels > 8 bits in order to keep our + * conversions simple. + */ + if (redMax >= (1 << 8)) + return false; + if (greenMax >= (1 << 8)) + return false; + if (blueMax >= (1 << 8)) + return false; + + totalBits = bits(redMax) + bits(greenMax) + bits(blueMax); + if (totalBits > bpp) + return false; + + if (((redMax << redShift) & (greenMax << greenShift)) != 0) + return false; + if (((redMax << redShift) & (blueMax << blueShift)) != 0) + return false; + if (((greenMax << greenShift) & (blueMax << blueShift)) != 0) + return false; + + return true; + } + + public void bufferFromPixel(ByteBuffer buffer, int p) { if (bigEndian) { switch (bpp) { case 32: - buffer[bufPtr++] = (p >> 24) & 0xff; - buffer[bufPtr++] = (p >> 16) & 0xff; + buffer.put((byte)((p >> 24) & 0xff)); + buffer.put((byte)((p >> 16) & 0xff)); break; case 16: - buffer[bufPtr++] = (p >> 8) & 0xff; + buffer.put((byte)((p >> 8) & 0xff)); break; case 8: - buffer[bufPtr++] = (p >> 0) & 0xff; + buffer.put((byte)((p >> 0) & 0xff)); break; } } else { - buffer[0] = (p >> 0) & 0xff; + buffer.put(0, (byte)((p >> 0) & 0xff)); if (bpp >= 16) { - buffer[1] = (p >> 8) & 0xff; + buffer.put(1, (byte)((p >> 8) & 0xff)); if (bpp == 32) { - buffer[2] = (p >> 16) & 0xff; - buffer[3] = (p >> 24) & 0xff; + buffer.put(2, (byte)((p >> 16) & 0xff)); + buffer.put(3, (byte)((p >> 24) & 0xff)); } } } } + public ColorModel getColorModel() + { + return model; + } + + public static ColorModel getColorModel(PixelFormat pf) { + if (!(pf.bpp == 32) && !(pf.bpp == 16) && !(pf.bpp == 8)) + throw new Exception("Internal error: bpp must be 8, 16, or 32 in PixelBuffer ("+pf.bpp+")"); + ColorModel cm; + switch (pf.depth) { + case 3: + // Fall-through to depth 8 + case 6: + // Fall-through to depth 8 + case 8: + int rmask = pf.redMax << pf.redShift; + int gmask = pf.greenMax << pf.greenShift; + int bmask = pf.blueMax << pf.blueShift; + cm = new DirectColorModel(8, rmask, gmask, bmask); + break; + case 16: + cm = new DirectColorModel(32, 0xF800, 0x07C0, 0x003E); + break; + case 24: + cm = new DirectColorModel(32, (0xff << 16), (0xff << 8), 0xff); + break; + case 32: + cm = new DirectColorModel(32, (0xff << pf.redShift), + (0xff << pf.greenShift), (0xff << pf.blueShift)); + break; + default: + throw new Exception("Unsupported color depth ("+pf.depth+")"); + } + assert(cm != null); + return cm; + } + + public ColorConvertOp getColorConvertOp(ColorSpace src) + { + // The overhead associated with initializing ColorConvertOps is + // enough to justify maintaining a static lookup table. + if (converters.containsKey(src.getType())) + return converters.get(src.getType()); + ColorSpace dst = model.getColorSpace(); + converters.put(src.getType(), new ColorConvertOp(src, dst, null)); + return converters.get(src.getType()); + } + + public ByteOrder getByteOrder() + { + if (isBigEndian()) + return ByteOrder.BIG_ENDIAN; + else + return ByteOrder.LITTLE_ENDIAN; + } + + public Raster rasterFromBuffer(Rect r, ByteBuffer buf) + { + Buffer dst; + DataBuffer db = null; + + SampleModel sm = + model.createCompatibleSampleModel(r.width(), r.height()); + switch (sm.getTransferType()) { + case DataBuffer.TYPE_INT: + dst = IntBuffer.allocate(r.area()).put(buf.asIntBuffer()); + db = new DataBufferInt(((IntBuffer)dst).array(), r.area()); + break; + case DataBuffer.TYPE_BYTE: + db = new DataBufferByte(buf.array(), r.area()); + break; + case DataBuffer.TYPE_SHORT: + dst = ShortBuffer.allocate(r.area()).put(buf.asShortBuffer()); + db = new DataBufferShort(((ShortBuffer)dst).array(), r.area()); + break; + } + assert(db != null); + return Raster.createRaster(sm, db, new java.awt.Point(0, 0)); + } + + private static HashMap<Integer, ColorConvertOp> converters; public int bpp; public int depth; @@ -301,4 +630,10 @@ public class PixelFormat { public int redShift; public int greenShift; public int blueShift; + + protected int redBits, greenBits, blueBits; + protected int maxBits, minBits; + protected boolean endianMismatch; + + private ColorModel model; } diff --git a/java/com/tigervnc/rfb/RREDecoder.java b/java/com/tigervnc/rfb/RREDecoder.java index 487aa3d0..c73c7a94 100644 --- a/java/com/tigervnc/rfb/RREDecoder.java +++ b/java/com/tigervnc/rfb/RREDecoder.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright 2016 Brian P. Hinz * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,30 +19,90 @@ 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); + } + } diff --git a/java/com/tigervnc/rfb/RawDecoder.java b/java/com/tigervnc/rfb/RawDecoder.java index b2219a24..71b79607 100644 --- a/java/com/tigervnc/rfb/RawDecoder.java +++ b/java/com/tigervnc/rfb/RawDecoder.java @@ -18,28 +18,25 @@ package com.tigervnc.rfb; +import com.tigervnc.rdr.*; + public class RawDecoder extends Decoder { - public RawDecoder(CMsgReader reader_) { reader = reader_; } + public RawDecoder() { super(DecoderFlags.DecoderPlain); } - public void readRect(Rect r, CMsgHandler handler) { - int x = r.tl.x; - int y = r.tl.y; - int w = r.width(); - int h = r.height(); - int[] imageBuf = new int[w*h]; - int nPixels = imageBuf.length; - int bytesPerRow = w * (reader.bpp() / 8); - while (h > 0) { - int nRows = nPixels / w; - if (nRows > h) nRows = h; - reader.getInStream().readPixels(imageBuf, nPixels, (reader.bpp() / 8), handler.cp.pf().bigEndian); - handler.imageRect(new Rect(x, y, x+w, y+nRows), imageBuf); - h -= nRows; - y += nRows; - } + public void readRect(Rect r, InStream is, + ConnParams cp, OutStream os) + { + os.copyBytes(is, r.area() * cp.pf().bpp/8); } - CMsgReader reader; static LogWriter vlog = new LogWriter("RawDecoder"); + public void decodeRect(Rect r, Object buffer, + int buflen, ConnParams cp, + ModifiablePixelBuffer pb) + { + assert(buflen >= r.area() * cp.pf().bpp/8); + pb.imageRect(cp.pf(), r, (byte[])buffer); + } + } diff --git a/java/com/tigervnc/rfb/Region.java b/java/com/tigervnc/rfb/Region.java new file mode 100644 index 00000000..f7da91de --- /dev/null +++ b/java/com/tigervnc/rfb/Region.java @@ -0,0 +1,102 @@ +/* Copyright (C) 2016 Brian P. Hinz. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +package com.tigervnc.rfb; + +import java.awt.*; +import java.awt.geom.*; + +public class Region extends Area { + + // Create an empty region + public Region() { + super(); + } + + // Create a rectangular region + public Region(Rect r) { + super(new Rectangle(r.tl.x, r.tl.y, r.width(), r.height())); + } + + public Region(Region r) { + super(r); + //intersect(r); + } + + public void clear() { reset(); } + + public void reset(Rect r) { + if (r.is_empty()) { + clear(); + } else { + clear(); + assign_union(new Region(r)); + /* + xrgn.numRects = 1; + xrgn.rects[0].x1 = xrgn.extents.x1 = r.tl.x; + xrgn.rects[0].y1 = xrgn.extents.y1 = r.tl.y; + xrgn.rects[0].x2 = xrgn.extents.x2 = r.br.x; + xrgn.rects[0].y2 = xrgn.extents.y2 = r.br.y; + */ + } + } + + public void translate(Point delta) { + AffineTransform t = + AffineTransform.getTranslateInstance((double)delta.x, (double)delta.y); + transform(t); + } + + public void assign_intersect(Region r) { + intersect(r); + } + + public void assign_union(Region r) { + add(r); + } + + public void assign_subtract(Region r) { + subtract(r); + } + + public Region intersect(Region r) { + Region ret = new Region(this); + ((Area)ret).intersect(this); + return ret; + } + + public Region union(Region r) { + Region ret = new Region(r); + ((Area)ret).add(this); + return ret; + } + + public Region subtract(Region r) { + Region ret = new Region(this); + ((Area)ret).subtract(r); + return ret; + } + + public boolean is_empty() { return isEmpty(); } + + public Rect get_bounding_rect() { + Rectangle b = getBounds(); + return new Rect((int)b.getX(), (int)b.getY(), + (int)b.getWidth(), (int)b.getHeight()); + } +} diff --git a/java/com/tigervnc/rfb/ScreenSet.java b/java/com/tigervnc/rfb/ScreenSet.java index a14f561d..173dd101 100644 --- a/java/com/tigervnc/rfb/ScreenSet.java +++ b/java/com/tigervnc/rfb/ScreenSet.java @@ -31,13 +31,18 @@ public class ScreenSet { screens = new ArrayList<Screen>(); } + public final ListIterator<Screen> begin() { return screens.listIterator(0); } + public final ListIterator<Screen> end() { + return screens.listIterator(screens.size()); + } public final int num_screens() { return screens.size(); } public final void add_screen(Screen screen) { screens.add(screen); } public final void remove_screen(int id) { - for (Iterator<Screen> iter = screens.iterator(); iter.hasNext(); ) { - Screen refScreen = (Screen)iter.next(); - if (refScreen.id == id) + ListIterator iter, nextiter; + for (iter = begin(); iter != end(); iter = nextiter) { + nextiter = iter; nextiter.next(); + if (((Screen)iter.next()).id == id) iter.remove(); } } @@ -68,9 +73,10 @@ public class ScreenSet { } public final void debug_print() { + vlog.debug(num_screens()+" screen(s)"); for (Iterator<Screen> iter = screens.iterator(); iter.hasNext(); ) { Screen refScreen = (Screen)iter.next(); - vlog.error(" "+refScreen.id+" (0x"+refScreen.id+"): "+ + vlog.debug(" "+refScreen.id+" (0x"+refScreen.id+"): "+ refScreen.dimensions.width()+"x"+refScreen.dimensions.height()+ "+"+refScreen.dimensions.tl.x+"+"+refScreen.dimensions.tl.y+ " (flags 0x"+refScreen.flags+")"); diff --git a/java/com/tigervnc/rfb/Security.java b/java/com/tigervnc/rfb/Security.java index a68ae3e9..e256e6eb 100644 --- a/java/com/tigervnc/rfb/Security.java +++ b/java/com/tigervnc/rfb/Security.java @@ -60,6 +60,8 @@ public class Security { public static final int secResultFailed = 1; public static final int secResultTooMany = 2; // deprecated + public Security() { } + public Security(StringParameter secTypes) { String secTypesStr; @@ -70,9 +72,9 @@ public class Security { secTypesStr = null; } - public static List<Integer> enabledSecTypes = new ArrayList<Integer>(); + private List<Integer> enabledSecTypes = new ArrayList<Integer>(); - public static final List<Integer> GetEnabledSecTypes() + public final List<Integer> GetEnabledSecTypes() { List<Integer> result = new ArrayList<Integer>(); @@ -98,7 +100,7 @@ public class Security { return (result); } - public static final List<Integer> GetEnabledExtSecTypes() + public final List<Integer> GetEnabledExtSecTypes() { List<Integer> result = new ArrayList<Integer>(); @@ -111,7 +113,7 @@ public class Security { return (result); } - public static final void EnableSecType(int secType) + public final void EnableSecType(int secType) { for (Iterator<Integer> i = enabledSecTypes.iterator(); i.hasNext(); ) @@ -134,7 +136,29 @@ public class Security { return false; } - public static void DisableSecType(int secType) { enabledSecTypes.remove((Object)secType); } + public String ToString() + { + Iterator<Integer> i; + String out = new String(""); + boolean firstpass = true; + String name; + + for (i = enabledSecTypes.iterator(); i.hasNext(); ) { + name = secTypeName((Integer)i.next()); + if (name.startsWith("[")) /* Unknown security type */ + continue; + + if (!firstpass) + out = out.concat(","); + else + firstpass = false; + out = out.concat(name); + } + + return out; + } + + public void DisableSecType(int secType) { enabledSecTypes.remove((Object)secType); } public static int secTypeNum(String name) { if (name.equalsIgnoreCase("None")) return secTypeNone; @@ -203,7 +227,9 @@ public class Security { return (result); } - public final void SetSecTypes(List<Integer> secTypes) { enabledSecTypes = secTypes; } + public final void SetSecTypes(List<Integer> secTypes) { + enabledSecTypes = secTypes; + } static LogWriter vlog = new LogWriter("Security"); } diff --git a/java/com/tigervnc/rfb/SecurityClient.java b/java/com/tigervnc/rfb/SecurityClient.java index 59499b1e..ff2433c2 100644 --- a/java/com/tigervnc/rfb/SecurityClient.java +++ b/java/com/tigervnc/rfb/SecurityClient.java @@ -78,9 +78,9 @@ public class SecurityClient extends Security { //UserPasswdGetter upg = null; String msg = null; - static StringParameter secTypes + public static StringParameter secTypes = new StringParameter("SecurityTypes", - "Specify which security scheme to use (None, VncAuth)", - "Ident,TLSIdent,X509Ident,X509Plain,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,VncAuth,None", Configuration.ConfigurationObject.ConfViewer); + "Specify which security scheme to use (None, VncAuth, Plain, Ident, TLSNone, TLSVnc, TLSPlain, TLSIdent, X509None, X509Vnc, X509Plain, X509Ident)", + "X509Ident,X509Plain,TLSIdent,TLSPlain,X509Vnc,TLSVnc,X509None,TLSNone,Ident,VncAuth,None", Configuration.ConfigurationObject.ConfViewer); } diff --git a/java/com/tigervnc/rfb/TightDecoder.java b/java/com/tigervnc/rfb/TightDecoder.java index b644cdb4..aa468eb8 100644 --- a/java/com/tigervnc/rfb/TightDecoder.java +++ b/java/com/tigervnc/rfb/TightDecoder.java @@ -22,56 +22,181 @@ package com.tigervnc.rfb; import com.tigervnc.rdr.InStream; +import com.tigervnc.rdr.MemInStream; +import com.tigervnc.rdr.OutStream; import com.tigervnc.rdr.ZlibInStream; import java.util.ArrayList; import java.io.InputStream; import java.awt.image.*; import java.awt.*; +import java.math.BigInteger; +import java.io.*; +import java.nio.*; +import javax.imageio.*; +import javax.imageio.stream.*; public class TightDecoder extends Decoder { final static int TIGHT_MAX_WIDTH = 2048; + final static int TIGHT_MIN_TO_COMPRESS = 12; // Compression control - final static int rfbTightExplicitFilter = 0x04; - final static int rfbTightFill = 0x08; - final static int rfbTightJpeg = 0x09; - final static int rfbTightMaxSubencoding = 0x09; + final static int tightExplicitFilter = 0x04; + final static int tightFill = 0x08; + final static int tightJpeg = 0x09; + final static int tightMaxSubencoding = 0x09; // Filters to improve compression efficiency - final static int rfbTightFilterCopy = 0x00; - final static int rfbTightFilterPalette = 0x01; - final static int rfbTightFilterGradient = 0x02; - final static int rfbTightMinToCompress = 12; + final static int tightFilterCopy = 0x00; + final static int tightFilterPalette = 0x01; + final static int tightFilterGradient = 0x02; - final static Toolkit tk = Toolkit.getDefaultToolkit(); - - public TightDecoder(CMsgReader reader_) { - reader = reader_; + public TightDecoder() { + super(DecoderFlags.DecoderPartiallyOrdered); zis = new ZlibInStream[4]; for (int i = 0; i < 4; i++) zis[i] = new ZlibInStream(); } - public void readRect(Rect r, CMsgHandler handler) + public void readRect(Rect r, InStream is, + ConnParams cp, OutStream os) { - InStream is = reader.getInStream(); - boolean cutZeros = false; - clientpf = handler.getPreferredPF(); - serverpf = handler.cp.pf(); - int bpp = serverpf.bpp; - cutZeros = false; - if (bpp == 32) { - if (serverpf.is888()) { - cutZeros = true; + int comp_ctl; + + comp_ctl = is.readU8(); + os.writeU8(comp_ctl); + + comp_ctl >>= 4; + + // "Fill" compression type. + if (comp_ctl == tightFill) { + if (cp.pf().is888()) + os.copyBytes(is, 3); + else + os.copyBytes(is, cp.pf().bpp/8); + return; + } + + // "JPEG" compression type. + if (comp_ctl == tightJpeg) { + int len; + + len = readCompact(is); + os.writeOpaque32(len); + os.copyBytes(is, len); + return; + } + + // Quit on unsupported compression type. + if (comp_ctl > tightMaxSubencoding) + throw new Exception("TightDecoder: bad subencoding value received"); + + // "Basic" compression type. + + int palSize = 0; + + if (r.width() > TIGHT_MAX_WIDTH) + throw new Exception("TightDecoder: too large rectangle ("+r.width()+" pixels)"); + + // Possible palette + if ((comp_ctl & tightExplicitFilter) != 0) { + int filterId; + + filterId = is.readU8() & 0xff; + os.writeU8(filterId); + + switch (filterId) { + case tightFilterPalette: + palSize = is.readU8() + 1; + os.writeU32(palSize - 1); + + if (cp.pf().is888()) + os.copyBytes(is, palSize * 3); + else + os.copyBytes(is, palSize * cp.pf().bpp/8); + break; + case tightFilterGradient: + if (cp.pf().bpp == 8) + throw new Exception("TightDecoder: invalid BPP for gradient filter"); + break; + case tightFilterCopy: + break; + default: + throw new Exception("TightDecoder: unknown filter code received"); } } - int comp_ctl = is.readU8(); + int rowSize, dataSize; + + if (palSize != 0) { + if (palSize <= 2) + rowSize = (r.width() + 7) / 8; + else + rowSize = r.width(); + } else if (cp.pf().is888()) { + rowSize = r.width() * 3; + } else { + rowSize = r.width() * cp.pf().bpp/8; + } - boolean bigEndian = handler.cp.pf().bigEndian; + dataSize = r.height() * rowSize; - // Flush zlib streams if we are told by the server to do so. + if (dataSize < TIGHT_MIN_TO_COMPRESS) { + os.copyBytes(is, dataSize); + } else { + int len; + + len = readCompact(is); + os.writeOpaque32(len); + os.copyBytes(is, len); + } + } + + public boolean doRectsConflict(Rect rectA, + Object bufferA, + int buflenA, + Rect rectB, + Object bufferB, + int buflenB, + ConnParams cp) + { + byte comp_ctl_a, comp_ctl_b; + + assert(buflenA >= 1); + assert(buflenB >= 1); + + comp_ctl_a = ((byte[])bufferA)[0]; + comp_ctl_b = ((byte[])bufferB)[0]; + + // Resets or use of zlib pose the same problem, so merge them + if ((comp_ctl_a & 0x80) == 0x00) + comp_ctl_a |= 1 << ((comp_ctl_a >> 4) & 0x03); + if ((comp_ctl_b & 0x80) == 0x00) + comp_ctl_b |= 1 << ((comp_ctl_b >> 4) & 0x03); + + if (((comp_ctl_a & 0x0f) & (comp_ctl_b & 0x0f)) != 0) + return true; + + return false; + } + + public void decodeRect(Rect r, Object buffer, + int buflen, ConnParams cp, + ModifiablePixelBuffer pb) + { + ByteBuffer bufptr; + PixelFormat pf = cp.pf(); + + int comp_ctl; + + bufptr = ByteBuffer.wrap((byte[])buffer); + + assert(buflen >= 1); + + comp_ctl = bufptr.get() & 0xff; + buflen -= 1; + + // Reset zlib streams if we are told by the server to do so. for (int i = 0; i < 4; i++) { if ((comp_ctl & 1) != 0) { zis[i].reset(); @@ -80,190 +205,216 @@ public class TightDecoder extends Decoder { } // "Fill" compression type. - if (comp_ctl == rfbTightFill) { - int[] pix = new int[1]; - if (cutZeros) { - byte[] bytebuf = new byte[3]; - is.readBytes(bytebuf, 0, 3); - serverpf.bufferFromRGB(pix, 0, bytebuf, 0, 1); + if (comp_ctl == tightFill) { + if (pf.is888()) { + ByteBuffer pix = ByteBuffer.allocate(4); + + assert(buflen >= 3); + + pf.bufferFromRGB(pix, bufptr, 1); + pb.fillRect(pf, r, pix.array()); } else { - pix[0] = is.readPixel(serverpf.bpp/8, serverpf.bigEndian); + assert(buflen >= pf.bpp/8); + byte[] pix = new byte[pf.bpp/8]; + bufptr.get(pix); + pb.fillRect(pf, r, pix); } - handler.fillRect(r, pix[0]); return; } // "JPEG" compression type. - if (comp_ctl == rfbTightJpeg) { - DECOMPRESS_JPEG_RECT(r, is, handler); + if (comp_ctl == tightJpeg) { + int len; + + WritableRaster buf; + + JpegDecompressor jd = new JpegDecompressor(); + + assert(buflen >= 4); + + len = bufptr.getInt(); + buflen -= 4; + + // We always use direct decoding with JPEG images + buf = pb.getBufferRW(r); + jd.decompress(bufptr, len, buf, r, pb.getPF()); + pb.commitBufferRW(r); return; } // Quit on unsupported compression type. - if (comp_ctl > rfbTightMaxSubencoding) { + if (comp_ctl > tightMaxSubencoding) throw new Exception("TightDecoder: bad subencoding value received"); - } // "Basic" compression type. int palSize = 0; - int[] palette = new int[256]; + ByteBuffer palette = ByteBuffer.allocate(256 * 4); boolean useGradient = false; - if ((comp_ctl & rfbTightExplicitFilter) != 0) { - int filterId = is.readU8(); + if ((comp_ctl & tightExplicitFilter) != 0) { + int filterId; + + assert(buflen >= 1); + + filterId = bufptr.get(); switch (filterId) { - case rfbTightFilterPalette: - palSize = is.readU8() + 1; - byte[] tightPalette; - if (cutZeros) { - tightPalette = new byte[256 * 3]; - is.readBytes(tightPalette, 0, palSize * 3); - serverpf.bufferFromRGB(palette, 0, tightPalette, 0, palSize); + case tightFilterPalette: + assert(buflen >= 1); + + palSize = bufptr.getInt() + 1; + buflen -= 4; + + if (pf.is888()) { + ByteBuffer tightPalette = ByteBuffer.allocate(palSize * 3); + + assert(buflen >= tightPalette.capacity()); + + bufptr.get(tightPalette.array(), 0, tightPalette.capacity()); + buflen -= tightPalette.capacity(); + + pf.bufferFromRGB(palette.duplicate(), tightPalette, palSize); } else { - is.readPixels(palette, palSize, serverpf.bpp/8, serverpf.bigEndian); + int len; + + len = palSize * pf.bpp/8; + + assert(buflen >= len); + + bufptr.get(palette.array(), 0, len); + buflen -= len; } break; - case rfbTightFilterGradient: + case tightFilterGradient: useGradient = true; break; - case rfbTightFilterCopy: + case tightFilterCopy: break; default: - throw new Exception("TightDecoder: unknown filter code recieved"); + assert(false); } } - int bppp = bpp; + // Determine if the data should be decompressed or just copied. + int rowSize, dataSize; + byte[] netbuf; + if (palSize != 0) { - bppp = (palSize <= 2) ? 1 : 8; - } else if (cutZeros) { - bppp = 24; + if (palSize <= 2) + rowSize = (r.width() + 7) / 8; + else + rowSize = r.width(); + } else if (pf.is888()) { + rowSize = r.width() * 3; + } else { + rowSize = r.width() * pf.bpp/8; } - // Determine if the data should be decompressed or just copied. - int rowSize = (r.width() * bppp + 7) / 8; - int dataSize = r.height() * rowSize; - int streamId = -1; - InStream input; - if (dataSize < rfbTightMinToCompress) { - input = is; + dataSize = r.height() * rowSize; + + if (dataSize < TIGHT_MIN_TO_COMPRESS) { + assert(buflen >= dataSize); } else { - int length = is.readCompactLength(); + int len; + int streamId; + MemInStream ms; + + assert(buflen >= 4); + + len = bufptr.getInt(); + buflen -= 4; + + assert(buflen >= len); + streamId = comp_ctl & 0x03; - zis[streamId].setUnderlying(is, length); - input = (ZlibInStream)zis[streamId]; - } + ms = new MemInStream(bufptr.array(), bufptr.position(), len); + zis[streamId].setUnderlying(ms, len); + + // Allocate netbuf and read in data + netbuf = new byte[dataSize]; - // Allocate netbuf and read in data - byte[] netbuf = new byte[dataSize]; - input.readBytes(netbuf, 0, dataSize); + zis[streamId].readBytes(netbuf, 0, dataSize); + zis[streamId].removeUnderlying(); + ms = null; + + bufptr = ByteBuffer.wrap(netbuf); + buflen = dataSize; + } + + ByteBuffer outbuf = ByteBuffer.allocate(r.area() * pf.bpp/8); int stride = r.width(); - int[] buf = reader.getImageBuf(r.area()); if (palSize == 0) { // Truecolor data. if (useGradient) { - if (bpp == 32 && cutZeros) { - FilterGradient24(netbuf, buf, stride, r); + if (pf.is888()) { + FilterGradient24(bufptr, pf, outbuf, stride, r); } else { - FilterGradient(netbuf, buf, stride, r); + switch (pf.bpp) { + case 8: + assert(false); + break; + case 16: + FilterGradient(bufptr, pf, outbuf, stride, r); + break; + case 32: + FilterGradient(bufptr, pf, outbuf, stride, r); + break; + } } } else { // Copy - int h = r.height(); - int ptr = 0; - int srcPtr = 0; + ByteBuffer ptr = (ByteBuffer)outbuf.duplicate().mark(); + ByteBuffer srcPtr = bufptr.duplicate(); int w = r.width(); - if (cutZeros) { - serverpf.bufferFromRGB(buf, ptr, netbuf, srcPtr, w*h); + int h = r.height(); + if (pf.is888()) { + while (h > 0) { + pf.bufferFromRGB(ptr.duplicate(), srcPtr.duplicate(), w); + ptr.position(ptr.position() + stride * pf.bpp/8); + srcPtr.position(srcPtr.position() + w * 3); + h--; + } } else { - int pixelSize = (bpp >= 24) ? 3 : bpp/8; while (h > 0) { - for (int i = 0; i < w; i++) { - if (bpp == 8) { - buf[ptr+i] = netbuf[srcPtr+i] & 0xff; - } else { - for (int j = pixelSize-1; j >= 0; j--) - buf[ptr+i] |= ((netbuf[srcPtr+i+j] & 0xff) << j*8); - } - } - ptr += stride; - srcPtr += w * pixelSize; + ptr.put(srcPtr.array(), srcPtr.position(), w * pf.bpp/8); + ptr.reset().position(ptr.position() + stride * pf.bpp/8).mark(); + srcPtr.position(srcPtr.position() + w * pf.bpp/8); h--; } } } } else { // Indexed color - int x, h = r.height(), w = r.width(), b, pad = stride - w; - int ptr = 0; - int srcPtr = 0, bits; - if (palSize <= 2) { - // 2-color palette - while (h > 0) { - for (x = 0; x < w / 8; x++) { - bits = netbuf[srcPtr++]; - for(b = 7; b >= 0; b--) { - buf[ptr++] = palette[bits >> b & 1]; - } - } - if (w % 8 != 0) { - bits = netbuf[srcPtr++]; - for (b = 7; b >= 8 - w % 8; b--) { - buf[ptr++] = palette[bits >> b & 1]; - } - } - ptr += pad; - h--; - } - } else { - // 256-color palette - while (h > 0) { - int endOfRow = ptr + w; - while (ptr < endOfRow) { - buf[ptr++] = palette[netbuf[srcPtr++] & 0xff]; - } - ptr += pad; - h--; - } + switch (pf.bpp) { + case 8: + FilterPalette8(palette, palSize, + bufptr, outbuf, stride, r); + break; + case 16: + FilterPalette16(palette.asShortBuffer(), palSize, + bufptr, outbuf.asShortBuffer(), stride, r); + break; + case 32: + FilterPalette32(palette.asIntBuffer(), palSize, + bufptr, outbuf.asIntBuffer(), stride, r); + break; } } - handler.imageRect(r, buf); - - if (streamId != -1) { - zis[streamId].reset(); - } - } + pb.imageRect(pf, r, outbuf.array()); - final private void DECOMPRESS_JPEG_RECT(Rect r, InStream is, CMsgHandler handler) - { - // Read length - int compressedLen = is.readCompactLength(); - if (compressedLen <= 0) - vlog.info("Incorrect data received from the server."); - - // Allocate netbuf and read in data - byte[] netbuf = new byte[compressedLen]; - is.readBytes(netbuf, 0, compressedLen); - - // Create an Image object from the JPEG data. - Image jpeg = tk.createImage(netbuf); - jpeg.setAccelerationPriority(1); - handler.imageRect(r, jpeg); - jpeg.flush(); } - final private void FilterGradient24(byte[] netbuf, int[] buf, int stride, - Rect r) + final private void FilterGradient24(ByteBuffer inbuf, + PixelFormat pf, ByteBuffer outbuf, + int stride, Rect r) { - int x, y, c; byte[] prevRow = new byte[TIGHT_MAX_WIDTH*3]; byte[] thisRow = new byte[TIGHT_MAX_WIDTH*3]; - byte[] pix = new byte[3]; + ByteBuffer pix = ByteBuffer.allocate(3); int[] est = new int[3]; // Set up shortcut variables @@ -273,38 +424,38 @@ public class TightDecoder extends Decoder { for (y = 0; y < rectHeight; y++) { /* First pixel in a row */ for (c = 0; c < 3; c++) { - pix[c] = (byte)(netbuf[y*rectWidth*3+c] + prevRow[c]); - thisRow[c] = pix[c]; + pix.put(c, (byte)(inbuf.get(y*rectWidth*3+c) + prevRow[c])); + thisRow[c] = pix.get(c); } - serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1); + pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1); /* Remaining pixels of a row */ for (x = 1; x < rectWidth; x++) { for (c = 0; c < 3; c++) { - est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]); - if (est[c] > 0xFF) { - est[c] = 0xFF; + est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c]; + if (est[c] > 0xff) { + est[c] = 0xff; } else if (est[c] < 0) { est[c] = 0; } - pix[c] = (byte)(netbuf[(y*rectWidth+x)*3+c] + est[c]); - thisRow[x*3+c] = pix[c]; + pix.put(c, (byte)(inbuf.get((y*rectWidth+x)*3+c) + est[c])); + thisRow[x*3+c] = pix.get(c); } - serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1); + pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1); } System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length); } } - final private void FilterGradient(byte[] netbuf, int[] buf, int stride, - Rect r) + final private void FilterGradient(ByteBuffer inbuf, + PixelFormat pf, ByteBuffer outbuf, + int stride, Rect r) { - int x, y, c; byte[] prevRow = new byte[TIGHT_MAX_WIDTH]; byte[] thisRow = new byte[TIGHT_MAX_WIDTH]; - byte[] pix = new byte[3]; + ByteBuffer pix = ByteBuffer.allocate(3); int[] est = new int[3]; // Set up shortcut variables @@ -313,19 +464,18 @@ public class TightDecoder extends Decoder { for (y = 0; y < rectHeight; y++) { /* First pixel in a row */ - // FIXME - //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth, 1, cm); + pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth), 1); for (c = 0; c < 3; c++) - pix[c] += prevRow[c]; + pix.put(c, (byte)(pix.get(c) + prevRow[c])); - System.arraycopy(pix, 0, thisRow, 0, pix.length); + System.arraycopy(pix.array(), 0, thisRow, 0, pix.capacity()); - serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1); + pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1); /* Remaining pixels of a row */ for (x = 1; x < rectWidth; x++) { for (c = 0; c < 3; c++) { - est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]); + est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c]; if (est[c] > 0xff) { est[c] = 0xff; } else if (est[c] < 0) { @@ -333,24 +483,156 @@ public class TightDecoder extends Decoder { } } - // FIXME - //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth+x, 1, cm); + pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth+x), 1); for (c = 0; c < 3; c++) - pix[c] += est[c]; + pix.put(c, (byte)(pix.get(c) + est[c])); - System.arraycopy(pix, 0, thisRow, x*3, pix.length); + System.arraycopy(pix.array(), 0, thisRow, x*3, pix.capacity()); - serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1); + pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1); } System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length); } } - private CMsgReader reader; + private void FilterPalette8(ByteBuffer palette, int palSize, + ByteBuffer inbuf, ByteBuffer outbuf, + int stride, Rect r) + { + // Indexed color + int x, h = r.height(), w = r.width(), b, pad = stride - w; + ByteBuffer ptr = outbuf.duplicate(); + byte bits; + ByteBuffer srcPtr = inbuf.duplicate(); + if (palSize <= 2) { + // 2-color palette + while (h > 0) { + for (x = 0; x < w / 8; x++) { + bits = srcPtr.get(); + for (b = 7; b >= 0; b--) { + ptr.put(palette.get(bits >> b & 1)); + } + } + if (w % 8 != 0) { + bits = srcPtr.get(); + for (b = 7; b >= 8 - w % 8; b--) { + ptr.put(palette.get(bits >> b & 1)); + } + } + ptr.position(ptr.position() + pad); + h--; + } + } else { + // 256-color palette + while (h > 0) { + int endOfRow = ptr.position() + w; + while (ptr.position() < endOfRow) { + ptr.put(palette.get(srcPtr.get())); + } + ptr.position(ptr.position() + pad); + h--; + } + } + } + + private void FilterPalette16(ShortBuffer palette, int palSize, + ByteBuffer inbuf, ShortBuffer outbuf, + int stride, Rect r) + { + // Indexed color + int x, h = r.height(), w = r.width(), b, pad = stride - w; + ShortBuffer ptr = outbuf.duplicate(); + byte bits; + ByteBuffer srcPtr = inbuf.duplicate(); + if (palSize <= 2) { + // 2-color palette + while (h > 0) { + for (x = 0; x < w / 8; x++) { + bits = srcPtr.get(); + for (b = 7; b >= 0; b--) { + ptr.put(palette.get(bits >> b & 1)); + } + } + if (w % 8 != 0) { + bits = srcPtr.get(); + for (b = 7; b >= 8 - w % 8; b--) { + ptr.put(palette.get(bits >> b & 1)); + } + } + ptr.position(ptr.position() + pad); + h--; + } + } else { + // 256-color palette + while (h > 0) { + int endOfRow = ptr.position() + w; + while (ptr.position() < endOfRow) { + ptr.put(palette.get(srcPtr.get())); + } + ptr.position(ptr.position() + pad); + h--; + } + } + } + + private void FilterPalette32(IntBuffer palette, int palSize, + ByteBuffer inbuf, IntBuffer outbuf, + int stride, Rect r) + { + // Indexed color + int x, h = r.height(), w = r.width(), b, pad = stride - w; + IntBuffer ptr = outbuf.duplicate(); + byte bits; + ByteBuffer srcPtr = inbuf.duplicate(); + if (palSize <= 2) { + // 2-color palette + while (h > 0) { + for (x = 0; x < w / 8; x++) { + bits = srcPtr.get(); + for (b = 7; b >= 0; b--) { + ptr.put(palette.get(bits >> b & 1)); + } + } + if (w % 8 != 0) { + bits = srcPtr.get(); + for (b = 7; b >= 8 - w % 8; b--) { + ptr.put(palette.get(bits >> b & 1)); + } + } + ptr.position(ptr.position() + pad); + h--; + } + } else { + // 256-color palette + while (h > 0) { + int endOfRow = ptr.position() + w; + while (ptr.position() < endOfRow) { + ptr.put(palette.get(srcPtr.get() & 0xff)); + } + ptr.position(ptr.position() + pad); + h--; + } + } + } + + public final int readCompact(InStream is) { + byte b; + int result; + + b = (byte)is.readU8(); + result = (int)b & 0x7F; + if ((b & 0x80) != 0) { + b = (byte)is.readU8(); + result |= ((int)b & 0x7F) << 7; + if ((b & 0x80) != 0) { + b = (byte)is.readU8(); + result |= ((int)b & 0xFF) << 14; + } + } + return result; + } + private ZlibInStream[] zis; - private PixelFormat serverpf; - private PixelFormat clientpf; - static LogWriter vlog = new LogWriter("TightDecoder"); } diff --git a/java/com/tigervnc/rfb/ZRLEDecoder.java b/java/com/tigervnc/rfb/ZRLEDecoder.java index e706510f..c1f908ab 100644 --- a/java/com/tigervnc/rfb/ZRLEDecoder.java +++ b/java/com/tigervnc/rfb/ZRLEDecoder.java @@ -18,22 +18,143 @@ package com.tigervnc.rfb; +import java.awt.image.*; +import java.nio.*; +import java.util.*; + import com.tigervnc.rdr.*; public class ZRLEDecoder extends Decoder { - public ZRLEDecoder(CMsgReader reader_) { - reader = reader_; + private static int readOpaque24A(InStream is) + { + is.check(3); + ByteBuffer r = ByteBuffer.allocate(4); + r.put(0, (byte)is.readU8()); + r.put(1, (byte)is.readU8()); + r.put(2, (byte)is.readU8()); + return ((ByteBuffer)r.rewind()).getInt(); + } + + private static int readOpaque24B(InStream is) + { + is.check(3); + ByteBuffer r = ByteBuffer.allocate(4); + r.put(2, (byte)is.readU8()); + r.put(1, (byte)is.readU8()); + r.put(0, (byte)is.readU8()); + return ((ByteBuffer)r.rewind()).getInt(); + } + + public ZRLEDecoder() { + super(DecoderFlags.DecoderOrdered); zis = new ZlibInStream(); } - public void readRect(Rect r, CMsgHandler handler) { - InStream is = reader.getInStream(); - int[] buf = reader.getImageBuf(64 * 64 * 4); - int bpp = handler.cp.pf().bpp; - int bytesPerPixel = (bpp > 24 ? 3 : bpp / 8); - boolean bigEndian = handler.cp.pf().bigEndian; + public void readRect(Rect r, InStream is, + ConnParams cp, OutStream os) + { + int len; + len = is.readU32(); + os.writeU32(len); + os.copyBytes(is, len); + } + + public void decodeRect(Rect r, Object buffer, + int buflen, ConnParams cp, + ModifiablePixelBuffer pb) + { + MemInStream is = new MemInStream((byte[])buffer, 0, buflen); + PixelFormat pf = cp.pf(); + ByteBuffer buf = ByteBuffer.allocate(64 * 64 * 4); + switch (pf.bpp) { + case 8: zrleDecode8(r, is, zis, buf, pf, pb); break; + case 16: zrleDecode16(r, is, zis, buf, pf, pb); break; + case 32: + int maxPixel = pf.pixelFromRGB(-1, -1, -1, pf.getColorModel()); + boolean fitsInLS3Bytes = maxPixel < (1<<24); + boolean fitsInMS3Bytes = (maxPixel & 0xff) == 0; + + if ((fitsInLS3Bytes && pf.isLittleEndian()) || + (fitsInMS3Bytes && pf.isBigEndian())) + { + zrleDecode24A(r, is, zis, buf, pf, pb); + } + else if ((fitsInLS3Bytes && pf.isBigEndian()) || + (fitsInMS3Bytes && pf.isLittleEndian())) + { + zrleDecode24B(r, is, zis, buf, pf, pb); + } + else + { + zrleDecode32(r, is, zis, buf, pf, pb); + } + break; + } + } + + private static enum PIXEL_T { U8, U16, U24A, U24B, U32 }; + + private static ByteBuffer READ_PIXEL(InStream is, PIXEL_T type) { + ByteBuffer b = ByteBuffer.allocate(4); + switch (type) { + case U8: + b.putInt(is.readOpaque8()); + return (ByteBuffer)ByteBuffer.allocate(1).put(b.get(3)).rewind(); + case U16: + b.putInt(is.readOpaque16()); + return (ByteBuffer)ByteBuffer.allocate(2).put(b.array(), 2, 2).rewind(); + case U24A: + return (ByteBuffer)b.putInt(readOpaque24A(is)).rewind(); + case U24B: + return (ByteBuffer)b.putInt(readOpaque24B(is)).rewind(); + case U32: + default: + return (ByteBuffer)b.putInt(is.readOpaque32()).rewind(); + } + } + + private void zrleDecode8(Rect r, InStream is, + ZlibInStream zis, ByteBuffer buf, + PixelFormat pf, ModifiablePixelBuffer pb) + { + ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U8); + } + + private void zrleDecode16(Rect r, InStream is, + ZlibInStream zis, ByteBuffer buf, + PixelFormat pf, ModifiablePixelBuffer pb) + { + ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U16); + } + + private void zrleDecode24A(Rect r, InStream is, + ZlibInStream zis, ByteBuffer buf, + PixelFormat pf, ModifiablePixelBuffer pb) + { + ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U24A); + } + + private void zrleDecode24B(Rect r, InStream is, + ZlibInStream zis, ByteBuffer buf, + PixelFormat pf, ModifiablePixelBuffer pb) + { + ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U24B); + } + + private void zrleDecode32(Rect r, InStream is, + ZlibInStream zis, ByteBuffer buf, + PixelFormat pf, ModifiablePixelBuffer pb) + { + ZRLE_DECODE(r, is, zis, buf, pf, pb, PIXEL_T.U32); + } + + private void ZRLE_DECODE(Rect r, InStream is, + ZlibInStream zis, ByteBuffer buf, + PixelFormat pf, ModifiablePixelBuffer pb, + PIXEL_T pix_t) + { int length = is.readU32(); zis.setUnderlying(is, length); Rect t = new Rect(); @@ -49,13 +170,16 @@ public class ZRLEDecoder extends Decoder { int mode = zis.readU8(); boolean rle = (mode & 128) != 0; int palSize = mode & 127; - int[] palette = new int[128]; + ByteBuffer palette = ByteBuffer.allocate(128 * pf.bpp/8); - zis.readPixels(palette, palSize, bytesPerPixel, bigEndian); + for (int i = 0; i < palSize; i++) { + palette.put(READ_PIXEL(zis, pix_t)); + } if (palSize == 1) { - int pix = palette[0]; - handler.fillRect(t, pix); + ByteBuffer pix = + ByteBuffer.allocate(pf.bpp/8).put(palette.array(), 0, pf.bpp/8); + pb.fillRect(pf, t, pix.array()); continue; } @@ -63,8 +187,17 @@ public class ZRLEDecoder extends Decoder { if (palSize == 0) { // raw - - zis.readPixels(buf, t.area(), bytesPerPixel, bigEndian); + switch (pix_t) { + case U24A: + case U24B: + ByteBuffer ptr = buf.duplicate(); + for (int iptr=0; iptr < t.area(); iptr++) { + ptr.put(READ_PIXEL(zis, pix_t)); + } + break; + default: + zis.readBytes(buf, t.area() * (pf.bpp/8)); + } } else { @@ -72,21 +205,21 @@ public class ZRLEDecoder extends Decoder { int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1))); - int ptr = 0; + ByteBuffer ptr = buf.duplicate(); for (int i = 0; i < t.height(); i++) { - int eol = ptr + t.width(); + int eol = ptr.position() + t.width()*pf.bpp/8; int b = 0; int nbits = 0; - while (ptr < eol) { + while (ptr.position() < eol) { if (nbits == 0) { b = zis.readU8(); nbits = 8; } nbits -= bppp; int index = (b >> nbits) & ((1 << bppp) - 1) & 127; - buf[ptr++] = palette[index]; + ptr.put(palette.array(), index*pf.bpp/8, pf.bpp/8); } } } @@ -97,10 +230,10 @@ public class ZRLEDecoder extends Decoder { // plain RLE - int ptr = 0; - int end = ptr + t.area(); - while (ptr < end) { - int pix = zis.readPixel(bytesPerPixel, bigEndian); + ByteBuffer ptr = buf.duplicate(); + int end = ptr.position() + t.area()*pf.bpp/8; + while (ptr.position() < end) { + ByteBuffer pix = READ_PIXEL(zis, pix_t); int len = 1; int b; do { @@ -108,19 +241,21 @@ public class ZRLEDecoder extends Decoder { len += b; } while (b == 255); - if (!(len <= end - ptr)) - throw new Exception("ZRLEDecoder: assertion (len <= end - ptr)" - +" failed"); + if (end - ptr.position() < len*(pf.bpp/8)) { + System.err.println("ZRLE decode error\n"); + throw new Exception("ZRLE decode error"); + } + + while (len-- > 0) ptr.put(pix); - while (len-- > 0) buf[ptr++] = pix; } } else { // palette RLE - int ptr = 0; - int end = ptr + t.area(); - while (ptr < end) { + ByteBuffer ptr = buf.duplicate(); + int end = ptr.position() + t.area()*pf.bpp/8; + while (ptr.position() < end) { int index = zis.readU8(); int len = 1; if ((index & 128) != 0) { @@ -130,27 +265,26 @@ public class ZRLEDecoder extends Decoder { len += b; } while (b == 255); - if (!(len <= end - ptr)) - throw new Exception("ZRLEDecoder: assertion " - +"(len <= end - ptr) failed"); + if (end - ptr.position() < len*(pf.bpp/8)) { + System.err.println("ZRLE decode error\n"); + throw new Exception("ZRLE decode error"); + } } index &= 127; - int pix = palette[index]; + while (len-- > 0) ptr.put(palette.array(), index*pf.bpp/8, pf.bpp/8); - while (len-- > 0) buf[ptr++] = pix; } } } - handler.imageRect(t, buf); + pb.imageRect(pf, t, buf.array()); } } - zis.reset(); + zis.removeUnderlying(); } - CMsgReader reader; - ZlibInStream zis; + private ZlibInStream zis; } diff --git a/java/com/tigervnc/vncviewer/BIPixelBuffer.java b/java/com/tigervnc/vncviewer/BIPixelBuffer.java deleted file mode 100644 index 9612b36f..00000000 --- a/java/com/tigervnc/vncviewer/BIPixelBuffer.java +++ /dev/null @@ -1,141 +0,0 @@ -/* Copyright (C) 2012 Brian P. Hinz - * Copyright (C) 2012 D. R. Commander. All Rights Reserved. - * - * This is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this software; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - */ - -package com.tigervnc.vncviewer; - -import java.awt.*; -import java.awt.image.*; - -import com.tigervnc.rfb.*; -import com.tigervnc.rfb.Exception; - -public class BIPixelBuffer extends PlatformPixelBuffer implements ImageObserver -{ - public BIPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) { - super(w, h, cc_, 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"); -} diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java index b9680ef7..8a2303b0 100644 --- a/java/com/tigervnc/vncviewer/CConn.java +++ b/java/com/tigervnc/vncviewer/CConn.java @@ -35,7 +35,9 @@ package com.tigervnc.vncviewer; import java.awt.*; +import java.awt.datatransfer.StringSelection; import java.awt.event.*; +import java.awt.Toolkit; import java.io.IOException; import java.io.InputStream; @@ -49,6 +51,7 @@ import javax.swing.ImageIcon; import java.net.InetSocketAddress; import java.net.SocketException; import java.util.*; +import java.util.prefs.*; import com.tigervnc.rdr.*; import com.tigervnc.rfb.*; @@ -57,17 +60,23 @@ import com.tigervnc.rfb.Exception; import com.tigervnc.network.Socket; import com.tigervnc.network.TcpSocket; +import static com.tigervnc.vncviewer.Parameters.*; + public class CConn extends CConnection implements - UserPasswdGetter, UserMsgBox, OptionsDialogCallback, - FdInStreamBlockCallback, ActionListener { + UserPasswdGetter, FdInStreamBlockCallback, ActionListener { - public final PixelFormat getPreferredPF() { return fullColourPF; } - static final PixelFormat verylowColourPF = + // 8 colours (1 bit per component) + static final PixelFormat verylowColorPF = new PixelFormat(8, 3, false, true, 1, 1, 1, 2, 1, 0); - static final PixelFormat lowColourPF = + + // 64 colours (2 bits per component) + static final PixelFormat lowColorPF = new PixelFormat(8, 6, false, true, 3, 3, 3, 4, 2, 0); - static final PixelFormat mediumColourPF = - new PixelFormat(8, 8, false, false, 7, 7, 3, 0, 3, 6); + + // 256 colours (2-3 bits per component) + static final PixelFormat mediumColorPF = + 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; @@ -75,67 +84,49 @@ public class CConn extends CConnection implements //////////////////////////////////////////////////////////////////// // The following methods are all called from the RFB thread - public CConn(VncViewer viewer_, Socket sock_, - String vncServerName) + public CConn(String vncServerName, Socket socket) { - sock = sock_; viewer = viewer_; + serverHost = null; serverPort = 0; desktop = null; pendingPFChange = false; currentEncoding = Encodings.encodingTight; lastServerEncoding = -1; - fullColour = viewer.fullColour.getValue(); - lowColourLevel = viewer.lowColourLevel.getValue(); - autoSelect = viewer.autoSelect.getValue(); formatChange = false; encodingChange = false; - fullScreen = viewer.fullScreen.getValue(); - menuKeyCode = MenuKey.getMenuKeyCode(); - options = new OptionsDialog(this); - options.initDialog(); - clipboardDialog = new ClipboardDialog(this); firstUpdate = true; pendingUpdate = false; continuousUpdates = false; forceNonincremental = true; supportsSyncFence = false; + + setShared(shared.getValue()); + sock = socket; downKeySym = new HashMap<Integer, Integer>(); - setShared(viewer.shared.getValue()); upg = this; - msg = this; - String encStr = viewer.preferredEncoding.getValue(); - int encNum = Encodings.encodingNum(encStr); - if (encNum != -1) { + int encNum = Encodings.encodingNum(preferredEncoding.getValue()); + if (encNum != -1) currentEncoding = encNum; - } + + cp.supportsLocalCursor = true; + cp.supportsDesktopResize = true; cp.supportsExtendedDesktopSize = true; + cp.supportsDesktopRename = true; + cp.supportsSetDesktopSize = false; cp.supportsClientRedirect = true; - cp.supportsDesktopRename = true; - cp.supportsLocalCursor = viewer.useLocalCursor.getValue(); - cp.customCompressLevel = viewer.customCompressLevel.getValue(); - cp.compressLevel = viewer.compressLevel.getValue(); - cp.noJpeg = viewer.noJpeg.getValue(); - cp.qualityLevel = viewer.qualityLevel.getValue(); - initMenu(); - - if (sock != null) { - String name = sock.getPeerEndpoint(); - vlog.info("Accepted connection from " + name); - } else { - if (vncServerName != null && - !viewer.alwaysShowServerDialog.getValue()) { - setServerName(Hostname.getHost(vncServerName)); - setServerPort(Hostname.getPort(vncServerName)); - } else { - ServerDialog dlg = new ServerDialog(options, vncServerName, this); - boolean ret = dlg.showDialog(); - if (!ret) { - close(); - return; - } - setServerName(viewer.vncServerName.getValueStr()); - setServerPort(viewer.vncServerPort.getValue()); - } + if (customCompressLevel.getValue()) + cp.compressLevel = compressLevel.getValue(); + else + cp.compressLevel = -1; + + if (!noJpeg.getValue()) + cp.qualityLevel = qualityLevel.getValue(); + else + cp.qualityLevel = -1; + + if (sock == null) { + setServerName(Hostname.getHost(vncServerName)); + setServerPort(Hostname.getPort(vncServerName)); try { - if (viewer.tunnel.getValue() || (viewer.via.getValue() != null)) { + if (tunnel.getValue() || !via.getValue().isEmpty()) { int localPort = TcpSocket.findFreeTcpPort(); if (localPort == 0) throw new Exception("Could not obtain free TCP port"); @@ -148,11 +139,22 @@ public class CConn extends CConnection implements throw new Exception(e.getMessage()); } vlog.info("connected to host "+getServerName()+" port "+getServerPort()); + } else { + String name = sock.getPeerEndpoint(); + if (listenMode.getValue()) + vlog.info("Accepted connection from " + name); + else + vlog.info("connected to host "+Hostname.getHost(name)+" port "+Hostname.getPort(name)); } + // See callback below sock.inStream().setBlockCallback(this); + setStreams(sock.inStream(), sock.outStream()); + initialiseProtocol(); + + OptionsDialog.addCallback("handleOptions", this); } public void refreshFramebuffer() @@ -165,21 +167,37 @@ public class CConn extends CConnection implements requestNewUpdate(); } - public boolean showMsgBox(int flags, String title, String text) - { - //StringBuffer titleText = new StringBuffer("VNC Viewer: "+title); - return true; - } - - // deleteWindow() is called when the user closes the desktop or menu windows. + public String connectionInfo() { + String info = new String("Desktop name: %s%n"+ + "Host: %s:%d%n"+ + "Size: %dx%d%n"+ + "Pixel format: %s%n"+ + " (server default: %s)%n"+ + "Requested encoding: %s%n"+ + "Last used encoding: %s%n"+ + "Line speed estimate: %d kbit/s%n"+ + "Protocol version: %d.%d%n"+ + "Security method: %s [%s]%n"); + String infoText = + String.format(info, cp.name(), + sock.getPeerName(), sock.getPeerPort(), + cp.width, cp.height, + cp.pf().print(), + serverPF.print(), + Encodings.encodingName(currentEncoding), + Encodings.encodingName(lastServerEncoding), + sock.inStream().kbitsPerSecond(), + cp.majorVersion, cp.minorVersion, + Security.secTypeName(csecurity.getType()), + csecurity.description()); - void deleteWindow() { - if (viewport != null) - viewport.dispose(); - viewport = null; + return infoText; } - // blockCallback() is called when reading from the socket would block. + // The RFB core is not properly asynchronous, so it calls this callback + // whenever it needs to block to wait for more data. Since FLTK is + // monitoring the socket, we just make sure FLTK gets to run. + public void blockCallback() { try { synchronized(this) { @@ -196,7 +214,7 @@ public class CConn extends CConnection implements public final boolean getUserPasswd(StringBuffer user, StringBuffer passwd) { String title = ("VNC Authentication [" +csecurity.description() + "]"); - String passwordFileStr = viewer.passwordFile.getValue(); + String passwordFileStr = passwordFile.getValue(); PasswdDialog dlg; if (user == null && !passwordFileStr.equals("")) { @@ -222,16 +240,16 @@ public class CConn extends CConnection implements if (user == null) { dlg = new PasswdDialog(title, (user == null), (passwd == null)); } else { - if ((passwd == null) && viewer.sendLocalUsername.getValue()) { + if ((passwd == null) && sendLocalUsername.getValue()) { user.append((String)System.getProperties().get("user.name")); return true; } - dlg = new PasswdDialog(title, viewer.sendLocalUsername.getValue(), + dlg = new PasswdDialog(title, sendLocalUsername.getValue(), (passwd == null)); } - if (!dlg.showDialog()) return false; + dlg.showDialog(); if (user != null) { - if (viewer.sendLocalUsername.getValue()) { + if (sendLocalUsername.getValue()) { user.append((String)System.getProperties().get("user.name")); } else { user.append(dlg.userEntry.getText()); @@ -242,23 +260,24 @@ public class CConn extends CConnection implements return true; } - // CConnection callback methods + ////////////////////// CConnection callback methods ////////////////////// // serverInit() is called when the serverInit message has been received. At // this point we create the desktop window and display it. We also tell the // server the pixel format and encodings to use and request the first update. - public void serverInit() { + public void serverInit() + { super.serverInit(); // If using AutoSelect with old servers, start in FullColor // mode. See comment in autoSelectFormatAndEncoding. - if (cp.beforeVersion(3, 8) && autoSelect) - fullColour = true; + if (cp.beforeVersion(3, 8) && autoSelect.getValue()) + fullColor.setParam(true); serverPF = cp.pf(); - desktop = new DesktopWindow(cp.width, cp.height, serverPF, this); - fullColourPF = desktop.getPreferredPF(); + desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this); + fullColorPF = desktop.getPreferredPF(); // Force a switch to the format and encoding we'd like formatChange = true; encodingChange = true; @@ -269,71 +288,22 @@ public class CConn extends CConnection implements // This initial update request is a bit of a corner case, so we need // to help out setting the correct format here. assert(pendingPFChange); - desktop.setServerPF(pendingPF); cp.setPF(pendingPF); pendingPFChange = false; - - if (viewer.embed.getValue()) { - setupEmbeddedFrame(); - } else { - recreateViewport(); - } - } - - void setupEmbeddedFrame() { - UIManager.getDefaults().put("ScrollPane.ancestorInputMap", - new UIDefaults.LazyInputMap(new Object[]{})); - JScrollPane 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); - desktop.setViewport(sp.getViewport()); - viewer.getContentPane().removeAll(); - viewer.add(sp); - viewer.addFocusListener(new FocusAdapter() { - public void focusGained(FocusEvent e) { - if (desktop.isAncestorOf(viewer)) - desktop.requestFocus(); - } - public void focusLost(FocusEvent e) { - releaseDownKeys(); - } - }); - viewer.validate(); - desktop.requestFocus(); } // 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) && @@ -346,26 +316,28 @@ public class CConn extends CConnection implements } // clientRedirect() migrates the client to another host/port - public void clientRedirect(int port, String host, - String x509subject) { + public void clientRedirect(int port, String host, String x509subject) + { try { sock.close(); - setServerPort(port); sock = new TcpSocket(host, port); vlog.info("Redirected to "+host+":"+port); - VncViewer.newViewer(viewer, sock, true); + setServerName(host); + setServerPort(port); + sock.inStream().setBlockCallback(this); + setStreams(sock.inStream(), sock.outStream()); + initialiseProtocol(); } catch (java.lang.Exception e) { throw new Exception(e.getMessage()); } } // 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. @@ -374,10 +346,23 @@ public class CConn extends CConnection implements // one. public void framebufferUpdateStart() { + ModifiablePixelBuffer pb; + PlatformPixelBuffer ppb; + + super.framebufferUpdateStart(); + // Note: This might not be true if sync fences are supported pendingUpdate = false; requestNewUpdate(); + + // We might still be rendering the previous update + pb = getFramebuffer(); + assert(pb != null); + ppb = (PlatformPixelBuffer)pb; + assert(ppb != null); + + //FIXME } // framebufferUpdateEnd() is called at the end of an update. @@ -386,110 +371,69 @@ public class CConn extends CConnection implements // appropriately, and then request another incremental update. public void framebufferUpdateEnd() { + super.framebufferUpdateEnd(); 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 && - viewer.desktopSize.getValue() != null && - viewer.desktopSize.getValue().split("x").length == 2) { - width = Integer.parseInt(viewer.desktopSize.getValue().split("x")[0]); - height = Integer.parseInt(viewer.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; } // Compute new settings based on updated bandwidth values - if (autoSelect) + if (autoSelect.getValue()) autoSelectFormatAndEncoding(); } // The rest of the callbacks are fairly self-explanatory... - public void setColourMapEntries(int firstColour, int nColours, int[] rgbs) { - desktop.setColourMapEntries(firstColour, nColours, rgbs); + public void setColourMapEntries(int firstColor, int nColors, int[] rgbs) + { + vlog.error("Invalid SetColourMapEntries from server!"); } - public void bell() { - if (viewer.acceptBell.getValue()) + public void bell() + { + if (acceptBell.getValue()) desktop.getToolkit().beep(); } - public void serverCutText(String str, int len) { - if (viewer.acceptClipboard.getValue()) - clipboardDialog.serverCutText(str, len); - } + public void serverCutText(String str, int len) + { + StringSelection buffer; - // 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) { - sock.inStream().startTiming(); - if (encoding != Encodings.encodingCopyRect) { - lastServerEncoding = encoding; - } - } + if (!acceptClipboard.getValue()) + return; - public void endRect(Rect r, int encoding) { - sock.inStream().stopTiming(); + ClipboardDialog.serverCutText(str); } - public void fillRect(Rect r, int p) { - desktop.fillRect(r.tl.x, r.tl.y, r.width(), r.height(), p); - } + public void dataRect(Rect r, int encoding) + { + sock.inStream().startTiming(); - public void imageRect(Rect r, Object p) { - desktop.imageRect(r.tl.x, r.tl.y, r.width(), r.height(), p); - } + if (encoding != Encodings.encodingCopyRect) + lastServerEncoding = encoding; - public void copyRect(Rect r, int sx, int sy) { - desktop.copyRect(r.tl.x, r.tl.y, r.width(), r.height(), sx, sy); + super.dataRect(r, encoding); + + 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); } @@ -524,11 +468,11 @@ public class CConn extends CConnection implements pf.read(memStream); - desktop.setServerPF(pf); cp.setPF(pf); } } + ////////////////////// Internal methods ////////////////////// private void resizeFramebuffer() { if (desktop == null) @@ -537,78 +481,7 @@ public class CConn extends CConnection implements if (continuousUpdates) writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height); - if ((cp.width == 0) && (cp.height == 0)) - return; - if ((desktop.width() == cp.width) && (desktop.height() == cp.height)) - return; - - desktop.resize(); - if (viewer.embed.getValue()) { - setupEmbeddedFrame(); - } else { - recreateViewport(); - } - } - - public void setEmbeddedFeatures(boolean s) { - menu.restore.setEnabled(s); - menu.minimize.setEnabled(s); - menu.maximize.setEnabled(s); - menu.fullScreen.setEnabled(s); - menu.newConn.setEnabled(s); - options.fullScreen.setEnabled(s); - options.fullScreenAllMonitors.setEnabled(s); - options.scalingFactor.setEnabled(s); - } - - // 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 (viewer.embed.getValue()) - return; - if (viewport != null) viewport.dispose(); - viewport = new Viewport(cp.name(), this); - viewport.setUndecorated(fullScreen); - 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) { - if (!viewer.fullScreenAllMonitors.getValue()) - viewport.setExtendedState(JFrame.MAXIMIZED_BOTH); - viewport.setBounds(viewport.getScreenBounds()); - if (!viewer.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 @@ -626,11 +499,12 @@ public class CConn extends CConnection implements // Note: The system here is fairly arbitrary and should be replaced // with something more intelligent at the server end. // - private void autoSelectFormatAndEncoding() { + private void autoSelectFormatAndEncoding() + { long kbitsPerSecond = sock.inStream().kbitsPerSecond(); long timeWaited = sock.inStream().timeWaited(); - boolean newFullColour = fullColour; - int newQualityLevel = cp.qualityLevel; + boolean newFullColor = fullColor.getValue(); + int newQualityLevel = qualityLevel.getValue(); // Always use Tight if (currentEncoding != Encodings.encodingTight) { @@ -643,17 +517,17 @@ public class CConn extends CConnection implements return; // Select appropriate quality level - if (!cp.noJpeg) { + if (!noJpeg.getValue()) { if (kbitsPerSecond > 16000) newQualityLevel = 8; else newQualityLevel = 6; - if (newQualityLevel != cp.qualityLevel) { + if (newQualityLevel != qualityLevel.getValue()) { vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - changing to quality "+newQualityLevel); cp.qualityLevel = newQualityLevel; - viewer.qualityLevel.setParam(Integer.toString(newQualityLevel)); + qualityLevel.setParam(newQualityLevel); encodingChange = true; } } @@ -670,14 +544,24 @@ public class CConn extends CConnection implements } // Select best color level - newFullColour = (kbitsPerSecond > 256); - if (newFullColour != fullColour) { + newFullColor = (kbitsPerSecond > 256); + if (newFullColor != fullColor.getValue()) { vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now "+ - (newFullColour ? "enabled" : "disabled")); - fullColour = newFullColour; + (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; } } @@ -691,15 +575,15 @@ public class CConn extends CConnection implements /* Catch incorrect requestNewUpdate calls */ assert(!pendingUpdate || supportsSyncFence); - if (fullColour) { - pf = fullColourPF; + if (fullColor.getValue()) { + pf = fullColorPF; } else { - if (lowColourLevel == 0) { - pf = verylowColourPF; - } else if (lowColourLevel == 1) { - pf = lowColourPF; + if (lowColorLevel.getValue() == 0) { + pf = verylowColorPF; + } else if (lowColorLevel.getValue() == 1) { + pf = lowColorPF; } else { - pf = mediumColourPF; + pf = mediumColorPF; } } @@ -739,6 +623,60 @@ public class CConn extends CConnection implements forceNonincremental = false; } + public void handleOptions() + { + + // 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 + // though. + if (!autoSelect.getValue()) { + int encNum = Encodings.encodingNum(preferredEncoding.getValue()); + + if (encNum != -1) + this.currentEncoding = encNum; + } + + this.cp.supportsLocalCursor = true; + + if (customCompressLevel.getValue()) + this.cp.compressLevel = compressLevel.getValue(); + else + this.cp.compressLevel = -1; + + if (!noJpeg.getValue() && !autoSelect.getValue()) + this.cp.qualityLevel = qualityLevel.getValue(); + else + this.cp.qualityLevel = -1; + + this.encodingChange = true; + + // Format changes refreshes the entire screen though and are therefore + // very costly. It's probably worth the effort to see if it is necessary + // here. + PixelFormat pf; + + if (fullColor.getValue()) { + pf = fullColorPF; + } else { + if (lowColorLevel.getValue() == 0) + pf = verylowColorPF; + else if (lowColorLevel.getValue() == 1) + pf = lowColorPF; + else + pf = mediumColorPF; + } + + if (!pf.equal(this.cp.pf())) { + this.formatChange = true; + + // Without fences, we cannot safely trigger an update request directly + // but must wait for the next update to arrive. + if (this.supportsSyncFence) + this.requestNewUpdate(); + } + + } //////////////////////////////////////////////////////////////////// // The following methods are all called from the GUI thread @@ -746,14 +684,12 @@ public class CConn extends CConnection implements // close() shuts down the socket, thus waking up the RFB thread. public void close() { if (closeListener != null) { - viewer.embed.setParam(true); - if (VncViewer.nViewers == 1) { - JFrame f = (JFrame)JOptionPane.getFrameForComponent(viewer); - if (f != null) - f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING)); - } + 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) @@ -763,51 +699,10 @@ public class CConn extends CConnection implements } } - // Menu callbacks. These are guaranteed only to be called after serverInit() - // has been called, since the menu is only accessible from the DesktopWindow - - private void initMenu() { - menu = new F8Menu(this); - } - - void showMenu(int x, int y) { - String os = System.getProperty("os.name"); - if (os.startsWith("Windows")) - com.sun.java.swing.plaf.windows.WindowsLookAndFeel.setMnemonicHidden(false); - menu.show(desktop, x, y); - } - - void showAbout() { - String pkgDate = ""; - String pkgTime = ""; - try { - Manifest manifest = new Manifest(VncViewer.timestamp); - Attributes attributes = manifest.getMainAttributes(); - pkgDate = attributes.getValue("Package-Date"); - pkgTime = attributes.getValue("Package-Time"); - } catch (java.lang.Exception e) { } - - Window fullScreenWindow = Viewport.getFullScreenWindow(); - if (fullScreenWindow != null) - Viewport.setFullScreenWindow(null); - String msg = - String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build, - VncViewer.buildDate, VncViewer.buildTime); - JOptionPane op = - new JOptionPane(msg, JOptionPane.INFORMATION_MESSAGE, - JOptionPane.DEFAULT_OPTION, VncViewer.logoIcon); - JDialog dlg = op.createDialog(desktop, "About TigerVNC Viewer for Java"); - dlg.setIconImage(VncViewer.frameIcon); - dlg.setAlwaysOnTop(true); - dlg.setVisible(true); - if (fullScreenWindow != null) - Viewport.setFullScreenWindow(fullScreenWindow); - } - void showInfo() { - Window fullScreenWindow = Viewport.getFullScreenWindow(); + Window fullScreenWindow = DesktopWindow.getFullScreenWindow(); if (fullScreenWindow != null) - Viewport.setFullScreenWindow(null); + DesktopWindow.setFullScreenWindow(null); String info = new String("Desktop name: %s%n"+ "Host: %s:%d%n"+ "Size: %dx%d%n"+ @@ -822,7 +717,7 @@ public class CConn extends CConnection implements String.format(info, cp.name(), sock.getPeerName(), sock.getPeerPort(), cp.width, cp.height, - desktop.getPF().print(), + cp.pf().print(), serverPF.print(), Encodings.encodingName(currentEncoding), Encodings.encodingName(lastServerEncoding), @@ -837,7 +732,7 @@ public class CConn extends CConnection implements dlg.setAlwaysOnTop(true); dlg.setVisible(true); if (fullScreenWindow != null) - Viewport.setFullScreenWindow(fullScreenWindow); + DesktopWindow.setFullScreenWindow(fullScreenWindow); } public void refresh() { @@ -845,516 +740,6 @@ public class CConn extends CConnection implements pendingUpdate = true; } - - // OptionsDialogCallback. setOptions() sets the options dialog's checkboxes - // etc to reflect our flags. getOptions() sets our flags according to the - // options dialog's checkboxes. They are both called from the GUI thread. - // Some of the flags are also accessed by the RFB thread. I believe that - // reading and writing boolean and int values in java is atomic, so there is - // no need for synchronization. - - public void setOptions() { - int digit; - options.autoSelect.setSelected(autoSelect); - options.fullColour.setSelected(fullColour); - options.veryLowColour.setSelected(!fullColour && lowColourLevel == 0); - options.lowColour.setSelected(!fullColour && lowColourLevel == 1); - options.mediumColour.setSelected(!fullColour && lowColourLevel == 2); - options.tight.setSelected(currentEncoding == Encodings.encodingTight); - options.zrle.setSelected(currentEncoding == Encodings.encodingZRLE); - options.hextile.setSelected(currentEncoding == Encodings.encodingHextile); - options.raw.setSelected(currentEncoding == Encodings.encodingRaw); - - options.customCompressLevel.setSelected(viewer.customCompressLevel.getValue()); - digit = 0 + viewer.compressLevel.getValue(); - if (digit >= 0 && digit <= 9) { - options.compressLevel.setSelectedItem(digit); - } else { - options.compressLevel.setSelectedItem(Integer.parseInt(viewer.compressLevel.getDefaultStr())); - } - options.noJpeg.setSelected(!viewer.noJpeg.getValue()); - digit = 0 + viewer.qualityLevel.getValue(); - if (digit >= 0 && digit <= 9) { - options.qualityLevel.setSelectedItem(digit); - } else { - options.qualityLevel.setSelectedItem(Integer.parseInt(viewer.qualityLevel.getDefaultStr())); - } - - options.viewOnly.setSelected(viewer.viewOnly.getValue()); - options.acceptClipboard.setSelected(viewer.acceptClipboard.getValue()); - options.sendClipboard.setSelected(viewer.sendClipboard.getValue()); - options.menuKey.setSelectedItem(KeyEvent.getKeyText(MenuKey.getMenuKeyCode())); - options.sendLocalUsername.setSelected(viewer.sendLocalUsername.getValue()); - - if (state() == RFBSTATE_NORMAL) { - options.shared.setEnabled(false); - options.secVeNCrypt.setEnabled(false); - options.encNone.setEnabled(false); - options.encTLS.setEnabled(false); - options.encX509.setEnabled(false); - options.x509ca.setEnabled(false); - options.caButton.setEnabled(false); - options.x509crl.setEnabled(false); - options.crlButton.setEnabled(false); - options.secIdent.setEnabled(false); - options.secNone.setEnabled(false); - options.secVnc.setEnabled(false); - options.secPlain.setEnabled(false); - options.sendLocalUsername.setEnabled(false); - options.cfLoadButton.setEnabled(false); - options.cfSaveAsButton.setEnabled(true); - options.sshTunnel.setEnabled(false); - options.sshUseGateway.setEnabled(false); - options.sshUser.setEnabled(false); - options.sshHost.setEnabled(false); - options.sshPort.setEnabled(false); - options.sshUseExt.setEnabled(false); - options.sshClient.setEnabled(false); - options.sshClientBrowser.setEnabled(false); - options.sshArgsDefault.setEnabled(false); - options.sshArgsCustom.setEnabled(false); - options.sshArguments.setEnabled(false); - options.sshConfig.setEnabled(false); - options.sshConfigBrowser.setEnabled(false); - options.sshKeyFile.setEnabled(false); - options.sshKeyFileBrowser.setEnabled(false); - } else { - options.shared.setSelected(viewer.shared.getValue()); - options.sendLocalUsername.setSelected(viewer.sendLocalUsername.getValue()); - options.cfSaveAsButton.setEnabled(false); - if (viewer.tunnel.getValue() || viewer.via.getValue() != null) - options.sshTunnel.setSelected(true); - if (viewer.via.getValue() != null) - options.sshUseGateway.setSelected(true); - options.sshUser.setText(Tunnel.getSshUser(this)); - options.sshHost.setText(Tunnel.getSshHost(this)); - options.sshPort.setText(Integer.toString(Tunnel.getSshPort(this))); - options.sshUseExt.setSelected(viewer.extSSH.getValue()); - File client = new File(viewer.extSSHClient.getValue()); - if (client.exists() && client.canRead()) - options.sshClient.setText(client.getAbsolutePath()); - if (viewer.extSSHArgs.getValue() == null) { - options.sshArgsDefault.setSelected(true); - options.sshArguments.setText(""); - } else { - options.sshArgsCustom.setSelected(true); - options.sshArguments.setText(viewer.extSSHArgs.getValue()); - } - File config = new File(viewer.sshConfig.getValue()); - if (config.exists() && config.canRead()) - options.sshConfig.setText(config.getAbsolutePath()); - options.sshKeyFile.setText(Tunnel.getSshKeyFile(this)); - - /* Process non-VeNCrypt sectypes */ - java.util.List<Integer> secTypes = new ArrayList<Integer>(); - secTypes = Security.GetEnabledSecTypes(); - for (Iterator<Integer> i = secTypes.iterator(); i.hasNext();) { - switch ((Integer)i.next()) { - case Security.secTypeVeNCrypt: - options.secVeNCrypt.setSelected(UserPreferences.getBool("viewer", "secVeNCrypt", true)); - break; - case Security.secTypeNone: - options.encNone.setSelected(true); - options.secNone.setSelected(UserPreferences.getBool("viewer", "secTypeNone", true)); - break; - case Security.secTypeVncAuth: - options.encNone.setSelected(true); - options.secVnc.setSelected(UserPreferences.getBool("viewer", "secTypeVncAuth", true)); - break; - } - } - - /* Process VeNCrypt subtypes */ - if (options.secVeNCrypt.isSelected()) { - java.util.List<Integer> secTypesExt = new ArrayList<Integer>(); - secTypesExt = Security.GetEnabledExtSecTypes(); - for (Iterator<Integer> iext = secTypesExt.iterator(); iext.hasNext();) { - switch ((Integer)iext.next()) { - case Security.secTypePlain: - options.encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true)); - options.secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true)); - break; - case Security.secTypeIdent: - options.encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true)); - options.secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true)); - break; - case Security.secTypeTLSNone: - options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true)); - options.secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true)); - break; - case Security.secTypeTLSVnc: - options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true)); - options.secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true)); - break; - case Security.secTypeTLSPlain: - options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true)); - options.secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true)); - break; - case Security.secTypeTLSIdent: - options.encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true)); - options.secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true)); - break; - case Security.secTypeX509None: - options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true)); - options.secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true)); - break; - case Security.secTypeX509Vnc: - options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true)); - options.secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true)); - break; - case Security.secTypeX509Plain: - options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true)); - options.secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true)); - break; - case Security.secTypeX509Ident: - options.encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true)); - options.secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true)); - break; - } - } - } - File caFile = new File(viewer.x509ca.getValue()); - if (caFile.exists() && caFile.canRead()) - options.x509ca.setText(caFile.getAbsolutePath()); - File crlFile = new File(viewer.x509crl.getValue()); - if (crlFile.exists() && crlFile.canRead()) - options.x509crl.setText(crlFile.getAbsolutePath()); - options.encNone.setEnabled(options.secVeNCrypt.isSelected()); - options.encTLS.setEnabled(options.secVeNCrypt.isSelected()); - options.encX509.setEnabled(options.secVeNCrypt.isSelected()); - options.x509ca.setEnabled(options.secVeNCrypt.isSelected() && - options.encX509.isSelected()); - options.caButton.setEnabled(options.secVeNCrypt.isSelected() && - options.encX509.isSelected()); - options.x509crl.setEnabled(options.secVeNCrypt.isSelected() && - options.encX509.isSelected()); - options.crlButton.setEnabled(options.secVeNCrypt.isSelected() && - options.encX509.isSelected()); - options.secIdent.setEnabled(options.secVeNCrypt.isSelected()); - options.secPlain.setEnabled(options.secVeNCrypt.isSelected()); - options.sendLocalUsername.setEnabled(options.secPlain.isSelected()|| - options.secIdent.isSelected()); - options.sshTunnel.setEnabled(true); - options.sshUseGateway.setEnabled(options.sshTunnel.isSelected()); - options.sshUser.setEnabled(options.sshTunnel.isSelected() && - options.sshUseGateway.isEnabled() && - options.sshUseGateway.isSelected()); - options.sshHost.setEnabled(options.sshTunnel.isSelected() && - options.sshUseGateway.isEnabled() && - options.sshUseGateway.isSelected()); - options.sshPort.setEnabled(options.sshTunnel.isSelected() && - options.sshUseGateway.isEnabled() && - options.sshUseGateway.isSelected()); - options.sshUseExt.setEnabled(options.sshTunnel.isSelected()); - options.sshClient.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - options.sshUseExt.isSelected()); - options.sshClientBrowser.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - options.sshUseExt.isSelected()); - options.sshArgsDefault.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - options.sshUseExt.isSelected()); - options.sshArgsCustom.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - options.sshUseExt.isSelected()); - options.sshArguments.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - options.sshUseExt.isSelected() && - options.sshArgsCustom.isSelected()); - options.sshConfig.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - !options.sshUseExt.isSelected()); - options.sshConfigBrowser.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - !options.sshUseExt.isSelected()); - options.sshKeyFile.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - !options.sshUseExt.isSelected()); - options.sshKeyFileBrowser.setEnabled(options.sshTunnel.isSelected() && - options.sshUseExt.isEnabled() && - !options.sshUseExt.isSelected()); - } - - options.fullScreen.setSelected(fullScreen); - options.fullScreenAllMonitors.setSelected(viewer.fullScreenAllMonitors.getValue()); - options.useLocalCursor.setSelected(viewer.useLocalCursor.getValue()); - options.acceptBell.setSelected(viewer.acceptBell.getValue()); - String scaleString = viewer.scalingFactor.getValue(); - if (scaleString.equalsIgnoreCase("Auto")) { - options.scalingFactor.setSelectedItem("Auto"); - } else if(scaleString.equalsIgnoreCase("FixedRatio")) { - options.scalingFactor.setSelectedItem("Fixed Aspect Ratio"); - } else { - digit = Integer.parseInt(scaleString); - if (digit >= 1 && digit <= 1000) { - options.scalingFactor.setSelectedItem(digit+"%"); - } else { - digit = Integer.parseInt(viewer.scalingFactor.getDefaultStr()); - options.scalingFactor.setSelectedItem(digit+"%"); - } - int scaleFactor = - Integer.parseInt(scaleString.substring(0, scaleString.length())); - } - if (viewer.desktopSize.getValue() != null && - viewer.desktopSize.getValue().split("x").length == 2) { - options.desktopSize.setSelected(true); - String desktopWidth = viewer.desktopSize.getValue().split("x")[0]; - options.desktopWidth.setText(desktopWidth); - String desktopHeight = viewer.desktopSize.getValue().split("x")[1]; - options.desktopHeight.setText(desktopHeight); - } - } - - public void getOptions() { - autoSelect = options.autoSelect.isSelected(); - if (fullColour != options.fullColour.isSelected()) { - formatChange = true; - forceNonincremental = true; - } - fullColour = options.fullColour.isSelected(); - if (!fullColour) { - int newLowColourLevel = (options.veryLowColour.isSelected() ? 0 : - options.lowColour.isSelected() ? 1 : 2); - if (newLowColourLevel != lowColourLevel) { - lowColourLevel = newLowColourLevel; - formatChange = true; - forceNonincremental = true; - } - } - int newEncoding = (options.zrle.isSelected() ? Encodings.encodingZRLE : - options.hextile.isSelected() ? Encodings.encodingHextile : - options.tight.isSelected() ? Encodings.encodingTight : - Encodings.encodingRaw); - if (newEncoding != currentEncoding) { - currentEncoding = newEncoding; - encodingChange = true; - } - - viewer.customCompressLevel.setParam(options.customCompressLevel.isSelected()); - if (cp.customCompressLevel != viewer.customCompressLevel.getValue()) { - cp.customCompressLevel = viewer.customCompressLevel.getValue(); - encodingChange = true; - } - if (Integer.parseInt(options.compressLevel.getSelectedItem().toString()) >= 0 && - Integer.parseInt(options.compressLevel.getSelectedItem().toString()) <= 9) { - viewer.compressLevel.setParam(options.compressLevel.getSelectedItem().toString()); - } else { - viewer.compressLevel.setParam(viewer.compressLevel.getDefaultStr()); - } - if (cp.compressLevel != viewer.compressLevel.getValue()) { - cp.compressLevel = viewer.compressLevel.getValue(); - encodingChange = true; - } - viewer.noJpeg.setParam(!options.noJpeg.isSelected()); - if (cp.noJpeg != viewer.noJpeg.getValue()) { - cp.noJpeg = viewer.noJpeg.getValue(); - encodingChange = true; - } - viewer.qualityLevel.setParam(options.qualityLevel.getSelectedItem().toString()); - if (cp.qualityLevel != viewer.qualityLevel.getValue()) { - cp.qualityLevel = viewer.qualityLevel.getValue(); - encodingChange = true; - } - if (!options.x509ca.getText().equals("")) - CSecurityTLS.x509ca.setParam(options.x509ca.getText()); - if (!options.x509crl.getText().equals("")) - CSecurityTLS.x509crl.setParam(options.x509crl.getText()); - viewer.sendLocalUsername.setParam(options.sendLocalUsername.isSelected()); - - viewer.viewOnly.setParam(options.viewOnly.isSelected()); - viewer.acceptClipboard.setParam(options.acceptClipboard.isSelected()); - viewer.sendClipboard.setParam(options.sendClipboard.isSelected()); - viewer.acceptBell.setParam(options.acceptBell.isSelected()); - String scaleString = - options.scalingFactor.getSelectedItem().toString(); - String oldScaleFactor = viewer.scalingFactor.getValue(); - if (scaleString.equalsIgnoreCase("Fixed Aspect Ratio")) { - scaleString = new String("FixedRatio"); - } else if (scaleString.equalsIgnoreCase("Auto")) { - scaleString = new String("Auto"); - } else { - scaleString=scaleString.substring(0, scaleString.length()-1); - } - if (!oldScaleFactor.equals(scaleString)) { - viewer.scalingFactor.setParam(scaleString); - if ((options.fullScreen.isSelected() == fullScreen) && - (desktop != null)) - recreateViewport(); - } - - clipboardDialog.setSendingEnabled(viewer.sendClipboard.getValue()); - viewer.menuKey.setParam(MenuKey.getMenuKeySymbols()[options.menuKey.getSelectedIndex()].name); - F8Menu.f8.setText("Send "+KeyEvent.getKeyText(MenuKey.getMenuKeyCode())); - - setShared(options.shared.isSelected()); - viewer.useLocalCursor.setParam(options.useLocalCursor.isSelected()); - if (cp.supportsLocalCursor != viewer.useLocalCursor.getValue()) { - cp.supportsLocalCursor = viewer.useLocalCursor.getValue(); - encodingChange = true; - if (desktop != null) - desktop.resetLocalCursor(); - } - viewer.extSSH.setParam(options.sshUseExt.isSelected()); - - checkEncodings(); - - if (state() != RFBSTATE_NORMAL) { - /* Process security types which don't use encryption */ - if (options.encNone.isSelected()) { - if (options.secNone.isSelected()) - Security.EnableSecType(Security.secTypeNone); - if (options.secVnc.isSelected()) - Security.EnableSecType(Security.secTypeVncAuth); - if (options.secPlain.isSelected()) - Security.EnableSecType(Security.secTypePlain); - if (options.secIdent.isSelected()) - Security.EnableSecType(Security.secTypeIdent); - } else { - Security.DisableSecType(Security.secTypeNone); - Security.DisableSecType(Security.secTypeVncAuth); - Security.DisableSecType(Security.secTypePlain); - Security.DisableSecType(Security.secTypeIdent); - } - - /* Process security types which use TLS encryption */ - if (options.encTLS.isSelected()) { - if (options.secNone.isSelected()) - Security.EnableSecType(Security.secTypeTLSNone); - if (options.secVnc.isSelected()) - Security.EnableSecType(Security.secTypeTLSVnc); - if (options.secPlain.isSelected()) - Security.EnableSecType(Security.secTypeTLSPlain); - if (options.secIdent.isSelected()) - Security.EnableSecType(Security.secTypeTLSIdent); - } else { - Security.DisableSecType(Security.secTypeTLSNone); - Security.DisableSecType(Security.secTypeTLSVnc); - Security.DisableSecType(Security.secTypeTLSPlain); - Security.DisableSecType(Security.secTypeTLSIdent); - } - - /* Process security types which use X509 encryption */ - if (options.encX509.isSelected()) { - if (options.secNone.isSelected()) - Security.EnableSecType(Security.secTypeX509None); - if (options.secVnc.isSelected()) - Security.EnableSecType(Security.secTypeX509Vnc); - if (options.secPlain.isSelected()) - Security.EnableSecType(Security.secTypeX509Plain); - if (options.secIdent.isSelected()) - Security.EnableSecType(Security.secTypeX509Ident); - } else { - Security.DisableSecType(Security.secTypeX509None); - Security.DisableSecType(Security.secTypeX509Vnc); - Security.DisableSecType(Security.secTypeX509Plain); - Security.DisableSecType(Security.secTypeX509Ident); - } - - /* Process *None security types */ - if (options.secNone.isSelected()) { - if (options.encNone.isSelected()) - Security.EnableSecType(Security.secTypeNone); - if (options.encTLS.isSelected()) - Security.EnableSecType(Security.secTypeTLSNone); - if (options.encX509.isSelected()) - Security.EnableSecType(Security.secTypeX509None); - } else { - Security.DisableSecType(Security.secTypeNone); - Security.DisableSecType(Security.secTypeTLSNone); - Security.DisableSecType(Security.secTypeX509None); - } - - /* Process *Vnc security types */ - if (options.secVnc.isSelected()) { - if (options.encNone.isSelected()) - Security.EnableSecType(Security.secTypeVncAuth); - if (options.encTLS.isSelected()) - Security.EnableSecType(Security.secTypeTLSVnc); - if (options.encX509.isSelected()) - Security.EnableSecType(Security.secTypeX509Vnc); - } else { - Security.DisableSecType(Security.secTypeVncAuth); - Security.DisableSecType(Security.secTypeTLSVnc); - Security.DisableSecType(Security.secTypeX509Vnc); - } - - /* Process *Plain security types */ - if (options.secPlain.isSelected()) { - if (options.encNone.isSelected()) - Security.EnableSecType(Security.secTypePlain); - if (options.encTLS.isSelected()) - Security.EnableSecType(Security.secTypeTLSPlain); - if (options.encX509.isSelected()) - Security.EnableSecType(Security.secTypeX509Plain); - } else { - Security.DisableSecType(Security.secTypePlain); - Security.DisableSecType(Security.secTypeTLSPlain); - Security.DisableSecType(Security.secTypeX509Plain); - } - - /* Process *Ident security types */ - if (options.secIdent.isSelected()) { - if (options.encNone.isSelected()) - Security.EnableSecType(Security.secTypeIdent); - if (options.encTLS.isSelected()) - Security.EnableSecType(Security.secTypeTLSIdent); - if (options.encX509.isSelected()) - Security.EnableSecType(Security.secTypeX509Ident); - } else { - Security.DisableSecType(Security.secTypeIdent); - Security.DisableSecType(Security.secTypeTLSIdent); - Security.DisableSecType(Security.secTypeX509Ident); - } - if (options.sshTunnel.isSelected()) { - if (options.sshUseGateway.isSelected()) { - String user = options.sshUser.getText(); - String host = options.sshHost.getText(); - String port = options.sshPort.getText(); - viewer.via.setParam(user+"@"+host+":"+port); - } else { - viewer.tunnel.setParam(true); - } - } - viewer.extSSH.setParam(options.sshUseExt.isSelected()); - viewer.extSSHClient.setParam(options.sshClient.getText()); - if (options.sshArgsCustom.isSelected()) - viewer.extSSHArgs.setParam(options.sshArguments.getText()); - viewer.sshConfig.setParam(options.sshConfig.getText()); - viewer.sshKeyFile.setParam(options.sshKeyFile.getText()); - } - String desktopSize = (options.desktopSize.isSelected()) ? - options.desktopWidth.getText() + "x" + options.desktopHeight.getText() : ""; - viewer.desktopSize.setParam(desktopSize); - if (options.fullScreen.isSelected() ^ fullScreen) { - viewer.fullScreenAllMonitors.setParam(options.fullScreenAllMonitors.isSelected()); - toggleFullScreen(); - } else { - if (viewer.fullScreenAllMonitors.getValue() != - options.fullScreenAllMonitors.isSelected()) { - viewer.fullScreenAllMonitors.setParam(options.fullScreenAllMonitors.isSelected()); - if (desktop != null) - recreateViewport(); - } else { - viewer.fullScreenAllMonitors.setParam(options.fullScreenAllMonitors.isSelected()); - } - } - } - - public void toggleFullScreen() { - if (viewer.embed.getValue()) - return; - fullScreen = !fullScreen; - menu.fullScreen.setSelected(fullScreen); - if (viewport != null) { - if (!viewport.lionFSSupported()) { - recreateViewport(); - } else { - viewport.toggleLionFS(); - } - } - } - // writeClientCutText() is called from the clipboard dialog public void writeClientCutText(String str, int len) { if (state() != RFBSTATE_NORMAL || shuttingDown) @@ -1369,11 +754,11 @@ public class CConn extends CConnection implements } public void writeKeyEvent(KeyEvent ev) { - if (viewer.viewOnly.getValue() || shuttingDown) + if (viewOnly.getValue() || 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 @@ -1388,9 +773,9 @@ public class CConn extends CConnection implements if (iter == null) { // Note that dead keys will raise this sort of error falsely // See https://bugs.openjdk.java.net/browse/JDK-6534883 - vlog.error("Unexpected key release of keyCode "+keyCode); + vlog.debug("Unexpected key release of keyCode "+keyCode); String fmt = ev.paramString().replaceAll("%","%%"); - vlog.error(String.format(fmt.replaceAll(",","%n "))); + vlog.debug(String.format(fmt.replaceAll(",","%n "))); return; } @@ -1474,15 +859,6 @@ public class CConn extends CConnection implements break; } - if (cp.width != desktop.scaledWidth || - cp.height != desktop.scaledHeight) { - int sx = (desktop.scaleWidthRatio == 1.00) ? - ev.getX() : (int)Math.floor(ev.getX() / desktop.scaleWidthRatio); - int sy = (desktop.scaleHeightRatio == 1.00) ? - ev.getY() : (int)Math.floor(ev.getY() / desktop.scaleHeightRatio); - ev.translatePoint(sx - ev.getX(), sy - ev.getY()); - } - writer().writePointerEvent(new Point(ev.getX(), ev.getY()), buttonMask); } @@ -1527,30 +903,13 @@ public class CConn extends CConnection implements //////////////////////////////////////////////////////////////////// // The following methods are called from both RFB and GUI threads - // checkEncodings() sends a setEncodings message if one is needed. - private void checkEncodings() { - if (encodingChange && (writer() != null)) { - vlog.info("Requesting " + Encodings.encodingName(currentEncoding) + - " encoding"); - writer().writeSetEncodings(currentEncoding, true); - encodingChange = false; - } - } - // the following never change so need no synchronization: - - // viewer object is only ever accessed by the GUI thread so needs no - // synchronization (except for one test in DesktopWindow - see comment - // there). - VncViewer viewer; - // 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. @@ -1559,27 +918,21 @@ public class CConn extends CConnection implements // reading and writing int and boolean is atomic in java, so no // synchronization of the following flags is needed: - int lowColourLevel; - // All menu, options, about and info stuff is done in the GUI thread (apart // from when constructed). - F8Menu menu; - OptionsDialog options; - - // clipboard sync issues? - ClipboardDialog clipboardDialog; // the following are only ever accessed by the GUI thread: int buttonMask; + private String serverHost; + private int serverPort; private Socket sock; protected DesktopWindow desktop; - // FIXME: should be private - public PixelFormat serverPF; - private PixelFormat fullColourPF; + private PixelFormat serverPF; + private PixelFormat fullColorPF; private boolean pendingPFChange; private PixelFormat pendingPF; @@ -1597,11 +950,6 @@ public class CConn extends CConnection implements private boolean supportsSyncFence; - public int menuKeyCode; - Viewport viewport; - private boolean fullColour; - private boolean autoSelect; - boolean fullScreen; private HashMap<Integer, Integer> downKeySym; public ActionListener closeListener = null; diff --git a/java/com/tigervnc/vncviewer/ClipboardDialog.java b/java/com/tigervnc/vncviewer/ClipboardDialog.java index 441846cf..4a5d9f27 100644 --- a/java/com/tigervnc/vncviewer/ClipboardDialog.java +++ b/java/com/tigervnc/vncviewer/ClipboardDialog.java @@ -26,83 +26,125 @@ import java.io.*; import java.nio.*; import javax.swing.*; import javax.swing.border.*; +import javax.swing.event.*; import javax.swing.text.*; import com.tigervnc.rfb.LogWriter; +import static com.tigervnc.vncviewer.Parameters.*; + class ClipboardDialog extends Dialog { - private class VncTransferHandler extends TransferHandler { - // Custom TransferHandler designed to limit the size of outbound - // clipboard transfers to VncViewer.maxCutText.getValue() bytes. - private LogWriter vlog = new LogWriter("VncTransferHandler"); - - public void exportToClipboard(JComponent c, Clipboard clip, int a) - throws IllegalStateException { - if (!(c instanceof JTextComponent)) return; - StringSelection selection = - new StringSelection(((JTextComponent)c).getText()); - clip.setContents(selection, null); - } + protected static class MyJTextArea extends JTextArea { + + private class VncTransferHandler extends TransferHandler { + // Custom TransferHandler designed to limit the size of outbound + // clipboard transfers to VncViewer.maxCutText.getValue() bytes. + private LogWriter vlog = new LogWriter("VncTransferHandler"); - public boolean importData(JComponent c, Transferable t) { - if (canImport(c, t.getTransferDataFlavors())) { + public void exportToClipboard(JComponent c, Clipboard clip, int a) + throws IllegalStateException { + if (!(c instanceof JTextComponent)) return; + String text = ((JTextComponent)c).getText(); try { - DataFlavor VncFlavor = null; - for (DataFlavor f : t.getTransferDataFlavors()) - if (f.isFlavorTextType() && f.isRepresentationClassInputStream()) - VncFlavor = f; - if (VncFlavor == null) return false; - Reader reader = (Reader)VncFlavor.getReaderForText(t); - CharBuffer cbuf = - CharBuffer.allocate(VncViewer.maxCutText.getValue()); - cbuf.limit(reader.read(cbuf.array(), 0, cbuf.length())); - reader.close(); - if (c instanceof JTextComponent) - ((JTextComponent)c).setText(cbuf.toString()); - return true; - } catch (OutOfMemoryError oome) { - vlog.error("ERROR: Too much data on local clipboard!"); - } catch (UnsupportedFlavorException ufe) { - // Skip import - vlog.info(ufe.toString()); - } catch (IOException ioe) { - // Skip import - vlog.info(ioe.toString()); + if (text.equals((String)clip.getData(DataFlavor.stringFlavor))) return; + } catch (IOException e) { + // worst case we set the clipboard contents unnecessarily + vlog.info(e.toString()); + } catch (UnsupportedFlavorException e) { + // worst case we set the clipboard contents unnecessarily + vlog.info(e.toString()); } - } - return false; - } + StringSelection selection = new StringSelection(text); + clip.setContents(selection, null); + } + + public boolean importData(JComponent c, Transferable t) { + if (canImport(c, t.getTransferDataFlavors())) { + try { + DataFlavor VncFlavor = null; + for (DataFlavor f : t.getTransferDataFlavors()) { + if (f.isMimeTypeEqual("text/plain") && + f.isRepresentationClassInputStream()) { + VncFlavor = f; + break; + } + } + if (VncFlavor == null) return false; + CharBuffer cbuf = + CharBuffer.allocate(maxCutText.getValue()); + Reader reader = (Reader)VncFlavor.getReaderForText(t); + int n = reader.read(cbuf.array(), 0, cbuf.length()); + reader.close(); + // reader returns -1 (EOF) for empty clipboard + cbuf.limit(n < 0 ? 0 : n); + if (c instanceof JTextComponent) + if (!cbuf.toString().equals(((JTextComponent)c).getText())) + ((JTextComponent)c).setText(cbuf.toString()); + return true; + } catch (OutOfMemoryError e) { + vlog.error("ERROR: Too much data on local clipboard!"); + } catch (UnsupportedFlavorException e) { + // Skip import + vlog.info(e.toString()); + } catch (IOException e) { + // Skip import + vlog.info(e.toString()); + } + } + return false; + } + + public boolean canImport(JComponent c, DataFlavor[] flavors) { + for (DataFlavor f : flavors) + if (f.isMimeTypeEqual("text/plain") && + f.isRepresentationClassReader()) + return true; + return false; + } + } + + private class MyTextListener implements DocumentListener { + public MyTextListener() { } + + public void changedUpdate(DocumentEvent e) { } - public boolean canImport(JComponent c, DataFlavor[] flavors) { - for (DataFlavor f : flavors) - if (f.isFlavorTextType() && f.isRepresentationClassReader()) - return true; - return false; + public void insertUpdate(DocumentEvent e) { + if (!listen) return; + String text = textArea.getText(); + if (sendClipboard.getValue()) + VncViewer.cc.writeClientCutText(text, text.length()); + } + + public void removeUpdate(DocumentEvent e) { } + } + + public MyJTextArea() { + super(); + setTransferHandler(new VncTransferHandler()); + getDocument().addDocumentListener(new MyTextListener()); + // If the textArea can receive the focus, then text within the textArea + // can be selected. On platforms that don't support separate selection + // and clipboard buffers, this triggers a replacement of the textAra's + // contents with the selected text. + setFocusable(false); + setLineWrap(false); + setWrapStyleWord(true); } } - public ClipboardDialog(CConn cc_) { + public ClipboardDialog() { super(false); setTitle("VNC Clipboard Viewer"); setPreferredSize(new Dimension(640, 480)); - addWindowFocusListener(new WindowAdapter() { + addWindowFocusListener(new WindowFocusListener() { // Necessary to ensure that updates from the system clipboard - // still occur when the ClipboardDialog has the focus. - public void WindowGainedFocus(WindowEvent e) { + // are propagated to the textArea when the dialog is visible. + public void windowGainedFocus(WindowEvent e) { clientCutText(); } + public void windowLostFocus(WindowEvent e) { } }); - cc = cc_; - textArea = new JTextArea(); - textArea.setTransferHandler(new VncTransferHandler()); - // If the textArea can receive the focus, then text within the textArea - // can be selected. On platforms that don't support separate selection - // and clipboard buffers, this triggers a replacement of the textAra's - // contents with the selected text. - textArea.setFocusable(false); - textArea.setLineWrap(false); - textArea.setWrapStyleWord(true); JScrollPane sp = new JScrollPane(textArea); getContentPane().add(sp, BorderLayout.CENTER); // button panel placed below the scrollpane @@ -118,19 +160,40 @@ class ClipboardDialog extends Dialog { pack(); } - public void serverCutText(String str, int len) { + public static void showDialog(Container c) { + if (dialog == null) + dialog = new ClipboardDialog(); + dialog.show(c); + } + + public void show(Container c) { + super.showDialog(c); + } + + public void endDialog() { + super.endDialog(); + dialog.dispose(); + } + + public static void serverCutText(String str) { + if (textArea.getText().equals(str)) + return; + // Update the text area with incoming serverCutText. We need to diable + // the DocumentListener temporarily to prevent an clientCutText msg from + // being sent back to the server when the textArea is updated. + listen = false; textArea.setText(str); textArea.copy(); + listen = true; } - public void clientCutText() { - int hc = textArea.getText().hashCode(); + public static void clientCutText() { + // Update the textArea with the current contents of the system clipboard. + // The TransferHandler ensures that the textArea's contents are only + // changed when they differ from the clipboard's. If the textArea is + // updated, the DocumentListener will trigger an RFB clientCutText msg. textArea.paste(); textArea.setCaretPosition(0); - String text = textArea.getText(); - if (cc.viewer.sendClipboard.getValue()) - if (hc != text.hashCode()) - cc.writeClientCutText(text, text.length()); } public void setSendingEnabled(boolean b) { @@ -140,18 +203,20 @@ class ClipboardDialog extends Dialog { public void actionPerformed(ActionEvent e) { Object s = e.getSource(); if (s instanceof JButton && (JButton)s == clearButton) { - serverCutText(new String(""), 0); + serverCutText(new String("")); } else if (s instanceof JButton && (JButton)s == sendButton) { String text = textArea.getText(); - cc.writeClientCutText(text, text.length()); + VncViewer.cc.writeClientCutText(text, text.length()); endDialog(); } else if (s instanceof JButton && (JButton)s == cancelButton) { endDialog(); } } - CConn cc; - JTextArea textArea; - JButton clearButton, sendButton, cancelButton; + private JButton clearButton, sendButton, cancelButton; + private static boolean listen = true; + static ClipboardDialog dialog; + static MyJTextArea textArea = new MyJTextArea(); + static Toolkit tk = Toolkit.getDefaultToolkit(); static LogWriter vlog = new LogWriter("ClipboardDialog"); } diff --git a/java/com/tigervnc/vncviewer/DesktopWindow.java b/java/com/tigervnc/vncviewer/DesktopWindow.java index ff48fc1c..e76a0156 100644 --- a/java/com/tigervnc/vncviewer/DesktopWindow.java +++ b/java/com/tigervnc/vncviewer/DesktopWindow.java @@ -1,8 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved. - * Copyright (C) 2009 Paul Donohue. All Rights Reserved. - * Copyright (C) 2010, 2012-2013 D. R. Commander. All Rights Reserved. - * Copyright (C) 2011-2014 Brian P. Hinz + * Copyright (C) 2011-2016 Brian P. Hinz + * Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,572 +18,631 @@ * 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, PixelFormat serverPF, - CConn cc_) { + public DesktopWindow(int w, int h, String name, + PixelFormat serverPF, CConn cc_) + { cc = cc_; - setSize(width, height); - setScaledSize(); - setOpaque(false); - 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(width, height, cc, 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) { - cc.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); - } - 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(); + } + } + }); + + 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); + + if (maximize.getValue()) + setExtendedState(JFrame.MAXIMIZED_BOTH); + } - 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(); } - // Methods called from the RFB thread - these need to be synchronized - // wherever they access data shared with the GUI thread. + // 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); + } - 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. + @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); + } - synchronized(cc.viewer.useLocalCursor) { - if (!cc.viewer.useLocalCursor.getValue()) - return; - } + @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); + } - hideLocalCursor(); + 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(); + } - cursor.hotspot = (hotspot != null) ? hotspot : new Point(0, 0); - cursor.setSize(w, h); - cursor.setPF(getPF()); + public PixelFormat getPreferredPF() + { + return viewport.getPreferredPF(); + } - cursorBacking.setSize(cursor.width(), cursor.height()); - cursorBacking.setPF(getPF()); + public void setName(String name) + { + setTitle(name); + } - cursor.data = new int[cursor.width() * cursor.height()]; - cursor.mask = new byte[cursor.maskLen()]; + // 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()); } - // 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 void fullscreen_off() + { + fullScreen.setParam(false); + dispose(); + setUndecorated(false); + setExtendedState(lastState); + setBounds(lastBounds); + setVisible(true); } - // 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(); - } + public boolean fullscreen_active() + { + return isUndecorated(); } - // 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); - } + private void handleDesktopSize() + { + if (!desktopSize.getValue().equals("")) { + int width, height; - 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(); - } + // An explicit size has been requested - 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(); - } + if (desktopSize.getValue().split("x").length != 2) + return; - 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(); + 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 void handleResizeTimeout() + { + DesktopWindow self = (DesktopWindow)this; - // 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); + assert(self != null); + + 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 = cc.viewer.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 (!cc.viewer.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 (!cc.viewer.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 (cc.viewer.embed.getValue()) - requestFocus(); - } - public void mouseExited(MouseEvent e) {} - // MouseWheel callback function - private void mouseWheelCB(MouseWheelEvent e) { - if (!cc.viewer.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); - cc.showMenu((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: - cc.showAbout(); - 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: - cc.options.showDialog(cc.viewport); - return; - case KeyEvent.VK_W: - VncViewer.newViewer(cc.viewer); - 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; - } - - // access to cc by different threads is specified in CConn - CConn cc; + public void handleFullscreenTimeout() + { + 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; - - // the following are only ever accessed by the GUI thread: - int lastX, lastY; - Rect damage = new Rect(); + if (self.delayedDesktopSize) { + self.handleDesktopSize(); + self.delayedDesktopSize = false; + } + } - static LogWriter vlog = new LogWriter("DesktopWindow"); + private CConn cc; + private JScrollPane scroll; + public Viewport viewport; + + private boolean firstUpdate; + private boolean delayedFullscreen; + private boolean delayedDesktopSize; + private boolean canDoLionFS; + private String lastScaleFactor; + private Rectangle lastBounds; + private int lastState; + private Timer timer; } + diff --git a/java/com/tigervnc/vncviewer/Dialog.java b/java/com/tigervnc/vncviewer/Dialog.java index 8bf19799..a2fb04fe 100644 --- a/java/com/tigervnc/vncviewer/Dialog.java +++ b/java/com/tigervnc/vncviewer/Dialog.java @@ -31,8 +31,10 @@ package com.tigervnc.vncviewer; import java.awt.*; import java.awt.Dialog.*; import java.awt.event.*; +import java.io.File; import javax.swing.*; import javax.swing.border.*; +import javax.swing.filechooser.*; import javax.swing.text.*; class Dialog extends JDialog implements ActionListener, @@ -52,7 +54,7 @@ class Dialog extends JDialog implements ActionListener, } } - public boolean showDialog(Component c) { + public void showDialog(Component c) { initDialog(); if (c != null) { setLocationRelativeTo(c); @@ -63,26 +65,19 @@ class Dialog extends JDialog implements ActionListener, int y = (dpySize.height - mySize.height) / 2; setLocation(x, y); } - fullScreenWindow = Viewport.getFullScreenWindow(); - if (fullScreenWindow != null) - Viewport.setFullScreenWindow(null); if (getModalityType() == ModalityType.APPLICATION_MODAL) setAlwaysOnTop(true); setVisible(true); - return ret; } - public boolean showDialog() { - return showDialog(null); + public void showDialog() { + showDialog(null); } 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 @@ -137,6 +132,33 @@ class Dialog extends JDialog implements ActionListener, return width + gap; } + public static File showChooser(String title, File defFile, + Container c, FileNameExtensionFilter f) { + JFileChooser fc = new JFileChooser(defFile); + fc.setDialogTitle(title); + fc.setApproveButtonText("OK \u21B5"); + fc.setFileHidingEnabled(false); + if (f != null) + fc.setFileFilter(f); + if (fc.showOpenDialog(c) == JFileChooser.APPROVE_OPTION) + return fc.getSelectedFile(); + else + return null; + } + + public static File showChooser(String title, File defFile, Container c) { + return showChooser(title, defFile, c, null); + } + + protected File showChooser(String title, File defFile, + FileNameExtensionFilter f) { + return showChooser(title, defFile, this, f); + } + + protected File showChooser(String title, File defFile) { + return showChooser(title, defFile, this); + } + protected class GroupedJRadioButton extends JRadioButton { public GroupedJRadioButton(String l, ButtonGroup g, JComponent c) { super(l); @@ -181,9 +203,9 @@ class Dialog extends JDialog implements ActionListener, JTextField jtf = (JTextField)editor.getEditorComponent(); jtf.setDocument(doc); } + } private Window fullScreenWindow; - protected boolean ret = true; } diff --git a/java/com/tigervnc/vncviewer/ExtProcess.java b/java/com/tigervnc/vncviewer/ExtProcess.java new file mode 100644 index 00000000..730a7427 --- /dev/null +++ b/java/com/tigervnc/vncviewer/ExtProcess.java @@ -0,0 +1,125 @@ +/* + * 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.vncviewer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; + +import com.tigervnc.rdr.*; +import com.tigervnc.rfb.*; +import com.tigervnc.rfb.Exception; +import com.tigervnc.network.*; + +import static com.tigervnc.vncviewer.Parameters.*; + +public class ExtProcess implements Runnable { + + private String cmd = null; + private LogWriter vlog = null; + private boolean shutdown = false; + private Process pid = null; + + private static class MyProcessLogger extends Thread { + private final BufferedReader err; + private final LogWriter vlog; + + public MyProcessLogger(Process p, LogWriter vlog) { + InputStreamReader reader = + new InputStreamReader(p.getErrorStream()); + err = new BufferedReader(reader); + this.vlog = vlog; + } + + @Override + public void run() { + try { + while (true) { + String msg = err.readLine(); + if (msg != null) + vlog.info(msg); + } + } catch(java.io.IOException e) { + vlog.info(e.getMessage()); + } finally { + try { + if (err != null) + err.close(); + } catch (java.io.IOException e ) { } + } + } + } + + private static class MyShutdownHook extends Thread { + + private Process proc = null; + + public MyShutdownHook(Process p) { + proc = p; + } + + @Override + public void run() { + try { + proc.exitValue(); + } catch (IllegalThreadStateException e) { + try { + // wait for CConn to shutdown the socket + Thread.sleep(500); + } catch(InterruptedException ie) { } + proc.destroy(); + } + } + } + + public ExtProcess(String command, LogWriter vlog, boolean shutdown) { + cmd = command; + this.vlog = vlog; + this.shutdown = shutdown; + } + + public ExtProcess(String command, LogWriter vlog) { + this(command, vlog, false); + } + + public ExtProcess(String command) { + this(command, null, false); + } + + public void run() { + try { + Runtime runtime = Runtime.getRuntime(); + pid = runtime.exec(cmd); + if (shutdown) + runtime.addShutdownHook(new MyShutdownHook(pid)); + if (vlog != null) + new MyProcessLogger(pid, vlog).start(); + pid.waitFor(); + } catch(InterruptedException e) { + vlog.info(e.getMessage()); + } catch(java.io.IOException e) { + vlog.info(e.getMessage()); + } + } + + //static LogWriter vlog = new LogWriter("ExtProcess"); +} diff --git a/java/com/tigervnc/vncviewer/F8Menu.java b/java/com/tigervnc/vncviewer/F8Menu.java index 472f11f8..0c67305a 100644 --- a/java/com/tigervnc/vncviewer/F8Menu.java +++ b/java/com/tigervnc/vncviewer/F8Menu.java @@ -22,48 +22,59 @@ package com.tigervnc.vncviewer; import java.awt.*; import java.awt.Cursor; import java.awt.event.*; +import java.io.File; +import javax.swing.filechooser.*; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JFileChooser; -import javax.swing.JPopupMenu; import javax.swing.JMenuItem; -import javax.swing.JCheckBoxMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; import com.tigervnc.rfb.*; +import static com.tigervnc.vncviewer.Parameters.*; + public class F8Menu extends JPopupMenu implements ActionListener { - public F8Menu(CConn cc_) { + public F8Menu(CConn cc) { super("VNC Menu"); setLightWeightPopupEnabled(false); - cc = cc_; + String os = System.getProperty("os.name"); + if (os.startsWith("Windows")) + com.sun.java.swing.plaf.windows.WindowsLookAndFeel.setMnemonicHidden(false); + this.cc = cc; restore = addMenuItem("Restore",KeyEvent.VK_R); - restore.setEnabled(!cc.viewer.embed.getValue()); + restore.setEnabled(!embed.getValue()); move = addMenuItem("Move"); move.setEnabled(false); size = addMenuItem("Size"); size.setEnabled(false); minimize = addMenuItem("Minimize", KeyEvent.VK_N); - minimize.setEnabled(!cc.viewer.embed.getValue()); + minimize.setEnabled(!embed.getValue()); maximize = addMenuItem("Maximize", KeyEvent.VK_X); - maximize.setEnabled(!cc.viewer.embed.getValue()); + maximize.setEnabled(!embed.getValue()); addSeparator(); exit = addMenuItem("Close Viewer", KeyEvent.VK_C); addSeparator(); - fullScreen = new JCheckBoxMenuItem("Full Screen"); - fullScreen.setMnemonic(KeyEvent.VK_F); - fullScreen.setSelected(cc.fullScreen); - fullScreen.addActionListener(this); - fullScreen.setEnabled(!cc.viewer.embed.getValue()); - add(fullScreen); + fullScreenCheckbox = new JCheckBoxMenuItem("Full Screen"); + fullScreenCheckbox.setMnemonic(KeyEvent.VK_F); + fullScreenCheckbox.setSelected(fullScreen.getValue()); + fullScreenCheckbox.addActionListener(this); + fullScreenCheckbox.setEnabled(!embed.getValue()); + add(fullScreenCheckbox); addSeparator(); clipboard = addMenuItem("Clipboard..."); addSeparator(); - f8 = addMenuItem("Send "+KeyEvent.getKeyText(MenuKey.getMenuKeyCode()), MenuKey.getMenuKeyCode()); + int keyCode = MenuKey.getMenuKeyCode(); + String keyText = KeyEvent.getKeyText(keyCode); + f8 = addMenuItem("Send "+keyText, keyCode); ctrlAltDel = addMenuItem("Send Ctrl-Alt-Del"); addSeparator(); refresh = addMenuItem("Refresh Screen", KeyEvent.VK_H); addSeparator(); newConn = addMenuItem("New connection...", KeyEvent.VK_W); - newConn.setEnabled(!cc.viewer.embed.getValue()); + newConn.setEnabled(!embed.getValue()); options = addMenuItem("Options...", KeyEvent.VK_O); save = addMenuItem("Save connection info as...", KeyEvent.VK_S); info = addMenuItem("Connection info...", KeyEvent.VK_I); @@ -94,19 +105,25 @@ public class F8Menu extends JPopupMenu implements ActionListener { public void actionPerformed(ActionEvent ev) { if (actionMatch(ev, exit)) { cc.close(); - } else if (actionMatch(ev, fullScreen)) { - cc.toggleFullScreen(); + } else if (actionMatch(ev, fullScreenCheckbox)) { + if (fullScreenCheckbox.isSelected()) + cc.desktop.fullscreen_on(); + else + cc.desktop.fullscreen_off(); } else if (actionMatch(ev, restore)) { - if (cc.fullScreen) 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 (cc.fullScreen) 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 (cc.fullScreen) 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)) { - cc.clipboardDialog.showDialog(cc.viewport); + ClipboardDialog.showDialog(cc.desktop); } else if (actionMatch(ev, f8)) { cc.writeKeyEvent(MenuKey.getMenuKeySym(), true); cc.writeKeyEvent(MenuKey.getMenuKeySym(), false); @@ -120,39 +137,67 @@ public class F8Menu extends JPopupMenu implements ActionListener { } else if (actionMatch(ev, refresh)) { cc.refresh(); } else if (actionMatch(ev, newConn)) { - VncViewer.newViewer(cc.viewer); + VncViewer.newViewer(); } else if (actionMatch(ev, options)) { - cc.options.showDialog(cc.viewport); + OptionsDialog.showDialog(cc.desktop); } else if (actionMatch(ev, save)) { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle("Save current configuration as:"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - Window fullScreenWindow = Viewport.getFullScreenWindow(); - if (fullScreenWindow != null) - Viewport.setFullScreenWindow(null); - int ret = fc.showOpenDialog(cc.viewport); - if (fullScreenWindow != null) - Viewport.setFullScreenWindow(fullScreenWindow); - if (ret == JFileChooser.APPROVE_OPTION) { - String filename = fc.getSelectedFile().toString(); - if (filename != null) - Configuration.save(filename); - } + String title = "Save the TigerVNC configuration to file"; + File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc")); + if (!dflt.exists() || !dflt.isFile()) + dflt = new File(FileUtils.getVncHomeDir()); + FileNameExtensionFilter filter = + new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc"); + File f = Dialog.showChooser(title, dflt, this, filter); + while (f != null && f.exists() && f.isFile()) { + String msg = f.getAbsolutePath(); + msg = msg.concat(" already exists. Do you want to overwrite?"); + Object[] options = {"Overwrite", "No \u21B5"}; + JOptionPane op = + new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[1]); + JDialog dlg = op.createDialog(this, "TigerVNC Viewer"); + dlg.setIconImage(VncViewer.frameIcon); + dlg.setAlwaysOnTop(true); + dlg.setVisible(true); + if (op.getValue() == options[0]) + break; + else + f = Dialog.showChooser(title, f, this, filter); + } + if (f != null && (!f.exists() || f.canWrite())) + saveViewerParameters(f.getAbsolutePath(), vncServerName.getValue()); } else if (actionMatch(ev, info)) { cc.showInfo(); } else if (actionMatch(ev, about)) { - cc.showAbout(); + VncViewer.showAbout(cc.desktop); } else if (actionMatch(ev, dismiss)) { firePopupMenuCanceled(); } } + 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; JMenuItem newConn, options, save, info, about, dismiss; static JMenuItem f8; - JCheckBoxMenuItem fullScreen; + JCheckBoxMenuItem fullScreenCheckbox; static LogWriter vlog = new LogWriter("F8Menu"); } diff --git a/java/com/tigervnc/vncviewer/JavaPixelBuffer.java b/java/com/tigervnc/vncviewer/JavaPixelBuffer.java new file mode 100644 index 00000000..b639673c --- /dev/null +++ b/java/com/tigervnc/vncviewer/JavaPixelBuffer.java @@ -0,0 +1,59 @@ +/* Copyright (C) 2012-2016 Brian P. Hinz + * Copyright (C) 2012 D. R. Commander. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +package com.tigervnc.vncviewer; + +import java.awt.*; +import java.awt.image.*; +import java.nio.ByteOrder; + +import com.tigervnc.rfb.*; + +public class JavaPixelBuffer extends PlatformPixelBuffer +{ + + public JavaPixelBuffer(int w, int h) { + super(getPreferredPF(), w, h, + getPreferredPF().getColorModel().createCompatibleWritableRaster(w,h)); + } + + private static PixelFormat getPreferredPF() + { + GraphicsEnvironment ge = + GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice gd = ge.getDefaultScreenDevice(); + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + ColorModel cm = gc.getColorModel(); + int depth = ((cm.getPixelSize() > 24) ? 24 : cm.getPixelSize()); + int bpp = (depth > 16 ? 32 : (depth > 8 ? 16 : 8)); + ByteOrder byteOrder = ByteOrder.nativeOrder(); + boolean bigEndian = (byteOrder == ByteOrder.BIG_ENDIAN ? true : false); + boolean trueColour = true; + int redShift = cm.getComponentSize()[0] + cm.getComponentSize()[1]; + int greenShift = cm.getComponentSize()[0]; + int blueShift = 0; + int redMask = ((int)Math.pow(2, cm.getComponentSize()[2]) - 1); + int greenMask = ((int)Math.pow(2, cm.getComponentSize()[1]) - 1); + int blueMmask = ((int)Math.pow(2, cm.getComponentSize()[0]) - 1); + return new PixelFormat(bpp, depth, bigEndian, trueColour, + redMask, greenMask, blueMmask, + redShift, greenShift, blueShift); + } + +} diff --git a/java/com/tigervnc/vncviewer/OptionsDialog.java b/java/com/tigervnc/vncviewer/OptionsDialog.java index 369b965d..a7c87784 100644 --- a/java/com/tigervnc/vncviewer/OptionsDialog.java +++ b/java/com/tigervnc/vncviewer/OptionsDialog.java @@ -22,14 +22,18 @@ package com.tigervnc.vncviewer; import java.awt.*; import java.awt.event.*; import java.io.File; +import java.lang.reflect.*; import java.text.Format; import java.text.NumberFormat; import javax.swing.*; import javax.swing.border.*; +import javax.swing.filechooser.*; import javax.swing.UIManager.*; import javax.swing.text.*; import java.util.*; +import java.util.List; import java.util.Map.Entry; +import java.util.prefs.*; import com.tigervnc.rfb.*; @@ -44,6 +48,8 @@ import static java.awt.GridBagConstraints.RELATIVE; import static java.awt.GridBagConstraints.REMAINDER; import static java.awt.GridBagConstraints.VERTICAL; +import static com.tigervnc.vncviewer.Parameters.*; + class OptionsDialog extends Dialog { private class IntegerDocument extends PlainDocument { @@ -88,47 +94,558 @@ class OptionsDialog extends Dialog { } } - // Constants - static LogWriter vlog = new LogWriter("OptionsDialog"); + private static Map<Object, String> callbacks = new HashMap<Object, String>(); + /* Compression */ + JCheckBox autoselectCheckbox; + + ButtonGroup encodingGroup; + JRadioButton tightButton; + JRadioButton zrleButton; + JRadioButton hextileButton; + JRadioButton rawButton; + + ButtonGroup colorlevelGroup; + JRadioButton fullcolorButton; + JRadioButton mediumcolorButton; + JRadioButton lowcolorButton; + JRadioButton verylowcolorButton; + + JCheckBox compressionCheckbox; + JCheckBox jpegCheckbox; + JComboBox compressionInput; + JComboBox jpegInput; + + /* Security */ + JCheckBox encNoneCheckbox; + JCheckBox encTLSCheckbox; + JCheckBox encX509Checkbox; + JTextField caInput; + JTextField crlInput; + JButton caChooser; + JButton crlChooser; + + JCheckBox authNoneCheckbox; + JCheckBox authVncCheckbox; + JCheckBox authPlainCheckbox; + JCheckBox authIdentCheckbox; + JCheckBox sendLocalUsernameCheckbox; + + /* Input */ + JCheckBox viewOnlyCheckbox; + JCheckBox acceptClipboardCheckbox; + JCheckBox sendClipboardCheckbox; + JComboBox menuKeyChoice; - CConn cc; - @SuppressWarnings({"rawtypes"}) - JComboBox menuKey, compressLevel, qualityLevel, scalingFactor; - ButtonGroup encodingGroup, colourGroup, sshArgsGroup; - JRadioButton zrle, hextile, tight, raw, fullColour, mediumColour, - lowColour, veryLowColour, sshArgsDefault, sshArgsCustom; - JCheckBox autoSelect, customCompressLevel, noJpeg, viewOnly, - acceptClipboard, sendClipboard, acceptBell, desktopSize, - fullScreen, fullScreenAllMonitors, shared, useLocalCursor, - secVeNCrypt, encNone, encTLS, encX509, secNone, secVnc, - secPlain, secIdent, sendLocalUsername, sshTunnel, sshUseExt, - sshUseGateway; - JButton okButton, cancelButton, caButton, crlButton, cfLoadButton, - cfSaveAsButton, defSaveButton, defReloadButton, defClearButton, - sshConfigBrowser, sshKeyFileBrowser, sshClientBrowser; - JTextField desktopWidth, desktopHeight, x509ca, x509crl, sshUser, sshHost, - sshPort, sshClient, sshArguments, sshConfig, sshKeyFile; - JTabbedPane tabPane; + /* Screen */ + JCheckBox desktopSizeCheckbox; + JTextField desktopWidthInput; + JTextField desktopHeightInput; + + ButtonGroup sizingGroup; + JRadioButton remoteResizeButton; + JRadioButton remoteScaleButton; + JComboBox scalingFactorInput; + + JCheckBox fullScreenCheckbox; + JCheckBox fullScreenAllMonitorsCheckbox; + + /* Misc. */ + JCheckBox sharedCheckbox; + JCheckBox dotWhenNoCursorCheckbox; + JCheckBox acceptBellCheckbox; + + /* SSH */ + JCheckBox tunnelCheckbox; + JCheckBox viaCheckbox; + JTextField viaUserInput; + JTextField viaHostInput; + JTextField viaPortInput; + JCheckBox extSSHCheckbox; + JTextField sshClientInput; + JButton sshClientChooser; + JRadioButton sshArgsDefaultButton; + JRadioButton sshArgsCustomButton; + JTextField sshArgsInput; + JTextField sshConfigInput; + JTextField sshKeyFileInput; + JButton sshConfigChooser; + JButton sshKeyFileChooser; @SuppressWarnings({"rawtypes","unchecked"}) - public OptionsDialog(CConn cc_) { + public OptionsDialog() { super(true); - cc = cc_; setTitle("VNC Viewer Options"); setResizable(false); getContentPane().setLayout( new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS)); - tabPane = new JTabbedPane(); + JTabbedPane tabPane = new JTabbedPane(); tabPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); encodingGroup = new ButtonGroup(); - colourGroup = new ButtonGroup(); - sshArgsGroup = new ButtonGroup(); - int indent = 0; + colorlevelGroup = new ButtonGroup(); + + // tabPane + tabPane.addTab("Compression", createCompressionPanel()); + tabPane.addTab("Security", createSecurityPanel()); + tabPane.addTab("Input", createInputPanel()); + tabPane.addTab("Screen", createScreenPanel()); + tabPane.addTab("Misc", createMiscPanel()); + tabPane.addTab("SSH", createSshPanel()); + tabPane.setBorder(BorderFactory.createEmptyBorder()); + // Resize the tabPane if necessary to prevent scrolling + 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; + if (tabPane.getPreferredSize().width < minWidth) + tabPane.setPreferredSize(new Dimension(minWidth, minHeight)); + + // button pane + JButton okButton = new JButton("OK \u21B5"); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + storeOptions(); + endDialog(); + } + }); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + endDialog(); + } + }); + + JPanel buttonPane = new JPanel(new GridLayout(1, 5, 10, 10)); + 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())); + buttonPane.add(cancelButton); + buttonPane.add(okButton); + + this.add(tabPane); + this.add(buttonPane); + addListeners(this); + pack(); + } + + public static void showDialog(Container c) { + OptionsDialog dialog = new OptionsDialog(); + dialog.show(c); + } + + public void show(Container c) { + loadOptions(); + super.showDialog(c); + } + + public static void addCallback(String cb, Object obj) + { + callbacks.put(obj, cb); + } + + public static void removeCallback(Object obj) + { + callbacks.remove(obj); + } + + public void endDialog() { + super.endDialog(); + // Making a new dialog is so cheap that it's not worth keeping + this.dispose(); + } + + public void setEmbeddedFeatures(boolean s) { + 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); + break; + case Encodings.encodingZRLE: + zrleButton.setSelected(true); + break; + case Encodings.encodingHextile: + hextileButton.setSelected(true); + break; + case Encodings.encodingRaw: + rawButton.setSelected(true); + break; + } + + if (fullColor.getValue()) + fullcolorButton.setSelected(true); + else { + switch (lowColorLevel.getValue()) { + case 0: + verylowcolorButton.setSelected(true); + break; + case 1: + lowcolorButton.setSelected(true); + break; + case 2: + mediumcolorButton.setSelected(true); + break; + } + } + + int digit = 0; + + compressionCheckbox.setSelected(customCompressLevel.getValue()); + jpegCheckbox.setSelected(!noJpeg.getValue()); + digit = 0 + compressLevel.getValue(); + compressionInput.setSelectedItem(digit); + digit = 0 + qualityLevel.getValue(); + jpegInput.setSelectedItem(digit); + + handleAutoselect(); + handleCompression(); + handleJpeg(); + + /* Security */ + Security security = new Security(SecurityClient.secTypes); + + List<Integer> secTypes; + Iterator<Integer> iter; + + List<Integer> secTypesExt; + Iterator<Integer> iterExt; + + 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()) { + case Security.secTypeNone: + encNoneCheckbox.setSelected(true); + authNoneCheckbox.setSelected(true); + break; + case Security.secTypeVncAuth: + encNoneCheckbox.setSelected(true); + authVncCheckbox.setSelected(true); + break; + } + } + + secTypesExt = security.GetEnabledExtSecTypes(); + for (iterExt = secTypesExt.iterator(); iterExt.hasNext(); ) { + switch ((Integer)iterExt.next()) { + case Security.secTypePlain: + encNoneCheckbox.setSelected(true); + authPlainCheckbox.setSelected(true); + break; + case Security.secTypeIdent: + encNoneCheckbox.setSelected(true); + authIdentCheckbox.setSelected(true); + break; + case Security.secTypeTLSNone: + encTLSCheckbox.setSelected(true); + authNoneCheckbox.setSelected(true); + break; + case Security.secTypeTLSVnc: + encTLSCheckbox.setSelected(true); + authVncCheckbox.setSelected(true); + break; + case Security.secTypeTLSPlain: + encTLSCheckbox.setSelected(true); + authPlainCheckbox.setSelected(true); + break; + case Security.secTypeTLSIdent: + encTLSCheckbox.setSelected(true); + authIdentCheckbox.setSelected(true); + break; + case Security.secTypeX509None: + encX509Checkbox.setSelected(true); + authNoneCheckbox.setSelected(true); + break; + case Security.secTypeX509Vnc: + encX509Checkbox.setSelected(true); + authVncCheckbox.setSelected(true); + break; + case Security.secTypeX509Plain: + encX509Checkbox.setSelected(true); + authPlainCheckbox.setSelected(true); + break; + case Security.secTypeX509Ident: + encX509Checkbox.setSelected(true); + authIdentCheckbox.setSelected(true); + break; + } + } + + File caFile = new File(CSecurityTLS.X509CA.getValueStr()); + if (caFile.exists() && caFile.canRead()) + caInput.setText(caFile.getAbsolutePath()); + File crlFile = new File(CSecurityTLS.X509CRL.getValueStr()); + if (crlFile.exists() && crlFile.canRead()) + crlInput.setText(crlFile.getAbsolutePath()); + + handleX509(); + handleSendLocalUsername(); + + /* Input */ + 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; + + if (desktopSize.getValueStr().isEmpty() || + desktopSize.getValueStr().split("x").length != 2) { + desktopSizeCheckbox.setSelected(false); + desktopWidthInput.setText("1024"); + desktopHeightInput.setText("768"); + } else { + desktopSizeCheckbox.setSelected(true); + width = desktopSize.getValueStr().split("x")[0]; + desktopWidthInput.setText(width); + 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()); + + scalingFactorInput.setSelectedItem("100%"); + 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()); + dotWhenNoCursorCheckbox.setSelected(dotWhenNoCursor.getValue()); + acceptBellCheckbox.setSelected(acceptBell.getValue()); + + /* SSH */ + File f; + tunnelCheckbox.setSelected(tunnel.getValue() || !via.getValueStr().isEmpty()); + viaCheckbox.setSelected(!via.getValueStr().isEmpty()); + if (viaCheckbox.isSelected()) { + viaUserInput.setText(Tunnel.getSshUser()); + viaHostInput.setText(Tunnel.getSshHost()); + viaPortInput.setText(Integer.toString(Tunnel.getSshPort())); + } + extSSHCheckbox.setSelected(extSSH.getValue()); + f = new File(extSSHClient.getValueStr()); + if (f.exists() && f.isFile() && f.canExecute()) + sshClientInput.setText(f.getAbsolutePath()); + if (extSSHArgs.getValueStr().isEmpty()) { + sshArgsDefaultButton.setSelected(true); + } else { + sshArgsCustomButton.setSelected(true); + sshArgsInput.setText(extSSHArgs.getValueStr()); + } + f = new File(sshKeyFile.getValueStr()); + if (f.exists() && f.isFile() && f.canRead()) + sshKeyFileInput.setText(f.getAbsolutePath()); + f = new File(sshConfig.getValueStr()); + if (f.exists() && f.isFile() && f.canRead()) + sshConfigInput.setText(f.getAbsolutePath()); + + handleTunnel(); + handleVia(); + handleExtSSH(); + handleEmbed(); + handleRfbState(); + } + + private void storeOptions() { + /* Compression */ + autoSelect.setParam(autoselectCheckbox.isSelected()); + + if (tightButton.isSelected()) + preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingTight)); + else if (zrleButton.isSelected()) + preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingZRLE)); + else if (hextileButton.isSelected()) + preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingHextile)); + else if (rawButton.isSelected()) + preferredEncoding.setParam(Encodings.encodingName(Encodings.encodingRaw)); + + fullColor.setParam(fullcolorButton.isSelected()); + if (verylowcolorButton.isSelected()) + lowColorLevel.setParam(0); + else if (lowcolorButton.isSelected()) + lowColorLevel.setParam(1); + else if (mediumcolorButton.isSelected()) + lowColorLevel.setParam(2); + + customCompressLevel.setParam(compressionCheckbox.isSelected()); + noJpeg.setParam(!jpegCheckbox.isSelected()); + compressLevel.setParam((Integer)compressionInput.getSelectedItem()); + qualityLevel.setParam((Integer)jpegInput.getSelectedItem()); + + /* Security */ + Security security = new Security(); + + /* Process security types which don't use encryption */ + if (encNoneCheckbox.isSelected()) { + if (authNoneCheckbox.isSelected()) + security.EnableSecType(Security.secTypeNone); + if (authVncCheckbox.isSelected()) + security.EnableSecType(Security.secTypeVncAuth); + if (authPlainCheckbox.isSelected()) + security.EnableSecType(Security.secTypePlain); + if (authIdentCheckbox.isSelected()) + security.EnableSecType(Security.secTypeIdent); + } + + /* Process security types which use TLS encryption */ + if (encTLSCheckbox.isSelected()) { + if (authNoneCheckbox.isSelected()) + security.EnableSecType(Security.secTypeTLSNone); + if (authVncCheckbox.isSelected()) + security.EnableSecType(Security.secTypeTLSVnc); + if (authPlainCheckbox.isSelected()) + security.EnableSecType(Security.secTypeTLSPlain); + if (authIdentCheckbox.isSelected()) + security.EnableSecType(Security.secTypeTLSIdent); + } + + /* Process security types which use X509 encryption */ + if (encX509Checkbox.isSelected()) { + if (authNoneCheckbox.isSelected()) + security.EnableSecType(Security.secTypeX509None); + if (authVncCheckbox.isSelected()) + security.EnableSecType(Security.secTypeX509Vnc); + if (authPlainCheckbox.isSelected()) + security.EnableSecType(Security.secTypeX509Plain); + if (authIdentCheckbox.isSelected()) + security.EnableSecType(Security.secTypeX509Ident); + } - // Compression tab + if (authIdentCheckbox.isSelected() || + authPlainCheckbox.isSelected()) { + sendLocalUsername.setParam(sendLocalUsernameCheckbox.isSelected()); + } + + SecurityClient.secTypes.setParam(security.ToString()); + + File caFile = new File(caInput.getText()); + if (caFile.exists() && caFile.canRead()) + CSecurityTLS.X509CA.setParam(caFile.getAbsolutePath()); + 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()); + sendClipboard.setParam(sendClipboardCheckbox.isSelected()); + + String menuKeyStr = + MenuKey.getMenuKeySymbols()[menuKeyChoice.getSelectedIndex()].name; + menuKey.setParam(menuKeyStr); + + /* Screen */ + if (desktopSizeCheckbox.isSelected() && + !desktopWidthInput.getText().isEmpty() && + !desktopHeightInput.getText().isEmpty()) { + String width = desktopWidthInput.getText(); + String height = desktopHeightInput.getText(); + desktopSize.setParam(width.concat("x").concat(height)); + } else { + desktopSize.setParam(""); + } + remoteResize.setParam(remoteResizeButton.isSelected()); + fullScreen.setParam(fullScreenCheckbox.isSelected()); + fullScreenAllMonitors.setParam(fullScreenAllMonitorsCheckbox.isSelected()); + + String scaleStr = + ((String)scalingFactorInput.getSelectedItem()).replace("%", ""); + scaleStr.replace("Fixed Aspect Ratio", "FixedRatio"); + scalingFactor.setParam(scaleStr); + + /* Misc. */ + shared.setParam(sharedCheckbox.isSelected()); + dotWhenNoCursor.setParam(dotWhenNoCursorCheckbox.isSelected()); + acceptBell.setParam(acceptBellCheckbox.isSelected()); + + /* SSH */ + tunnel.setParam(tunnelCheckbox.isSelected()); + if (viaCheckbox.isSelected() && + !viaUserInput.getText().isEmpty() && + !viaHostInput.getText().isEmpty() && + !viaPortInput.getText().isEmpty()) { + String sshUser = viaUserInput.getText(); + String sshHost = viaHostInput.getText(); + String sshPort = viaPortInput.getText(); + String viaStr = sshUser.concat("@").concat(sshHost).concat(":").concat(sshPort); + via.setParam(viaStr); + } + extSSH.setParam(extSSHCheckbox.isSelected()); + if (!sshClientInput.getText().isEmpty()) + extSSHClient.setParam(sshClientInput.getText()); + if (sshArgsDefaultButton.isSelected()) + if (!sshArgsInput.getText().isEmpty()) + extSSHArgs.setParam(sshArgsInput.getText()); + if (!sshConfigInput.getText().isEmpty()) + sshConfig.setParam(sshConfigInput.getText()); + if (!sshKeyFileInput.getText().isEmpty()) + sshKeyFile.setParam(sshKeyFileInput.getText()); + + try { + 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) { + vlog.error("NoSuchMethodException: "+e.getMessage()); + } catch (IllegalAccessException e) { + vlog.error("IllegalAccessException: "+e.getMessage()); + } catch (InvocationTargetException e) { + vlog.error("InvocationTargetException: "+e.getMessage()); + } + } + + private JPanel createCompressionPanel() { JPanel FormatPanel = new JPanel(); FormatPanel.setLayout(new BoxLayout(FormatPanel, BoxLayout.PAGE_AXIS)); @@ -138,61 +655,72 @@ class OptionsDialog extends Dialog { autoSelectPane.setLayout(new BoxLayout(autoSelectPane, BoxLayout.LINE_AXIS)); autoSelectPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); - autoSelect = new JCheckBox("Auto Select"); - autoSelectPane.add(autoSelect); + autoselectCheckbox = new JCheckBox("Auto Select"); + autoselectCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleAutoselect(); + } + }); + autoSelectPane.add(autoselectCheckbox); autoSelectPane.add(Box.createHorizontalGlue()); JPanel encodingPanel = new JPanel(new GridLayout(4, 1)); - encodingPanel. - setBorder(BorderFactory.createTitledBorder("Preferred encoding")); - tight = new GroupedJRadioButton("Tight", - encodingGroup, encodingPanel); - zrle = new GroupedJRadioButton("ZRLE", - encodingGroup, encodingPanel); - hextile = new GroupedJRadioButton("Hextile", - encodingGroup, encodingPanel); - raw = new GroupedJRadioButton("Raw", encodingGroup, encodingPanel); - - JPanel colourPanel = new JPanel(new GridLayout(4, 1)); - colourPanel.setBorder(BorderFactory.createTitledBorder("Color level")); - fullColour = new GroupedJRadioButton("Full (all available colors)", - colourGroup, colourPanel); - mediumColour = new GroupedJRadioButton("Medium (256 colors)", - colourGroup, colourPanel); - lowColour = new GroupedJRadioButton("Low (64 colours)", - colourGroup, colourPanel); - veryLowColour = new GroupedJRadioButton("Very low(8 colors)", - colourGroup, colourPanel); + encodingPanel.setBorder(BorderFactory.createTitledBorder("Preferred encoding")); + tightButton = new GroupedJRadioButton("Tight", encodingGroup, encodingPanel); + zrleButton = new GroupedJRadioButton("ZRLE", encodingGroup, encodingPanel); + hextileButton = new GroupedJRadioButton("Hextile", encodingGroup, encodingPanel); + rawButton = new GroupedJRadioButton("Raw", encodingGroup, encodingPanel); + + JPanel colorPanel = new JPanel(new GridLayout(4, 1)); + colorPanel.setBorder(BorderFactory.createTitledBorder("Color level")); + fullcolorButton = new GroupedJRadioButton("Full (all available colors)", + colorlevelGroup, colorPanel); + mediumcolorButton = new GroupedJRadioButton("Medium (256 colors)", + colorlevelGroup, colorPanel); + lowcolorButton = new GroupedJRadioButton("Low (64 colors)", + colorlevelGroup, colorPanel); + verylowcolorButton = new GroupedJRadioButton("Very low (8 colors)", + colorlevelGroup, colorPanel); JPanel encodingPane = new JPanel(new GridLayout(1, 2, 5, 0)); encodingPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); encodingPane.add(encodingPanel); - encodingPane.add(colourPanel); + encodingPane.add(colorPanel); JPanel tightPanel = new JPanel(new GridBagLayout()); - customCompressLevel = new JCheckBox("Custom Compression Level"); + compressionCheckbox = new JCheckBox("Custom Compression Level"); + compressionCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleCompression(); + } + }); Object[] compressionLevels = { 1, 2, 3, 4, 5, 6 }; - compressLevel = new MyJComboBox(compressionLevels); - ((MyJComboBox)compressLevel).setDocument(new IntegerDocument(1)); - compressLevel.setPrototypeDisplayValue("0."); - compressLevel.setEditable(true); + compressionInput = new MyJComboBox(compressionLevels); + ((MyJComboBox)compressionInput).setDocument(new IntegerDocument(1)); + compressionInput.setPrototypeDisplayValue("0."); + compressionInput.setEditable(true); JLabel compressionLabel = new JLabel("Level (1=fast, 6=best [4-6 are rarely useful])"); - noJpeg = new JCheckBox("Allow JPEG Compression"); + jpegCheckbox = new JCheckBox("Allow JPEG Compression"); + jpegCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleJpeg(); + } + }); Object[] qualityLevels = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - qualityLevel = new MyJComboBox(qualityLevels); - qualityLevel.setPrototypeDisplayValue("0."); + jpegInput = new MyJComboBox(qualityLevels); + jpegInput.setPrototypeDisplayValue("0."); JLabel qualityLabel = new JLabel("Quality (0=poor, 9=best)"); - tightPanel.add(customCompressLevel, + tightPanel.add(compressionCheckbox, new GridBagConstraints(0, 0, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - indent = getButtonLabelInset(customCompressLevel); - tightPanel.add(compressLevel, + int indent = getButtonLabelInset(compressionCheckbox); + tightPanel.add(compressionInput, new GridBagConstraints(0, 1, 1, 1, LIGHT, LIGHT, @@ -206,15 +734,15 @@ class OptionsDialog extends Dialog { LINE_START, HORIZONTAL, new Insets(0, 5, 0, 0), NONE, NONE)); - tightPanel.add(noJpeg, + tightPanel.add(jpegCheckbox, new GridBagConstraints(0, 2, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(5, 0, 0, 0), NONE, NONE)); - indent = getButtonLabelInset(noJpeg); - tightPanel.add(qualityLevel, + indent = getButtonLabelInset(jpegCheckbox); + tightPanel.add(jpegInput, new GridBagConstraints(0, 3, 1, 1, LIGHT, LIGHT, @@ -238,8 +766,10 @@ class OptionsDialog extends Dialog { FormatPanel.add(autoSelectPane); FormatPanel.add(encodingPane); FormatPanel.add(tightPanel); + return FormatPanel; + } - // security tab + private JPanel createSecurityPanel() { JPanel SecPanel = new JPanel(); SecPanel.setLayout(new BoxLayout(SecPanel, BoxLayout.PAGE_AXIS)); @@ -249,46 +779,67 @@ class OptionsDialog extends Dialog { vencryptPane.setLayout(new BoxLayout(vencryptPane, BoxLayout.LINE_AXIS)); vencryptPane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0)); - secVeNCrypt = new JCheckBox("Extended encryption and "+ - "authentication methods (VeNCrypt)"); - vencryptPane.add(secVeNCrypt); - vencryptPane.add(Box.createHorizontalGlue()); JPanel encrPanel = new JPanel(new GridBagLayout()); encrPanel.setBorder(BorderFactory.createTitledBorder("Encryption")); - encNone = new JCheckBox("None"); - encTLS = new JCheckBox("Anonymous TLS"); - encX509 = new JCheckBox("TLS with X.509 certificates"); + encNoneCheckbox = new JCheckBox("None"); + encTLSCheckbox = new JCheckBox("Anonymous TLS"); + encX509Checkbox = new JCheckBox("TLS with X.509 certificates"); + encX509Checkbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleX509(); + } + }); JLabel caLabel = new JLabel("X.509 CA Certificate"); - x509ca = new JTextField(); - x509ca.setName(Configuration.getParam("x509ca").getName()); - caButton = new JButton("Browse"); + caInput = new JTextField(); + caChooser = new JButton("Browse"); + caChooser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComponent c = ((JButton)e.getSource()).getRootPane(); + File dflt = new File(CSecurityTLS.X509CA.getValueStr()); + FileNameExtensionFilter filter = + new FileNameExtensionFilter("X.509 certificate", "crt", "cer", "pem"); + File f = showChooser("Path to X509 CA certificate", dflt, c, filter); + if (f != null && f.exists() && f.canRead()) + caInput.setText(f.getAbsolutePath()); + } + }); JLabel crlLabel = new JLabel("X.509 CRL file"); - x509crl = new JTextField(); - x509crl.setName(Configuration.getParam("x509crl").getName()); - crlButton = new JButton("Browse"); - encrPanel.add(encNone, + crlInput = new JTextField(); + crlChooser = new JButton("Browse"); + crlChooser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComponent c = ((JButton)e.getSource()).getRootPane(); + File dflt = new File(CSecurityTLS.X509CRL.getValueStr()); + FileNameExtensionFilter filter = + new FileNameExtensionFilter("X.509 CRL", "crl"); + File f = showChooser("Path to X509 CRL file", dflt, c, filter); + if (f != null && f.exists() && f.canRead()) + crlInput.setText(f.getAbsolutePath()); + } + }); + encrPanel.add(encNoneCheckbox, new GridBagConstraints(0, 0, REMAINDER, 1, HEAVY, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - encrPanel.add(encTLS, + encrPanel.add(encTLSCheckbox, new GridBagConstraints(0, 1, REMAINDER, 1, HEAVY, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - encrPanel.add(encX509, + encrPanel.add(encX509Checkbox, new GridBagConstraints(0, 2, 3, 1, HEAVY, LIGHT, LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - indent = getButtonLabelInset(encX509); + int indent = getButtonLabelInset(encX509Checkbox); encrPanel.add(caLabel, new GridBagConstraints(0, 3, 1, 1, @@ -296,14 +847,14 @@ class OptionsDialog extends Dialog { LINE_END, NONE, new Insets(0, indent, 5, 0), 0, 0)); - encrPanel.add(x509ca, + encrPanel.add(caInput, new GridBagConstraints(1, 3, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 5, 5, 0), 0, 0)); - encrPanel.add(caButton, + encrPanel.add(caChooser, new GridBagConstraints(2, 3, 1, 1, LIGHT, LIGHT, @@ -317,14 +868,14 @@ class OptionsDialog extends Dialog { LINE_END, NONE, new Insets(0, indent, 0, 0), 0, 0)); - encrPanel.add(x509crl, + encrPanel.add(crlInput, new GridBagConstraints(1, 4, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 5, 0, 0), 0, 0)); - encrPanel.add(crlButton, + encrPanel.add(crlChooser, new GridBagConstraints(2, 4, 1, 1, LIGHT, LIGHT, @@ -335,40 +886,50 @@ class OptionsDialog extends Dialog { JPanel authPanel = new JPanel(new GridBagLayout()); authPanel.setBorder(BorderFactory.createTitledBorder("Authentication")); - secNone = new JCheckBox("None"); - secVnc = new JCheckBox("Standard VNC"); - secPlain = new JCheckBox("Plaintext"); - secIdent = new JCheckBox("Ident"); - sendLocalUsername = new JCheckBox("Send Local Username"); - authPanel.add(secNone, + authNoneCheckbox = new JCheckBox("None"); + authVncCheckbox = new JCheckBox("Standard VNC"); + authPlainCheckbox = new JCheckBox("Plaintext"); + authPlainCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleSendLocalUsername(); + } + }); + authIdentCheckbox = new JCheckBox("Ident"); + authIdentCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleSendLocalUsername(); + } + }); + sendLocalUsernameCheckbox = new JCheckBox("Send Local Username"); + authPanel.add(authNoneCheckbox, new GridBagConstraints(0, 0, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - authPanel.add(secVnc, + authPanel.add(authVncCheckbox, new GridBagConstraints(0, 1, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - authPanel.add(secPlain, + authPanel.add(authPlainCheckbox, new GridBagConstraints(0, 2, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 2, 0), NONE, NONE)); - authPanel.add(secIdent, + authPanel.add(authIdentCheckbox, new GridBagConstraints(0, 3, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(2, 0, 0, 0), NONE, NONE)); - authPanel.add(sendLocalUsername, + authPanel.add(sendLocalUsernameCheckbox, new GridBagConstraints(1, 2, 1, 2, HEAVY, LIGHT, @@ -404,35 +965,40 @@ class OptionsDialog extends Dialog { LINE_START, BOTH, new Insets(0, 0, 0, 0), NONE, NONE)); + return SecPanel; + } - // Input tab + private JPanel createInputPanel() { JPanel inputPanel = new JPanel(new GridBagLayout()); inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - viewOnly = new JCheckBox("View Only (ignore mouse & keyboard)"); - acceptClipboard = new JCheckBox("Accept clipboard from server"); - sendClipboard = new JCheckBox("Send clipboard to server"); - JLabel menuKeyLabel = new JLabel("Menu Key"); + viewOnlyCheckbox = new JCheckBox("View only (ignore mouse and keyboard)"); + acceptClipboardCheckbox = new JCheckBox("Accept clipboard from server"); + sendClipboardCheckbox = new JCheckBox("Send clipboard to server"); + JLabel menuKeyLabel = new JLabel("Menu key"); String[] menuKeys = new String[MenuKey.getMenuKeySymbolCount()]; + //String[] menuKeys = new String[MenuKey.getMenuKeySymbolCount()+1]; + //menuKeys[0] = "None"; for (int i = 0; i < MenuKey.getMenuKeySymbolCount(); i++) menuKeys[i] = MenuKey.getKeyText(MenuKey.getMenuKeySymbols()[i]); - menuKey = new JComboBox(menuKeys); + //menuKeys[i+1] = MenuKey.getKeyText(MenuKey.getMenuKeySymbols()[i]); + menuKeyChoice = new JComboBox(menuKeys); - inputPanel.add(viewOnly, + inputPanel.add(viewOnlyCheckbox, new GridBagConstraints(0, 0, REMAINDER, 1, HEAVY, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - inputPanel.add(acceptClipboard, + inputPanel.add(acceptClipboardCheckbox, new GridBagConstraints(0, 1, REMAINDER, 1, HEAVY, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - inputPanel.add(sendClipboard, + inputPanel.add(sendClipboardCheckbox, new GridBagConstraints(0, 2, REMAINDER, 1, HEAVY, LIGHT, @@ -446,7 +1012,7 @@ class OptionsDialog extends Dialog { LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - inputPanel.add(menuKey, + inputPanel.add(menuKeyChoice, new GridBagConstraints(1, 3, 1, 1, HEAVY, LIGHT, @@ -460,108 +1026,147 @@ class OptionsDialog extends Dialog { LINE_START, BOTH, new Insets(0, 0, 0, 0), NONE, NONE)); + return inputPanel; + } - // Screen tab + private JPanel createScreenPanel() { JPanel ScreenPanel = new JPanel(new GridBagLayout()); ScreenPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - desktopSize = new JCheckBox("Resize remote session on connect"); - desktopSize.setEnabled(!cc.viewer.embed.getValue() && - (cc.viewer.desktopSize.getValue() != null)); - desktopWidth = new IntegerTextField(5); - desktopWidth.setEnabled(desktopSize.isSelected()); - desktopHeight = new IntegerTextField(5); - desktopHeight.setEnabled(desktopSize.isSelected()); + + 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) { + handleDesktopSize(); + } + }); + desktopWidthInput = new IntegerTextField(5); + desktopHeightInput = new IntegerTextField(5); JPanel desktopSizePanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0)); - desktopSizePanel.add(desktopWidth); + desktopSizePanel.add(desktopWidthInput); desktopSizePanel.add(new JLabel(" x ")); - desktopSizePanel.add(desktopHeight); - fullScreen = new JCheckBox("Full-screen mode"); - fullScreen.setEnabled(!cc.viewer.embed.getValue()); - fullScreenAllMonitors = - new JCheckBox("Enable full-screen mode over all monitors"); - fullScreenAllMonitors.setEnabled(!cc.viewer.embed.getValue()); + desktopSizePanel.add(desktopHeightInput); + 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%" }; - scalingFactor = new MyJComboBox(scalingFactors); - scalingFactor.setEditable(true); - scalingFactor.setEnabled(!cc.viewer.embed.getValue()); - ScreenPanel.add(desktopSize, + scalingFactorInput = new MyJComboBox(scalingFactors); + scalingFactorInput.setEditable(true); + 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, LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - indent = getButtonLabelInset(desktopSize); - ScreenPanel.add(desktopSizePanel, + int indent = getButtonLabelInset(desktopSizeCheckbox); + SizingPanel.add(desktopSizePanel, new GridBagConstraints(0, 1, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, indent, 0, 0), NONE, NONE)); - ScreenPanel.add(fullScreen, + SizingPanel.add(remoteResizeButton, new GridBagConstraints(0, 2, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - indent = getButtonLabelInset(fullScreen); - ScreenPanel.add(fullScreenAllMonitors, + 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(scalingFactor, + 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, new Insets(0, 0, 0, 0), NONE, NONE)); + return ScreenPanel; + } - // Misc tab + private JPanel createMiscPanel() { JPanel MiscPanel = new JPanel(new GridBagLayout()); MiscPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - shared = - new JCheckBox("Shared connection (do not disconnect other viewers)"); - useLocalCursor = new JCheckBox("Render cursor locally"); - acceptBell = new JCheckBox("Beep when requested by the server"); - MiscPanel.add(shared, + sharedCheckbox = + new JCheckBox("Shared (don't disconnect other viewers)"); + dotWhenNoCursorCheckbox = new JCheckBox("Show dot when no cursor"); + acceptBellCheckbox = new JCheckBox("Beep when requested by the server"); + MiscPanel.add(sharedCheckbox, new GridBagConstraints(0, 0, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - MiscPanel.add(useLocalCursor, + MiscPanel.add(dotWhenNoCursorCheckbox, new GridBagConstraints(0, 1, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - MiscPanel.add(acceptBell, + MiscPanel.add(acceptBellCheckbox, new GridBagConstraints(0, 2, 1, 1, LIGHT, LIGHT, @@ -575,52 +1180,101 @@ class OptionsDialog extends Dialog { LINE_START, BOTH, new Insets(0, 0, 0, 0), NONE, NONE)); + return MiscPanel; + } - // SSH tab + private JPanel createSshPanel() { JPanel sshPanel = new JPanel(new GridBagLayout()); sshPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - sshTunnel = new JCheckBox("Tunnel VNC over SSH"); + ButtonGroup sshArgsGroup = new ButtonGroup(); + tunnelCheckbox = new JCheckBox("Tunnel VNC over SSH"); + tunnelCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleTunnel(); + } + }); JPanel tunnelPanel = new JPanel(new GridBagLayout()); - sshUseGateway = new JCheckBox("Use SSH gateway"); + viaCheckbox = new JCheckBox("Use SSH gateway"); + viaCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleVia(); + } + }); JLabel sshUserLabel = new JLabel("Username"); - sshUser = new JTextField(); + viaUserInput = new JTextField(); JLabel sshUserAtLabel = new JLabel("@"); JLabel sshHostLabel = new JLabel("Hostname (or IP address)"); - sshHost = new JTextField(""); + viaHostInput = new JTextField(""); JLabel sshPortLabel = new JLabel("Port"); - sshPort = new IntegerTextField(5); + viaPortInput = new IntegerTextField(5); - sshUseExt = new JCheckBox("Use external SSH client"); - sshClient = new JTextField(); - sshClient.setName(Configuration.getParam("extSSHClient").getName()); - sshClientBrowser = new JButton("Browse"); + extSSHCheckbox = new JCheckBox("Use external SSH client"); + extSSHCheckbox.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + handleExtSSH(); + } + }); + sshClientInput = new JTextField(); + sshClientChooser = new JButton("Browse"); + sshClientChooser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComponent c = ((JButton)e.getSource()).getRootPane(); + File dflt = new File(extSSHClient.getValueStr()); + File f = showChooser("Path to external SSH client", dflt, c); + if (f != null && f.exists() && f.isFile() && f.canExecute()) + sshClientInput.setText(f.getAbsolutePath()); + } + }); JLabel sshConfigLabel = new JLabel("SSH config file"); - sshConfig = new JTextField(); - sshConfig.setName(Configuration.getParam("sshConfig").getName()); - sshConfigBrowser = new JButton("Browse"); + sshConfigInput = new JTextField(); + sshConfigChooser = new JButton("Browse"); + sshConfigChooser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComponent c = ((JButton)e.getSource()).getRootPane(); + File dflt = new File(sshConfig.getValueStr()); + File f = showChooser("Path to OpenSSH client config file", dflt, c); + if (f != null && f.exists() && f.isFile() && f.canRead()) + sshConfigInput.setText(f.getAbsolutePath()); + } + }); JLabel sshKeyFileLabel = new JLabel("SSH identity file"); - sshKeyFile = new JTextField(); - sshKeyFile.setName(Configuration.getParam("sshKeyFile").getName()); - sshKeyFileBrowser = new JButton("Browse"); + sshKeyFileInput = new JTextField(); + sshKeyFileChooser = new JButton("Browse"); + sshKeyFileChooser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComponent c = ((JButton)e.getSource()).getRootPane(); + File f = showChooser("Path to SSH key file", null, c); + if (f != null && f.exists() && f.isFile() && f.canRead()) + sshKeyFileInput.setText(f.getAbsolutePath()); + } + }); JPanel sshArgsPanel = new JPanel(new GridBagLayout()); JLabel sshArgsLabel = new JLabel("Arguments:"); - sshArgsDefault = - new GroupedJRadioButton("Default", sshArgsGroup, sshArgsPanel); - sshArgsCustom = - new GroupedJRadioButton("Custom", sshArgsGroup, sshArgsPanel); - sshArguments = new JTextField(); + sshArgsDefaultButton = new GroupedJRadioButton("Default", sshArgsGroup, sshArgsPanel); + sshArgsDefaultButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sshArgsInput.setEnabled(sshArgsCustomButton.isSelected()); + } + }); + sshArgsCustomButton = new GroupedJRadioButton("Custom", sshArgsGroup, sshArgsPanel); + sshArgsCustomButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sshArgsInput.setEnabled(sshArgsCustomButton.isSelected()); + } + }); + sshArgsInput = new JTextField(); JPanel gatewayPanel = new JPanel(new GridBagLayout()); - gatewayPanel.add(sshUseGateway, + gatewayPanel.add(viaCheckbox, new GridBagConstraints(0, 0, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - indent = getButtonLabelInset(sshUseGateway); + int indent = getButtonLabelInset(viaCheckbox); gatewayPanel.add(sshUserLabel, new GridBagConstraints(0, 1, 1, 1, @@ -642,7 +1296,7 @@ class OptionsDialog extends Dialog { LINE_START, HORIZONTAL, new Insets(0, 5, 4, 0), NONE, NONE)); - gatewayPanel.add(sshUser, + gatewayPanel.add(viaUserInput, new GridBagConstraints(0, 2, 1, 1, LIGHT, LIGHT, @@ -656,14 +1310,14 @@ class OptionsDialog extends Dialog { LINE_START, HORIZONTAL, new Insets(0, 2, 0, 2), NONE, NONE)); - gatewayPanel.add(sshHost, + gatewayPanel.add(viaHostInput, new GridBagConstraints(2, 2, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 0, 0, 0), NONE, NONE)); - gatewayPanel.add(sshPort, + gatewayPanel.add(viaPortInput, new GridBagConstraints(3, 2, 1, 1, LIGHT, LIGHT, @@ -672,21 +1326,21 @@ class OptionsDialog extends Dialog { NONE, NONE)); JPanel clientPanel = new JPanel(new GridBagLayout()); - clientPanel.add(sshUseExt, + clientPanel.add(extSSHCheckbox, new GridBagConstraints(0, 0, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - clientPanel.add(sshClient, + clientPanel.add(sshClientInput, new GridBagConstraints(1, 0, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 5, 0, 0), NONE, NONE)); - clientPanel.add(sshClientBrowser, + clientPanel.add(sshClientChooser, new GridBagConstraints(2, 0, 1, 1, LIGHT, LIGHT, @@ -700,28 +1354,28 @@ class OptionsDialog extends Dialog { LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - sshArgsPanel.add(sshArgsDefault, + sshArgsPanel.add(sshArgsDefaultButton, new GridBagConstraints(1, 1, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 5, 0, 0), NONE, NONE)); - sshArgsPanel.add(sshArgsCustom, + sshArgsPanel.add(sshArgsCustomButton, new GridBagConstraints(2, 1, 1, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 5, 0, 0), NONE, NONE)); - sshArgsPanel.add(sshArguments, + sshArgsPanel.add(sshArgsInput, new GridBagConstraints(3, 1, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 5, 0, 0), NONE, NONE)); - indent = getButtonLabelInset(sshUseExt); + indent = getButtonLabelInset(extSSHCheckbox); clientPanel.add(sshArgsPanel, new GridBagConstraints(0, 1, REMAINDER, 1, @@ -731,7 +1385,9 @@ class OptionsDialog extends Dialog { NONE, NONE)); JPanel opensshPanel = new JPanel(new GridBagLayout()); - opensshPanel.setBorder(BorderFactory.createTitledBorder("Embedded SSH client configuration")); + TitledBorder border = + BorderFactory.createTitledBorder("Embedded SSH client configuration"); + opensshPanel.setBorder(border); opensshPanel.add(sshConfigLabel, new GridBagConstraints(0, 0, 1, 1, @@ -739,14 +1395,14 @@ class OptionsDialog extends Dialog { LINE_START, NONE, new Insets(0, 0, 5, 0), NONE, NONE)); - opensshPanel.add(sshConfig, + opensshPanel.add(sshConfigInput, new GridBagConstraints(1, 0, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 5, 5, 0), NONE, NONE)); - opensshPanel.add(sshConfigBrowser, + opensshPanel.add(sshConfigChooser, new GridBagConstraints(2, 0, 1, 1, LIGHT, LIGHT, @@ -760,14 +1416,14 @@ class OptionsDialog extends Dialog { LINE_START, NONE, new Insets(0, 0, 0, 0), NONE, NONE)); - opensshPanel.add(sshKeyFile, + opensshPanel.add(sshKeyFileInput, new GridBagConstraints(1, 1, 1, 1, HEAVY, LIGHT, LINE_START, HORIZONTAL, new Insets(0, 5, 0, 0), NONE, NONE)); - opensshPanel.add(sshKeyFileBrowser, + opensshPanel.add(sshKeyFileChooser, new GridBagConstraints(2, 1, 1, 1, LIGHT, LIGHT, @@ -796,14 +1452,14 @@ class OptionsDialog extends Dialog { new Insets(0, 0, 0, 0), NONE, NONE)); - sshPanel.add(sshTunnel, + sshPanel.add(tunnelCheckbox, new GridBagConstraints(0, 0, REMAINDER, 1, LIGHT, LIGHT, LINE_START, NONE, new Insets(0, 0, 4, 0), NONE, NONE)); - indent = getButtonLabelInset(sshTunnel); + indent = getButtonLabelInset(tunnelCheckbox); sshPanel.add(tunnelPanel, new GridBagConstraints(0, 2, REMAINDER, 1, @@ -818,601 +1474,149 @@ class OptionsDialog extends Dialog { LINE_START, BOTH, new Insets(0, 0, 0, 0), NONE, NONE)); + return sshPanel; + } - // load/save tab - JPanel loadSavePanel = new JPanel(new GridBagLayout()); - loadSavePanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5)); - JPanel configPanel = new JPanel(new GridBagLayout()); - configPanel. - setBorder(BorderFactory.createTitledBorder("Configuration File")); - cfLoadButton = new JButton("Load"); - cfSaveAsButton = new JButton("Save As..."); - configPanel.add(cfLoadButton, - new GridBagConstraints(0, 0, - 1, 1, - HEAVY, LIGHT, - CENTER, HORIZONTAL, - new Insets(0, 0, 5, 0), - NONE, NONE)); - configPanel.add(cfSaveAsButton, - new GridBagConstraints(0, 1, - 1, 1, - HEAVY, HEAVY, - CENTER, HORIZONTAL, - new Insets(0, 0, 0, 0), - NONE, NONE)); + private void handleAutoselect() + { + ButtonGroup[] groups = { encodingGroup, colorlevelGroup }; + for (ButtonGroup grp : groups) { + Enumeration<AbstractButton> elems = grp.getElements(); + while (elems.hasMoreElements()) + elems.nextElement().setEnabled(!autoselectCheckbox.isSelected()); + } - JPanel defaultsPanel = new JPanel(new GridBagLayout()); - defaultsPanel.setBorder(BorderFactory.createTitledBorder("Defaults")); - defClearButton = new JButton("Clear"); - defReloadButton = new JButton("Reload"); - defSaveButton = new JButton("Save"); - defaultsPanel.add(defClearButton, - new GridBagConstraints(0, 0, - 1, 1, - HEAVY, LIGHT, - CENTER, HORIZONTAL, - new Insets(0, 0, 5, 0), - NONE, NONE)); - defaultsPanel.add(defReloadButton, - new GridBagConstraints(0, 1, - 1, 1, - HEAVY, LIGHT, - CENTER, HORIZONTAL, - new Insets(0, 0, 5, 0), - NONE, NONE)); - defaultsPanel.add(defSaveButton, - new GridBagConstraints(0, 2, - 1, 1, - HEAVY, HEAVY, - CENTER, HORIZONTAL, - new Insets(0, 0, 0, 0), - NONE, NONE)); - - loadSavePanel.add(configPanel, - new GridBagConstraints(0, 0, - 1, 1, - HEAVY, LIGHT, - PAGE_START, HORIZONTAL, - new Insets(0, 0, 0, 0), - NONE, NONE)); - loadSavePanel.add(Box.createRigidArea(new Dimension(5, 0)), - new GridBagConstraints(1, 1, - 1, 1, - LIGHT, LIGHT, - LINE_START, NONE, - new Insets(0, 0, 0, 0), - NONE, NONE)); - loadSavePanel.add(defaultsPanel, - new GridBagConstraints(2, 0, - 1, 1, - HEAVY, LIGHT, - PAGE_START, HORIZONTAL, - new Insets(0, 0, 0, 0), - NONE, NONE)); - loadSavePanel.add(Box.createRigidArea(new Dimension(5, 0)), - new GridBagConstraints(0, 1, - REMAINDER, REMAINDER, - HEAVY, HEAVY, - LINE_START, BOTH, - new Insets(0, 0, 0, 0), - NONE, NONE)); + // JPEG setting is also affected by autoselection + jpegCheckbox.setEnabled(!autoselectCheckbox.isSelected()); + handleJpeg(); + } - // tabPane - tabPane.addTab("Compression", FormatPanel); - tabPane.addTab("Security", SecPanel); - tabPane.addTab("Input", inputPanel); - tabPane.addTab("Screen", ScreenPanel); - tabPane.addTab("Misc", MiscPanel); - tabPane.addTab("SSH", sshPanel); - tabPane.addTab("Load / Save", loadSavePanel); - tabPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - // Resize the tabPane if necessary to prevent scrolling - Insets tpi = - (Insets)UIManager.get("TabbedPane:TabbedPaneTabArea.contentMargins"); - int minWidth = tpi.left + tpi.right; - for (int i = 0; i < tabPane.getTabCount(); i++) - minWidth += tabPane.getBoundsAt(i).width; - int minHeight = tabPane.getPreferredSize().height; - if (tabPane.getPreferredSize().width < minWidth) - tabPane.setPreferredSize(new Dimension(minWidth, minHeight)); + private void handleCompression() + { + compressionInput.setEnabled(compressionCheckbox.isSelected()); + } - // button pane - okButton = new JButton("OK"); - cancelButton = new JButton("Cancel"); + private void handleJpeg() + { + if (jpegCheckbox.isSelected() && + !autoselectCheckbox.isSelected()) + jpegInput.setEnabled(true); + else + jpegInput.setEnabled(false); + } - JPanel buttonPane = new JPanel(new GridLayout(1, 5, 10, 10)); - 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())); - buttonPane.add(okButton); - buttonPane.add(cancelButton); + private void handleX509() + { + caInput.setEnabled(encX509Checkbox.isSelected()); + caChooser.setEnabled(encX509Checkbox.isSelected()); + crlInput.setEnabled(encX509Checkbox.isSelected()); + crlChooser.setEnabled(encX509Checkbox.isSelected()); + } - this.add(tabPane); - this.add(buttonPane); - addListeners(this); - pack(); + private void handleSendLocalUsername() + { + boolean value = authIdentCheckbox.isSelected() || + authPlainCheckbox.isSelected(); + sendLocalUsernameCheckbox.setEnabled(value); } - public void initDialog() { - if (cc != null) cc.setOptions(); - zrle.setEnabled(!autoSelect.isSelected()); - hextile.setEnabled(!autoSelect.isSelected()); - tight.setEnabled(!autoSelect.isSelected()); - raw.setEnabled(!autoSelect.isSelected()); - fullColour.setEnabled(!autoSelect.isSelected()); - mediumColour.setEnabled(!autoSelect.isSelected()); - lowColour.setEnabled(!autoSelect.isSelected()); - veryLowColour.setEnabled(!autoSelect.isSelected()); - compressLevel.setEnabled(customCompressLevel.isSelected()); - qualityLevel.setEnabled(noJpeg.isSelected()); - sendLocalUsername.setEnabled(secVeNCrypt.isEnabled() && - (secPlain.isSelected() || secIdent.isSelected())); - sshArguments.setEnabled(sshTunnel.isSelected() && - (sshUseExt.isSelected() && sshArgsCustom.isSelected())); + private void handleDesktopSize() + { + desktopWidthInput.setEnabled(desktopSizeCheckbox.isSelected()); + desktopHeightInput.setEnabled(desktopSizeCheckbox.isSelected()); } - private void updatePreferences() { - if (autoSelect.isSelected()) { - UserPreferences.set("global", "AutoSelect", true); - } else { - UserPreferences.set("global", "AutoSelect", false); - if (zrle.isSelected()) { - UserPreferences.set("global", "PreferredEncoding", "ZRLE"); - } else if (hextile.isSelected()) { - UserPreferences.set("global", "PreferredEncoding", "hextile"); - } else if (tight.isSelected()) { - UserPreferences.set("global", "PreferredEncoding", "Tight"); - } else if (raw.isSelected()) { - UserPreferences.set("global", "PreferredEncoding", "raw"); - } - } - if (fullColour.isSelected()) { - UserPreferences.set("global", "FullColour", true); - } else { - UserPreferences.set("global", "FullColour", false); - if (mediumColour.isSelected()) { - UserPreferences.set("global", "LowColorLevel", 2); - } else if (lowColour.isSelected()) { - UserPreferences.set("global", "LowColorLevel", 1); - } else if (veryLowColour.isSelected()) { - UserPreferences.set("global", "LowColorLevel", 0); - } - } - UserPreferences.set("global", "NoJPEG", !noJpeg.isSelected()); - UserPreferences.set("global", - "QualityLevel", (Integer)qualityLevel.getSelectedItem()); - UserPreferences.set("global", - "CustomCompressLevel", customCompressLevel.isSelected()); - UserPreferences.set("global", - "CompressLevel", (Integer)compressLevel.getSelectedItem()); - UserPreferences.set("global", "ViewOnly", viewOnly.isSelected()); - UserPreferences.set("global", - "AcceptClipboard", acceptClipboard.isSelected()); - UserPreferences.set("global", "SendClipboard", sendClipboard.isSelected()); - String menuKeyStr = - MenuKey.getMenuKeySymbols()[menuKey.getSelectedIndex()].name; - UserPreferences.set("global", "MenuKey", menuKeyStr); - String desktopSizeString = - desktopSize.isSelected() ? - desktopWidth.getText() + "x" + desktopHeight.getText() : ""; - UserPreferences.set("global", "DesktopSize", desktopSizeString); - UserPreferences.set("global", "FullScreen", fullScreen.isSelected()); - UserPreferences.set("global", - "FullScreenAllMonitors", fullScreenAllMonitors.isSelected()); - UserPreferences.set("global", "Shared", shared.isSelected()); - UserPreferences.set("global", - "UseLocalCursor", useLocalCursor.isSelected()); - UserPreferences.set("global", "AcceptBell", acceptBell.isSelected()); - String scaleString = scalingFactor.getSelectedItem().toString(); - if (scaleString.equalsIgnoreCase("Auto")) { - UserPreferences.set("global", "ScalingFactor", "Auto"); - } else if(scaleString.equalsIgnoreCase("Fixed Aspect Ratio")) { - UserPreferences.set("global", "ScalingFactor", "FixedRatio"); + private void handleRemoteResize() + { + scalingFactorInput.setEnabled(!remoteResizeButton.isSelected()); + } + + private void handleTunnel() + { + viaCheckbox.setEnabled(tunnelCheckbox.isSelected()); + extSSHCheckbox.setEnabled(tunnelCheckbox.isSelected()); + if (tunnelCheckbox.isSelected()) { + JComponent[] components = { viaUserInput, viaHostInput, viaPortInput }; + for (JComponent c : components) + c.setEnabled(viaCheckbox.isSelected()); + sshClientInput.setEnabled(extSSHCheckbox.isSelected()); + sshClientChooser.setEnabled(extSSHCheckbox.isSelected()); + sshArgsDefaultButton.setEnabled(extSSHCheckbox.isSelected()); + sshArgsCustomButton.setEnabled(extSSHCheckbox.isSelected()); + sshArgsInput.setEnabled(extSSHCheckbox.isSelected()); + sshConfigInput.setEnabled(!extSSHCheckbox.isSelected()); + sshConfigChooser.setEnabled(!extSSHCheckbox.isSelected()); + sshKeyFileInput.setEnabled(!extSSHCheckbox.isSelected()); + sshKeyFileChooser.setEnabled(!extSSHCheckbox.isSelected()); } else { - scaleString=scaleString.substring(0, scaleString.length()-1); - UserPreferences.set("global", "ScalingFactor", scaleString); - } - UserPreferences.set("viewer", "secVeNCrypt", secVeNCrypt.isSelected()); - UserPreferences.set("viewer", "encNone", encNone.isSelected()); - UserPreferences.set("viewer", "encTLS", encTLS.isSelected()); - UserPreferences.set("viewer", "encX509", encX509.isSelected()); - UserPreferences.set("viewer", "secNone", secNone.isSelected()); - UserPreferences.set("viewer", "secVnc", secVnc.isSelected()); - UserPreferences.set("viewer", "secPlain", secPlain.isSelected()); - UserPreferences.set("viewer", "secIdent", secIdent.isSelected()); - UserPreferences.set("global", - "SendLocalUsername", sendLocalUsername.isSelected()); - if (!CSecurityTLS.x509ca.getValueStr().equals("")) - UserPreferences.set("viewer", "x509ca", - CSecurityTLS.x509ca.getValueStr()); - if (!CSecurityTLS.x509crl.getValueStr().equals("")) - UserPreferences.set("viewer", "x509crl", - CSecurityTLS.x509crl.getValueStr()); - UserPreferences.set("global", "Tunnel", sshTunnel.isSelected()); - if (sshUseGateway.isSelected()) { - String via = sshUser.getText()+"@"+sshHost.getText()+":"+sshPort.getText(); - UserPreferences.set("global", "Via", via); + JComponent[] components = { + viaUserInput, viaHostInput, viaPortInput, sshClientInput, + sshClientChooser, sshArgsDefaultButton, sshArgsCustomButton, + sshArgsInput, sshConfigInput, sshConfigChooser, sshKeyFileInput, + sshKeyFileChooser, }; + for (JComponent c : components) + c.setEnabled(false); } - if (sshUseExt.isSelected()) { - UserPreferences.set("global", "extSSH", sshUseExt.isSelected()); - UserPreferences.set("global", "extSSHClient", sshClient.getText()); - if (!sshArguments.getText().isEmpty()) - UserPreferences.set("global", "extSSHArgs", sshArguments.getText()); - } - UserPreferences.set("global", "SSHConfig", sshConfig.getText()); - UserPreferences.set("global", "SSHKeyFile", sshKeyFile.getText()); } - private void restorePreferences() { - autoSelect.setSelected(UserPreferences.getBool("global", "AutoSelect")); - if (!autoSelect.isSelected()) { - if (UserPreferences.getBool("global", "FullColour")) { - fullColour.setSelected(true); - } else { - switch (UserPreferences.getInt("global", "LowColorLevel")) { - case 2: - mediumColour.setSelected(true); - break; - case 1: - lowColour.setSelected(true); - break; - case 0: - veryLowColour.setSelected(true); - break; - } - } - String encoding = UserPreferences.get("global", "PreferredEncoding"); - if (encoding != null) { - switch (Encodings.encodingNum(encoding)) { - case Encodings.encodingZRLE: - zrle.setSelected(true); - break; - case Encodings.encodingHextile: - hextile.setSelected(true); - break; - case Encodings.encodingRaw: - raw.setSelected(true); - break; - default: - tight.setSelected(true); - } - } - } - noJpeg.setSelected(!UserPreferences.getBool("global", "NoJPEG")); - qualityLevel.setSelectedItem(UserPreferences.getInt("global", - "QualityLevel")); - customCompressLevel.setSelected(UserPreferences.getBool("global", - "CustomCompressLevel")); - compressLevel.setSelectedItem(UserPreferences.getInt("global", - "CompressLevel")); - viewOnly.setSelected(UserPreferences.getBool("global", "ViewOnly")); - acceptClipboard.setSelected(UserPreferences.getBool("global", - "AcceptClipboard")); - sendClipboard.setSelected(UserPreferences.getBool("global", - "SendClipboard")); - menuKey.setSelectedItem(UserPreferences.get("global", "MenuKey")); - desktopSize.setSelected(!UserPreferences.get("global", "DesktopSize").isEmpty()); - if (desktopSize.isSelected()) { - String desktopSizeString = UserPreferences.get("global", "DesktopSize"); - desktopWidth.setText(desktopSizeString.split("x")[0]); - desktopHeight.setText(desktopSizeString.split("x")[1]); + private void handleVia() + { + if (tunnelCheckbox.isSelected()) { + viaUserInput.setEnabled(viaCheckbox.isSelected()); + viaHostInput.setEnabled(viaCheckbox.isSelected()); + viaPortInput.setEnabled(viaCheckbox.isSelected()); } - fullScreen.setSelected(UserPreferences.getBool("global", "FullScreen")); - fullScreenAllMonitors.setSelected(UserPreferences.getBool("global", - "FullScreenAllMonitors")); - if (shared.isEnabled()) - shared.setSelected(UserPreferences.getBool("global", "Shared")); - useLocalCursor.setSelected(UserPreferences.getBool("global", - "UseLocalCursor")); - acceptBell.setSelected(UserPreferences.getBool("global", "AcceptBell")); - String scaleString = UserPreferences.get("global", "ScalingFactor"); - if (scaleString != null) { - if (scaleString.equalsIgnoreCase("Auto")) { - scalingFactor.setSelectedItem("Auto"); - } else if (scaleString.equalsIgnoreCase("FixedRatio")) { - scalingFactor.setSelectedItem("Fixed Aspect Ratio"); - } else { - scalingFactor.setSelectedItem(scaleString+"%"); - } - } - if (secVeNCrypt.isEnabled()) { - secVeNCrypt.setSelected(UserPreferences.getBool("viewer", - "secVeNCrypt", true)); - if (secVeNCrypt.isSelected()) { - encNone.setSelected(UserPreferences.getBool("viewer", "encNone", true)); - encTLS.setSelected(UserPreferences.getBool("viewer", "encTLS", true)); - encX509.setSelected(UserPreferences.getBool("viewer", "encX509", true)); - secPlain.setSelected(UserPreferences.getBool("viewer", "secPlain", true)); - secIdent.setSelected(UserPreferences.getBool("viewer", "secIdent", true)); - sendLocalUsername.setSelected(UserPreferences.getBool("global", - "SendLocalUsername")); - } - } - if (secNone.isEnabled()) - secNone.setSelected(UserPreferences.getBool("viewer", "secNone", true)); - if (secVnc.isEnabled()) - secVnc.setSelected(UserPreferences.getBool("viewer", "secVnc", true)); - sshTunnel.setSelected(UserPreferences.getBool("global", "Tunnel")); - sshUseGateway.setSelected(UserPreferences.get("global", "Via") != null); - if (sshUseGateway.isSelected()) - cc.viewer.via.setParam(UserPreferences.get("global", "Via")); - sshUser.setText(Tunnel.getSshUser(cc)); - sshHost.setText(Tunnel.getSshHost(cc)); - sshPort.setText(Integer.toString(Tunnel.getSshPort(cc))); - sshUseExt.setSelected(UserPreferences.getBool("global", "extSSH")); - File f = new File(UserPreferences.get("global", "extSSHClient")); - if (f.exists() && f.canExecute()) - sshClient.setText(f.getAbsolutePath()); - sshArguments.setText(UserPreferences.get("global", "extSSHArgs")); - if (sshArguments.getText().isEmpty()) - sshArgsDefault.setSelected(true); - else - sshArgsCustom.setSelected(true); - f = new File(UserPreferences.get("global", "SSHConfig")); - if (f.exists() && f.canRead()) - sshConfig.setText(f.getAbsolutePath()); - if (UserPreferences.get("global", "SSHKeyFile") != null) { - f = new File(UserPreferences.get("global", "SSHKeyFile")); - if (f.exists() && f.canRead()) - sshKeyFile.setText(f.getAbsolutePath()); - } else { - sshKeyFile.setText(Tunnel.getSshKeyFile(cc)); - } - sshUseGateway.setEnabled(sshTunnel.isSelected()); - sshUser.setEnabled(sshTunnel.isSelected() && - sshUseGateway.isEnabled() && - sshUseGateway.isSelected()); - sshHost.setEnabled(sshTunnel.isSelected() && - sshUseGateway.isEnabled() && - sshUseGateway.isSelected()); - sshPort.setEnabled(sshTunnel.isSelected() && - sshUseGateway.isEnabled() && - sshUseGateway.isSelected()); - sshUseExt.setEnabled(sshTunnel.isSelected()); - sshClient.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled()); - sshClientBrowser.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshArgsDefault.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshArgsCustom.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshArguments.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - sshUseExt.isSelected() && - sshArgsCustom.isSelected()); - sshConfig.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - sshConfigBrowser.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - sshKeyFile.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - sshKeyFileBrowser.setEnabled(sshTunnel.isSelected() && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); } - public void endDialog() { - super.endDialog(); - if (cc.viewport != null && cc.viewport.isVisible()) { - cc.viewport.toFront(); - cc.viewport.requestFocus(); + private void handleExtSSH() + { + if (tunnelCheckbox.isSelected()) { + sshClientInput.setEnabled(extSSHCheckbox.isSelected()); + sshClientChooser.setEnabled(extSSHCheckbox.isSelected()); + sshArgsDefaultButton.setEnabled(extSSHCheckbox.isSelected()); + sshArgsCustomButton.setEnabled(extSSHCheckbox.isSelected()); + sshConfigInput.setEnabled(!extSSHCheckbox.isSelected()); + sshConfigChooser.setEnabled(!extSSHCheckbox.isSelected()); + sshKeyFileInput.setEnabled(!extSSHCheckbox.isSelected()); + sshKeyFileChooser.setEnabled(!extSSHCheckbox.isSelected()); + if (sshArgsCustomButton.isSelected()) + sshArgsInput.setEnabled(extSSHCheckbox.isSelected()); + else + sshArgsInput.setEnabled(false); } } - public void actionPerformed(ActionEvent e) { - Object s = e.getSource(); - if (s instanceof JButton) { - JButton button = (JButton)s; - if (button == okButton) { - JTextField[] fields = - { x509ca, x509crl, sshClient, sshConfig, sshKeyFile }; - for (JTextField field : fields) { - if (field.getText() != null && !field.getText().equals("")) { - File f = new File(field.getText()); - if (!f.exists() || !f.canRead()) { - String msg = new String("The file "+f.getAbsolutePath()+ - " specified for option "+field.getName()+ - " does not exist or cannot be read. Please"+ - " correct before proceeding."); - JOptionPane.showMessageDialog(this, msg, "WARNING", - JOptionPane.WARNING_MESSAGE); - return; - } - } - } - if (cc != null) cc.getOptions(); - endDialog(); - } else if (button == cancelButton) { - endDialog(); - } else if (button == cfLoadButton) { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle("Path to configuration file"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) { - String filename = fc.getSelectedFile().toString(); - if (filename != null) - Configuration.load(filename); - cc.setOptions(); - } - } else if (button == cfSaveAsButton) { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle("Save current configuration as:"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) { - String filename = fc.getSelectedFile().toString(); - if (filename != null) - Configuration.save(filename); - } - } else if (button == defSaveButton) { - updatePreferences(); - UserPreferences.save(); - } else if (button == defReloadButton) { - restorePreferences(); - } else if (button == defClearButton) { - UserPreferences.clear(); - cc.setOptions(); - } else if (button == caButton) { - JFileChooser fc = - new JFileChooser(new File(CSecurityTLS.getDefaultCA())); - fc.setDialogTitle("Path to X509 CA certificate"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) - x509ca.setText(fc.getSelectedFile().toString()); - } else if (button == crlButton) { - JFileChooser fc = - new JFileChooser(new File(CSecurityTLS.getDefaultCRL())); - fc.setDialogTitle("Path to X509 CRL file"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) - x509crl.setText(fc.getSelectedFile().toString()); - } else if (button == sshClientBrowser) { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle("Path to external SSH client"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) - sshClient.setText(fc.getSelectedFile().toString()); - } else if (button == sshConfigBrowser) { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle("Path to OpenSSH client config file"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) - sshConfig.setText(fc.getSelectedFile().toString()); - } else if (button == sshKeyFileBrowser) { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle("Path to SSH key file"); - fc.setApproveButtonText("OK"); - fc.setFileHidingEnabled(false); - int ret = fc.showOpenDialog(this); - if (ret == JFileChooser.APPROVE_OPTION) - sshKeyFile.setText(fc.getSelectedFile().toString()); - } - } else if (s instanceof JRadioButton) { - JRadioButton button = (JRadioButton)s; - if (button == sshArgsCustom || button == sshArgsDefault) { - sshArguments.setEnabled(sshArgsCustom.isSelected()); - } + private void handleEmbed() + { + if (embed.getValue()) { + 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 void itemStateChanged(ItemEvent e) { - Object s = e.getSource(); - if (s instanceof JCheckBox) { - JCheckBox item = (JCheckBox)s; - boolean enable = item.isSelected(); - if (item == autoSelect) { - ButtonGroup[] groups = { encodingGroup, colourGroup }; - for (ButtonGroup grp : groups) { - Enumeration<AbstractButton> elems = grp.getElements(); - while (elems.hasMoreElements()) - elems.nextElement().setEnabled(!enable); - } - } else if (item == customCompressLevel) { - compressLevel.setEnabled(enable); - } else if (item == desktopSize) { - desktopWidth.setEnabled(enable); - desktopHeight.setEnabled(enable); - } else if (item == noJpeg) { - qualityLevel.setEnabled(enable); - } else if (item == encX509) { - x509ca.setEnabled(enable); - caButton.setEnabled(enable); - x509crl.setEnabled(enable); - crlButton.setEnabled(enable); - } else if (item == secVeNCrypt) { - encNone.setEnabled(enable); - encTLS.setEnabled(enable); - encX509.setEnabled(enable); - x509ca.setEnabled(enable && encX509.isSelected()); - caButton.setEnabled(enable && encX509.isSelected()); - x509crl.setEnabled(enable && encX509.isSelected()); - crlButton.setEnabled(enable && encX509.isSelected()); - secIdent.setEnabled(enable); - secPlain.setEnabled(enable); - sendLocalUsername.setEnabled(enable); - } else if (item == encNone) { - secNone.setSelected(enable && - UserPreferences.getBool("viewer", "secNone", true)); - secVnc.setSelected(enable && - UserPreferences.getBool("viewer", "secVnc", true)); - } else if (item == secIdent || item == secPlain) { - sendLocalUsername.setEnabled(secIdent.isSelected() || - secPlain.isSelected()); - } else if (item == sshTunnel) { - sshUseGateway.setEnabled(enable); - sshUser.setEnabled(enable && - sshUseGateway.isEnabled() && - sshUseGateway.isSelected()); - sshHost.setEnabled(enable && - sshUseGateway.isEnabled() && - sshUseGateway.isSelected()); - sshPort.setEnabled(enable && - sshUseGateway.isEnabled() && - sshUseGateway.isSelected()); - sshUseExt.setEnabled(enable); - sshClient.setEnabled(enable && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshClientBrowser.setEnabled(enable && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshArgsDefault.setEnabled(enable && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshArgsCustom.setEnabled(enable && - sshUseExt.isEnabled() && - sshUseExt.isSelected()); - sshArguments.setEnabled(enable && - sshUseExt.isEnabled() && - sshUseExt.isSelected() && - sshArgsCustom.isSelected()); - sshConfig.setEnabled(enable && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - sshConfigBrowser.setEnabled(enable && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - sshKeyFile.setEnabled(enable && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - sshKeyFileBrowser.setEnabled(enable && - sshUseExt.isEnabled() && - !sshUseExt.isSelected()); - } else if (item == sshUseExt) { - sshClient.setEnabled(enable); - sshClientBrowser.setEnabled(enable); - sshArgsDefault.setEnabled(enable); - sshArgsCustom.setEnabled(enable); - sshArguments.setEnabled(enable && sshArgsCustom.isSelected()); - sshConfig.setEnabled(!enable); - sshConfigBrowser.setEnabled(!enable); - sshKeyFile.setEnabled(!enable); - sshKeyFileBrowser.setEnabled(!enable); - } else if (item == sshUseGateway) { - sshUser.setEnabled(enable); - sshHost.setEnabled(enable); - sshPort.setEnabled(enable); - } + private void handleRfbState() + { + CConn cc = VncViewer.cc; + if (cc != null && cc.state() == CConnection.RFBSTATE_NORMAL) { + JComponent[] components = { + encNoneCheckbox, encTLSCheckbox, encX509Checkbox, authNoneCheckbox, + authVncCheckbox, authVncCheckbox, authIdentCheckbox, authPlainCheckbox, + sendLocalUsernameCheckbox, caInput, caChooser, crlInput, crlChooser, + sharedCheckbox, tunnelCheckbox, viaCheckbox, viaUserInput, viaHostInput, + viaPortInput, extSSHCheckbox, sshClientInput, sshClientChooser, + sshArgsDefaultButton, sshArgsCustomButton, sshArgsInput, sshConfigInput, + sshKeyFileInput, sshConfigChooser, sshKeyFileChooser, + }; + for (JComponent c : components) + c.setEnabled(false); } } + + static LogWriter vlog = new LogWriter("OptionsDialog"); } diff --git a/java/com/tigervnc/vncviewer/OptionsDialogCallback.java b/java/com/tigervnc/vncviewer/OptionsDialogCallback.java deleted file mode 100644 index efb8c069..00000000 --- a/java/com/tigervnc/vncviewer/OptionsDialogCallback.java +++ /dev/null @@ -1,24 +0,0 @@ -/* 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.vncviewer; - -public interface OptionsDialogCallback { - public void setOptions(); - public void getOptions(); -} diff --git a/java/com/tigervnc/vncviewer/Parameters.java b/java/com/tigervnc/vncviewer/Parameters.java new file mode 100644 index 00000000..50e26cba --- /dev/null +++ b/java/com/tigervnc/vncviewer/Parameters.java @@ -0,0 +1,646 @@ +/* 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.vncviewer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.util.StringTokenizer; + +import com.tigervnc.rfb.*; +import com.tigervnc.rfb.Exception; + +public class Parameters { + + public static BoolParameter noLionFS + = new BoolParameter("NoLionFS", + "On Mac systems, setting this parameter will force the use of the old "+ + "(pre-Lion) full-screen mode, even if the viewer is running on OS X 10.7 "+ + "Lion or later.", + false); + + 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); + + 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); + + public static StringParameter passwordFile + = new StringParameter("PasswordFile", + "Password file for VNC authentication", + ""); + + public static AliasParameter passwd + = new AliasParameter("passwd", + "Alias for PasswordFile", + passwordFile); + + public static BoolParameter autoSelect + = new BoolParameter("AutoSelect", + "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); + + public static AliasParameter fullColorAlias + = new AliasParameter("FullColour", + "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); + + public static AliasParameter lowColorLevelAlias + = new AliasParameter("LowColourLevel", + "Alias for LowColorLevel", + lowColorLevel); + + public static StringParameter preferredEncoding + = new StringParameter("PreferredEncoding", + "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); + + public static BoolParameter shared + = new BoolParameter("Shared", + "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); + + public static BoolParameter fullScreenAllMonitors + = new BoolParameter("FullScreenAllMonitors", + "Enable full screen over all monitors", + true); + + public static BoolParameter acceptClipboard + = new BoolParameter("AcceptClipboard", + "Accept clipboard changes from the server", + true); + + public static BoolParameter sendClipboard + = new BoolParameter("SendClipboard", + "Send clipboard changes to the server", + true); + + public static IntParameter maxCutText + = new IntParameter("MaxCutText", + "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"); + + public static StringParameter desktopSize + = new StringParameter("DesktopSize", + "Reconfigure desktop size on the server on connect (if possible)", + ""); + + public static BoolParameter listenMode + = new BoolParameter("listen", + "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"); + + 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); + + public static StringParameter vncServerName + = new StringParameter("Server", + "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); + + public static StringParameter via + = new StringParameter("Via", + "Automatically create an encrypted TCP tunnel to "+ + "the gateway machine, then connect to the VNC host "+ + "through that tunnel. By default, this option invokes "+ + "SSH local port forwarding using the embedded JSch "+ + "client, however an external SSH client may be specified "+ + "using the \"-extSSH\" parameter. Note that when using "+ + "the -via option, the VNC host machine name should be "+ + "specified from the point of view of the gateway machine, "+ + "e.g. \"localhost\" denotes the gateway, "+ + "not the machine on which the viewer was launched. "+ + "See the System Properties section below for "+ + "information on configuring the -Via option.", ""); + + public static BoolParameter tunnel + = new BoolParameter("Tunnel", + "The -Tunnel command is basically a shorthand for the "+ + "-via command when the VNC server and SSH gateway are "+ + "one and the same. -Tunnel creates an SSH connection "+ + "to the server and forwards the VNC through the tunnel "+ + "without the need to specify anything else.", false); + + public static BoolParameter extSSH + = new BoolParameter("extSSH", + "By default, SSH tunneling uses the embedded JSch client "+ + "for tunnel creation. This option causes the client to "+ + "invoke an external SSH client application for all tunneling "+ + "operations. By default, \"/usr/bin/ssh\" is used, however "+ + "the path to the external application may be specified using "+ + "the -SSHClient option.", false); + + public static StringParameter extSSHClient + = new StringParameter("extSSHClient", + "Specifies the path to an external SSH client application "+ + "that is to be used for tunneling operations when the -extSSH "+ + "option is in effect.", "/usr/bin/ssh"); + + public static StringParameter extSSHArgs + = new StringParameter("extSSHArgs", + "Specifies the arguments string or command template to be used "+ + "by the external SSH client application when the -extSSH option "+ + "is in effect. The string will be processed according to the same "+ + "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+ + "system properties, and can be used to override those in a more "+ + "command-line friendly way. If not specified, then the appropriate "+ + "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", ""); + + public static StringParameter sshConfig + = new StringParameter("SSHConfig", + "Specifies the path to an OpenSSH configuration file that to "+ + "be parsed by the embedded JSch SSH client during tunneling "+ + "operations.", FileUtils.getHomeDir()+".ssh/config"); + + public static StringParameter sshKey + = new StringParameter("SSHKey", + "When using the Via or Tunnel options with the embedded SSH client, "+ + "this parameter specifies the text of the SSH private key to use when "+ + "authenticating with the SSH server. You can use \\n within the string "+ + "to specify a new line.", ""); + + public static StringParameter sshKeyFile + = new StringParameter("SSHKeyFile", + "When using the Via or Tunnel options with the embedded SSH client, "+ + "this parameter specifies a file that contains an SSH private key "+ + "(or keys) to use when authenticating with the SSH server. If not "+ + "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+ + "Otherwise, the client will fallback to prompting for an SSH password.", + ""); + + public static StringParameter sshKeyPass + = new StringParameter("SSHKeyPass", + "When using the Via or Tunnel options with the embedded SSH client, "+ + "this parameter specifies the passphrase for the SSH key.", ""); + + public static BoolParameter customCompressLevel + = new BoolParameter("CustomCompressLevel", + "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); + + public static BoolParameter noJpeg + = new BoolParameter("NoJPEG", + "Disable lossy JPEG compression in Tight encoding.", + false); + + public static IntParameter qualityLevel + = new IntParameter("QualityLevel", + "JPEG quality level. 0 = Low, 9 = High", + 8); + + private static final String IDENTIFIER_STRING + = "TigerVNC Configuration file Version 1.0"; + + static VoidParameter[] parameterArray = { + CSecurityTLS.X509CA, + CSecurityTLS.X509CRL, + SecurityClient.secTypes, + autoSelect, + fullColor, + lowColorLevel, + preferredEncoding, + customCompressLevel, + compressLevel, + noJpeg, + qualityLevel, + maximize, + fullScreen, + fullScreenAllMonitors, + desktopSize, + remoteResize, + viewOnly, + shared, + acceptClipboard, + sendClipboard, + menuKey, + noLionFS, + sendLocalUsername, + maxCutText, + scalingFactor, + acceptBell, + via, + tunnel, + extSSH, + extSSHClient, + extSSHArgs, + sshConfig, + sshKeyFile, + }; + + + 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()) { + saveToReg(servername); + return; + /* + String homeDir = FileUtils.getVncHomeDir(); + if (homeDir == null) + throw new Exception("Failed to read configuration file, "+ + "can't obtain home directory path."); + filepath = homeDir.concat("default.tigervnc"); + */ + } 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"); + } catch (java.lang.Exception e) { + throw new Exception(e.getMessage()); + } + + 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())) + pw.println(String.format("%s=%s",parameterArray[i].getName(), + parameterArray[i].getValueStr())); + } else if (parameterArray[i] instanceof IntParameter) { + //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) + pw.println(String.format("%s=%s",parameterArray[i].getName(), + parameterArray[i].getValueStr())); + } else if (parameterArray[i] instanceof BoolParameter) { + //if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) + pw.println(String.format("%s=%s",parameterArray[i].getName(), + parameterArray[i].getValueStr())); + } else { + vlog.error(String.format("Unknown parameter type for parameter %s", + parameterArray[i].getName())); + } + } + + pw.flush(); + pw.close(); + } + + public static String loadViewerParameters(String filename) throws Exception { + String servername = ""; + + String filepath; + if (filename == null) { + + return loadFromReg(); + + /* + String homeDir = FileUtils.getVncHomeDir(); + if (homeDir == null) + throw new Exception("Failed to read configuration file, "+ + "can't obtain home directory path."); + filepath = homeDir.concat("default.tigervnc"); + */ + } else { + filepath = filename; + } + + /* Read parameters from file */ + File f = new File(filepath); + if (!f.exists() || !f.canRead()) { + if (filename == null || filename.isEmpty()) + return null; + throw new Exception(String.format("Failed to read configuration file, can't open %s", + filepath)); + } + + String line = ""; + LineNumberReader reader; + try { + reader = new LineNumberReader(new FileReader(f)); + } catch (FileNotFoundException e) { + throw new Exception(e.getMessage()); + } + + int lineNr = 0; + while (line != null) { + + // Read the next line + try { + line = reader.readLine(); + lineNr = reader.getLineNumber(); + if (line == null) + break; + } catch (IOException e) { + throw new Exception(String.format("Failed to read line %d in file %s: %s", + lineNr, filepath, e.getMessage())); + } + + // Make sure that the first line of the file has the file identifier string + if(lineNr == 1) { + if(line.equals(IDENTIFIER_STRING)) + continue; + else + throw new Exception(String.format("Configuration file %s is in an invalid format", filename)); + } + + // Skip empty lines and comments + if (line.trim().isEmpty() || line.trim().startsWith("#")) + continue; + + // Find the parameter value + int idx = line.indexOf("="); + if (idx == -1) { + vlog.error(String.format("Failed to read line %d in file %s: %s", + lineNr, filename, "Invalid format")); + continue; + } + String value = line.substring(idx+1).trim(); + boolean invalidParameterName = true; // Will be set to false below if + // the line contains a valid name. + + if (line.substring(0,idx).trim().equalsIgnoreCase("ServerName")) { + if (value.length() > 256) { + vlog.error(String.format("Failed to read line %d in file %s: %s", + lineNr, filepath, "Invalid format or too large value")); + continue; + } + servername = value; + invalidParameterName = false; + } else { + for (int i = 0; i < parameterArray.length; i++) { + if (parameterArray[i] instanceof StringParameter) { + if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) { + if (value.length() > 256) { + vlog.error(String.format("Failed to read line %d in file %s: %s", + lineNr, filepath, "Invalid format or too large value")); + continue; + } + ((StringParameter)parameterArray[i]).setParam(value); + invalidParameterName = false; + } + } else if (parameterArray[i] instanceof IntParameter) { + if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) { + ((IntParameter)parameterArray[i]).setParam(value); + invalidParameterName = false; + } + } else if (parameterArray[i] instanceof BoolParameter) { + if (line.substring(0,idx).trim().equalsIgnoreCase(parameterArray[i].getName())) { + ((BoolParameter)parameterArray[i]).setParam(value); + invalidParameterName = false; + } + } else { + vlog.error(String.format("Unknown parameter type for parameter %s", + parameterArray[i].getName())); + + } + } + } + + if (invalidParameterName) + vlog.info(String.format("Unknown parameter %s on line %d in file %s", + line, lineNr, filepath)); + } + try { + reader.close(); + } catch (IOException e) { + vlog.info(e.getMessage()); + } finally { + try { + if (reader != null) + reader.close(); + } catch (IOException e) { } + } + + return servername; + } + + public static void saveToReg(String servername) { + String hKey = "global"; + + if (servername != null && !servername.isEmpty()) { + UserPreferences.set(hKey, "ServerName", servername); + updateConnHistory(servername); + } + + for (int i = 0; i < parameterArray.length; i++) { + if (parameterArray[i] instanceof StringParameter) { + UserPreferences.set(hKey, parameterArray[i].getName(), + parameterArray[i].getValueStr()); + } else if (parameterArray[i] instanceof IntParameter) { + UserPreferences.set(hKey, parameterArray[i].getName(), + ((IntParameter)parameterArray[i]).getValue()); + } else if (parameterArray[i] instanceof BoolParameter) { + UserPreferences.set(hKey, parameterArray[i].getName(), + ((BoolParameter)parameterArray[i]).getValue()); + } else { + vlog.error(String.format("Unknown parameter type for parameter %s", + parameterArray[i].getName())); + } + } + + UserPreferences.save(hKey); + } + + 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) { + String stringValue = + UserPreferences.get(hKey, parameterArray[i].getName()); + ((StringParameter)parameterArray[i]).setParam(stringValue); + } + } else if (parameterArray[i] instanceof IntParameter) { + if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) { + int intValue = + UserPreferences.getInt(hKey, parameterArray[i].getName()); + ((IntParameter)parameterArray[i]).setParam(intValue); + } + } else if (parameterArray[i] instanceof BoolParameter) { + if (UserPreferences.get(hKey, parameterArray[i].getName()) != null) { + boolean booleanValue = + UserPreferences.getBool(hKey, parameterArray[i].getName()); + ((BoolParameter)parameterArray[i]).setParam(booleanValue); + } + } else { + vlog.error(String.format("Unknown parameter type for parameter %s", + parameterArray[i].getName())); + } + } + + return servername; + } + + public static String loadAppletParameters(VncViewer applet) { + String servername = applet.getParameter("Server"); + String serverport = applet.getParameter("Port"); + String embedParam = applet.getParameter("Embed"); + + if (servername == null) + servername = applet.getCodeBase().getHost(); + + if (serverport != null) + servername = servername.concat("::"+serverport); + else + servername = servername.concat("::5900"); + + if (embedParam != null) + embed.setParam(embedParam); + + for (int i = 0; i < parameterArray.length; i++) { + String value = applet.getParameter(parameterArray[i].getName()); + if (value == null) + continue; + if (parameterArray[i] instanceof StringParameter) { + if (value.length() > 256) { + vlog.error(String.format("Failed to read applet parameter %s: %s", + parameterArray[i].getName(), + "Invalid format or too large value")); + continue; + } + ((StringParameter)parameterArray[i]).setParam(value); + } else if (parameterArray[i] instanceof IntParameter) { + ((IntParameter)parameterArray[i]).setParam(value); + } else if (parameterArray[i] instanceof BoolParameter) { + ((BoolParameter)parameterArray[i]).setParam(value); + } else { + vlog.error(String.format("Unknown parameter type for parameter %s", + parameterArray[i].getName())); + + } + } + + return servername; + } + + private static void updateConnHistory(String serverName) { + String hKey = "ServerDialog"; + if (serverName != null && !serverName.isEmpty()) { + String valueStr = UserPreferences.get(hKey, "history"); + String t = (valueStr == null) ? "" : valueStr; + StringTokenizer st = new StringTokenizer(t, ","); + StringBuffer sb = new StringBuffer().append(serverName); + while (st.hasMoreTokens()) { + String str = st.nextToken(); + if (!str.equals(serverName) && !str.equals("")) + sb.append(',').append(str); + } + UserPreferences.set(hKey, "history", sb.toString()); + UserPreferences.save(hKey); + } + } + +} diff --git a/java/com/tigervnc/vncviewer/PasswdDialog.java b/java/com/tigervnc/vncviewer/PasswdDialog.java index fbaf9911..9d5cc7cb 100644 --- a/java/com/tigervnc/vncviewer/PasswdDialog.java +++ b/java/com/tigervnc/vncviewer/PasswdDialog.java @@ -149,7 +149,6 @@ class PasswdDialog extends Dialog implements UserInfo, String instruction, String[] prompt, boolean[] echo) { - vlog.info("OK"); Container panel = new JPanel(new GridBagLayout()); ((JPanel)panel).setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); diff --git a/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java b/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java index e0e24f06..564eb8eb 100644 --- a/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java +++ b/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java @@ -24,86 +24,38 @@ import java.awt.image.*; import java.nio.ByteOrder; import com.tigervnc.rfb.*; +import com.tigervnc.rfb.Point; -abstract public class PlatformPixelBuffer extends PixelBuffer +public class PlatformPixelBuffer extends FullFramePixelBuffer { - public PlatformPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) { - cc = cc_; - desktop = desktop_; - PixelFormat nativePF = getNativePF(); - if (nativePF.depth > cc.serverPF.depth) { - setPF(cc.serverPF); - } 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; - CConn cc; - DesktopWindow desktop; - static LogWriter vlog = new LogWriter("PlatformPixelBuffer"); } diff --git a/java/com/tigervnc/vncviewer/ServerDialog.java b/java/com/tigervnc/vncviewer/ServerDialog.java index 172bde6f..4b3f26bf 100644 --- a/java/com/tigervnc/vncviewer/ServerDialog.java +++ b/java/com/tigervnc/vncviewer/ServerDialog.java @@ -21,8 +21,10 @@ package com.tigervnc.vncviewer; import java.awt.*; import java.awt.event.*; +import java.io.File; import javax.swing.*; import javax.swing.border.*; +import javax.swing.filechooser.*; import javax.swing.WindowConstants.*; import java.util.*; @@ -30,38 +32,41 @@ import com.tigervnc.rfb.*; import static java.awt.GridBagConstraints.HORIZONTAL; import static java.awt.GridBagConstraints.LINE_START; +import static java.awt.GridBagConstraints.LINE_END; import static java.awt.GridBagConstraints.NONE; import static java.awt.GridBagConstraints.REMAINDER; -class ServerDialog extends Dialog { +import static com.tigervnc.vncviewer.Parameters.*; - @SuppressWarnings({"unchecked","rawtypes"}) - public ServerDialog(OptionsDialog options_, - String defaultServerName, CConn cc_) { +class ServerDialog extends Dialog implements Runnable { + @SuppressWarnings({"unchecked","rawtypes"}) + public ServerDialog() { super(true); - cc = cc_; setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); setTitle("VNC Viewer: Connection Details"); setResizable(false); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { - if (VncViewer.nViewers == 1) { - cc.viewer.exit(1); - } else { - ret = false; - endDialog(); - } + endDialog(); + System.exit(1); } }); - options = options_; - - JLabel serverLabel = new JLabel("VNC Server:", JLabel.RIGHT); + JLabel serverLabel = new JLabel("VNC server:", JLabel.RIGHT); String valueStr = new String(""); if (UserPreferences.get("ServerDialog", "history") != null) valueStr = UserPreferences.get("ServerDialog", "history"); server = new MyJComboBox(valueStr.split(",")); + server.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JComboBox s = (JComboBox)e.getSource(); + if (e.getActionCommand().equals("comboBoxEdited")) { + s.insertItemAt(editor.getItem(), 0); + s.setSelectedIndex(0); + } + } + }); if (valueStr.equals("")) server.setPrototypeDisplayValue("255.255.255.255:5900"); @@ -74,7 +79,7 @@ class ServerDialog extends Dialog { if (e.getKeyCode() == KeyEvent.VK_ENTER) { server.insertItemAt(editor.getItem(), 0); server.setSelectedIndex(0); - commit(); + handleConnect(); } } }); @@ -84,9 +89,41 @@ class ServerDialog extends Dialog { JLabel icon = new JLabel(VncViewer.logoIcon); optionsButton = new JButton("Options..."); + optionsButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleOptions(); + } + }); + JButton loadButton = new JButton("Load..."); + loadButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleLoad(); + } + }); + JButton saveAsButton = new JButton("Save As..."); + saveAsButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleSaveAs(); + } + }); aboutButton = new JButton("About..."); - okButton = new JButton("OK"); + aboutButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleAbout(); + } + }); cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleCancel(); + } + }); + connectButton = new JButton("Connect \u21B5"); + connectButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleConnect(); + } + }); contentPane.add(icon, new GridBagConstraints(0, 0, @@ -109,81 +146,137 @@ class ServerDialog extends Dialog { LINE_START, HORIZONTAL, new Insets(5, 0, 5, 5), NONE, NONE)); - JPanel buttonPane = new JPanel(); - buttonPane.setLayout(new GridLayout(1, 4, 5, 5)); - buttonPane.add(aboutButton); - buttonPane.add(optionsButton); - buttonPane.add(okButton); - buttonPane.add(cancelButton); - contentPane.add(buttonPane, + JPanel buttonPane1 = new JPanel(); + Box box = Box.createHorizontalBox(); + JSeparator separator1 = new JSeparator(); + JSeparator separator2 = new JSeparator(); + GroupLayout layout = new GroupLayout(buttonPane1); + buttonPane1.setLayout(layout); + layout.setAutoCreateGaps(false); + layout.setAutoCreateContainerGaps(false); + layout.setHorizontalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10) + .addComponent(optionsButton)) + .addComponent(separator1) + .addGroup(layout.createSequentialGroup() + .addGap(10) + .addComponent(aboutButton))) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(loadButton) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(saveAsButton) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(box) + .addGap(10)) + .addComponent(separator2) + .addGroup(layout.createSequentialGroup() + .addComponent(cancelButton) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(connectButton) + .addGap(10))) + ); + layout.setVerticalGroup( + layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(optionsButton) + .addComponent(loadButton) + .addComponent(saveAsButton) + .addComponent(box)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(separator1) + .addComponent(separator2)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(aboutButton) + .addComponent(cancelButton) + .addComponent(connectButton)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + ); + layout.linkSize(SwingConstants.HORIZONTAL, + optionsButton, loadButton, saveAsButton, + aboutButton, cancelButton, box); + contentPane.add(buttonPane1, new GridBagConstraints(0, 1, REMAINDER, 1, LIGHT, LIGHT, LINE_START, HORIZONTAL, - new Insets(5, 5, 5, 5), + new Insets(5, 0, 10, 0), NONE, NONE)); - addListeners(this); pack(); } - @SuppressWarnings({"unchecked","rawtypes"}) - public void actionPerformed(ActionEvent e) { - Object s = e.getSource(); - if (s instanceof JButton && (JButton)s == okButton) { - commit(); - } else if (s instanceof JButton && (JButton)s == cancelButton) { - if (VncViewer.nViewers == 1) - cc.viewer.exit(1); - ret = false; - endDialog(); - } else if (s instanceof JButton && (JButton)s == optionsButton) { - options.showDialog(this); - } else if (s instanceof JButton && (JButton)s == aboutButton) { - cc.showAbout(); - } else if (s instanceof JComboBox && (JComboBox)s == server) { - if (e.getActionCommand().equals("comboBoxEdited")) { - server.insertItemAt(editor.getItem(), 0); - server.setSelectedIndex(0); - } + public void run() { + this.showDialog(); + } + + private void handleOptions() { + OptionsDialog.showDialog(this); + } + + private void handleLoad() { + String title = "Select a TigerVNC configuration file"; + File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc")); + FileNameExtensionFilter filter = + new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc"); + File f = showChooser(title, dflt, filter); + if (f != null && f.exists() && f.canRead()) + loadViewerParameters(f.getAbsolutePath()); + } + + private void handleSaveAs() { + String title = "Save the TigerVNC configuration to file"; + File dflt = new File(FileUtils.getVncHomeDir().concat("default.tigervnc")); + if (!dflt.exists() || !dflt.isFile()) + dflt = new File(FileUtils.getVncHomeDir()); + FileNameExtensionFilter filter = + new FileNameExtensionFilter("TigerVNC configuration (*.tigervnc)", "tigervnc"); + File f = showChooser(title, dflt, filter); + while (f != null && f.exists() && f.isFile()) { + String msg = f.getAbsolutePath(); + msg = msg.concat(" already exists. Do you want to overwrite?"); + Object[] options = {"Overwrite", "No \u21B5"}; + JOptionPane op = + new JOptionPane(msg, JOptionPane.QUESTION_MESSAGE, + JOptionPane.OK_CANCEL_OPTION, null, options, options[1]); + JDialog dlg = op.createDialog(this, "TigerVNC Viewer"); + dlg.setIconImage(VncViewer.frameIcon); + dlg.setAlwaysOnTop(true); + dlg.setVisible(true); + if (op.getValue() == options[0]) + break; + else + f = showChooser(title, f, filter); } + if (f != null && (!f.exists() || f.canWrite())) + saveViewerParameters(f.getAbsolutePath(), (String)server.getSelectedItem()); + } + + private void handleAbout() { + VncViewer.showAbout(this); } - private void commit() { + private void handleCancel() { + vncServerName.setParam(""); + endDialog(); + } + + private void handleConnect() { String serverName = (String)server.getSelectedItem(); - if (serverName == null || serverName.equals("")) { - vlog.error("Invalid servername specified"); - if (VncViewer.nViewers == 1) - cc.viewer.exit(1); - ret = false; - endDialog(); - } - // set params - Configuration.setParam("Server", Hostname.getHost(serverName)); - Configuration.setParam("Port", - Integer.toString(Hostname.getPort(serverName))); - // Update the history list - String valueStr = UserPreferences.get("ServerDialog", "history"); - String t = (valueStr == null) ? "" : valueStr; - StringTokenizer st = new StringTokenizer(t, ","); - StringBuffer sb = - new StringBuffer().append((String)server.getSelectedItem()); - while (st.hasMoreTokens()) { - String str = st.nextToken(); - if (!str.equals((String)server.getSelectedItem()) && !str.equals("")) { - sb.append(','); - sb.append(str); - } - } - UserPreferences.set("ServerDialog", "history", sb.toString()); - UserPreferences.save("ServerDialog"); + vncServerName.setParam(serverName); + saveViewerParameters(null, serverName); endDialog(); } - CConn cc; @SuppressWarnings("rawtypes") MyJComboBox server; ComboBoxEditor editor; - JButton aboutButton, optionsButton, okButton, cancelButton; + JButton aboutButton, optionsButton, connectButton, cancelButton; OptionsDialog options; static LogWriter vlog = new LogWriter("ServerDialog"); diff --git a/java/com/tigervnc/vncviewer/Tunnel.java b/java/com/tigervnc/vncviewer/Tunnel.java index 90ab38ef..bb98ec7c 100644 --- a/java/com/tigervnc/vncviewer/Tunnel.java +++ b/java/com/tigervnc/vncviewer/Tunnel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2016 All Rights Reserved. + * Copyright (C) 2012-2016 Brian P. Hinz. All Rights Reserved. * Copyright (C) 2000 Const Kaplinsky. All Rights Reserved. * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. * @@ -43,6 +43,8 @@ import com.jcraft.jsch.Logger; import com.jcraft.jsch.OpenSSHConfig; import com.jcraft.jsch.Session; +import static com.tigervnc.vncviewer.Parameters.*; + public class Tunnel { private final static String DEFAULT_TUNNEL_TEMPLATE @@ -56,27 +58,27 @@ public class Tunnel { String remoteHost; remotePort = cc.getServerPort(); - if (cc.viewer.tunnel.getValue()) { + if (tunnel.getValue()) { gatewayHost = cc.getServerName(); remoteHost = "localhost"; } else { - gatewayHost = getSshHost(cc); + gatewayHost = getSshHost(); remoteHost = cc.getServerName(); } - String pattern = cc.viewer.extSSHArgs.getValue(); - if (pattern == null) { - if (cc.viewer.tunnel.getValue()) + String pattern = extSSHArgs.getValue(); + if (pattern == null || pattern.isEmpty()) { + if (tunnel.getValue()) pattern = System.getProperty("VNC_TUNNEL_CMD"); else pattern = System.getProperty("VNC_VIA_CMD"); } - if (cc.viewer.extSSH.getValue() || + if (extSSH.getValue() || (pattern != null && pattern.length() > 0)) { - createTunnelExt(gatewayHost, remoteHost, remotePort, localPort, pattern, cc); + createTunnelExt(gatewayHost, remoteHost, remotePort, localPort, pattern); } else { - createTunnelJSch(gatewayHost, remoteHost, remotePort, localPort, cc); + createTunnelJSch(gatewayHost, remoteHost, remotePort, localPort); } } @@ -99,10 +101,10 @@ public class Tunnel { } } - public static String getSshHost(CConn cc) { - String sshHost = cc.viewer.via.getValue(); - if (sshHost == null) - return cc.getServerName(); + public static String getSshHost() { + String sshHost = via.getValue(); + if (sshHost.isEmpty()) + return vncServerName.getValue(); int end = sshHost.indexOf(":"); if (end < 0) end = sshHost.length(); @@ -110,37 +112,42 @@ public class Tunnel { return sshHost; } - public static String getSshUser(CConn cc) { + public static String getSshUser() { String sshUser = (String)System.getProperties().get("user.name"); - String via = cc.viewer.via.getValue(); - if (via != null && via.indexOf("@") > 0) - sshUser = via.substring(0, via.indexOf("@")); + String viaStr = via.getValue(); + if (!viaStr.isEmpty() && viaStr.indexOf("@") > 0) + sshUser = viaStr.substring(0, viaStr.indexOf("@")); return sshUser; } - public static int getSshPort(CConn cc) { + public static int getSshPort() { String sshPort = "22"; - String via = cc.viewer.via.getValue(); - if (via != null && via.indexOf(":") > 0) - sshPort = via.substring(via.indexOf(":")+1, via.length()); + String viaStr = via.getValue(); + if (!viaStr.isEmpty() && viaStr.indexOf(":") > 0) + sshPort = viaStr.substring(viaStr.indexOf(":")+1, viaStr.length()); return Integer.parseInt(sshPort); } - public static String getSshKeyFile(CConn cc) { - if (cc.viewer.sshKeyFile.getValue() != null) - return cc.viewer.sshKeyFile.getValue(); + public static String getSshKeyFile() { + if (!sshKeyFile.getValue().isEmpty()) + return sshKeyFile.getValue(); String[] ids = { "id_dsa", "id_rsa" }; for (String id : ids) { File f = new File(FileUtils.getHomeDir()+".ssh/"+id); if (f.exists() && f.canRead()) return(f.getAbsolutePath()); } - return null; + return ""; + } + + public static String getSshKey() { + if (!sshKey.getValue().isEmpty()) + return sshKeyFile.getValue().replaceAll("\\\\n", "\n"); + return ""; } private static void createTunnelJSch(String gatewayHost, String remoteHost, - int remotePort, int localPort, - CConn cc) throws Exception { + int remotePort, int localPort) throws Exception { JSch.setLogger(new MyJSchLogger()); JSch jsch=new JSch(); @@ -152,44 +159,39 @@ public class Tunnel { if (knownHosts.exists() && knownHosts.canRead()) jsch.setKnownHosts(knownHosts.getAbsolutePath()); ArrayList<File> privateKeys = new ArrayList<File>(); - String sshKeyFile = cc.options.sshKeyFile.getText(); - String sshKey = cc.viewer.sshKey.getValue(); - if (sshKey != null) { - String sshKeyPass = cc.viewer.sshKeyPass.getValue(); + if (!getSshKey().isEmpty()) { byte[] keyPass = null, key; - if (sshKeyPass != null) - keyPass = sshKeyPass.getBytes(); - sshKey = sshKey.replaceAll("\\\\n", "\n"); - key = sshKey.getBytes(); - jsch.addIdentity("TigerVNC", key, null, keyPass); - } else if (!sshKeyFile.equals("")) { - File f = new File(sshKeyFile); + if (!sshKeyPass.getValue().isEmpty()) + keyPass = sshKeyPass.getValue().getBytes(); + jsch.addIdentity("TigerVNC", getSshKey().getBytes(), null, keyPass); + } else if (!getSshKeyFile().isEmpty()) { + File f = new File(getSshKeyFile()); if (!f.exists() || !f.canRead()) - throw new Exception("Cannot access SSH key file "+ sshKeyFile); + throw new Exception("Cannot access SSH key file "+getSshKeyFile()); privateKeys.add(f); } for (Iterator<File> i = privateKeys.iterator(); i.hasNext();) { File privateKey = (File)i.next(); if (privateKey.exists() && privateKey.canRead()) - if (cc.viewer.sshKeyPass.getValue() != null) + if (!sshKeyPass.getValue().isEmpty()) jsch.addIdentity(privateKey.getAbsolutePath(), - cc.viewer.sshKeyPass.getValue()); + sshKeyPass.getValue()); else jsch.addIdentity(privateKey.getAbsolutePath()); } - - String user = getSshUser(cc); + + String user = getSshUser(); String label = new String("SSH Authentication"); PasswdDialog dlg = new PasswdDialog(label, (user == null ? false : true), false); dlg.userEntry.setText(user != null ? user : ""); - File ssh_config = new File(cc.viewer.sshConfig.getValue()); + File ssh_config = new File(sshConfig.getValue()); if (ssh_config.exists() && ssh_config.canRead()) { ConfigRepository repo = OpenSSHConfig.parse(ssh_config.getAbsolutePath()); jsch.setConfigRepository(repo); } - Session session=jsch.getSession(user, gatewayHost, getSshPort(cc)); + Session session=jsch.getSession(user, gatewayHost, getSshPort()); session.setUserInfo(dlg); // OpenSSHConfig doesn't recognize StrictHostKeyChecking if (session.getConfig("StrictHostKeyChecking") == null) @@ -201,93 +203,19 @@ public class Tunnel { } } - private static class MyExtProcess implements Runnable { - - private String cmd = null; - private Process pid = null; - - private static class MyProcessLogger extends Thread { - private final BufferedReader err; - - public MyProcessLogger(Process p) { - InputStreamReader reader = - new InputStreamReader(p.getErrorStream()); - err = new BufferedReader(reader); - } - - @Override - public void run() { - try { - while (true) { - String msg = err.readLine(); - if (msg != null) - vlog.info(msg); - } - } catch(java.io.IOException e) { - vlog.info(e.getMessage()); - } finally { - try { - if (err != null) - err.close(); - } catch (java.io.IOException e ) { } - } - } - } - - private static class MyShutdownHook extends Thread { - - private Process proc = null; - - public MyShutdownHook(Process p) { - proc = p; - } - - @Override - public void run() { - try { - proc.exitValue(); - } catch (IllegalThreadStateException e) { - try { - // wait for CConn to shutdown the socket - Thread.sleep(500); - } catch(InterruptedException ie) { } - proc.destroy(); - } - } - } - - public MyExtProcess(String command) { - cmd = command; - } - - public void run() { - try { - Runtime runtime = Runtime.getRuntime(); - pid = runtime.exec(cmd); - runtime.addShutdownHook(new MyShutdownHook(pid)); - new MyProcessLogger(pid).start(); - pid.waitFor(); - } catch(InterruptedException e) { - vlog.info(e.getMessage()); - } catch(java.io.IOException e) { - vlog.info(e.getMessage()); - } - } - } - private static void createTunnelExt(String gatewayHost, String remoteHost, int remotePort, int localPort, - String pattern, CConn cc) throws Exception { + String pattern) throws Exception { if (pattern == null || pattern.length() < 1) { - if (cc.viewer.tunnel.getValue()) + if (tunnel.getValue()) pattern = DEFAULT_TUNNEL_TEMPLATE; else pattern = DEFAULT_VIA_TEMPLATE; } String cmd = fillCmdPattern(pattern, gatewayHost, remoteHost, - remotePort, localPort, cc); + remotePort, localPort); try { - Thread t = new Thread(new MyExtProcess(cmd)); + Thread t = new Thread(new ExtProcess(cmd, vlog, true)); t.start(); // wait for the ssh process to start Thread.sleep(1000); @@ -298,21 +226,21 @@ public class Tunnel { private static String fillCmdPattern(String pattern, String gatewayHost, String remoteHost, int remotePort, - int localPort, CConn cc) { + int localPort) { boolean H_found = false, G_found = false, R_found = false, L_found = false; boolean P_found = false; - String cmd = cc.options.sshClient.getText() + " "; + String cmd = extSSHClient.getValue() + " "; pattern.replaceAll("^\\s+", ""); - String user = getSshUser(cc); - int sshPort = getSshPort(cc); + String user = getSshUser(); + int sshPort = getSshPort(); gatewayHost = user + "@" + gatewayHost; for (int i = 0; i < pattern.length(); i++) { if (pattern.charAt(i) == '%') { switch (pattern.charAt(++i)) { case 'H': - cmd += (cc.viewer.tunnel.getValue() ? gatewayHost : remoteHost); + cmd += (tunnel.getValue() ? gatewayHost : remoteHost); H_found = true; continue; case 'G': @@ -342,7 +270,7 @@ public class Tunnel { if (!H_found || !R_found || !L_found) throw new Exception("%H, %R or %L absent in tunneling command template."); - if (!cc.viewer.tunnel.getValue() && !G_found) + if (!tunnel.getValue() && !G_found) throw new Exception("%G pattern absent in tunneling command template."); vlog.info("SSH command line: "+cmd); diff --git a/java/com/tigervnc/vncviewer/Viewport.java b/java/com/tigervnc/vncviewer/Viewport.java index b55744bd..bf07d2d5 100644 --- a/java/com/tigervnc/vncviewer/Viewport.java +++ b/java/com/tigervnc/vncviewer/Viewport.java @@ -1,6 +1,8 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright (C) 2011-2015 Brian P. Hinz - * Copyright (C) 2012-2013 D. R. Commander. All Rights Reserved. + * Copyright (C) 2006 Constantin Kaplinsky. All Rights Reserved. + * Copyright (C) 2009 Paul Donohue. All Rights Reserved. + * Copyright (C) 2010, 2012-2013 D. R. Commander. All Rights Reserved. + * Copyright (C) 2011-2014 Brian P. Hinz * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,203 +20,443 @@ * 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 javax.imageio.*; +import java.io.*; + import com.tigervnc.rfb.*; -import java.lang.Exception; -import java.awt.Rectangle; +import com.tigervnc.rfb.Cursor; +import com.tigervnc.rfb.Point; -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 com.tigervnc.vncviewer.Parameters.*; + +class Viewport extends JPanel implements MouseListener, + MouseMotionListener, MouseWheelListener, KeyListener { + + static LogWriter vlog = new LogWriter("Viewport"); -public class Viewport extends JFrame -{ - public Viewport(String name, CConn cc_) { + 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 (!VncViewer.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) { - if (VncViewer.nViewers == 1) { - if (cc.closeListener != null) { - cc.close(); - } else { - cc.viewer.exit(1); - } - } else { - 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 = cc.viewer.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 && - !cc.fullScreen) { - 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); } - } - }); - } - boolean lionFSSupported() { return canDoLionFS; } + 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; - 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()); } + + 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 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 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); } - public JViewport getViewport() { - return sp.getViewport(); + private PlatformPixelBuffer createFramebuffer(PixelFormat pf, int w, int h) + { + PlatformPixelBuffer fb; + + fb = new JavaPixelBuffer(w, h); + + return fb; } - public void setGeometry(int x, int y, int w, int h) { - pack(); - if (!cc.fullScreen) - setLocation(x, y); + // + // Callback methods to determine geometry of our Component. + // + + public Dimension getPreferredSize() { + return new Dimension(scaledWidth, scaledHeight); } - public Dimension getScreenSize() { - return getScreenBounds().getSize(); + public Dimension getMinimumSize() { + return new Dimension(scaledWidth, scaledHeight); } - public Rectangle getScreenBounds() { - GraphicsEnvironment ge = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - Rectangle r = new Rectangle(); - setMaximizedBounds(null); - if (cc.viewer.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 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 { - GraphicsDevice gd = ge.getDefaultScreenDevice(); - GraphicsConfiguration gc = gd.getDefaultConfiguration(); - r = gc.getBounds(); + g2.drawImage(image, 0, 0, null); } - return r; + g2.dispose(); } - public static Window getFullScreenWindow() { - GraphicsEnvironment ge = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice gd = ge.getDefaultScreenDevice(); - Window fullScreenWindow = gd.getFullScreenWindow(); - return fullScreenWindow; + // 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 static void setFullScreenWindow(Window fullScreenWindow) { - GraphicsEnvironment ge = - GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice gd = ge.getDefaultScreenDevice(); - if (gd.isFullScreenSupported()) - gd.setFullScreenWindow(fullScreenWindow); + // 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) {} - CConn cc; - JScrollPane sp; - boolean canDoLionFS; - static LogWriter vlog = new LogWriter("Viewport"); -} + // MouseWheel callback function + private void mouseWheelCB(MouseWheelEvent e) { + if (!viewOnly.getValue()) + cc.writeWheelEvent(e); + } + + 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 { + 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; + } + if (scaledWidth != getWidth() || scaledHeight != getHeight()) + setSize(new Dimension(scaledWidth, scaledHeight)); + } + + 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 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(); + } + */ + } + + // 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(); + +} diff --git a/java/com/tigervnc/vncviewer/VncViewer.java b/java/com/tigervnc/vncviewer/VncViewer.java index fc9c7b59..f5b31775 100644 --- a/java/com/tigervnc/vncviewer/VncViewer.java +++ b/java/com/tigervnc/vncviewer/VncViewer.java @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2011 Pierre Ossman <ossman@cendio.se> for Cendio AB * Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved. - * Copyright (C) 2011-2015 Brian P. Hinz + * Copyright (C) 2011-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 @@ -35,29 +35,37 @@ import java.awt.event.*; import java.awt.Color; import java.awt.Graphics; import java.awt.Image; +import java.io.BufferedReader; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.IOException; import java.io.File; import java.lang.Character; import java.lang.reflect.*; +import java.net.URL; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.*; import javax.swing.*; +import javax.swing.border.*; import javax.swing.plaf.FontUIResource; +import javax.swing.SwingUtilities; import javax.swing.UIManager.*; import com.tigervnc.rdr.*; import com.tigervnc.rfb.*; import com.tigervnc.network.*; +import static com.tigervnc.vncviewer.Parameters.*; + public class VncViewer extends javax.swing.JApplet implements Runnable, ActionListener { - public static final String aboutText = new String("TigerVNC Java Viewer v%s (%s)%n"+ - "Built on %s at %s%n"+ - "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+ - "See http://www.tigervnc.org for information on TigerVNC."); + public static final String aboutText = + new String("TigerVNC Java Viewer v%s (%s)%n"+ + "Built on %s at %s%n"+ + "Copyright (C) 1999-2016 TigerVNC Team and many others (see README.txt)%n"+ + "See http://www.tigervnc.org for information on TigerVNC."); public static String version = null; public static String build = null; @@ -73,6 +81,7 @@ public class VncViewer extends javax.swing.JApplet VncViewer.class.getResourceAsStream("timestamp"); public static final String os = System.getProperty("os.name").toLowerCase(); + private static VncViewer applet; public static void setLookAndFeel() { try { @@ -133,12 +142,12 @@ public class VncViewer extends javax.swing.JApplet viewer.start(); } + public VncViewer() { + // Only called in applet mode + this(new String[0]); + } public VncViewer(String[] argv) { - embed.setParam(false); - - // load user preferences - UserPreferences.load("global"); SecurityClient.setDefaults(); @@ -150,14 +159,24 @@ public class VncViewer extends javax.swing.JApplet Configuration.enableViewerParams(); + /* Load the default parameter settings */ + String defaultServerName; + try { + defaultServerName = loadViewerParameters(null); + } catch (com.tigervnc.rfb.Exception e) { + defaultServerName = ""; + vlog.info(e.getMessage()); + } + // Override defaults with command-line options for (int i = 0; i < argv.length; i++) { if (argv[i].length() == 0) continue; if (argv[i].equalsIgnoreCase("-config")) { - if (++i >= argv.length) usage(); - Configuration.load(argv[i]); + if (++i >= argv.length) + usage(); + defaultServerName = loadViewerParameters(argv[i]); continue; } @@ -181,28 +200,11 @@ public class VncViewer extends javax.swing.JApplet usage(); } - if (vncServerName.getValue() != null) + if (!vncServerName.getValue().isEmpty()) usage(); vncServerName.setParam(argv[i]); } - if (!autoSelect.hasBeenSet()) { - // Default to AutoSelect=0 if -PreferredEncoding or -FullColor is used - autoSelect.setParam(!preferredEncoding.hasBeenSet() && - !fullColour.hasBeenSet() && - !fullColourAlias.hasBeenSet()); - } - if (!fullColour.hasBeenSet() && !fullColourAlias.hasBeenSet()) { - // Default to FullColor=0 if AutoSelect=0 && LowColorLevel is set - if (!autoSelect.getValue() && (lowColourLevel.hasBeenSet() || - lowColourLevelAlias.hasBeenSet())) { - fullColour.setParam(false); - } - } - if (!customCompressLevel.hasBeenSet()) { - // Default to CustomCompressLevel=1 if CompressLevel is used. - customCompressLevel.setParam(compressLevel.hasBeenSet()); - } } public static void usage() { @@ -272,26 +274,27 @@ public class VncViewer extends javax.swing.JApplet System.exit(1); } - public VncViewer() { - UserPreferences.load("global"); - embed.setParam(true); - } - - public static void newViewer(VncViewer oldViewer, Socket sock, boolean close) { - VncViewer viewer = new VncViewer(); - viewer.embed.setParam(oldViewer.embed.getValue()); - viewer.sock = sock; - viewer.start(); - if (close) - oldViewer.exit(0); - } - - public static void newViewer(VncViewer oldViewer, Socket sock) { - newViewer(oldViewer, sock, false); - } - - public static void newViewer(VncViewer oldViewer) { - newViewer(oldViewer, null); + public static void newViewer() { + String cmd = "java -jar "; + try { + URL url = + VncViewer.class.getProtectionDomain().getCodeSource().getLocation(); + File f = new File(url.toURI()); + if (!f.exists() || !f.canRead()) { + String msg = new String("The jar file "+f.getAbsolutePath()+ + " does not exist or cannot be read."); + JOptionPane.showMessageDialog(null, msg, "ERROR", + JOptionPane.ERROR_MESSAGE); + return; + } + cmd = cmd.concat(f.getAbsolutePath()); + Thread t = new Thread(new ExtProcess(cmd, vlog)); + t.start(); + } catch (java.net.URISyntaxException e) { + vlog.info(e.getMessage()); + } catch (java.lang.Exception e) { + vlog.info(e.getMessage()); + } } public boolean isAppletDragStart(MouseEvent e) { @@ -311,7 +314,7 @@ public class VncViewer extends javax.swing.JApplet public void appletDragStarted() { embed.setParam(false); - cc.recreateViewport(); + //cc.recreateViewport(); JFrame f = (JFrame)JOptionPane.getFrameForComponent(this); // The default JFrame created by the drag event will be // visible briefly between appletDragStarted and Finished. @@ -320,7 +323,6 @@ public class VncViewer extends javax.swing.JApplet } public void appletDragFinished() { - cc.setEmbeddedFeatures(true); JFrame f = (JFrame)JOptionPane.getFrameForComponent(this); if (f != null) f.dispose(); @@ -331,24 +333,72 @@ public class VncViewer extends javax.swing.JApplet } public void appletRestored() { - cc.setEmbeddedFeatures(false); 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); @@ -361,31 +411,40 @@ public class VncViewer extends javax.swing.JApplet } } + public static void showAbout(Container parent) { + String pkgDate = ""; + String pkgTime = ""; + try { + Manifest manifest = new Manifest(VncViewer.timestamp); + Attributes attributes = manifest.getMainAttributes(); + pkgDate = attributes.getValue("Package-Date"); + pkgTime = attributes.getValue("Package-Time"); + } catch (java.lang.Exception e) { } + + Window fullScreenWindow = DesktopWindow.getFullScreenWindow(); + if (fullScreenWindow != null) + DesktopWindow.setFullScreenWindow(null); + String msg = + String.format(VncViewer.aboutText, VncViewer.version, VncViewer.build, + VncViewer.buildDate, VncViewer.buildTime); + Object[] options = {"Close \u21B5"}; + JOptionPane op = + new JOptionPane(msg, JOptionPane.INFORMATION_MESSAGE, + JOptionPane.DEFAULT_OPTION, VncViewer.logoIcon, options); + JDialog dlg = op.createDialog(parent, "About TigerVNC Viewer for Java"); + dlg.setIconImage(VncViewer.frameIcon); + dlg.setAlwaysOnTop(true); + dlg.setVisible(true); + if (fullScreenWindow != null) + DesktopWindow.setFullScreenWindow(fullScreenWindow); + } + public void start() { - vlog.debug("start called"); - getTimestamp(); - if (embed.getValue() && nViewers == 0) { - alwaysShowServerDialog.setParam(false); - Configuration.global().readAppletParams(this); - fullScreen.setParam(false); - scalingFactor.setParam("100"); - String host = getCodeBase().getHost(); - if (vncServerName.getValue() == null && vncServerPort.getValue() != 0) { - int port = vncServerPort.getValue(); - vncServerName.setParam(host + ((port >= 5900 && port <= 5999) - ? (":"+(port-5900)) - : ("::"+port))); - } - } - nViewers++; thread = new Thread(this); thread.start(); } public void exit(int n) { - nViewers--; - if (nViewers > 0) - return; if (embed.getValue()) destroy(); else @@ -425,14 +484,13 @@ public class VncViewer extends javax.swing.JApplet } } - CConn cc; public void run() { cc = null; if (listenMode.getValue()) { int port = 5500; - if (vncServerName.getValue() != null && + if (!vncServerName.getValue().isEmpty() && Character.isDigit(vncServerName.getValue().charAt(0))) port = Integer.parseInt(vncServerName.getValue()); @@ -446,15 +504,26 @@ public class VncViewer extends javax.swing.JApplet vlog.info("Listening on port "+port); - while (true) { - Socket new_sock = listener.accept(); - if (new_sock != null) - newViewer(this, new_sock, true); + while (sock == null) + sock = listener.accept(); + } else { + if (alwaysShowServerDialog.getValue() || sock == null) { + if (vncServerName.getValue().isEmpty()) { + try { + SwingUtilities.invokeAndWait(new ServerDialog()); + } catch (InvocationTargetException e) { + reportException(e); + } catch (InterruptedException e) { + reportException(e); + } + if (vncServerName.getValue().isEmpty()) + exit(0); + } } } try { - cc = new CConn(this, sock, vncServerName.getValue()); + cc = new CConn(vncServerName.getValue(), sock); while (!cc.shuttingDown) cc.processMsg(); exit(0); @@ -462,7 +531,7 @@ public class VncViewer extends javax.swing.JApplet if (cc == null || !cc.shuttingDown) { reportException(e); if (cc != null) - cc.deleteWindow(); + cc.close(); } else if (embed.getValue()) { reportException(new java.lang.Exception("Connection closed")); exit(0); @@ -471,243 +540,12 @@ public class VncViewer extends javax.swing.JApplet } } - 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); - - 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); - - BoolParameter useLocalCursor - = new BoolParameter("UseLocalCursor", - "Render the mouse cursor locally", - true); - BoolParameter sendLocalUsername - = new BoolParameter("SendLocalUsername", - "Send the local username for SecurityTypes "+ - "such as Plain rather than prompting", - true); - StringParameter passwordFile - = new StringParameter("PasswordFile", - "Password file for VNC authentication", - ""); - AliasParameter passwd - = new AliasParameter("passwd", - "Alias for PasswordFile", - passwordFile); - BoolParameter autoSelect - = new BoolParameter("AutoSelect", - "Auto select pixel format and encoding", - true); - BoolParameter fullColour - = new BoolParameter("FullColour", - "Use full colour - otherwise 6-bit colour is "+ - "used until AutoSelect decides the link is "+ - "fast enough", - true); - AliasParameter fullColourAlias - = new AliasParameter("FullColor", - "Alias for FullColour", - fullColour); - IntParameter lowColourLevel - = new IntParameter("LowColorLevel", - "Color level to use on slow connections. "+ - "0 = Very Low (8 colors), 1 = Low (64 colors), "+ - "2 = Medium (256 colors)", - 2); - AliasParameter lowColourLevelAlias - = new AliasParameter("LowColourLevel", - "Alias for LowColorLevel", - lowColourLevel); - StringParameter preferredEncoding - = new StringParameter("PreferredEncoding", - "Preferred encoding to use (Tight, ZRLE, "+ - "hextile or raw) - implies AutoSelect=0", - "Tight"); - BoolParameter viewOnly - = new BoolParameter("ViewOnly", - "Don't send any mouse or keyboard events to "+ - "the server", - false); - BoolParameter shared - = new BoolParameter("Shared", - "Don't disconnect other viewers upon "+ - "connection - share the desktop instead", - false); - BoolParameter fullScreen - = new BoolParameter("FullScreen", - "Full Screen Mode", - false); - BoolParameter fullScreenAllMonitors - = new BoolParameter("FullScreenAllMonitors", - "Enable full screen over all monitors", - true); - BoolParameter acceptClipboard - = new BoolParameter("AcceptClipboard", - "Accept clipboard changes from the server", - true); - BoolParameter sendClipboard - = new BoolParameter("SendClipboard", - "Send clipboard changes to the server", - true); - static IntParameter maxCutText - = new IntParameter("MaxCutText", - "Maximum permitted length of an outgoing clipboard update", - 262144); - StringParameter menuKey - = new StringParameter("MenuKey", - "The key which brings up the popup menu", - "F8"); - StringParameter desktopSize - = new StringParameter("DesktopSize", - "Reconfigure desktop size on the server on "+ - "connect (if possible)", ""); - BoolParameter listenMode - = new BoolParameter("listen", - "Listen for connections from VNC servers", - false); - 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"); - 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); - StringParameter vncServerName - = new StringParameter("Server", - "The VNC server <host>[:<dpyNum>] or "+ - "<host>::<port>", - null); - IntParameter vncServerPort - = new IntParameter("Port", - "The VNC server's port number, assuming it is on "+ - "the host from which the applet was downloaded", - 0); - BoolParameter acceptBell - = new BoolParameter("AcceptBell", - "Produce a system beep when requested to by the server.", - true); - StringParameter via - = new StringParameter("Via", - "Automatically create an encrypted TCP tunnel to "+ - "the gateway machine, then connect to the VNC host "+ - "through that tunnel. By default, this option invokes "+ - "SSH local port forwarding using the embedded JSch "+ - "client, however an external SSH client may be specified "+ - "using the \"-extSSH\" parameter. Note that when using "+ - "the -via option, the VNC host machine name should be "+ - "specified from the point of view of the gateway machine, "+ - "e.g. \"localhost\" denotes the gateway, "+ - "not the machine on which the viewer was launched. "+ - "See the System Properties section below for "+ - "information on configuring the -Via option.", null); - BoolParameter tunnel - = new BoolParameter("Tunnel", - "The -Tunnel command is basically a shorthand for the "+ - "-via command when the VNC server and SSH gateway are "+ - "one and the same. -Tunnel creates an SSH connection "+ - "to the server and forwards the VNC through the tunnel "+ - "without the need to specify anything else.", false); - BoolParameter extSSH - = new BoolParameter("extSSH", - "By default, SSH tunneling uses the embedded JSch client "+ - "for tunnel creation. This option causes the client to "+ - "invoke an external SSH client application for all tunneling "+ - "operations. By default, \"/usr/bin/ssh\" is used, however "+ - "the path to the external application may be specified using "+ - "the -SSHClient option.", false); - StringParameter extSSHClient - = new StringParameter("extSSHClient", - "Specifies the path to an external SSH client application "+ - "that is to be used for tunneling operations when the -extSSH "+ - "option is in effect.", "/usr/bin/ssh"); - StringParameter extSSHArgs - = new StringParameter("extSSHArgs", - "Specifies the arguments string or command template to be used "+ - "by the external SSH client application when the -extSSH option "+ - "is in effect. The string will be processed according to the same "+ - "pattern substitution rules as the VNC_TUNNEL_CMD and VNC_VIA_CMD "+ - "system properties, and can be used to override those in a more "+ - "command-line friendly way. If not specified, then the appropriate "+ - "VNC_TUNNEL_CMD or VNC_VIA_CMD command template will be used.", null); - StringParameter sshConfig - = new StringParameter("SSHConfig", - "Specifies the path to an OpenSSH configuration file that to "+ - "be parsed by the embedded JSch SSH client during tunneling "+ - "operations.", FileUtils.getHomeDir()+".ssh/config"); - StringParameter sshKey - = new StringParameter("SSHKey", - "When using the Via or Tunnel options with the embedded SSH client, "+ - "this parameter specifies the text of the SSH private key to use when "+ - "authenticating with the SSH server. You can use \\n within the string "+ - "to specify a new line.", null); - StringParameter sshKeyFile - = new StringParameter("SSHKeyFile", - "When using the Via or Tunnel options with the embedded SSH client, "+ - "this parameter specifies a file that contains an SSH private key "+ - "(or keys) to use when authenticating with the SSH server. If not "+ - "specified, ~/.ssh/id_dsa or ~/.ssh/id_rsa will be used (if they exist). "+ - "Otherwise, the client will fallback to prompting for an SSH password.", - null); - StringParameter sshKeyPass - = new StringParameter("SSHKeyPass", - "When using the Via or Tunnel options with the embedded SSH client, "+ - "this parameter specifies the passphrase for the SSH key.", null); - BoolParameter customCompressLevel - = new BoolParameter("CustomCompressLevel", - "Use custom compression level. "+ - "Default if CompressLevel is specified.", - false); - IntParameter compressLevel - = new IntParameter("CompressLevel", - "Use specified compression level "+ - "0 = Low, 6 = High", - 1); - BoolParameter noJpeg - = new BoolParameter("NoJPEG", - "Disable lossy JPEG compression in Tight encoding.", - false); - IntParameter qualityLevel - = new IntParameter("QualityLevel", - "JPEG quality level. "+ - "0 = Low, 9 = High", - 8); - StringParameter x509ca - = new StringParameter("X509CA", - "Path to CA certificate to use when authenticating remote servers "+ - "using any of the X509 security schemes (X509None, X509Vnc, etc.). "+ - "Must be in PEM format.", - FileUtils.getHomeDir()+".vnc/x509_ca.pem"); - StringParameter x509crl - = new StringParameter("X509CRL", - "Path to certificate revocation list to use in conjunction with "+ - "-X509CA. Must also be in PEM format.", - FileUtils.getHomeDir()+".vnc/x509_crl.pem"); - StringParameter config - = new StringParameter("config", + public static CConn cc; + public static StringParameter config + = new StringParameter("Config", "Specifies a configuration file to load.", null); Thread thread; Socket sock; - static int nViewers; static LogWriter vlog = new LogWriter("VncViewer"); } |