/* 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 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;
#region 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;
#endregion
///
/// 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;
public event EventHandler ConnectionSuccess;
///
/// The encodings used. Note that these are ordered: preferred encoding first.
///
private static readonly int[] encodings = {
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 readonly object pauseMonitor = new object();
private volatile bool running = true;
private bool paused;
private int _width;
private int _height;
private bool _incremental;
private bool qemu_ext_key_encoding;
private PixelFormat pixelFormat;
private PixelFormat pixelFormatCursor;
private byte[] _data = new byte[1228800]; //640*480*32bpp
private byte[] data_8bpp;
private readonly long imageUpdateThreshold;
public readonly object updateMonitor = new object();
[System.Diagnostics.CodeAnalysis.SuppressMessage("csharpsquid",
"S5547:Cipher algorithms should be robust",
Justification = "Needed by the server side.")]
private DESCryptoServiceProvider des = new DESCryptoServiceProvider
{
Padding = PaddingMode.None,
Mode = CipherMode.ECB
};
public VNCStream(IVNCGraphicsClient client, Stream stream, bool startPaused)
{
this.client = client;
this.stream = new MyStream(stream);
paused = startPaused;
if (!Win32.QueryPerformanceFrequency(out var freq))
{
System.Diagnostics.Trace.Assert(false);
}
imageUpdateThreshold = freq / 3;
}
public void Connect(char[] password)
{
System.Diagnostics.Trace.Assert(thread == null);
thread = new Thread(Run)
{
Name = $"VNC connection to {client.VmName} - {client.UUID}",
IsBackground = true
};
thread.Start(password);
}
private void CheckProtocolVersion()
{
byte[] buffer = new byte[12];
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("Unexpected protocol version " + s);
int major = Int32.Parse(match.Groups[1].Value);
int minor = Int32.Parse(match.Groups[2].Value);
if (major < 3)
throw new VNCException($"Unsupported protocol version {major}.{minor}");
}
private void SendProtocolVersion()
{
lock (writeLock)
{
byte[] bytes = Encoding.ASCII.GetBytes("RFB 003.003\n");
stream.Write(bytes, 0, bytes.Length);
stream.Flush();
}
}
private void ReadPixelFormat()
{
bitsPerPixel = stream.readCard8();
depth = stream.readCard8();
bigEndian = stream.readFlag();
trueColor = stream.readFlag();
redMax = stream.readCard16();
greenMax = stream.readCard16();
blueMax = stream.readCard16();
redShift = stream.readCard8();
greenShift = stream.readCard8();
blueShift = stream.readCard8();
stream.readPadding(3);
Log.Debug("readPixelFormat " + bitsPerPixel + " " + depth);
}
private void WritePixelFormat()
{
Log.Debug("writePixelFormat " + bitsPerPixel + " " + depth);
stream.writeInt8(SET_PIXEL_FORMAT);
stream.writePadding(3);
stream.writeInt8(bitsPerPixel);
stream.writeInt8(depth);
stream.writeFlag(bigEndian);
stream.writeFlag(trueColor);
stream.writeInt16(redMax);
stream.writeInt16(greenMax);
stream.writeInt16(blueMax);
stream.writeInt8(redShift);
stream.writeInt8(greenShift);
stream.writeInt8(blueShift);
stream.writePadding(3);
}
private void Force32bpp()
{
Log.Debug("force32bpp()");
bitsPerPixel = 32;
depth = 24;
trueColor = true;
redMax = 255;
greenMax = 255;
blueMax = 255;
redShift = 16;
greenShift = 8;
blueShift = 0;
// Note that we keep the endian value from the server.
SetupPixelFormat();
lock (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");
stream.writeInt8(SET_ENCODINGS);
stream.writePadding(1);
stream.writeInt16(encodings.Length);
foreach (var encoding in encodings)
stream.writeInt32(encoding);
}
private void WriteFramebufferUpdateRequest(int x, int y, int width, int height, bool incremental)
{
stream.writeInt8(FRAMEBUFFER_UPDATE_REQUEST);
stream.writeFlag(incremental);
stream.writeInt16(x);
stream.writeInt16(y);
stream.writeInt16(width);
stream.writeInt16(height);
}
private void AuthenticationExchange(char[] password)
{
Log.Debug("authenticationExchange");
int scheme = stream.readCard32();
switch (scheme)
{
case 0:
var reason = stream.readString();
throw new VNCException("connection failed: " + reason);
case 1:
// no authentication needed
break;
case 2:
PasswordAuthentication(password);
break;
default:
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]);
ICryptoTransform cipher = des.CreateEncryptor(keyBytes, null);
byte[] challenge = new byte[16];
stream.readFully(challenge, 0, 16);
byte[] response = cipher.TransformFinalBlock(challenge, 0, 16);
stream.Write(response, 0, 16);
stream.Flush();
int status = stream.readCard32();
switch (status)
{
case 0:
break;
case 1:
case 2:
throw new VNCAuthenticationException();
default:
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 InitializeClient()
{
Log.Debug("clientInitialisation");
lock (writeLock)
{
stream.writeFlag(true); // shared
stream.Flush();
}
}
private void InitializateServer()
{
Log.Debug("serverInitialisation");
int width = stream.readCard16();
int height = stream.readCard16();
ReadPixelFormat();
stream.readString(); /* The desktop name -- we don't care. */
if (trueColor)
{
SetupPixelFormat();
lock (writeLock)
{
WritePixelFormat();
}
}
else
{
Force32bpp();
}
DesktopSize(width, height);
lock (writeLock)
{
WriteSetEncodings();
}
}
/**
* Expects to be lock on writeLock.
*/
private void WriteKey(int command, bool down, int key)
{
stream.writeInt8(command); //Send Scancodes
stream.writeFlag(down);
stream.writePadding(2);
stream.writeInt32(key);
}
private void WriteQemuExtKey(int command, bool down, int key, int sym)
{
stream.writeInt8(command);
stream.writeInt8(QEMU_EXT_KEY_EVENT);
stream.writePadding(1);
stream.writeFlag(down);
stream.writeInt32(sym);
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 (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);
}
stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
public void keyCodeEvent(bool down, int key)
{
lock (writeLock)
{
try
{
WriteKey(KEY_EVENT, down, key);
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 (writeLock)
{
try
{
PointerEvent_(buttonMask, x, y);
stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
public void PointerWheelEvent(int x, int y, int r)
{
lock (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);
}
stream.Flush();
}
catch (IOException e)
{
Log.Warn(e, e);
}
}
}
private void PointerEvent_(int buttonMask, int x, int y)
{
stream.writeInt8(POINTER_EVENT);
stream.writeInt8(buttonMask);
stream.writeInt16(x);
stream.writeInt16(y);
}
public void ClientCutText(string text)
{
Log.Debug("cutEvent");
lock (writeLock)
{
try
{
stream.writeInt8(CLIENT_CUT_TEXT);
stream.writePadding(3);
stream.writeString(text);
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 maskLength, bool cursor)
{
if (width == 0 || height == 0)
return;
byte[] dataToRender;
int stride;
if (bitsPerPixel == 32)
{
stride = width * 4;
dataToRender = _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;
dataToRender = 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.
dataToRender[p] = (byte)((_data[i] & 0x1f) | ((_data[i] & 0xe0) >> 1));
dataToRender[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.
dataToRender[p] = _data[i];
dataToRender[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, dataToRender, p, w2);
i += w2;
p += stride;
}
}
}
else if (bitsPerPixel == 8)
{
stride = width * 4;
dataToRender = 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, dataToRender);
}
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!");
stream.readFully(_data, start, length);
int maskLength = 0;
if (cursor)
{
// 1 bit mask.
int scanline = (width + 7) >> 3;
maskLength = scanline * height;
System.Diagnostics.Trace.Assert(_data.Length >= start + length + maskLength);
stream.readFully(_data, start + length, maskLength);
}
CreateImage(width, height, x, y, start, length, maskLength, cursor);
}
private void ReadCopyRectangleEncoding(int dx, int dy, int width, int height)
{
int x = stream.readCard16();
int y = 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;
switch (bitsPerPixel)
{
case 32:
pixel = (uint)(color[start] |
color[start + 1] << 8 |
color[start + 2] << 16 |
color[start + 3] << 24);
break;
case 16:
pixel = (uint)(color[start] |
color[start + 1] << 8);
break;
default:
pixel = color[start];
break;
}
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];
stream.readFully(color, 0, n);
return ReadColorBytes(color, 0);
}
private void ReadRREEncoding(int x, int y, int width, int height)
{
int n = stream.readCard32();
Color background = ReadColor();
client.ClientFillRectangle(x, y, width, height, background);
for (int i = 0; i < n; ++i)
{
Color foreground = ReadColor();
int rx = stream.readCard16();
int ry = stream.readCard16();
int rw = stream.readCard16();
int rh = stream.readCard16();
client.ClientFillRectangle(x + rx, y + ry, rw, rh, foreground);
}
}
private void ReadCoRREEncoding(int x, int y, int width, int height)
{
int n = stream.readCard32();
Color background = ReadColor();
client.ClientFillRectangle(x, y, width, height, background);
for (int i = 0; i < n; ++i)
{
Color foreground = ReadColor();
int rx = stream.readCard8();
int ry = stream.readCard8();
int rw = stream.readCard8();
int rh = stream.readCard8();
client.ClientFillRectangle(x + rx, y + ry, rw, rh, foreground);
}
}
private void ReadFillRectangles(int rx, int ry, int n)
{
int pixelSize = (bitsPerPixel + 7) >> 3;
int length = n * (pixelSize + 2);
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 = stream.readCard8();
int sx = sxy >> 4;
int sy = sxy & 0xf;
int swh = 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 = stream.readCard8();
if ((mask & RAW_SUBENCODING) != 0)
{
int length = swidth * sheight * 4;
stream.readFully(_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(_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 = stream.readCard8();
if ((mask & SUBRECTS_COLORED_SUBENCODING) != 0)
{
int length = n * 6; //assume 32bpp
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 >> redShift) & redMax);
color[1] = (byte)((pixel >> greenShift) & greenMax);
color[0] = (byte)((pixel >> blueShift) & 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;
FillRectBytes(buff, stride, sx + tx, sy + ty, tw, th, color);
}
}
else
{
for (int i = 0; i < n; ++i)
{
int txy = stream.readCard8();
int tx = txy >> 4;
int ty = txy & 0xf;
int twh = stream.readCard8();
int tw = (twh >> 4) + 1;
int th = (twh & 0xf) + 1;
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 = 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 = 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()
{
stream.readPadding(1);
int n = stream.readCard16();
Log.Debug("reading " + n + " rectangles");
bool fb_updated = false;
Win32.QueryPerformanceCounter(out var start);
for (int i = 0; i < n; ++i)
{
int x = stream.readCard16();
int y = stream.readCard16();
int width = stream.readCard16();
int height = stream.readCard16();
int encoding = 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 var 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)
{
_width = width;
_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()
{
stream.readPadding(3);
String text = stream.readString();
client.ClientCutText(text);
}
private void ReadServerMessage()
{
Log.Debug("readServerMessage");
int type = stream.readCard8();
switch (type)
{
case FRAME_BUFFER_UPDATE:
Log.Debug("Update");
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);
}
}
private void Run(object o)
{
char[] password = (char[])o;
try
{
CheckProtocolVersion();
SendProtocolVersion();
AuthenticationExchange(password);
InitializeClient();
InitializateServer();
if (ConnectionSuccess != null)
ConnectionSuccess(this, null);
// Request a full framebuffer update the first time
_incremental = false;
while (running)
{
lock (writeLock)
{
WriteFramebufferUpdateRequest(0, 0, _width, _height, _incremental);
stream.Flush();
}
_incremental = true;
ReadServerMessage();
lock (pauseMonitor)
{
lock(updateMonitor)
Monitor.PulseAll(updateMonitor);
if (paused)
Monitor.Wait(pauseMonitor);
}
}
}
catch (Exception e)
{
if (running && ErrorOccurred != null)
ErrorOccurred(this, e);
}
}
public void Close()
{
if (!running)
return;
running = false;
try
{
stream.Close();
lock (pauseMonitor)
Monitor.PulseAll(pauseMonitor);
thread.Interrupt();
}
catch
{
// ignored
}
}
public void Pause()
{
paused = true;
}
public void UnPause(bool fullupdate = false)
{
_incremental = !fullupdate;
paused = false;
lock (pauseMonitor)
Monitor.PulseAll(pauseMonitor);
}
}
}