From b408d0a2dbc9a76937c76e783ffb08d8e5f1ada9 Mon Sep 17 00:00:00 2001 From: "Brian P. Hinz" Date: Thu, 21 Nov 2019 20:00:49 -0500 Subject: RFB refactoring to sync with native client --- java/com/tigervnc/rfb/AuthFailureException.java | 6 +- java/com/tigervnc/rfb/CConnection.java | 458 +++++++++++++++++++----- java/com/tigervnc/rfb/CMsgHandler.java | 43 +-- java/com/tigervnc/rfb/CMsgReader.java | 36 +- java/com/tigervnc/rfb/CMsgWriter.java | 116 +----- java/com/tigervnc/rfb/ConnFailedException.java | 6 +- java/com/tigervnc/rfb/ConnParams.java | 243 ------------- java/com/tigervnc/rfb/CopyRectDecoder.java | 10 +- java/com/tigervnc/rfb/DecodeManager.java | 18 +- java/com/tigervnc/rfb/Decoder.java | 9 +- java/com/tigervnc/rfb/HextileDecoder.java | 9 +- java/com/tigervnc/rfb/RREDecoder.java | 10 +- java/com/tigervnc/rfb/RawDecoder.java | 11 +- java/com/tigervnc/rfb/ScreenSet.java | 4 - java/com/tigervnc/rfb/ServerParams.java | 234 ++++++++++++ java/com/tigervnc/rfb/TightDecoder.java | 24 +- java/com/tigervnc/rfb/ZRLEDecoder.java | 7 +- java/com/tigervnc/vncviewer/CConn.java | 322 ++++------------- java/com/tigervnc/vncviewer/DesktopWindow.java | 28 +- java/com/tigervnc/vncviewer/OptionsDialog.java | 2 +- java/com/tigervnc/vncviewer/Viewport.java | 44 +-- 21 files changed, 843 insertions(+), 797 deletions(-) delete mode 100644 java/com/tigervnc/rfb/ConnParams.java create mode 100644 java/com/tigervnc/rfb/ServerParams.java (limited to 'java') diff --git a/java/com/tigervnc/rfb/AuthFailureException.java b/java/com/tigervnc/rfb/AuthFailureException.java index 542270cb..39dcd786 100644 --- a/java/com/tigervnc/rfb/AuthFailureException.java +++ b/java/com/tigervnc/rfb/AuthFailureException.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2019 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 @@ -19,5 +20,8 @@ package com.tigervnc.rfb; public class AuthFailureException extends Exception { - public AuthFailureException(String s) { super(s); } + public AuthFailureException() { super("Authentication failure"); } + public AuthFailureException(String reason) { + super("Authentication failure: "+reason); + } } diff --git a/java/com/tigervnc/rfb/CConnection.java b/java/com/tigervnc/rfb/CConnection.java index 17c47521..aaa2f490 100644 --- a/java/com/tigervnc/rfb/CConnection.java +++ b/java/com/tigervnc/rfb/CConnection.java @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright (C) 2011-2012 Brian P. Hinz + * Copyright (C) 2011-2019 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 @@ -30,12 +30,24 @@ import com.tigervnc.rdr.*; abstract public class CConnection extends CMsgHandler { + static LogWriter vlog = new LogWriter("CConnection"); + + private static final String osName = + System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + public CConnection() { super(); - csecurity = null; is = null; os = null; reader_ = null; writer_ = null; + csecurity = null; + supportsLocalCursor = false; supportsDesktopResize = false; + is = null; os = null; reader_ = null; writer_ = null; shared = false; - state_ = RFBSTATE_UNINITIALISED; + state_ = stateEnum.RFBSTATE_UNINITIALISED; + pendingPFChange = false; preferredEncoding = Encodings.encodingTight; + compressLevel = 2; qualityLevel = -1; + formatChange = false; encodingChange = false; + firstUpdate = true; pendingUpdate = false; continuousUpdates = false; + forceNonincremental = true; framebuffer = null; decoder = new DecodeManager(this); security = new SecurityClient(); } @@ -46,13 +58,9 @@ abstract public class CConnection extends CMsgHandler { // 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; - } + public 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; } + public void setServerPort(int port_) { serverPort = port_; } // setStreams() sets the streams to be used for the connection. These must // be set before initialiseProtocol() and processMsg() are called. The @@ -66,6 +74,10 @@ abstract public class CConnection extends CMsgHandler { os = os_; } + // 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; } + // 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 @@ -75,6 +87,11 @@ abstract public class CConnection extends CMsgHandler { { decoder.flush(); + if (fb != null) { + assert(fb.width() == server.width()); + assert(fb.height() == server.height()); + } + if ((framebuffer != null) && (fb != null)) { Rect rect = new Rect(); @@ -115,7 +132,7 @@ abstract public class CConnection extends CMsgHandler { // there is data to read on the InStream. public final void initialiseProtocol() { - state_ = RFBSTATE_PROTOCOL_VERSION; + state_ = stateEnum.RFBSTATE_PROTOCOL_VERSION; } // processMsg() should be called whenever there is data to read on the @@ -139,35 +156,55 @@ abstract public class CConnection extends CMsgHandler { private void processVersionMsg() { + ByteBuffer verStr = ByteBuffer.allocate(12); + int majorVersion; + int minorVersion; + vlog.debug("reading protocol version"); - AtomicBoolean done = new AtomicBoolean(); - if (!cp.readVersion(is, done)) { - state_ = RFBSTATE_INVALID; + + if (!is.checkNoWait(12)) + return; + + is.readBytes(verStr, 12); + + if ((new String(verStr.array())).matches("RFB \\d{3}\\.\\d{3}\\n")) { + majorVersion = + Integer.parseInt((new String(verStr.array())).substring(4,7)); + minorVersion = + Integer.parseInt((new String(verStr.array())).substring(8,11)); + } else { + state_ = stateEnum.RFBSTATE_INVALID; throw new Exception("reading version failed: not an RFB server?"); } - if (!done.get()) return; + + server.setVersion(majorVersion, minorVersion); vlog.info("Server supports RFB protocol version " - +cp.majorVersion+"."+ cp.minorVersion); + +server.majorVersion+"."+ server.minorVersion); // The only official RFB protocol versions are currently 3.3, 3.7 and 3.8 - if (cp.beforeVersion(3,3)) { + if (server.beforeVersion(3,3)) { String msg = ("Server gave unsupported RFB protocol version "+ - cp.majorVersion+"."+cp.minorVersion); + server.majorVersion+"."+server.minorVersion); vlog.error(msg); - state_ = RFBSTATE_INVALID; + state_ = stateEnum.RFBSTATE_INVALID; throw new Exception(msg); - } else if (cp.beforeVersion(3,7)) { - cp.setVersion(3,3); - } else if (cp.afterVersion(3,8)) { - cp.setVersion(3,8); + } else if (server.beforeVersion(3,7)) { + server.setVersion(3,3); + } else if (server.afterVersion(3,8)) { + server.setVersion(3,8); } - cp.writeVersion(os); - state_ = RFBSTATE_SECURITY_TYPES; + verStr.clear(); + verStr.put(String.format("RFB %03d.%03d\n", + majorVersion, minorVersion).getBytes()).flip(); + os.writeBytes(verStr.array(), 0, 12); + os.flush(); + + state_ = stateEnum.RFBSTATE_SECURITY_TYPES; vlog.info("Using RFB protocol version "+ - cp.majorVersion+"."+cp.minorVersion); + server.majorVersion+"."+server.minorVersion); } private void processSecurityTypesMsg() @@ -179,7 +216,7 @@ abstract public class CConnection extends CMsgHandler { List secTypes = new ArrayList(); secTypes = security.GetEnabledSecTypes(); - if (cp.isVersion(3,3)) { + if (server.isVersion(3,3)) { // legacy 3.3 server may only offer "vnc authentication" or "none" @@ -244,12 +281,12 @@ abstract public class CConnection extends CMsgHandler { } if (secType == Security.secTypeInvalid) { - state_ = RFBSTATE_INVALID; + state_ = stateEnum.RFBSTATE_INVALID; vlog.error("No matching security types"); throw new Exception("No matching security types"); } - state_ = RFBSTATE_SECURITY; + state_ = stateEnum.RFBSTATE_SECURITY; csecurity = security.GetCSecurity(secType); processSecurityMsg(); } @@ -257,7 +294,7 @@ abstract public class CConnection extends CMsgHandler { private void processSecurityMsg() { vlog.debug("processing security message"); if (csecurity.processMsg(this)) { - state_ = RFBSTATE_SECURITY_RESULT; + state_ = stateEnum.RFBSTATE_SECURITY_RESULT; processSecurityResultMsg(); } } @@ -265,7 +302,7 @@ abstract public class CConnection extends CMsgHandler { private void processSecurityResultMsg() { vlog.debug("processing security result message"); int result; - if (cp.beforeVersion(3,8) && csecurity.getType() == Security.secTypeNone) { + if (server.beforeVersion(3,8) && csecurity.getType() == Security.secTypeNone) { result = Security.secResultOK; } else { if (!is.checkNoWait(1)) return; @@ -284,12 +321,10 @@ abstract public class CConnection extends CMsgHandler { default: throw new Exception("Unknown security result from server"); } - String reason; - if (cp.beforeVersion(3,8)) - reason = "Authentication failure"; - else - reason = is.readString(); - state_ = RFBSTATE_INVALID; + state_ = stateEnum.RFBSTATE_INVALID; + if (server.beforeVersion(3,8)) + throw new AuthFailureException(); + String reason = is.readString(); throw new AuthFailureException(reason); } @@ -299,22 +334,22 @@ abstract public class CConnection extends CMsgHandler { } private void throwConnFailedException() { - state_ = RFBSTATE_INVALID; + state_ = stateEnum.RFBSTATE_INVALID; String reason; reason = is.readString(); throw new ConnFailedException(reason); } private void securityCompleted() { - state_ = RFBSTATE_INITIALISATION; + state_ = stateEnum.RFBSTATE_INITIALISATION; reader_ = new CMsgReader(this, is); - writer_ = new CMsgWriter(cp, os); + writer_ = new CMsgWriter(server, os); vlog.debug("Authentication success!"); authSuccess(); writer_.writeClientInit(shared); } - // Methods to be overridden in a derived class + // Methods overridden from CMsgHandler // Note: These must be called by any deriving classes @@ -322,14 +357,79 @@ abstract public class CConnection extends CMsgHandler { decoder.flush(); super.setDesktopSize(w,h); + + if (continuousUpdates) + writer().writeEnableContinuousUpdates(true, 0, 0, + server.width(), + server.height()); + + resizeFramebuffer(); + assert(framebuffer != null); + assert(framebuffer.width() == server.width()); + assert(framebuffer.height() == server.height()); } public void setExtendedDesktopSize(int reason, - int result, int w, int h, + int result, + int w, int h, ScreenSet layout) { decoder.flush(); super.setExtendedDesktopSize(reason, result, w, h, layout); + + if (continuousUpdates) + writer().writeEnableContinuousUpdates(true, 0, 0, + server.width(), + server.height()); + + resizeFramebuffer(); + assert(framebuffer != null); + assert(framebuffer.width() == server.width()); + assert(framebuffer.height() == server.height()); + } + + public void endOfContinuousUpdates() + { + super.endOfContinuousUpdates(); + + // We've gotten the marker for a format change, so make the pending + // one active + if (pendingPFChange) { + server.setPF(pendingPF); + pendingPFChange = false; + + // We might have another change pending + if (formatChange) + requestNewUpdate(); + } + } + // serverInit() is called when the ServerInit message is received. The + // derived class must call on to CConnection::serverInit(). + public void serverInit(int width, int height, + PixelFormat pf, String name) + { + super.serverInit(width, height, pf, name); + + state_ = stateEnum.RFBSTATE_NORMAL; + vlog.debug("initialisation done"); + + initDone(); + assert(framebuffer != null); + // FIXME: even if the client is scaling? + assert(framebuffer.width() == server.width()); + assert(framebuffer.height() == server.height()); + + // We want to make sure we call SetEncodings at least once + encodingChange = true; + + requestNewUpdate(); + + // This initial update request is a bit of a corner case, so we need + // to help out setting the correct format here. + if (pendingPFChange) { + server.setPF(pendingPF); + pendingPFChange = false; + } } public void readAndDecodeRect(Rect r, int encoding, @@ -339,13 +439,16 @@ abstract public class CConnection extends CMsgHandler { decoder.flush(); } - // 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(); + + assert(framebuffer != null); + + // Note: This might not be true if continuous updates are supported + pendingUpdate = false; + + requestNewUpdate(); } public void framebufferUpdateEnd() @@ -353,6 +456,25 @@ abstract public class CConnection extends CMsgHandler { decoder.flush(); super.framebufferUpdateEnd(); + + // 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 && !continuousUpdates) { + server.setPF(pendingPF); + pendingPFChange = false; + } + + if (firstUpdate) { + if (server.supportsContinuousUpdates) { + vlog.info("Enabling continuous updates"); + continuousUpdates = true; + writer().writeEnableContinuousUpdates(true, 0, 0, + server.width(), + server.height()); + } + + firstUpdate = false; + } } public void dataRect(Rect r, int encoding) @@ -360,20 +482,87 @@ abstract public class CConnection extends CMsgHandler { decoder.decodeRect(r, encoding, framebuffer); } + // Methods to be overridden in a derived class + // authSuccess() is called when authentication has succeeded. - public void authSuccess() + public void authSuccess() { } + + // initDone() is called when the connection is fully established + // and standard messages can be sent. This is called before the + // initial FramebufferUpdateRequest giving a derived class the + // chance to modify pixel format and settings. The derived class + // must also make sure it has provided a valid framebuffer before + // returning. + public void initDone() { } + + // resizeFramebuffer() is called whenever the framebuffer + // dimensions or the screen layout changes. A subclass must make + // sure the pixel buffer has been updated once this call returns. + public void resizeFramebuffer() { + assert(false); } - // serverInit() is called when the ServerInit message is received. The - // derived class must call on to CConnection::serverInit(). - public void serverInit() + // refreshFramebuffer() forces a complete refresh of the entire + // framebuffer + public void refreshFramebuffer() { - state_ = RFBSTATE_NORMAL; - vlog.debug("initialisation done"); + forceNonincremental = true; + + // Without fences, we cannot safely trigger an update request directly + // but must wait for the next update to arrive. + if (continuousUpdates) + requestNewUpdate(); } - // Other methods + // setPreferredEncoding()/getPreferredEncoding() adjusts which + // encoding is listed first as a hint to the server that it is the + // preferred one + public void setPreferredEncoding(int encoding) + { + if (preferredEncoding == encoding) + return; + + preferredEncoding = encoding; + encodingChange = true; + } + + public int getPreferredEncoding() + { + return preferredEncoding; + } + + // setCompressLevel()/setQualityLevel() controls the encoding hints + // sent to the server + public void setCompressLevel(int level) + { + if (compressLevel == level) + return; + + compressLevel = level; + encodingChange = true; + } + + public void setQualityLevel(int level) + { + if (qualityLevel == level) + return; + + qualityLevel = level; + encodingChange = true; + } + + // setPF() controls the pixel format requested from the server. + // server.pf() will automatically be adjusted once the new format + // is active. + public void setPF(PixelFormat pf) + { + if (server.pf().equal(pf) && !formatChange) + return; + + nextPF = pf; + formatChange = true; + } public CMsgReader reader() { return reader_; } public CMsgWriter writer() { return writer_; } @@ -384,26 +573,24 @@ abstract public class CConnection extends CMsgHandler { // 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 boolean isSecure() { return csecurity != null ? csecurity.isSecure() : false; } - - public static final int RFBSTATE_UNINITIALISED = 0; - public static final int RFBSTATE_PROTOCOL_VERSION = 1; - public static final int RFBSTATE_SECURITY_TYPES = 2; - public static final int RFBSTATE_SECURITY = 3; - public static final int RFBSTATE_SECURITY_RESULT = 4; - public static final int RFBSTATE_INITIALISATION = 5; - public static final int RFBSTATE_NORMAL = 6; - public static final int RFBSTATE_INVALID = 7; + boolean isSecure() { return csecurity != null ? csecurity.isSecure() : false; } - public int state() { return state_; } + public enum stateEnum { + RFBSTATE_UNINITIALISED, + RFBSTATE_PROTOCOL_VERSION, + RFBSTATE_SECURITY_TYPES, + RFBSTATE_SECURITY, + RFBSTATE_SECURITY_RESULT, + RFBSTATE_INITIALISATION, + RFBSTATE_NORMAL, + RFBSTATE_INVALID + }; - public int getServerPort() { return serverPort; } - public void setServerPort(int port) { - serverPort = port; - } + public stateEnum state() { return state_; } - protected void setState(int s) { state_ = s; } + protected void setState(stateEnum s) { state_ = s; } protected void setReader(CMsgReader r) { reader_ = r; } protected void setWriter(CMsgWriter w) { writer_ = w; } @@ -420,44 +607,147 @@ abstract public class CConnection extends CMsgHandler { // We cannot guarantee any synchronisation at this level flags = 0; - synchronized(this) { - writer().writeFence(flags, len, data); + writer().writeFence(flags, len, data); + } + + // requestNewUpdate() requests an update from the server, having set the + // format and encoding appropriately. + private void requestNewUpdate() + { + if (formatChange && !pendingPFChange) { + /* Catch incorrect requestNewUpdate calls */ + assert(!pendingUpdate || continuousUpdates); + + // We have to make sure we switch the internal format at a safe + // time. For continuous updates we temporarily disable updates and + // look for a EndOfContinuousUpdates message to see when to switch. + // For classical updates we just got a new update right before this + // function was called, so we need to make sure we finish that + // update before we can switch. + + pendingPFChange = true; + pendingPF = nextPF; + + if (continuousUpdates) + writer().writeEnableContinuousUpdates(false, 0, 0, 0, 0); + + writer().writeSetPixelFormat(pendingPF); + + if (continuousUpdates) + writer().writeEnableContinuousUpdates(true, 0, 0, + server.width(), + server.height()); + formatChange = false; + } + + if (encodingChange) { + updateEncodings(); + encodingChange = false; + } + + if (forceNonincremental || !continuousUpdates) { + pendingUpdate = true; + writer().writeFramebufferUpdateRequest(new Rect(0, 0, + server.width(), + server.height()), + !forceNonincremental); } + + forceNonincremental = false; + } + + // Ask for encodings based on which decoders are supported. Assumes higher + // encoding numbers are more desirable. + + private void updateEncodings() + { + List encodings = new ArrayList(); + + if (server.supportsLocalCursor) { + // JRE on Windows does not support alpha cursor + if (!osName.contains("windows")) + encodings.add(Encodings.pseudoEncodingCursorWithAlpha); + encodings.add(Encodings.pseudoEncodingCursor); + encodings.add(Encodings.pseudoEncodingXCursor); + } + if (server.supportsDesktopResize) { + encodings.add(Encodings.pseudoEncodingDesktopSize); + encodings.add(Encodings.pseudoEncodingExtendedDesktopSize); + } + if (server.supportsClientRedirect) + encodings.add(Encodings.pseudoEncodingClientRedirect); + + encodings.add(Encodings.pseudoEncodingDesktopName); + encodings.add(Encodings.pseudoEncodingLastRect); + encodings.add(Encodings.pseudoEncodingContinuousUpdates); + encodings.add(Encodings.pseudoEncodingFence); + + if (Decoder.supported(preferredEncoding)) { + encodings.add(preferredEncoding); + } + + encodings.add(Encodings.encodingCopyRect); + + for (int i = Encodings.encodingMax; i >= 0; i--) { + if ((i != preferredEncoding) && Decoder.supported(i)) + encodings.add(i); + } + + if (compressLevel >= 0 && compressLevel <= 9) + encodings.add(Encodings.pseudoEncodingCompressLevel0 + compressLevel); + if (qualityLevel >= 0 && qualityLevel <= 9) + encodings.add(Encodings.pseudoEncodingQualityLevel0 + qualityLevel); + + writer().writeSetEncodings(encodings); } private void throwAuthFailureException() { String reason; - vlog.debug("state="+state()+", ver="+cp.majorVersion+"."+cp.minorVersion); - if (state() == RFBSTATE_SECURITY_RESULT && !cp.beforeVersion(3,8)) { + vlog.debug("state="+state()+", ver="+server.majorVersion+"."+server.minorVersion); + if (state() == stateEnum.RFBSTATE_SECURITY_RESULT && !server.beforeVersion(3,8)) { reason = is.readString(); } else { reason = "Authentication failure"; } - state_ = RFBSTATE_INVALID; + state_ = stateEnum.RFBSTATE_INVALID; vlog.error(reason); throw new AuthFailureException(reason); } + public CSecurity csecurity; + public SecurityClient security; + + protected boolean supportsLocalCursor; + protected boolean supportsDesktopResize; + private InStream is; private OutStream os; private CMsgReader reader_; private CMsgWriter writer_; private boolean deleteStreamsWhenDone; private boolean shared; - private int state_ = RFBSTATE_UNINITIALISED; + private stateEnum state_; private String serverName; + private int serverPort; - protected ModifiablePixelBuffer framebuffer; - private DecodeManager decoder; + private boolean pendingPFChange; + private PixelFormat pendingPF; - public CSecurity csecurity; - public SecurityClient security; - public static final int maxSecTypes = 8; - int nSecTypes; - int[] secTypes; - int serverPort; - boolean clientSecTypeOrder; + private int preferredEncoding; + private int compressLevel; + private int qualityLevel; - static LogWriter vlog = new LogWriter("CConnection"); + private boolean formatChange; + private PixelFormat nextPF; + private boolean encodingChange; + + private boolean firstUpdate; + private boolean pendingUpdate; + private boolean continuousUpdates; + + private boolean forceNonincremental; + + protected ModifiablePixelBuffer framebuffer; + private DecodeManager decoder; } diff --git a/java/com/tigervnc/rfb/CMsgHandler.java b/java/com/tigervnc/rfb/CMsgHandler.java index 2f3151b7..5b62f961 100644 --- a/java/com/tigervnc/rfb/CMsgHandler.java +++ b/java/com/tigervnc/rfb/CMsgHandler.java @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2009-2011 Pierre Ossman for Cendio AB * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright (C) 2011 Brian P. Hinz + * Copyright (C) 2011-2019 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 @@ -27,59 +27,62 @@ package com.tigervnc.rfb; abstract public class CMsgHandler { + static LogWriter vlog = new LogWriter("CMsgHandler"); + public CMsgHandler() { - cp = new ConnParams(); + server = new ServerParams(); } public void setDesktopSize(int width, int height) { - cp.width = width; - cp.height = height; + server.setDimensions(width, height); } public void setExtendedDesktopSize(int reason, int result, int width, int height, ScreenSet layout) { - cp.supportsSetDesktopSize = true; + server.supportsSetDesktopSize = true; if ((reason == screenTypes.reasonClient) && (result != screenTypes.resultSuccess)) return; - if (!layout.validate(width, height)) - vlog.error("Server sent us an invalid screen layout"); - - cp.width = width; - cp.height = height; - cp.screenLayout = layout; + server.setDimensions(width, height, layout); } + abstract public void setCursor(int width, int height, Point hotspot, + byte[] data); + public void setPixelFormat(PixelFormat pf) { - cp.setPF(pf); + server.setPF(pf); } public void setName(String name) { - cp.setName(name); + server.setName(name); } public void fence(int flags, int len, byte[] data) { - cp.supportsFence = true; + server.supportsFence = true; } public void endOfContinuousUpdates() { - cp.supportsContinuousUpdates = true; + server.supportsContinuousUpdates = true; } abstract public void clientRedirect(int port, String host, String x509subject); - abstract public void setCursor(int width, int height, Point hotspot, - byte[] data); - abstract public void serverInit(); + public void serverInit(int width, int height, + PixelFormat pf, String name) + { + server.setDimensions(width, height); + server.setPF(pf); + server.setName(name); + } abstract public void readAndDecodeRect(Rect r, int encoding, ModifiablePixelBuffer pb); @@ -93,7 +96,5 @@ abstract public class CMsgHandler { abstract public void bell(); abstract public void serverCutText(String str, int len); - public ConnParams cp; - - static LogWriter vlog = new LogWriter("CMsgHandler"); + public ServerParams server; } diff --git a/java/com/tigervnc/rfb/CMsgReader.java b/java/com/tigervnc/rfb/CMsgReader.java index e65db8c6..56b761e4 100644 --- a/java/com/tigervnc/rfb/CMsgReader.java +++ b/java/com/tigervnc/rfb/CMsgReader.java @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright (C) 2011-2017 Brian P. Hinz + * Copyright (C) 2011-2019 Brian P. Hinz * Copyright (C) 2017 Pierre Ossman for Cendio AB * * This is free software; you can redistribute it and/or modify @@ -34,27 +34,26 @@ import com.tigervnc.rdr.*; public class CMsgReader { + static LogWriter vlog = new LogWriter("CMsgReader"); + protected CMsgReader(CMsgHandler handler_, InStream is_) { imageBufIdealSize = 0; handler = handler_; is = is_; + nUpdateRectsLeft = 0; imageBuf = null; imageBufSize = 0; - 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(); + handler.serverInit(width, height, pf, name); } public void readMsg() @@ -82,6 +81,7 @@ public class CMsgReader { readEndOfContinuousUpdates(); break; default: + vlog.error("unknown message type "+type); throw new Exception("unknown message type"); } } else { @@ -148,11 +148,13 @@ public class CMsgReader { { is.skip(3); int len = is.readU32(); + if (len > 256*1024) { is.skip(len); vlog.error("cut text too long ("+len+" bytes) - ignoring"); return; } + ByteBuffer buf = ByteBuffer.allocate(len); is.readBytes(buf, len); Charset latin1 = Charset.forName("ISO-8859-1"); @@ -196,10 +198,10 @@ public class CMsgReader { protected void readRect(Rect r, int encoding) { - if ((r.br.x > handler.cp.width) || (r.br.y > handler.cp.height)) { + if ((r.br.x > handler.server.width()) || (r.br.y > handler.server.height())) { vlog.error("Rect too big: "+r.width()+"x"+r.height()+" at "+ - r.tl.x+","+r.tl.y+" exceeds "+handler.cp.width+"x"+ - handler.cp.height); + r.tl.x+","+r.tl.y+" exceeds "+handler.server.width()+"x"+ + handler.server.height()); throw new Exception("Rect too big"); } @@ -268,7 +270,7 @@ public class CMsgReader { protected void readSetCursor(int width, int height, Point hotspot) { - int data_len = width * height * (handler.cp.pf().bpp/8); + int data_len = width * height * (handler.server.pf().bpp/8); int mask_len = ((width+7)/8) * height; ByteBuffer data = ByteBuffer.allocate(data_len); ByteBuffer mask = ByteBuffer.allocate(mask_len); @@ -295,9 +297,9 @@ public class CMsgReader { else out.put((byte)0); - handler.cp.pf().rgbFromBuffer(out.duplicate(), in.duplicate(), 1); + handler.server.pf().rgbFromBuffer(out.duplicate(), in.duplicate(), 1); - in.position(in.position() + handler.cp.pf().bpp/8); + in.position(in.position() + handler.server.pf().bpp/8); out.position(out.position() + 3); } } @@ -320,10 +322,10 @@ public class CMsgReader { encoding = is.readS32(); - origPF = handler.cp.pf(); - handler.cp.setPF(rgbaPF); + origPF = handler.server.pf(); + handler.server.setPF(rgbaPF); handler.readAndDecodeRect(pb.getRect(), encoding, pb); - handler.cp.setPF(origPF); + handler.server.setPF(origPF); // ARGB with pre-multiplied alpha works best for BufferedImage if (pb.area() > 0) { @@ -412,7 +414,7 @@ public class CMsgReader { imageBuf = new int[imageBufSize]; } if (nPixels != 0) - nPixels = imageBufSize / (handler.cp.pf().bpp / 8); + nPixels = imageBufSize / (handler.server.pf().bpp / 8); return imageBuf; } @@ -425,6 +427,4 @@ public class CMsgReader { protected int nUpdateRectsLeft; protected int[] imageBuf; protected int imageBufSize; - - static LogWriter vlog = new LogWriter("CMsgReader"); } diff --git a/java/com/tigervnc/rfb/CMsgWriter.java b/java/com/tigervnc/rfb/CMsgWriter.java index 164fa293..f89e40d4 100644 --- a/java/com/tigervnc/rfb/CMsgWriter.java +++ b/java/com/tigervnc/rfb/CMsgWriter.java @@ -1,6 +1,6 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2009-2011 Pierre Ossman for Cendio AB - * Copyright (C) 2011 Brian P. Hinz + * Copyright (C) 2011-2019 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 @@ -23,13 +23,13 @@ package com.tigervnc.rfb; import com.tigervnc.rdr.*; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.util.Iterator; +import java.util.*; public class CMsgWriter { - protected CMsgWriter(ConnParams cp_, OutStream os_) + protected CMsgWriter(ServerParams server_, OutStream os_) { - cp = cp_; + server = server_; os = os_; } @@ -46,98 +46,20 @@ public class CMsgWriter { endMsg(); } - synchronized public void writeSetEncodings(int nEncodings, int[] encodings) + synchronized public void writeSetEncodings(List encodings) { startMsg(MsgTypes.msgTypeSetEncodings); os.skip(1); - os.writeU16(nEncodings); - for (int i = 0; i < nEncodings; i++) - os.writeU32(encodings[i]); + os.writeU16(encodings.size()); + for (Iterator i = encodings.iterator(); i.hasNext();) + os.writeU32(i.next()); endMsg(); } - // Ask for encodings based on which decoders are supported. Assumes higher - // encoding numbers are more desirable. - - synchronized public void writeSetEncodings(int preferredEncoding, boolean useCopyRect) - { - int nEncodings = 0; - int[] encodings = new int[Encodings.encodingMax+3]; - - if (cp.supportsLocalCursor) { - if (cp.supportsLocalCursorWithAlpha) - encodings[nEncodings++] = Encodings.pseudoEncodingCursorWithAlpha; - encodings[nEncodings++] = Encodings.pseudoEncodingCursor; - encodings[nEncodings++] = Encodings.pseudoEncodingXCursor; - } - if (cp.supportsDesktopResize) - encodings[nEncodings++] = Encodings.pseudoEncodingDesktopSize; - if (cp.supportsExtendedDesktopSize) - encodings[nEncodings++] = Encodings.pseudoEncodingExtendedDesktopSize; - if (cp.supportsDesktopRename) - encodings[nEncodings++] = Encodings.pseudoEncodingDesktopName; - if (cp.supportsClientRedirect) - encodings[nEncodings++] = Encodings.pseudoEncodingClientRedirect; - - encodings[nEncodings++] = Encodings.pseudoEncodingLastRect; - encodings[nEncodings++] = Encodings.pseudoEncodingContinuousUpdates; - encodings[nEncodings++] = Encodings.pseudoEncodingFence; - - if (Decoder.supported(preferredEncoding)) { - encodings[nEncodings++] = preferredEncoding; - } - - if (useCopyRect) { - encodings[nEncodings++] = Encodings.encodingCopyRect; - } - - /* - * Prefer encodings in this order: - * - * Tight, ZRLE, Hextile, * - */ - - if ((preferredEncoding != Encodings.encodingTight) && - Decoder.supported(Encodings.encodingTight)) - encodings[nEncodings++] = Encodings.encodingTight; - - if ((preferredEncoding != Encodings.encodingZRLE) && - Decoder.supported(Encodings.encodingZRLE)) - encodings[nEncodings++] = Encodings.encodingZRLE; - - if ((preferredEncoding != Encodings.encodingHextile) && - Decoder.supported(Encodings.encodingHextile)) - encodings[nEncodings++] = Encodings.encodingHextile; - - // 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)) - encodings[nEncodings++] = i; - } - } - - 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) + if (!server.supportsSetDesktopSize) throw new Exception("Server does not support SetDesktopSize"); startMsg(MsgTypes.msgTypeSetDesktopSize); @@ -176,7 +98,7 @@ public class CMsgWriter { synchronized public void writeEnableContinuousUpdates(boolean enable, int x, int y, int w, int h) { - if (!cp.supportsContinuousUpdates) + if (!server.supportsContinuousUpdates) throw new Exception("Server does not support continuous updates"); startMsg(MsgTypes.msgTypeEnableContinuousUpdates); @@ -193,7 +115,7 @@ public class CMsgWriter { synchronized public void writeFence(int flags, int len, byte[] data) { - if (!cp.supportsFence) + if (!server.supportsFence) throw new Exception("Server does not support fences"); if (len > 64) throw new Exception("Too large fence payload"); @@ -211,7 +133,7 @@ public class CMsgWriter { endMsg(); } - synchronized public void keyEvent(int keysym, boolean down) + synchronized public void writeKeyEvent(int keysym, boolean down) { startMsg(MsgTypes.msgTypeKeyEvent); os.writeU8(down?1:0); @@ -220,13 +142,13 @@ public class CMsgWriter { endMsg(); } - synchronized public void pointerEvent(Point pos, int buttonMask) + synchronized public void writePointerEvent(Point pos, int buttonMask) { Point p = new Point(pos.x,pos.y); if (p.x < 0) p.x = 0; if (p.y < 0) p.y = 0; - if (p.x >= cp.width) p.x = cp.width - 1; - if (p.y >= cp.height) p.y = cp.height - 1; + if (p.x >= server.width()) p.x = server.width() - 1; + if (p.y >= server.height()) p.y = server.height() - 1; startMsg(MsgTypes.msgTypePointerEvent); os.writeU8(buttonMask); @@ -246,14 +168,14 @@ public class CMsgWriter { endMsg(); } - synchronized public void startMsg(int type) { + synchronized protected void startMsg(int type) { os.writeU8(type); } - synchronized public void endMsg() { + synchronized protected void endMsg() { os.flush(); } - ConnParams cp; - OutStream os; + protected ServerParams server; + protected OutStream os; } diff --git a/java/com/tigervnc/rfb/ConnFailedException.java b/java/com/tigervnc/rfb/ConnFailedException.java index 7c329146..518ff9af 100644 --- a/java/com/tigervnc/rfb/ConnFailedException.java +++ b/java/com/tigervnc/rfb/ConnFailedException.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2019 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 @@ -19,5 +20,8 @@ package com.tigervnc.rfb; public class ConnFailedException extends Exception { - public ConnFailedException(String s) { super(s); } + public ConnFailedException() { super("Connection failed"); } + public ConnFailedException(String reason) { + super("Connection faied: "+reason); + } } diff --git a/java/com/tigervnc/rfb/ConnParams.java b/java/com/tigervnc/rfb/ConnParams.java deleted file mode 100644 index ce3af718..00000000 --- a/java/com/tigervnc/rfb/ConnParams.java +++ /dev/null @@ -1,243 +0,0 @@ -/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * 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 - * 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.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -public class 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; - supportsLocalCursorWithAlpha = false; - supportsDesktopResize = false; supportsExtendedDesktopSize = false; - supportsDesktopRename = false; supportsLastRect = false; - supportsSetDesktopSize = false; supportsFence = false; - supportsContinuousUpdates = false; - supportsClientRedirect = false; - compressLevel = 6; qualityLevel = -1; fineQualityLevel = -1; - subsampling = subsampleUndefined; name_ = null; verStrPos = 0; - - encodings_ = new ArrayList(); - screenLayout = new ScreenSet(); - - setName(""); - } - - public boolean readVersion(InStream is, AtomicBoolean done) - { - if (verStrPos >= 12) return false; - verStr = new StringBuilder(13); - while (is.checkNoWait(1) && verStrPos < 12) { - verStr.insert(verStrPos++,(char)is.readU8()); - } - - if (verStrPos < 12) { - done.set(false); - return true; - } - done.set(true); - verStr.insert(12,'0'); - verStrPos = 0; - if (verStr.toString().matches("RFB \\d{3}\\.\\d{3}\\n0")) { - majorVersion = Integer.parseInt(verStr.substring(4,7)); - minorVersion = Integer.parseInt(verStr.substring(8,11)); - return true; - } - return false; - } - - public void writeVersion(OutStream os) - { - String str = String.format("RFB %03d.%03d\n", majorVersion, minorVersion); - os.writeBytes(str.getBytes(), 0, 12); - os.flush(); - } - - public int majorVersion; - public int minorVersion; - - public void setVersion(int major, int minor) { - majorVersion = major; minorVersion = minor; - } - public boolean isVersion(int major, int minor) { - return majorVersion == major && minorVersion == minor; - } - public boolean beforeVersion(int major, int minor) { - return (majorVersion < major || - (majorVersion == major && minorVersion < minor)); - } - public boolean afterVersion(int major, int minor) { - return !beforeVersion(major,minor+1); - } - - public int width; - public int height; - public ScreenSet screenLayout; - - public PixelFormat pf() { return pf_; } - public void setPF(PixelFormat pf) { - - pf_ = pf; - - 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_; } - public void setName(String name) - { - name_ = name; - } - - public boolean supportsEncoding(int encoding) - { - return encodings_.indexOf(encoding) != -1; - } - - public void setEncodings(int nEncodings, int[] encodings) - { - useCopyRect = false; - supportsLocalCursor = false; - supportsLocalCursorWithAlpha = false; - supportsDesktopResize = false; - supportsExtendedDesktopSize = false; - supportsLocalXCursor = false; - supportsLastRect = false; - compressLevel = -1; - qualityLevel = -1; - fineQualityLevel = -1; - subsampling = subsampleUndefined; - - encodings_.clear(); - encodings_.add(Encodings.encodingRaw); - - for (int i = nEncodings-1; i >= 0; i--) { - switch (encodings[i]) { - case Encodings.encodingCopyRect: - useCopyRect = true; - break; - case Encodings.pseudoEncodingCursor: - supportsLocalCursor = true; - break; - case Encodings.pseudoEncodingXCursor: - supportsLocalXCursor = true; - break; - case Encodings.pseudoEncodingCursorWithAlpha: - supportsLocalCursorWithAlpha = true; - break; - case Encodings.pseudoEncodingDesktopSize: - supportsDesktopResize = true; - break; - case Encodings.pseudoEncodingExtendedDesktopSize: - supportsExtendedDesktopSize = true; - break; - case Encodings.pseudoEncodingDesktopName: - supportsDesktopRename = true; - break; - case Encodings.pseudoEncodingLastRect: - supportsLastRect = true; - break; - case Encodings.pseudoEncodingFence: - supportsFence = true; - break; - case Encodings.pseudoEncodingContinuousUpdates: - supportsContinuousUpdates = true; - break; - case Encodings.pseudoEncodingClientRedirect: - supportsClientRedirect = 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; - - if (encodings[i] >= Encodings.pseudoEncodingQualityLevel0 && - encodings[i] <= Encodings.pseudoEncodingQualityLevel9) - qualityLevel = encodings[i] - Encodings.pseudoEncodingQualityLevel0; - - if (encodings[i] >= Encodings.pseudoEncodingFineQualityLevel0 && - encodings[i] <= Encodings.pseudoEncodingFineQualityLevel100) - fineQualityLevel = encodings[i] - Encodings.pseudoEncodingFineQualityLevel0; - - if (encodings[i] > 0) - encodings_.add(encodings[i]); - } - } - - public boolean useCopyRect; - - public boolean supportsLocalCursor; - public boolean supportsLocalXCursor; - public boolean supportsLocalCursorWithAlpha; - public boolean supportsDesktopResize; - public boolean supportsExtendedDesktopSize; - public boolean supportsDesktopRename; - public boolean supportsLastRect; - public boolean supportsClientRedirect; - - public boolean supportsSetDesktopSize; - public boolean supportsFence; - public boolean supportsContinuousUpdates; - - public int compressLevel; - public int qualityLevel; - public int fineQualityLevel; - public int subsampling; - - private PixelFormat pf_; - private String name_; - 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 index 40452e21..47861f9c 100644 --- a/java/com/tigervnc/rfb/CopyRectDecoder.java +++ b/java/com/tigervnc/rfb/CopyRectDecoder.java @@ -1,5 +1,5 @@ /* Copyright 2014 Pierre Ossman for Cendio AB - * Copyright 2016 Brian P. Hinz + * Copyright 2016-2019 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 @@ -26,27 +26,27 @@ public class CopyRectDecoder extends Decoder { public CopyRectDecoder() { super(DecoderFlags.DecoderPlain); } public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os) + ServerParams server, OutStream os) { os.copyBytes(is, 4); } public void getAffectedRegion(Rect rect, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, Region region) { MemInStream is = new MemInStream((byte[])buffer, 0, buflen); int srcX = is.readU16(); int srcY = is.readU16(); - super.getAffectedRegion(rect, buffer, buflen, cp, region); + super.getAffectedRegion(rect, buffer, buflen, server, region); region.assign_union(new Region(rect.translate(new Point(srcX-rect.tl.x, srcY-rect.tl.y)))); } public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb) { MemInStream is = new MemInStream((byte[])buffer, 0, buflen); diff --git a/java/com/tigervnc/rfb/DecodeManager.java b/java/com/tigervnc/rfb/DecodeManager.java index d8494446..d117a0b3 100644 --- a/java/com/tigervnc/rfb/DecodeManager.java +++ b/java/com/tigervnc/rfb/DecodeManager.java @@ -1,5 +1,5 @@ /* Copyright 2015 Pierre Ossman for Cendio AB - * Copyright 2016 Brian P. Hinz + * Copyright 2016-2019 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 @@ -107,9 +107,9 @@ public class DecodeManager { if (threads.size() == 1) { bufferStream = freeBuffers.getFirst(); bufferStream.clear(); - decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream); + decoder.readRect(r, conn.getInStream(), conn.server, bufferStream); decoder.decodeRect(r, (Object)bufferStream.data(), bufferStream.length(), - conn.cp, pb); + conn.server, pb); return; } @@ -134,7 +134,7 @@ public class DecodeManager { // Read the rect bufferStream.clear(); - decoder.readRect(r, conn.getInStream(), conn.cp, bufferStream); + decoder.readRect(r, conn.getInStream(), conn.server, bufferStream); // Then try to put it on the queue entry = new QueueEntry(); @@ -143,12 +143,12 @@ public class DecodeManager { entry.rect = r; entry.encoding = encoding; entry.decoder = decoder; - entry.cp = conn.cp; + entry.server = conn.server; entry.pb = pb; entry.bufferStream = bufferStream; decoder.getAffectedRegion(r, bufferStream.data(), - bufferStream.length(), conn.cp, + bufferStream.length(), conn.server, entry.affectedRegion); queueMutex.lock(); @@ -228,7 +228,7 @@ public class DecodeManager { public Rect rect; public int encoding; public Decoder decoder; - public ConnParams cp; + public ServerParams server; public ModifiablePixelBuffer pb; public MemOutStream bufferStream; public Region affectedRegion; @@ -289,7 +289,7 @@ public class DecodeManager { try { entry.decoder.decodeRect(entry.rect, entry.bufferStream.data(), entry.bufferStream.length(), - entry.cp, entry.pb); + entry.server, entry.pb); } catch (com.tigervnc.rdr.Exception e) { manager.setThreadException(e); } catch(java.lang.Exception e) { @@ -363,7 +363,7 @@ public class DecodeManager { entry2.rect, entry2.bufferStream.data(), entry2.bufferStream.length(), - entry.cp)) + entry.server)) lockedRegion.assign_union(entry.affectedRegion); continue next; } diff --git a/java/com/tigervnc/rfb/Decoder.java b/java/com/tigervnc/rfb/Decoder.java index 6bbed85e..0216176d 100644 --- a/java/com/tigervnc/rfb/Decoder.java +++ b/java/com/tigervnc/rfb/Decoder.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2019 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 @@ -38,14 +39,14 @@ abstract public class Decoder { } abstract public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os); + ServerParams server, OutStream os); abstract public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb); public void getAffectedRegion(Rect rect, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, Region region) { region.reset(rect); @@ -54,7 +55,7 @@ abstract public class Decoder { public boolean doRectsConflict(Rect rectA, Object bufferA, int buflenA, Rect rectB, Object bufferB, int buflenB, - ConnParams cp) + ServerParams server) { return false; } diff --git a/java/com/tigervnc/rfb/HextileDecoder.java b/java/com/tigervnc/rfb/HextileDecoder.java index 4c99deeb..c5717782 100644 --- a/java/com/tigervnc/rfb/HextileDecoder.java +++ b/java/com/tigervnc/rfb/HextileDecoder.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2019 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,12 +36,12 @@ public class HextileDecoder extends Decoder { public HextileDecoder() { super(DecoderFlags.DecoderPlain); } public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os) + ServerParams server, OutStream os) { Rect t = new Rect(); int bytesPerPixel; - bytesPerPixel = cp.pf().bpp/8; + bytesPerPixel = server.pf().bpp/8; for (t.tl.y = r.tl.y; t.tl.y < r.br.y; t.tl.y += 16) { @@ -81,11 +82,11 @@ public class HextileDecoder extends Decoder { } public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb) { MemInStream is = new MemInStream((byte[])buffer, 0, buflen); - PixelFormat pf = cp.pf(); + PixelFormat pf = server.pf(); switch (pf.bpp) { case 8: hextileDecode8(r, is, pf, pb); break; case 16: hextileDecode16(r, is, pf, pb); break; diff --git a/java/com/tigervnc/rfb/RREDecoder.java b/java/com/tigervnc/rfb/RREDecoder.java index c73c7a94..2fa98d0c 100644 --- a/java/com/tigervnc/rfb/RREDecoder.java +++ b/java/com/tigervnc/rfb/RREDecoder.java @@ -1,5 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. - * Copyright 2016 Brian P. Hinz + * Copyright 2016-2019 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 @@ -28,22 +28,22 @@ public class RREDecoder extends Decoder { public RREDecoder() { super(DecoderFlags.DecoderPlain); } public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os) + ServerParams server, OutStream os) { int numRects; numRects = is.readU32(); os.writeU32(numRects); - os.copyBytes(is, cp.pf().bpp/8 + numRects * (cp.pf().bpp/8 + 8)); + os.copyBytes(is, server.pf().bpp/8 + numRects * (server.pf().bpp/8 + 8)); } public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb) { MemInStream is = new MemInStream((byte[])buffer, 0, buflen); - PixelFormat pf = cp.pf(); + PixelFormat pf = server.pf(); switch (pf.bpp) { case 8: rreDecode8 (r, is, pf, pb); break; case 16: rreDecode16(r, is, pf, pb); break; diff --git a/java/com/tigervnc/rfb/RawDecoder.java b/java/com/tigervnc/rfb/RawDecoder.java index 71b79607..386845ed 100644 --- a/java/com/tigervnc/rfb/RawDecoder.java +++ b/java/com/tigervnc/rfb/RawDecoder.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2019 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 @@ -25,18 +26,18 @@ public class RawDecoder extends Decoder { public RawDecoder() { super(DecoderFlags.DecoderPlain); } public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os) + ServerParams server, OutStream os) { - os.copyBytes(is, r.area() * cp.pf().bpp/8); + os.copyBytes(is, r.area() * server.pf().bpp/8); } static LogWriter vlog = new LogWriter("RawDecoder"); public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb) { - assert(buflen >= r.area() * cp.pf().bpp/8); - pb.imageRect(cp.pf(), r, (byte[])buffer); + assert(buflen >= r.area() * server.pf().bpp/8); + pb.imageRect(server.pf(), r, (byte[])buffer); } } diff --git a/java/com/tigervnc/rfb/ScreenSet.java b/java/com/tigervnc/rfb/ScreenSet.java index 92437da1..4c4e543e 100644 --- a/java/com/tigervnc/rfb/ScreenSet.java +++ b/java/com/tigervnc/rfb/ScreenSet.java @@ -83,10 +83,6 @@ public class ScreenSet { } } - // FIXME: List order shouldn't matter - //inline bool operator(const ScreenSet& r) const { return screens == r.screens; } - //inline bool operator(const ScreenSet& r) const { return screens != r.screens; } - public ArrayList screens; static LogWriter vlog = new LogWriter("ScreenSet"); diff --git a/java/com/tigervnc/rfb/ServerParams.java b/java/com/tigervnc/rfb/ServerParams.java new file mode 100644 index 00000000..1ed64186 --- /dev/null +++ b/java/com/tigervnc/rfb/ServerParams.java @@ -0,0 +1,234 @@ +/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2011 D. R. Commander. All Rights Reserved. + * Copyright (C) 2012-2019 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.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ServerParams { + + 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 ServerParams() + { + majorVersion = 0; minorVersion = 0; + width_ = 0; height_ = 0; useCopyRect = false; + supportsLocalCursor = false; supportsLocalXCursor = false; + supportsLocalCursorWithAlpha = false; + supportsDesktopResize = false; supportsExtendedDesktopSize = false; + supportsDesktopRename = false; supportsLastRect = false; + supportsSetDesktopSize = false; supportsFence = false; + supportsContinuousUpdates = false; + supportsClientRedirect = false; + compressLevel = 6; qualityLevel = -1; fineQualityLevel = -1; + subsampling = subsampleUndefined; name_ = null; verStrPos = 0; + + encodings_ = new ArrayList(); + screenLayout_ = new ScreenSet(); + + setName(""); + } + + public int majorVersion; + public int minorVersion; + + public void setVersion(int major, int minor) { + majorVersion = major; minorVersion = minor; + } + public boolean isVersion(int major, int minor) { + return majorVersion == major && minorVersion == minor; + } + public boolean beforeVersion(int major, int minor) { + return (majorVersion < major || + (majorVersion == major && minorVersion < minor)); + } + public boolean afterVersion(int major, int minor) { + return !beforeVersion(major,minor+1); + } + + public int width() { return width_; } + public int height() { return height_; } + public ScreenSet screenLayout() { return screenLayout_; } + + public void setDimensions(int width, int height) + { + ScreenSet layout = new ScreenSet(); + layout.add_screen(new Screen(0, 0, 0, width, height, 0)); + setDimensions(width, height, layout); + } + + public void setDimensions(int width, int height, ScreenSet layout) + { + if (!layout.validate(width, height)) + throw new Exception("Attempted to configure an invalid screen layout"); + + width_ = width; + height_ = height; + screenLayout_ = layout; + } + + public PixelFormat pf() { return pf_; } + public void setPF(PixelFormat pf) { + + pf_ = pf; + + 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_; } + public void setName(String name) + { + name_ = name; + } + + public boolean supportsEncoding(int encoding) + { + return encodings_.indexOf(encoding) != -1; + } + + public void setEncodings(int nEncodings, int[] encodings) + { + useCopyRect = false; + supportsLocalCursor = false; + supportsLocalCursorWithAlpha = false; + supportsDesktopResize = false; + supportsExtendedDesktopSize = false; + supportsLocalXCursor = false; + supportsLastRect = false; + compressLevel = -1; + qualityLevel = -1; + fineQualityLevel = -1; + subsampling = subsampleUndefined; + + encodings_.clear(); + encodings_.add(Encodings.encodingRaw); + + for (int i = nEncodings-1; i >= 0; i--) { + switch (encodings[i]) { + case Encodings.encodingCopyRect: + useCopyRect = true; + break; + case Encodings.pseudoEncodingCursor: + supportsLocalCursor = true; + break; + case Encodings.pseudoEncodingXCursor: + supportsLocalXCursor = true; + break; + case Encodings.pseudoEncodingCursorWithAlpha: + supportsLocalCursorWithAlpha = true; + break; + case Encodings.pseudoEncodingDesktopSize: + supportsDesktopResize = true; + break; + case Encodings.pseudoEncodingExtendedDesktopSize: + supportsExtendedDesktopSize = true; + break; + case Encodings.pseudoEncodingDesktopName: + supportsDesktopRename = true; + break; + case Encodings.pseudoEncodingLastRect: + supportsLastRect = true; + break; + case Encodings.pseudoEncodingFence: + supportsFence = true; + break; + case Encodings.pseudoEncodingContinuousUpdates: + supportsContinuousUpdates = true; + break; + case Encodings.pseudoEncodingClientRedirect: + supportsClientRedirect = 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; + + if (encodings[i] >= Encodings.pseudoEncodingQualityLevel0 && + encodings[i] <= Encodings.pseudoEncodingQualityLevel9) + qualityLevel = encodings[i] - Encodings.pseudoEncodingQualityLevel0; + + if (encodings[i] >= Encodings.pseudoEncodingFineQualityLevel0 && + encodings[i] <= Encodings.pseudoEncodingFineQualityLevel100) + fineQualityLevel = encodings[i] - Encodings.pseudoEncodingFineQualityLevel0; + + if (encodings[i] > 0) + encodings_.add(encodings[i]); + } + } + + public boolean useCopyRect; + + public boolean supportsLocalCursor; + public boolean supportsLocalXCursor; + public boolean supportsLocalCursorWithAlpha; + public boolean supportsDesktopResize; + public boolean supportsExtendedDesktopSize; + public boolean supportsDesktopRename; + public boolean supportsLastRect; + public boolean supportsClientRedirect; + + public boolean supportsSetDesktopSize; + public boolean supportsFence; + public boolean supportsContinuousUpdates; + + public int compressLevel; + public int qualityLevel; + public int fineQualityLevel; + public int subsampling; + + private int width_; + private int height_; + private ScreenSet screenLayout_; + + private PixelFormat pf_; + private String name_; + private Cursor cursor_; + private ArrayList encodings_; + private StringBuilder verStr; + private int verStrPos; +} diff --git a/java/com/tigervnc/rfb/TightDecoder.java b/java/com/tigervnc/rfb/TightDecoder.java index 262fcc34..f8cd23c3 100644 --- a/java/com/tigervnc/rfb/TightDecoder.java +++ b/java/com/tigervnc/rfb/TightDecoder.java @@ -1,7 +1,7 @@ /* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. * Copyright 2004-2005 Cendio AB. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. - * Copyright (C) 2011-2012 Brian P. Hinz + * Copyright (C) 2011-2019 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 @@ -59,7 +59,7 @@ public class TightDecoder extends Decoder { } public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os) + ServerParams server, OutStream os) { int comp_ctl; @@ -70,10 +70,10 @@ public class TightDecoder extends Decoder { // "Fill" compression type. if (comp_ctl == tightFill) { - if (cp.pf().is888()) + if (server.pf().is888()) os.copyBytes(is, 3); else - os.copyBytes(is, cp.pf().bpp/8); + os.copyBytes(is, server.pf().bpp/8); return; } @@ -110,13 +110,13 @@ public class TightDecoder extends Decoder { palSize = is.readU8() + 1; os.writeU32(palSize - 1); - if (cp.pf().is888()) + if (server.pf().is888()) os.copyBytes(is, palSize * 3); else - os.copyBytes(is, palSize * cp.pf().bpp/8); + os.copyBytes(is, palSize * server.pf().bpp/8); break; case tightFilterGradient: - if (cp.pf().bpp == 8) + if (server.pf().bpp == 8) throw new Exception("TightDecoder: invalid BPP for gradient filter"); break; case tightFilterCopy: @@ -133,10 +133,10 @@ public class TightDecoder extends Decoder { rowSize = (r.width() + 7) / 8; else rowSize = r.width(); - } else if (cp.pf().is888()) { + } else if (server.pf().is888()) { rowSize = r.width() * 3; } else { - rowSize = r.width() * cp.pf().bpp/8; + rowSize = r.width() * server.pf().bpp/8; } dataSize = r.height() * rowSize; @@ -158,7 +158,7 @@ public class TightDecoder extends Decoder { Rect rectB, Object bufferB, int buflenB, - ConnParams cp) + ServerParams server) { byte comp_ctl_a, comp_ctl_b; @@ -181,11 +181,11 @@ public class TightDecoder extends Decoder { } public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb) { ByteBuffer bufptr; - PixelFormat pf = cp.pf(); + PixelFormat pf = server.pf(); int comp_ctl; diff --git a/java/com/tigervnc/rfb/ZRLEDecoder.java b/java/com/tigervnc/rfb/ZRLEDecoder.java index 03692b96..bd49ab43 100644 --- a/java/com/tigervnc/rfb/ZRLEDecoder.java +++ b/java/com/tigervnc/rfb/ZRLEDecoder.java @@ -1,4 +1,5 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. + * Copyright (C) 2019 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 @@ -52,7 +53,7 @@ public class ZRLEDecoder extends Decoder { } public void readRect(Rect r, InStream is, - ConnParams cp, OutStream os) + ServerParams server, OutStream os) { int len; @@ -62,11 +63,11 @@ public class ZRLEDecoder extends Decoder { } public void decodeRect(Rect r, Object buffer, - int buflen, ConnParams cp, + int buflen, ServerParams server, ModifiablePixelBuffer pb) { MemInStream is = new MemInStream((byte[])buffer, 0, buflen); - PixelFormat pf = cp.pf(); + PixelFormat pf = server.pf(); switch (pf.bpp) { case 8: zrleDecode8(r, is, zis, pf, pb); break; case 16: zrleDecode16(r, is, zis, pf, pb); break; diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java index 75f6b74b..08a82b32 100644 --- a/java/com/tigervnc/vncviewer/CConn.java +++ b/java/com/tigervnc/vncviewer/CConn.java @@ -1,7 +1,7 @@ /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * Copyright 2009-2013 Pierre Ossman for Cendio AB * Copyright (C) 2011-2013 D. R. Commander. All Rights Reserved. - * Copyright (C) 2011-2017 Brian P. Hinz + * Copyright (C) 2011-2019 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 @@ -83,42 +83,21 @@ public class CConn extends CConnection implements public CConn(String vncServerName, Socket socket) { serverHost = null; serverPort = 0; desktop = null; - pendingPFChange = false; - currentEncoding = Encodings.encodingTight; lastServerEncoding = -1; - formatChange = false; encodingChange = false; - firstUpdate = true; pendingUpdate = false; continuousUpdates = false; - forceNonincremental = true; supportsSyncFence = false; + updateCount = 0; pixelCount = 0; + lastServerEncoding = -1; setShared(shared.getValue()); sock = socket; - int encNum = Encodings.encodingNum(preferredEncoding.getValue()); - if (encNum != -1) - currentEncoding = encNum; - - cp.supportsLocalCursor = true; - if (VncViewer.os.contains("windows")) - // JRE on Windows does not support alpha cursor - cp.supportsLocalCursorWithAlpha = false; - else - cp.supportsLocalCursorWithAlpha = true; - - cp.supportsDesktopResize = true; - cp.supportsExtendedDesktopSize = true; - cp.supportsDesktopRename = true; - - cp.supportsSetDesktopSize = false; - cp.supportsClientRedirect = true; + server.supportsLocalCursor = true; + server.supportsDesktopResize = true; + server.supportsClientRedirect = true; if (customCompressLevel.getValue()) - cp.compressLevel = compressLevel.getValue(); - else - cp.compressLevel = -1; + setCompressLevel(compressLevel.getValue()); if (!noJpeg.getValue()) - cp.qualityLevel = qualityLevel.getValue(); - else - cp.qualityLevel = -1; + setQualityLevel(qualityLevel.getValue()); if (sock == null) { setServerName(Hostname.getHost(vncServerName)); @@ -160,16 +139,6 @@ public class CConn extends CConnection implements OptionsDialog.addCallback("handleOptions", this); } - public void refreshFramebuffer() - { - forceNonincremental = true; - - // Without fences, we cannot safely trigger an update request directly - // but must wait for the next update to arrive. - if (supportsSyncFence) - requestNewUpdate(); - } - public String connectionInfo() { String info = new String("Desktop name: %s%n"+ "Host: %s:%d%n"+ @@ -182,21 +151,31 @@ public class CConn extends CConnection implements "Protocol version: %d.%d%n"+ "Security method: %s [%s]%n"); String infoText = - String.format(info, cp.name(), + String.format(info, server.name(), sock.getPeerName(), sock.getPeerPort(), - cp.width, cp.height, - cp.pf().print(), + server.width(), server.height(), + server.pf().print(), serverPF.print(), - Encodings.encodingName(currentEncoding), + Encodings.encodingName(getPreferredEncoding()), Encodings.encodingName(lastServerEncoding), sock.inStream().kbitsPerSecond(), - cp.majorVersion, cp.minorVersion, + server.majorVersion, server.minorVersion, Security.secTypeName(csecurity.getType()), csecurity.description()); return infoText; } + public int getUpdateCount() + { + return updateCount; + } + + public int getPixelCount() + { + return pixelCount; + } + // 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. @@ -216,31 +195,24 @@ public class CConn extends CConnection implements // 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 initDone() { - super.serverInit(); - // If using AutoSelect with old servers, start in FullColor // mode. See comment in autoSelectFormatAndEncoding. - if (cp.beforeVersion(3, 8) && autoSelect.getValue()) + if (server.beforeVersion(3, 8) && autoSelect.getValue()) fullColor.setParam(true); - serverPF = cp.pf(); + serverPF = server.pf(); - desktop = new DesktopWindow(cp.width, cp.height, cp.name(), serverPF, this); + desktop = new DesktopWindow(server.width(), server.height(), + server.name(), serverPF, this); fullColorPF = desktop.getPreferredPF(); // Force a switch to the format and encoding we'd like - formatChange = true; encodingChange = true; - - // And kick off the update cycle - requestNewUpdate(); - - // This initial update request is a bit of a corner case, so we need - // to help out setting the correct format here. - assert(pendingPFChange); - cp.setPF(pendingPF); - pendingPFChange = false; + updatePixelFormat(); + int encNum = Encodings.encodingNum(preferredEncoding.getValue()); + if (encNum != -1) + setPreferredEncoding(encNum); } // setDesktopSize() is called when the desktop size changes (including when @@ -252,8 +224,8 @@ public class CConn extends CConnection implements } // setExtendedDesktopSize() is a more advanced version of setDesktopSize() - public void setExtendedDesktopSize(int reason, int result, int w, int h, - ScreenSet layout) + public void setExtendedDesktopSize(int reason, int result, + int w, int h, ScreenSet layout) { super.setExtendedDesktopSize(reason, result, w, h, layout); @@ -299,23 +271,9 @@ 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. @@ -326,23 +284,9 @@ public class CConn extends CConnection implements { super.framebufferUpdateEnd(); - desktop.updateWindow(); - - if (firstUpdate) { - // 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); + updateCount++; - 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) { - cp.setPF(pendingPF); - pendingPFChange = false; - } + desktop.updateWindow(); // Compute new settings based on updated bandwidth values if (autoSelect.getValue()) @@ -382,6 +326,8 @@ public class CConn extends CConnection implements super.dataRect(r, encoding); sock.inStream().stopTiming(); + + pixelCount += r.area(); } public void setCursor(int width, int height, Point hotspot, @@ -393,7 +339,7 @@ public class CConn extends CConnection implements public void fence(int flags, int len, byte[] data) { // can't call super.super.fence(flags, len, data); - cp.supportsFence = true; + server.supportsFence = true; if ((flags & fenceTypes.fenceFlagRequest) != 0) { // We handle everything synchronously so we trivially honor these modes @@ -402,39 +348,15 @@ public class CConn extends CConnection implements writer().writeFence(flags, len, data); return; } - - if (len == 0) { - // Initial probe - if ((flags & fenceTypes.fenceFlagSyncNext) != 0) { - supportsSyncFence = true; - - if (cp.supportsContinuousUpdates) { - vlog.info("Enabling continuous updates"); - continuousUpdates = true; - writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height); - } - } - } else { - // Pixel format change - MemInStream memStream = new MemInStream(data, 0, len); - PixelFormat pf = new PixelFormat(); - - pf.read(memStream); - - cp.setPF(pf); - } } ////////////////////// Internal methods ////////////////////// - private void resizeFramebuffer() + public void resizeFramebuffer() { if (desktop == null) return; - if (continuousUpdates) - writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height); - - desktop.resizeFramebuffer(cp.width, cp.height); + desktop.resizeFramebuffer(server.width(), server.height()); } // autoSelectFormatAndEncoding() chooses the format and encoding appropriate @@ -456,14 +378,11 @@ public class CConn extends CConnection implements { long kbitsPerSecond = sock.inStream().kbitsPerSecond(); long timeWaited = sock.inStream().timeWaited(); - boolean newFullColor = fullColor.getValue(); + boolean newFullColour = fullColor.getValue(); int newQualityLevel = qualityLevel.getValue(); // Always use Tight - if (currentEncoding != Encodings.encodingTight) { - currentEncoding = Encodings.encodingTight; - encodingChange = true; - } + setPreferredEncoding(Encodings.encodingTight); // Check that we have a decent bandwidth measurement if ((kbitsPerSecond == 0) || (timeWaited < 100)) @@ -479,13 +398,12 @@ public class CConn extends CConnection implements if (newQualityLevel != qualityLevel.getValue()) { vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - changing to quality "+newQualityLevel); - cp.qualityLevel = newQualityLevel; qualityLevel.setParam(newQualityLevel); - encodingChange = true; + setQualityLevel(newQualityLevel); } } - if (cp.beforeVersion(3, 8)) { + if (server.beforeVersion(3, 8)) { // Xvnc from TightVNC 1.2.9 sends out FramebufferUpdates with // cursors "asynchronously". If this happens in the middle of a // pixel format change, the server will encode the cursor with @@ -497,83 +415,38 @@ public class CConn extends CConnection implements } // Select best color level - newFullColor = (kbitsPerSecond > 256); - if (newFullColor != fullColor.getValue()) { - vlog.info("Throughput "+kbitsPerSecond+ - " kbit/s - full color is now "+ - (newFullColor ? "enabled" : "disabled")); - fullColor.setParam(newFullColor); - formatChange = 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; + newFullColour = (kbitsPerSecond > 256); + if (newFullColour != fullColor.getValue()) { + if (newFullColour) + vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now enabled"); + else + vlog.info("Throughput "+kbitsPerSecond+ " kbit/s - full color is now disabled"); + fullColor.setParam(newFullColour); + updatePixelFormat(); } } - // requestNewUpdate() requests an update from the server, having set the + // updatePixelFormat() requests an update from the server, having set the // format and encoding appropriately. - private void requestNewUpdate() + private void updatePixelFormat() { - if (formatChange) { - PixelFormat pf; - - /* Catch incorrect requestNewUpdate calls */ - assert(!pendingUpdate || supportsSyncFence); - - if (fullColor.getValue()) { - pf = fullColorPF; - } else { - if (lowColorLevel.getValue() == 0) { - pf = verylowColorPF; - } else if (lowColorLevel.getValue() == 1) { - pf = lowColorPF; - } else { - pf = mediumColorPF; - } - } - - if (supportsSyncFence) { - // We let the fence carry the pixel format and switch once we - // get the response back. That way we will be synchronised with - // when the server switches. - MemOutStream memStream = new MemOutStream(); - - pf.write(memStream); + PixelFormat pf; - writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext, - memStream.length(), (byte[])memStream.data()); + if (fullColor.getValue()) { + pf = fullColorPF; + } else { + if (lowColorLevel.getValue() == 0) { + pf = verylowColorPF; + } else if (lowColorLevel.getValue() == 1) { + pf = lowColorPF; } else { - // New requests are sent out at the start of processing the last - // one, so we cannot switch our internal format right now (doing so - // would mean misdecoding the current update). - pendingPFChange = true; - pendingPF = pf; + pf = mediumColorPF; } - - String str = pf.print(); - vlog.info("Using pixel format " + str); - writer().writeSetPixelFormat(pf); - - formatChange = false; - } - - checkEncodings(); - - if (forceNonincremental || !continuousUpdates) { - pendingUpdate = true; - writer().writeFramebufferUpdateRequest(new Rect(0, 0, cp.width, cp.height), - !forceNonincremental); } - forceNonincremental = false; + String str = pf.print(); + vlog.info("Using pixel format " + str); + setPF(pf); } public void handleOptions() @@ -587,48 +460,20 @@ public class CConn extends CConnection implements int encNum = Encodings.encodingNum(preferredEncoding.getValue()); if (encNum != -1) - this.currentEncoding = encNum; + this.setPreferredEncoding(encNum); } - this.cp.supportsLocalCursor = true; - if (customCompressLevel.getValue()) - this.cp.compressLevel = compressLevel.getValue(); + this.setCompressLevel(compressLevel.getValue()); else - this.cp.compressLevel = -1; + this.setCompressLevel(-1); if (!noJpeg.getValue() && !autoSelect.getValue()) - this.cp.qualityLevel = qualityLevel.getValue(); + this.setQualityLevel(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(); - } + this.setQualityLevel(-1); + this.updatePixelFormat(); } //////////////////////////////////////////////////////////////////// @@ -654,7 +499,7 @@ public class CConn extends CConnection implements // writeClientCutText() is called from the clipboard dialog public void writeClientCutText(String str, int len) { - if (state() != RFBSTATE_NORMAL || shuttingDown) + if ((state() != stateEnum.RFBSTATE_NORMAL) || shuttingDown) return; writer().writeClientCutText(str, len); } @@ -698,24 +543,13 @@ public class CConn extends CConnection implements protected DesktopWindow desktop; + private int updateCount; + private int pixelCount; + private PixelFormat serverPF; private PixelFormat fullColorPF; - private boolean pendingPFChange; - private PixelFormat pendingPF; - - private int currentEncoding, lastServerEncoding; - - private boolean formatChange; - private boolean encodingChange; - - private boolean firstUpdate; - private boolean pendingUpdate; - private boolean continuousUpdates; - - private boolean forceNonincremental; - - private boolean supportsSyncFence; + private int lastServerEncoding; public ActionListener closeListener = null; diff --git a/java/com/tigervnc/vncviewer/DesktopWindow.java b/java/com/tigervnc/vncviewer/DesktopWindow.java index 42ecad8a..416031f2 100644 --- a/java/com/tigervnc/vncviewer/DesktopWindow.java +++ b/java/com/tigervnc/vncviewer/DesktopWindow.java @@ -129,7 +129,7 @@ public class DesktopWindow extends JFrame // 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) + remoteResize.getValue() && cc.server.supportsSetDesktopSize) timer.start(); } else { String scaleString = scalingFactor.getValue(); @@ -233,7 +233,7 @@ public class DesktopWindow extends JFrame setExtendedState(JFrame.MAXIMIZED_BOTH); } - if (cc.cp.supportsSetDesktopSize && !desktopSize.getValue().equals("")) { + if (cc.server.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) @@ -354,7 +354,7 @@ public class DesktopWindow extends JFrame // to scroll) we just report a single virtual screen that covers // the entire framebuffer. - layout = cc.cp.screenLayout; + layout = cc.server.screenLayout(); // Not sure why we have no screens, but adding a new one should be // safe as there is nothing to conflict with... @@ -418,8 +418,8 @@ public class DesktopWindow extends JFrame sy -= viewport_rect.tl.y; // Look for perfectly matching existing screen... - for (iter = cc.cp.screenLayout.begin(); - iter != cc.cp.screenLayout.end(); iter.next()) { + for (iter = cc.server.screenLayout().begin(); + iter != cc.server.screenLayout().end(); iter.next()) { Screen screen = iter.next(); iter.previous(); if ((screen.dimensions.tl.x == sx) && (screen.dimensions.tl.y == sy) && @@ -429,7 +429,7 @@ public class DesktopWindow extends JFrame } // Found it? - if (iter != cc.cp.screenLayout.end()) { + if (iter != cc.server.screenLayout().end()) { layout.add_screen(iter.next()); continue; } @@ -438,14 +438,14 @@ public class DesktopWindow extends JFrame Random rng = new Random(); while (true) { id = rng.nextInt(); - for (iter = cc.cp.screenLayout.begin(); - iter != cc.cp.screenLayout.end(); iter.next()) { + for (iter = cc.server.screenLayout().begin(); + iter != cc.server.screenLayout().end(); iter.next()) { Screen screen = iter.next(); iter.previous(); if (screen.id == id) break; } - if (iter == cc.cp.screenLayout.end()) + if (iter == cc.server.screenLayout().end()) break; } @@ -460,14 +460,14 @@ public class DesktopWindow extends JFrame } // Do we actually change anything? - if ((width == cc.cp.width) && - (height == cc.cp.height) && - (layout == cc.cp.screenLayout)) + if ((width == cc.server.width()) && + (height == cc.server.height()) && + (layout == cc.server.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)); + cc.server.width(), cc.server.height(), width, height)); layout.debug_print(); if (!layout.validate(width, height)) { @@ -592,7 +592,7 @@ public class DesktopWindow extends JFrame 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); + viewport.setScaledSize(cc.server.width(), cc.server.height()); } else { scroll.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER); scroll.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER); diff --git a/java/com/tigervnc/vncviewer/OptionsDialog.java b/java/com/tigervnc/vncviewer/OptionsDialog.java index c2847a86..f0d4cb00 100644 --- a/java/com/tigervnc/vncviewer/OptionsDialog.java +++ b/java/com/tigervnc/vncviewer/OptionsDialog.java @@ -1606,7 +1606,7 @@ class OptionsDialog extends Dialog { private void handleRfbState() { CConn cc = VncViewer.cc; - if (cc != null && cc.state() == CConnection.RFBSTATE_NORMAL) { + if (cc != null && cc.state() == CConnection.stateEnum.RFBSTATE_NORMAL) { JComponent[] components = { encNoneCheckbox, encTLSCheckbox, encX509Checkbox, authNoneCheckbox, authVncCheckbox, authVncCheckbox, authIdentCheckbox, authPlainCheckbox, diff --git a/java/com/tigervnc/vncviewer/Viewport.java b/java/com/tigervnc/vncviewer/Viewport.java index 1ce411c5..1f21beef 100644 --- a/java/com/tigervnc/vncviewer/Viewport.java +++ b/java/com/tigervnc/vncviewer/Viewport.java @@ -135,8 +135,8 @@ class Viewport extends JPanel implements ActionListener { public void updateWindow() { Rect r = frameBuffer.getDamage(); if (!r.is_empty()) { - if (cc.cp.width != scaledWidth || - cc.cp.height != scaledHeight) { + if (cc.server.width() != scaledWidth || + cc.server.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()); @@ -309,8 +309,8 @@ class Viewport extends JPanel implements ActionListener { public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; synchronized(frameBuffer.getImage()) { - if (cc.cp.width != scaledWidth || - cc.cp.height != scaledHeight) { + if (cc.server.width() != scaledWidth || + cc.server.height() != scaledHeight) { g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.drawImage(frameBuffer.getImage(), 0, 0, @@ -342,14 +342,14 @@ class Viewport extends JPanel implements ActionListener { scaledWidth = width; scaledHeight = height; } else { - float widthRatio = (float)width / (float)cc.cp.width; - float heightRatio = (float)height / (float)cc.cp.height; + float widthRatio = (float)width / (float)cc.server.width(); + float heightRatio = (float)height / (float)cc.server.height(); float ratio = Math.min(widthRatio, heightRatio); - scaledWidth = (int)Math.floor(cc.cp.width * ratio); - scaledHeight = (int)Math.floor(cc.cp.height * ratio); + scaledWidth = (int)Math.floor(cc.server.width() * ratio); + scaledHeight = (int)Math.floor(cc.server.height() * ratio); } - scaleRatioX = (float)scaledWidth / (float)cc.cp.width; - scaleRatioY = (float)scaledHeight / (float)cc.cp.height; + scaleRatioX = (float)scaledWidth / (float)cc.server.width(); + scaleRatioY = (float)scaledHeight / (float)cc.server.height(); } if (scaledWidth != getWidth() || scaledHeight != getHeight()) setSize(new Dimension(scaledWidth, scaledHeight)); @@ -360,15 +360,15 @@ class Viewport extends JPanel implements ActionListener { if (!viewOnly.getValue()) { if (buttonMask != lastButtonMask || !pos.equals(lastPointerPos)) { try { - if (cc.cp.width != scaledWidth || - cc.cp.height != scaledHeight) { + if (cc.server.width() != scaledWidth || + cc.server.height() != scaledHeight) { int sx = (scaleRatioX == 1.00) ? pos.x : (int)Math.floor(pos.x / scaleRatioX); int sy = (scaleRatioY == 1.00) ? pos.y : (int)Math.floor(pos.y / scaleRatioY); pos = pos.translate(new Point(sx - pos.x, sy - pos.y)); } - cc.writer().pointerEvent(pos, buttonMask); + cc.writer().writePointerEvent(pos, buttonMask); } catch (Exception e) { vlog.error("%s", e.getMessage()); cc.close(); @@ -430,8 +430,8 @@ class Viewport extends JPanel implements ActionListener { downKeySym.containsValue(XK_Alt_R)) { vlog.debug("Faking release of AltGr (Ctrl_L+Alt_R)"); try { - cc.writer().keyEvent(XK_Control_L, false); - cc.writer().keyEvent(XK_Alt_R, false); + cc.writer().writeKeyEvent(XK_Control_L, false); + cc.writer().writeKeyEvent(XK_Alt_R, false); } catch (Exception e) { vlog.error("%s", e.getMessage()); cc.close(); @@ -450,9 +450,9 @@ class Viewport extends JPanel implements ActionListener { try { // Fake keycode? if (keyCode > 0xffff) - cc.writer().keyEvent(keySym, true); + cc.writer().writeKeyEvent(keySym, true); else - cc.writer().keyEvent(keySym, true); + cc.writer().writeKeyEvent(keySym, true); } catch (Exception e) { vlog.error("%s", e.getMessage()); cc.close(); @@ -464,8 +464,8 @@ class Viewport extends JPanel implements ActionListener { downKeySym.containsValue(XK_Alt_R)) { vlog.debug("Restoring AltGr state"); try { - cc.writer().keyEvent(XK_Control_L, true); - cc.writer().keyEvent(XK_Alt_R, true); + cc.writer().writeKeyEvent(XK_Control_L, true); + cc.writer().writeKeyEvent(XK_Alt_R, true); } catch (Exception e) { vlog.error("%s", e.getMessage()); cc.close(); @@ -493,9 +493,9 @@ class Viewport extends JPanel implements ActionListener { try { if (keyCode > 0xffff) - cc.writer().keyEvent(iter, false); + cc.writer().writeKeyEvent(iter, false); else - cc.writer().keyEvent(iter, false); + cc.writer().writeKeyEvent(iter, false); } catch (Exception e) { vlog.error("%s", e.getMessage()); cc.close(); @@ -773,7 +773,7 @@ class Viewport extends JPanel implements ActionListener { { setMenuKey(); /* - setScaledSize(cc.cp.width, cc.cp.height); + setScaledSize(cc.server.width(), cc.server.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 -- cgit v1.2.3