/* Copyright (c) Citrix Systems, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.Threading;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using XenCenterLib;
namespace DotNetVnc
{
public class VNCStream
{
private const int THUMBNAIL_SLEEP_TIME = 500;
private const int RAW_ENCODING = 0;
private const int COPY_RECTANGLE_ENCODING = 1;
private const int RRE_ENCODING = 2;
private const int CORRE_ENCODING = 4;
private const int HEXTILE_ENCODING = 5;
private const int CURSOR_PSEUDO_ENCODING = -239;
private const int DESKTOP_SIZE_PSEUDO_ENCODING = -223;
private const int XENCENTER_ENCODING = -254;
private const int QEMU_EXT_KEY_ENCODING = -258;
private const int SET_PIXEL_FORMAT = 0;
private const int SET_ENCODINGS = 2;
private const int FRAMEBUFFER_UPDATE_REQUEST = 3;
private const int KEY_EVENT = 4;
private const int KEY_SCAN_EVENT = 254;
private const int QEMU_MSG = 255;
private const int POINTER_EVENT = 5;
private const int CLIENT_CUT_TEXT = 6;
private const int RAW_SUBENCODING = 1;
private const int BACKGROUND_SPECIFIED_SUBENCODING = 2;
private const int FOREGROUND_SPECIFIED_SUBENCODING = 4;
private const int ANY_SUBRECTS_SUBENCODING = 8;
private const int SUBRECTS_COLORED_SUBENCODING = 16;
private const int FRAME_BUFFER_UPDATE = 0;
private const int BELL = 2;
private const int SERVER_CUT_TEXT = 3;
private const int QEMU_EXT_KEY_EVENT = 0;
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private Thread thread = null;
/**
* Current color properties
*/
private int bitsPerPixel;
private int bytesPerPixel;
private int depth;
private bool bigEndian;
private bool trueColor;
private int redMax;
private int greenMax;
private int blueMax;
private int redMaxPlus1;
private int greenMaxPlus1;
private int blueMaxPlus1;
private int redMaxOver2;
private int greenMaxOver2;
private int blueMaxOver2;
private int redShift;
private int greenShift;
private int blueShift;
private bool rgb565;
///
/// This event will be fired when an error occurs. The helper thread is guaranteed to be
/// closing down at this point.
///
public event Action ErrorOccurred = null;
public event EventHandler ConnectionSuccess = null;
/**
* The encodings used. Note that these are ordered: preferred encoding first.
*/
private static readonly int[] encodings = new int[] {
//HEXTILE_ENCODING,
CORRE_ENCODING,
RRE_ENCODING,
COPY_RECTANGLE_ENCODING,
RAW_ENCODING,
CURSOR_PSEUDO_ENCODING,
DESKTOP_SIZE_PSEUDO_ENCODING,
XENCENTER_ENCODING,
QEMU_EXT_KEY_ENCODING
};
private readonly IVNCGraphicsClient client;
private readonly MyStream stream;
private readonly Object writeLock = new Object();
private Object pauseMonitor = new Object();
private volatile bool running = true;
private int width;
private int height;
private bool incremental;
private bool qemu_ext_key_encoding = false;
private PixelFormat pixelFormat;
private PixelFormat pixelFormatCursor;
private byte[] data = new byte[1228800]; //640*480*32bpp
private byte[] data_8bpp = null;
private readonly long imageUpdateThreshold;
/*
public struct StatsEntry
{
public double time;
public int size;
}
*/
public VNCStream(IVNCGraphicsClient client, Stream stream, bool startPaused)
{
this.client = client;
this.stream = new MyStream(stream);
this.paused = startPaused;
long freq;
if (!Win32.QueryPerformanceFrequency(out freq))
{
System.Diagnostics.Trace.Assert(false);
}
imageUpdateThreshold = freq / 3;
}
public void connect(char[] password)
{
System.Diagnostics.Trace.Assert(thread == null);
thread = new Thread(this.run);
thread.Name = String.Format("VNC connection to {0} - {1}", client.VmName, client.UUID);
thread.IsBackground = true;
thread.Start(password);
}
private ProtocolVersion getProtocolVersion()
{
byte[] buffer = new byte[12];
this.stream.readFully(buffer, 0, 12);
char[] chars = new char[12];
Encoding.ASCII.GetDecoder().GetChars(buffer, 0, 12, chars, 0);
String s = new String(chars);
Regex regex = new Regex("RFB ([0-9]{3})\\.([0-9]{3})\n");
Match match = regex.Match(s);
if (!match.Success)
{
throw new VNCException("expected protocol version: " + s);
}
return new ProtocolVersion(Int32.Parse(match.Groups[1].Value),
Int32.Parse(match.Groups[2].Value));
}
private void sendProtocolVersion()
{
lock (this.writeLock)
{
byte[] bytes = Encoding.ASCII.GetBytes("RFB 003.003\n");
this.stream.Write(bytes, 0, bytes.Length);
this.stream.Flush();
}
}
private void readPixelFormat()
{
this.bitsPerPixel = this.stream.readCard8();
this.depth = this.stream.readCard8();
this.bigEndian = this.stream.readFlag();
this.trueColor = this.stream.readFlag();
this.redMax = this.stream.readCard16();
this.greenMax = this.stream.readCard16();
this.blueMax = this.stream.readCard16();
this.redShift = this.stream.readCard8();
this.greenShift = this.stream.readCard8();
this.blueShift = this.stream.readCard8();
this.stream.readPadding(3);
Log.Debug("readPixelFormat " + this.bitsPerPixel +
" " + this.depth);
}
private void writePixelFormat()
{
Log.Debug("writePixelFormat " + this.bitsPerPixel +
" " + this.depth);
this.stream.writeInt8(SET_PIXEL_FORMAT);
this.stream.writePadding(3);
this.stream.writeInt8(this.bitsPerPixel);
this.stream.writeInt8(this.depth);
this.stream.writeFlag(this.bigEndian);
this.stream.writeFlag(this.trueColor);
this.stream.writeInt16(this.redMax);
this.stream.writeInt16(this.greenMax);
this.stream.writeInt16(this.blueMax);
this.stream.writeInt8(this.redShift);
this.stream.writeInt8(this.greenShift);
this.stream.writeInt8(this.blueShift);
this.stream.writePadding(3);
}
private void force32bpp()
{
Log.Debug("force32bpp()");
this.bitsPerPixel = 32;
this.depth = 24;
this.trueColor = true;
this.redMax = 255;
this.greenMax = 255;
this.blueMax = 255;
this.redShift = 16;
this.greenShift = 8;
this.blueShift = 0;
// Note that we keep the endian value from the server.
setupPixelFormat();
lock (this.writeLock)
{
writePixelFormat();
}
}
private void setupPixelFormat()
{
Log.Debug("setupPixelFormat(" + bitsPerPixel + ")");
bytesPerPixel = bitsPerPixel >> 3;
redMaxPlus1 = redMax + 1;
greenMaxPlus1 = greenMax + 1;
blueMaxPlus1 = blueMax + 1;
redMaxOver2 = redMax >> 1;
greenMaxOver2 = greenMax >> 1;
blueMaxOver2 = blueMax >> 1;
if (bitsPerPixel == 32 || bitsPerPixel == 8)
{
pixelFormat = PixelFormat.Format32bppRgb;
pixelFormatCursor = PixelFormat.Format32bppArgb;
}
else if (bitsPerPixel == 16)
{
rgb565 = redShift == 11;
pixelFormat =
rgb565 ?
PixelFormat.Format16bppRgb565 :
PixelFormat.Format16bppRgb555;
pixelFormatCursor = PixelFormat.Format16bppArgb1555;
}
else
{
throw new IOException("unexpected bits per pixel: " + bitsPerPixel);
}
}
private void writeSetEncodings()
{
Log.Debug("writeSetEncodings");
this.stream.writeInt8(SET_ENCODINGS);
this.stream.writePadding(1);
this.stream.writeInt16(encodings.Length);
for (int i = 0; i < encodings.Length; ++i)
{
this.stream.writeInt32(encodings[i]);
}
}
private void writeFramebufferUpdateRequest(
int x, int y, int width, int height, bool incremental
)
{
this.stream.writeInt8(FRAMEBUFFER_UPDATE_REQUEST);
this.stream.writeFlag(incremental);
this.stream.writeInt16(x);
this.stream.writeInt16(y);
this.stream.writeInt16(width);
this.stream.writeInt16(height);
}
private void handshake()
{
ProtocolVersion protocolVersion = getProtocolVersion();
if (protocolVersion.major < 3)
{
throw new VNCException(
"don't know protocol version " + protocolVersion.major
);
}
}
private void authenticationExchange(char[] password)
{
Log.Debug("authenticationExchange");
int scheme = this.stream.readCard32();
if (scheme == 0)
{
String reason = this.stream.readString();
throw new VNCException("connection failed: " + reason);
}
else if (scheme == 1)
{
// no authentication needed
}
else if (scheme == 2)
{
PasswordAuthentication(password);
}
else
{
throw new VNCException(
"unexpected authentication scheme: " + scheme
);
}
}
private void PasswordAuthentication(char[] password)
{
byte[] keyBytes = new byte[8];
for (int i = 0; (i < 8) && (i < password.Length); ++i)
{
keyBytes[i] = reverse((byte)password[i]);
}
DESCryptoServiceProvider des =
new DESCryptoServiceProvider();
des.Padding = PaddingMode.None;
des.Mode = CipherMode.ECB;
ICryptoTransform chiper =
des.CreateEncryptor(keyBytes, null);
byte[] challenge = new byte[16];
this.stream.readFully(challenge, 0, 16);
byte[] response = chiper.TransformFinalBlock(challenge, 0, 16);
this.stream.Write(response, 0, 16);
this.stream.Flush();
int status = this.stream.readCard32();
if (status == 0)
{
// ok
}
else if (status == 1 || status == 2)
{
throw new VNCAuthenticationException();
}
else
{
throw new VNCException("Bad Authentication Response");
}
}
private static byte reverse(byte v)
{
byte r = 0;
if ((v & 0x01) != 0) r |= 0x80;
if ((v & 0x02) != 0) r |= 0x40;
if ((v & 0x04) != 0) r |= 0x20;
if ((v & 0x08) != 0) r |= 0x10;
if ((v & 0x10) != 0) r |= 0x08;
if ((v & 0x20) != 0) r |= 0x04;
if ((v & 0x40) != 0) r |= 0x02;
if ((v & 0x80) != 0) r |= 0x01;
return r;
}
private void clientInitialization()
{
Log.Debug("clientInitialisation");
lock (this.writeLock)
{
this.stream.writeFlag(true); // shared
this.stream.Flush();
}
}
private void serverInitialization()
{
Log.Debug("serverInitialisation");
int width = this.stream.readCard16();
int height = this.stream.readCard16();
readPixelFormat();
stream.readString(); /* The desktop name -- we don't care. */
if (trueColor)
{
setupPixelFormat();
lock (writeLock)
{
writePixelFormat();
}
}
else
{
force32bpp();
}
desktopSize(width, height);
lock (this.writeLock)
{
writeSetEncodings();
}
}
/**
* Expects to be lock on writeLock.
*/
private void writeKey(int command, bool down, int key)
{
this.stream.writeInt8(command); //Send Scancodes
this.stream.writeFlag(down);
this.stream.writePadding(2);
this.stream.writeInt32(key);
}
private void writeQemuExtKey(int command, bool down, int key, int sym)
{
this.stream.writeInt8(command);
this.stream.writeInt8(QEMU_EXT_KEY_EVENT);
this.stream.writePadding(1);
this.stream.writeFlag(down);
this.stream.writeInt32(sym);
this.stream.writeInt32(key);
}
/**
* use_qemu_ext_key_encoding: Dictates if we want to use QEMU_EXT_KEY encoding.
*
* XS6.2 doesn't properly support QEMU_EXT_KEY and XS6.5 supports QEMU_EXT_KEY encoding
* only if XS65ESP1051 is applied, so restrict QEMU_EXT_KEY encoding to Inverness and above.
*/
public void keyScanEvent(bool down, int key, int sym, bool use_qemu_ext_key_encoding)
{
lock (this.writeLock)
{
try
{
if (qemu_ext_key_encoding && use_qemu_ext_key_encoding)
{
writeQemuExtKey(QEMU_MSG, down, key, sym);
}
else
{
writeKey(KEY_SCAN_EVENT, down, key);
}
this.stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
public void keyCodeEvent(bool down, int key)
{
lock (this.writeLock)
{
try
{
writeKey(KEY_EVENT, down, key);
this.stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
public void pointerEvent(int buttonMask, int x, int y)
{
if (x < 0)
{
x = 0;
}
else if (x >= width)
{
x = width - 1;
}
if (y < 0)
{
y = 0;
}
else if (y >= height)
{
y = height - 1;
}
lock (this.writeLock)
{
try
{
pointerEvent_(buttonMask, x, y);
this.stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
public void pointerWheelEvent(int x, int y, int r)
{
lock (this.writeLock)
{
try
{
/*
The RFB protocol specifies a down-up pair for each
scroll of the wheel, on button 4 for scrolling up, and
button 5 for scrolling down.
*/
int m;
if (r < 0)
{
r = -r;
m = 8;
}
else
{
m = 16;
}
for (int i = 0; i < r; i++)
{
pointerEvent_(m, x, y);
pointerEvent_(0, x, y);
}
this.stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
private void pointerEvent_(int buttonMask, int x, int y)
{
this.stream.writeInt8(POINTER_EVENT);
this.stream.writeInt8(buttonMask);
this.stream.writeInt16(x);
this.stream.writeInt16(y);
}
public void clientCutText(String text)
{
Log.Debug("cutEvent");
lock (this.writeLock)
{
try
{
this.stream.writeInt8(CLIENT_CUT_TEXT);
this.stream.writePadding(3);
this.stream.writeString(text);
this.stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
///
/// Creates an image in place in this.data. It expects the pixel data to already be in this.data.
///
/// the start of image data in this.data
/// the length of image data in this data
/// length of cursor mask (after the image in this.data), as specified by
/// the RFB protocol specification for the Cursor pseudo-encoding (1-bpp, packed). If 0,
/// the mask is assumed to be totally opaque (as used by normal "raw" packets). Masks are not
/// supported for 8-bpp images.
private void createImage(int width, int height, int x, int y, int start, int length, int mask_length, bool cursor)
{
if (width == 0 || height == 0)
return;
byte[] data_to_render;
int stride;
if (bitsPerPixel == 32)
{
stride = width * 4;
data_to_render = data;
System.Diagnostics.Debug.Assert(length == height * stride);
if (cursor)
{
// for mask
int j = 0; // bit within the current byte (k)
int k = start + length; //byte
int m = 0; // bit within the current row
for (int i = start; i < start + length; i += 4)
{
bool mask = (data[k] & (1 << (7 - j))) == 0;
data[i + 3] = (byte)(mask ? 0 : 0xff);
j++;
m++;
if (m == width)
{
j = 0;
m = 0;
k++;
}
else if (j > 7)
{
j = 0;
k++;
}
}
}
}
else if (bitsPerPixel == 16)
{
// Bitmap requires that stride is a multiple of 4, so we
// will have to expand the data if width is odd.
bool expand_data = width % 2 == 1;
int stride_correction = expand_data ? 2 : 0;
stride = width * 2 + stride_correction;
data_to_render = expand_data ? new byte[stride * height] : data;
System.Diagnostics.Debug.Assert(length == height * width * 2);
if (cursor)
{
int p = 0; // Byte within the destination data_to_render.
// for mask
int j = 0; // bit within the current byte (k)
int k = start + length; //byte
int m = 0; // bit within the current row
for (int i = start; i < start + length; i += 2)
{
bool mask = (data[k] & (1 << (7 - j))) == 0;
byte mask_bit = (byte)(mask ? 0 : 0x80);
if (rgb565)
{
// Convert the 565 data into 1555.
data_to_render[p] = (byte)((data[i] & 0x1f) | ((data[i] & 0xe0) >> 1));
data_to_render[p + 1] = (byte)(((data[i + 1] & 0x7) >> 1) | (data[i + 1] & 0x78) | mask_bit);
}
else
{
// Add the mask bit -- everything else is OK because it's already 555.
data_to_render[p] = data[i];
data_to_render[p + 1] = (byte)(data[i + 1] | mask_bit);
}
j++;
m++;
p += 2;
if (m == width)
{
j = 0;
m = 0;
k++;
p += stride_correction;
}
else if (j > 7)
{
j = 0;
k++;
}
}
}
else if (expand_data)
{
int w2 = width * 2;
int i = start; // Byte within the source data.
int p = 0; // Byte within the destination data_to_render.
for (int m = 0; m < height; m++)
{
Array.Copy(data, i, data_to_render, p, w2);
i += w2;
p += stride;
}
}
}
else if (bitsPerPixel == 8)
{
stride = width * 4;
data_to_render = data_8bpp;
System.Diagnostics.Debug.Assert(length == width * height);
// for mask
int j = 0; // bit within the current byte (k)
int k = start + length; //byte
int m = 0; // bit within the current row
for (int i = start, n = 0; i < start + length; i++, n += 4)
{
data_8bpp[n + 2] = (byte)(((((data[i] >> redShift) & redMax) << 8) + redMaxOver2) / redMaxPlus1);
data_8bpp[n + 1] = (byte)(((((data[i] >> greenShift) & greenMax) << 8) + greenMaxOver2) / greenMaxPlus1);
data_8bpp[n] = (byte)(((((data[i] >> blueShift) & blueMax) << 8) + blueMaxOver2) / blueMaxPlus1);
if (cursor)
{
bool mask = (data[k] & (1 << (7 - j))) == 0;
data_8bpp[n + 3] = (byte)(mask ? 0 : 0xff);
j++;
m++;
if (m == width)
{
j = 0;
m = 0;
k++;
}
else if (j > 7)
{
j = 0;
k++;
}
}
else
{
data_8bpp[n + 3] = 0;
}
}
}
else
{
throw new Exception("unexpected bits per pixel");
}
BitmapToClient(width, height, x, y, start, stride, cursor, data_to_render);
}
private void BitmapToClient(int width, int height, int x, int y, int start, int stride, bool cursor, byte [] img)
{
GCHandle handle = GCHandle.Alloc(img, GCHandleType.Pinned);
try
{
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(img, start);
using (Bitmap bitmap = new Bitmap(width, height, stride, cursor ? pixelFormatCursor : pixelFormat, pointer))
{
if (cursor)
{
client.ClientSetCursor(bitmap, x, y, width, height);
}
else
{
client.ClientDrawImage(bitmap, x, y, width, height);
}
}
}
catch (ArgumentException exn)
{
Log.Error(exn, exn);
}
finally
{
handle.Free();
}
}
private void readRawEncoding(int x, int y, int width, int height)
{
readRawEncoding_(0, x, y, width, height, false);
}
/**
* @param mask If true, read a mask after the raw data, as used by the
* Cursor pseudo-encoding.
* @param start The position in this.data to start using
*/
private void readRawEncoding_(int start, int x, int y, int width, int height, bool cursor)
{
if (width < 0 || height < 0)
{
throw new VNCException("Invalid size: " + width + " x " +
height);
}
int length = width * height * bytesPerPixel;
if (data.Length < start + length)
throw new VNCException("Server error: received rectangle bigger than desktop!");
this.stream.readFully(this.data, start, length);
int mask_length = 0;
if (cursor)
{
// 1 bit mask.
int scanline = (width + 7) >> 3;
mask_length = scanline * height;
System.Diagnostics.Trace.Assert(this.data.Length >= start + length + mask_length);
this.stream.readFully(this.data, start + length, mask_length);
}
createImage(width, height, x, y, start, length, mask_length, cursor);
}
private void readCopyRectangleEncoding(int dx, int dy, int width, int height)
{
int x = this.stream.readCard16();
int y = this.stream.readCard16();
client.ClientCopyRectangle(x, y, width, height, dx, dy);
}
private Color readColor()
{
byte[] color = readColorBytes();
return Color.FromArgb(color[2], color[1], color[0]);
}
private byte[] readColorBytes(byte[] color, int start)
{
uint pixel =
bitsPerPixel == 32 ?
(uint)(color[start] |
color[start + 1] << 8 |
color[start + 2] << 16 |
color[start + 3] << 24) :
bitsPerPixel == 16 ?
(uint)(color[start] |
color[start + 1] << 8) :
(uint)color[start];
byte[] newColor = new byte[4];
//ARGB Encoding
newColor[3] = 0xFF;
newColor[2] = (byte)(((((pixel >> redShift) & redMax) << 8) + redMaxOver2) / redMaxPlus1);
newColor[1] = (byte)(((((pixel >> greenShift) & greenMax) << 8) + greenMaxOver2) / greenMaxPlus1);
newColor[0] = (byte)(((((pixel >> blueShift) & blueMax) << 8) + blueMaxOver2) / blueMaxPlus1);
return newColor;
}
private byte[] readColorBytes()
{
int n = bitsPerPixel >> 3;
byte[] color = new byte[n];
this.stream.readFully(color, 0, n);
return readColorBytes(color, 0);
}
private void readRREEncoding(int x, int y, int width, int height)
{
int n = this.stream.readCard32();
Color background = readColor();
client.ClientFillRectangle(x, y, width, height, background);
for (int i = 0; i < n; ++i)
{
Color foreground = readColor();
int rx = this.stream.readCard16();
int ry = this.stream.readCard16();
int rw = this.stream.readCard16();
int rh = this.stream.readCard16();
client.ClientFillRectangle(x + rx, y + ry, rw, rh, foreground);
}
}
private void readCoRREEncoding(int x, int y, int width, int height)
{
int n = this.stream.readCard32();
Color background = readColor();
client.ClientFillRectangle(x, y, width, height, background);
for (int i = 0; i < n; ++i)
{
Color foreground = readColor();
int rx = this.stream.readCard8();
int ry = this.stream.readCard8();
int rw = this.stream.readCard8();
int rh = this.stream.readCard8();
client.ClientFillRectangle(x + rx, y + ry, rw, rh, foreground);
}
}
private void readFillRectangles(int rx, int ry, int n)
{
int pixelSize = (this.bitsPerPixel + 7) >> 3;
int length = n * (pixelSize + 2);
this.stream.readFully(data, 0, length);
int index = 0;
for (int i = 0; i < n; ++i)
{
Color foreground = readColor();
int sxy = data[index++] & 0xff;
int sx = sxy >> 4;
int sy = sxy & 0xf;
int swh = data[index++] & 0xff;
int sw = (swh >> 4) + 1;
int sh = (swh & 0xf) + 1;
client.ClientFillRectangle(
rx + sx, ry + sy, sw, sh, foreground
);
}
}
private void readRectangles(int rx, int ry, int n, Color foreground)
{
for (int i = 0; i < n; ++i)
{
int sxy = this.stream.readCard8();
int sx = sxy >> 4;
int sy = sxy & 0xf;
int swh = this.stream.readCard8();
int sw = (swh >> 4) + 1;
int sh = (swh & 0xf) + 1;
client.ClientFillRectangle(
rx + sx, ry + sy, sw, sh, foreground
);
}
}
private void fillRectBytes(byte[] data, int stride, int x, int y, int width, int height, byte[] color)
{
// Fast path thin rectangles
int p = (y * stride) + (x * 4);
int skip;
if (width == 1 && height == 1)
{
data[p + 0] = color[0];
data[p + 1] = color[1];
data[p + 2] = color[2];
data[p + 3] = color[3];
}
else if (width == 1)
{
skip = stride - 4;
for (int i = 0; i < height; i++)
{
data[p + 0] = color[0];
data[p + 1] = color[1];
data[p + 2] = color[2];
data[p + 3] = color[3];
p += skip;
}
}
else if (height == 1)
{
for (int i = 0; i < width; i++)
{
data[p + 0] = color[0];
data[p + 1] = color[1];
data[p + 2] = color[2];
data[p + 3] = color[3];
p += 4;
}
}
else
{
skip = stride - (width * 4);
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
data[p + 0] = color[0];
data[p + 1] = color[1];
data[p + 2] = color[2];
data[p + 3] = color[3];
p += 4;
}
p += skip;
}
}
}
private void readHextileEncoding(int x, int y, int width, int height)
{
/*
* Basically, we have two ways of doing this.
* 1. Draw the hextile to a byte buffer, convert to image
* and draw to client
* 2. Draw the individual rectangles of the hex encoding
* directly to the client
*
* I have found its faster to do 1) when size is > 64
*/
//GraphicsUtils.startTime();
if (width * height > 64)
{
byte[] background = { 0, 0, 0, 0xFF }; // Black
byte[] foreground = { 0xFF, 0xFF, 0xFF, 0xFF }; // White
byte[] buff = new byte[width * height * 4]; //assume 32 bpp
int stride = width * 4;
for (int sy = 0; sy < height; sy += 16)
{
int sheight = Math.Min(16, height - sy);
for (int sx = 0; sx < width; sx += 16)
{
int swidth = Math.Min(16, width - sx);
int mask = this.stream.readCard8();
if ((mask & RAW_SUBENCODING) != 0)
{
int length = swidth * sheight * 4;
this.stream.readFully(this.data, 0, length);
int index = 0;
int skip = stride - (swidth * 4);
int p = (sy * stride) + (sx * 4);
for (int i = 0; i < sheight; i++)
{
for (int j = 0; j < swidth; j++)
{
byte[] color = readColorBytes(this.data, index);
index += 4; //assumed 32bpp here
buff[p + 3] = color[3];
buff[p + 2] = color[2];
buff[p + 1] = color[1];
buff[p + 0] = color[0];
p += 4;
}
p += skip;
}
}
else
{
if ((mask & BACKGROUND_SPECIFIED_SUBENCODING) != 0)
{
background = readColorBytes();
}
fillRectBytes(buff, stride, sx, sy, swidth, sheight, background);
if ((mask & FOREGROUND_SPECIFIED_SUBENCODING) != 0)
{
foreground = readColorBytes();
}
if ((mask & ANY_SUBRECTS_SUBENCODING) != 0)
{
int n = this.stream.readCard8();
if ((mask & SUBRECTS_COLORED_SUBENCODING) != 0)
{
int length = n * 6; //assume 32bpp
this.stream.readFully(data, 0, length);
int index = 0;
for (int i = 0; i < n; ++i)
{
byte[] color = new byte[4];
uint pixel = (uint)(data[index + 0] & 0xFF | data[index + 1] << 8
| data[index + 2] << 16 | data[index + 3] << 24);
//ARGB Encoding
color[3] = 0xFF;
color[2] = (byte)((pixel >> this.redShift) & this.redMax);
color[1] = (byte)((pixel >> this.greenShift) & this.greenMax);
color[0] = (byte)((pixel >> this.blueShift) & this.blueMax);
index += 4;
int txy = data[index++] & 0xff;
int tx = txy >> 4;
int ty = txy & 0xf;
int twh = data[index++] & 0xff;
int tw = (twh >> 4) + 1;
int th = (twh & 0xf) + 1;
this.fillRectBytes(
buff, stride, sx + tx, sy + ty, tw, th, color
);
}
}
else
{
for (int i = 0; i < n; ++i)
{
int txy = this.stream.readCard8();
int tx = txy >> 4;
int ty = txy & 0xf;
int twh = this.stream.readCard8();
int tw = (twh >> 4) + 1;
int th = (twh & 0xf) + 1;
this.fillRectBytes(
buff, stride, sx + tx, sy + ty, tw, th, foreground
);
}
}
}
}
}
}
// Now convert to image and write to screen
GCHandle handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
try
{
IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
using (Bitmap bitmap = new Bitmap(width, height, stride, PixelFormat.Format32bppArgb, pointer))
{
client.ClientDrawImage(bitmap, x, y, width, height);
}
}
finally
{
handle.Free();
}
}
else
{
Color foreground = Color.White;
Color background = Color.Black;
int xCount = (width + 15) >> 4;
int yCount = (height + 15) >> 4;
for (int yi = 0; yi < yCount; ++yi)
{
int ry = y + (yi << 4);
int rh = (yi == (yCount - 1)) ? height & 0xf : 16;
if (rh == 0)
{
rh = 16;
}
for (int xi = 0; xi < xCount; ++xi)
{
int rx = x + (xi << 4);
int rw = (xi == (xCount - 1)) ? width & 0xf : 16;
if (rw == 0)
{
rw = 16;
}
int mask = this.stream.readCard8();
if ((mask & RAW_SUBENCODING) != 0)
{
readRawEncoding(rx, ry, rw, rh);
}
else
{
if ((mask & BACKGROUND_SPECIFIED_SUBENCODING) != 0)
{
background = readColor();
}
client.ClientFillRectangle(rx, ry, rw, rh, background);
if ((mask & FOREGROUND_SPECIFIED_SUBENCODING) != 0)
{
foreground = readColor();
}
if ((mask & ANY_SUBRECTS_SUBENCODING) != 0)
{
int n = this.stream.readCard8();
if ((mask & SUBRECTS_COLORED_SUBENCODING) != 0)
{
readFillRectangles(rx, ry, n);
}
else
{
readRectangles(rx, ry, n, foreground);
}
}
}
}
}
}
/*double time = GraphicsUtils.endTime("hextile");
int size = width * height;
StatsEntry entry = new StatsEntry();
entry.time = time;
entry.size = size;
this.client.stats.Add(entry);*/
}
private void readCursorPseudoEncoding(int x, int y, int width,
int height)
{
readRawEncoding_(0, x, y, width, height, true);
}
private void readFrameBufferUpdate()
{
this.stream.readPadding(1);
int n = this.stream.readCard16();
Log.Debug("reading " + n + " rectangles");
bool fb_updated = false;
long start;
long end;
Win32.QueryPerformanceCounter(out start);
for (int i = 0; i < n; ++i)
{
int x = this.stream.readCard16();
int y = this.stream.readCard16();
int width = this.stream.readCard16();
int height = this.stream.readCard16();
int encoding = this.stream.readCard32();
Log.Debug("read " + x + " " + y + " " + width + " " +
height + " " + encoding);
switch (encoding)
{
case RAW_ENCODING:
readRawEncoding(x, y, width, height);
break;
case RRE_ENCODING:
readRREEncoding(x, y, width, height);
break;
case CORRE_ENCODING:
readCoRREEncoding(x, y, width, height);
break;
case COPY_RECTANGLE_ENCODING:
readCopyRectangleEncoding(x, y, width, height);
break;
case HEXTILE_ENCODING:
readHextileEncoding(x, y, width, height);
break;
case CURSOR_PSEUDO_ENCODING:
readCursorPseudoEncoding(x, y, width, height);
break;
case DESKTOP_SIZE_PSEUDO_ENCODING:
desktopSize(width, height);
// Since the desktop size has changed, we want a full buffer update next time
incremental = false;
break;
case QEMU_EXT_KEY_ENCODING:
qemu_ext_key_encoding = true;
break;
default:
throw new VNCException("unimplemented encoding: " + encoding);
}
Win32.QueryPerformanceCounter(out end);
if (end - start > imageUpdateThreshold)
{
client.ClientFrameBufferUpdate();
start = end;
fb_updated = true;
}
else
{
fb_updated = false;
}
}
if (!fb_updated)
client.ClientFrameBufferUpdate();
}
private void desktopSize(int width, int height)
{
this.width = width;
this.height = height;
int neededBytes = width * height * bytesPerPixel;
if (neededBytes > data.Length)
{
data = new byte[neededBytes];
}
if (bitsPerPixel == 8 && (data_8bpp == null || neededBytes * 4 > data_8bpp.Length))
{
data_8bpp = new byte[neededBytes * 4];
}
client.ClientDesktopSize(width, height);
}
private void readServerCutText()
{
this.stream.readPadding(3);
String text = this.stream.readString();
client.ClientCutText(text);
}
private void readServerMessage()
{
Log.Debug("readServerMessage");
int type = this.stream.readCard8();
switch (type)
{
case FRAME_BUFFER_UPDATE:
Log.Debug("Update");
//GraphicsUtils.startTime();
readFrameBufferUpdate();
//GraphicsUtils.endTime("readFrameBufferUpdate");
break;
case BELL:
Log.Debug("Bell");
client.ClientBell();
break;
case SERVER_CUT_TEXT:
Log.Debug("Cut text");
readServerCutText();
break;
default:
throw new VNCException("unknown server message: " + type);
}
}
public readonly Object updateMonitor = new Object();
private void run(object o)
{
char[] password = (char[])o;
try
{
handshake();
sendProtocolVersion();
authenticationExchange(password);
clientInitialization();
serverInitialization();
if (ConnectionSuccess != null)
ConnectionSuccess(this, null);
// Request a full framebuffer update the first time
incremental = false;
while (running)
{
lock (this.writeLock)
{
writeFramebufferUpdateRequest(0, 0, width, height, incremental);
this.stream.Flush();
}
incremental = true;
readServerMessage();
lock (pauseMonitor)
{
lock(updateMonitor)
Monitor.PulseAll(updateMonitor);
if (paused)
Monitor.Wait(pauseMonitor);
}
}
}
catch (Exception e)
{
if (running && this.ErrorOccurred != null)
ErrorOccurred(this, e);
}
}
///
/// Nothrow guarantee.
///
public void Close()
{
if (!running)
return;
running = false;
try
{
stream.Close();
lock (pauseMonitor)
Monitor.PulseAll(this.pauseMonitor);
thread.Interrupt();
}
catch
{
}
}
private bool paused = true;
public void Pause()
{
paused = true;
}
public void Unpause(bool fullupdate)
{
incremental = !fullupdate;
paused = false;
lock (this.pauseMonitor)
Monitor.PulseAll(this.pauseMonitor);
}
public void Unpause()
{
Unpause(false);
}
}
}