@@ -159,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(); |
@@ -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; |
@@ -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_) | |||
{ | |||
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) | |||
{ | |||
reader_ = null; | |||
writer_ = null; | |||
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); | |||
@@ -241,52 +311,30 @@ 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; | |||
} | |||
// 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_; | |||
} | |||
// Methods to be overridden in a derived class | |||
// 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; } | |||
// Note: These must be called by any deriving classes | |||
// 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. | |||
@@ -299,11 +347,20 @@ abstract public class CConnection extends CMsgHandler { | |||
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(). | |||
@@ -313,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; | |||
@@ -353,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) | |||
{ | |||
@@ -383,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"); |
@@ -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"); |
@@ -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; | |||
@@ -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"); | |||
} |
@@ -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; | |||
} |
@@ -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+")"); |