Move tsclient to rosapps (not included in build process and superseded by Geds mstsc...
[reactos.git] / rosapps / applications / net / tsclient / porting-tools / rdesktop-core-tester / activex.cpp
diff --git a/rosapps/applications/net/tsclient/porting-tools/rdesktop-core-tester/activex.cpp b/rosapps/applications/net/tsclient/porting-tools/rdesktop-core-tester/activex.cpp
new file mode 100644 (file)
index 0000000..80a03b6
--- /dev/null
@@ -0,0 +1,6095 @@
+#include "stdafx.h"
+
+namespace MSTSCLib
+{
+#include "mstsclib_h.h"
+};
+
+namespace MSTSCLib_Redist
+{
+// extremely ew, but actually the cleanest way to import the alternate UUIDs
+#include "mstsclib_redist_i.c"
+};
+
+#include "rdesktop/rdesktop.h"
+#include "rdesktop/proto.h"
+
+namespace
+{
+#ifdef _MSC_VER
+extern "C" char __ImageBase;
+#endif
+
+HMODULE GetCurrentModule()
+{
+       return reinterpret_cast<HMODULE>(&__ImageBase);
+}
+
+}
+
+namespace
+{
+
+LONG g_moduleRefCount = 0;
+
+void lockServer()
+{
+       InterlockedIncrement(&g_moduleRefCount);
+}
+
+void unlockServer()
+{
+       InterlockedDecrement(&g_moduleRefCount);
+}
+
+bool canUnloadServer()
+{
+       return g_moduleRefCount == 0;
+}
+
+}
+
+namespace
+{
+
+void FreeLpsz(LPSTR lpsz)
+{
+       if(lpsz)
+               delete[] lpsz;
+}
+
+LPSTR AllocLpsz(const CHAR * lpsz, size_t cb)
+{
+       LPSTR lpszNew = new CHAR[cb + 1];
+
+       if(lpszNew == NULL)
+               return NULL;
+
+       CopyMemory(lpszNew, lpsz, cb);
+       lpszNew[cb] = 0;
+
+       return lpszNew;
+}
+
+LPSTR AllocLpsz(const WCHAR * lpwsz, int cchIn)
+{
+       int cch = WideCharToMultiByte(CP_ACP, 0, lpwsz, cchIn, NULL, 0, NULL, NULL);
+
+       if(cch <= 0)
+               return NULL;
+
+       LPSTR lpsz = new CHAR[cch];
+
+       if(lpsz == NULL)
+               return NULL;
+
+       cch = WideCharToMultiByte(CP_ACP, 0, lpwsz, cchIn, lpsz, cch, NULL, NULL);
+
+       if(cch <= 0)
+       {
+               FreeLpsz(lpsz);
+               return NULL;
+       }
+
+       return lpsz;
+}
+
+LPSTR BstrToLpsz(BSTR bstr)
+{
+       return AllocLpsz(bstr, SysStringLen(bstr));
+}
+
+BSTR LpszToBstr(LPSTR lpsz)
+{
+       int cch = MultiByteToWideChar(CP_ACP, 0, lpsz, -1, NULL, 0);
+
+       if(cch <= 0)
+               return NULL;
+
+       BSTR bstr = SysAllocStringLen(NULL, cch);
+
+       if(bstr == NULL)
+               return NULL;
+
+       cch = MultiByteToWideChar(CP_ACP, 0, lpsz, -1, bstr, cch);
+
+       if(cch <= 0)
+       {
+               SysFreeString(bstr);
+               return NULL;
+       }
+
+       return bstr;
+}
+
+}
+
+namespace
+{
+
+template<class T, class U> T aligndown(const T& X, const U& align)
+{
+       return X & ~(T(align) - 1);
+}
+
+template<class T, class U> T alignup(const T& X, const U& align)
+{
+       return aligndown(X + (align - 1), align);
+}
+
+/* Convert between bitmap formats */
+uint8 * win32_convert_scanlines(int width, int height, int bitcount, int fromalign, int toalign, const uint8 * data, uint8 ** buffer)
+{
+       // TBD: profile & optimize the most common cases
+       assert(width > 0);
+       assert(height);
+       assert(bitcount && bitcount <= 32);
+       assert(fromalign <= toalign);
+       assert(data);
+       assert(buffer);
+
+       bool flipped = height < 0;
+
+       if(flipped)
+               height = - height;
+
+       int bytesperrow = alignup(width * bitcount, 8) / 8;
+       int fromstride = alignup(bytesperrow, fromalign);
+       int tostride = alignup(bytesperrow, toalign);
+       assert(fromstride <= tostride);
+
+       int datasize = tostride * height;
+
+       uint8 * dibits = new uint8[datasize];
+
+       const uint8 * src = data;
+       uint8 * dest = dibits;
+
+       const int pad = tostride - fromstride;
+
+       assert(pad < 4);
+       __assume(pad < 4);
+
+       if(flipped)
+       {
+               dest += (height - 1) * tostride;
+               tostride = - tostride;
+       }
+
+       for(int i = 0; i < height; ++ i)
+       {
+               memcpy(dest, src, fromstride);
+               memset(dest + fromstride, 0, pad);
+               src += fromstride;
+               dest += tostride;
+       }
+
+       *buffer = dibits;
+       return dibits;
+}
+
+/* Creates bitmaps */
+HBITMAP win32_create_dib(LONG width, LONG height, WORD bitcount, const BYTE * data)
+{
+       struct b_
+       {
+               BITMAPINFO bmi;
+               RGBQUAD colormap[256 - ARRAYSIZE(RTL_FIELD_TYPE(BITMAPINFO, bmiColors))];
+       }
+       b;
+
+       b.bmi.bmiHeader.biSize = sizeof(b.bmi.bmiHeader);
+       b.bmi.bmiHeader.biWidth = width;
+       b.bmi.bmiHeader.biHeight = height;
+       b.bmi.bmiHeader.biPlanes = 1;
+       b.bmi.bmiHeader.biBitCount = bitcount;
+       b.bmi.bmiHeader.biCompression = BI_RGB;
+       b.bmi.bmiHeader.biSizeImage = 0;
+       b.bmi.bmiHeader.biXPelsPerMeter = 0;
+       b.bmi.bmiHeader.biYPelsPerMeter = 0;
+
+       if(bitcount > 8)
+       {
+               b.bmi.bmiHeader.biClrUsed = 0;
+               b.bmi.bmiHeader.biClrImportant = 0;
+       }
+       else
+       {
+               b.bmi.bmiHeader.biClrUsed = 2 << bitcount;
+               b.bmi.bmiHeader.biClrImportant = 2 << bitcount;
+
+               // TODO: palette
+       }
+
+       // FIXME: beyond ugly
+       HDC hdc = CreateCompatibleDC(NULL);
+
+       if(hdc == NULL)
+               return NULL;
+
+       HBITMAP hbm = CreateDIBitmap(hdc, &b.bmi.bmiHeader, CBM_INIT, data, &b.bmi, DIB_RGB_COLORS);
+
+       if(hbm == NULL)
+               error("CreateDIBitmap %dx%dx%d failed\n", width, height, bitcount);
+
+       DeleteDC(hdc);
+       return hbm;
+}
+
+/* Creates brushes */
+HBRUSH win32_create_brush(BRUSH * brush, COLORREF fgcolour)
+{
+       if(brush == NULL)
+               return (HBRUSH)GetStockObject(NULL_BRUSH);
+
+       switch(brush->style)
+       {
+       case BS_SOLID:
+       case BS_NULL:
+       case BS_HATCHED:
+       case BS_PATTERN:
+       case BS_PATTERN8X8:
+               break;
+
+       default:
+               return NULL;
+       }
+
+       switch(brush->style)
+       {
+       case BS_SOLID:
+               return CreateSolidBrush(fgcolour);
+
+       case BS_HATCHED:
+               return CreateHatchBrush(brush->pattern[0], fgcolour);
+
+       case BS_NULL:
+               return (HBRUSH)GetStockObject(NULL_BRUSH);
+
+       case BS_PATTERN:
+       case BS_PATTERN8X8:
+               {
+                       uint16 pattern[8];
+
+                       for(size_t i = 0; i < 8; ++ i)
+                               pattern[i] = brush->pattern[i];
+
+                       HBITMAP hpattern = CreateBitmap(8, 8, 1, 1, pattern);
+                       HBRUSH hbr = CreatePatternBrush(hpattern);
+                       DeleteObject(hpattern);
+                       return hbr;
+               }
+
+       DEFAULT_UNREACHABLE;
+       }
+}
+};
+
+/*
+       "sealed" can improve optimizations by asserting a class cannot be derived
+       from, optimizing out accesses to the v-table from inside the class
+*/
+#if defined(_MSC_VER) && _MSC_VER >= 1400
+#define SEALED_ sealed
+#else
+#define SEALED_
+#endif
+
+
+/* Class that implements the RDP client GUI */
+class RdpClientUI
+{
+public:
+       // TODO: pass the client settings relevant to the GUI here
+       HRESULT Initialize(HWND hwndParent)
+       {
+               // TODO: create the various windows
+               // TODO: create display window thread
+               // TODO: create input thread
+               return E_FAIL;
+       }
+
+public:
+       static BOOL Startup()
+       {
+               WNDCLASSEX wcexUI = { sizeof(wcexUI) };
+               WNDCLASSEX wcexConsole = { sizeof(wcexConsole) };
+               WNDCLASSEX wcexDisplay = { sizeof(wcexDisplay) };
+               WNDCLASSEX wcexInput = { sizeof(wcexInput) };
+
+               HBRUSH nullBrush = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
+
+               wcexUI.lpfnWndProc = NULL; // TODO
+               wcexUI.hInstance = GetCurrentModule();
+               wcexUI.hCursor = LoadCursor(NULL, IDC_ARROW);
+               wcexUI.hbrBackground = nullBrush;
+               wcexUI.lpszClassName = TEXT("MissTosca_UI");
+
+               wcexConsole.style = CS_VREDRAW | CS_HREDRAW;
+               wcexConsole.lpfnWndProc = NULL; // TODO
+               wcexConsole.hInstance = GetCurrentModule();
+               wcexConsole.hCursor = LoadCursor(NULL, IDC_ARROW);
+               wcexConsole.hbrBackground = nullBrush;
+               wcexConsole.lpszClassName = TEXT("MissTosca_Console");
+
+               wcexDisplay.style = CS_VREDRAW | CS_HREDRAW;
+               wcexDisplay.lpfnWndProc = NULL; // TODO
+               wcexDisplay.hInstance = GetCurrentModule();
+               wcexDisplay.hCursor = LoadCursor(NULL, IDC_ARROW);
+               wcexDisplay.hbrBackground = nullBrush;
+               wcexDisplay.lpszClassName = TEXT("MissTosca_Display");
+
+               wcexInput.style = CS_VREDRAW | CS_HREDRAW;
+               wcexInput.lpfnWndProc = NULL; // TODO
+               wcexInput.hInstance = GetCurrentModule();
+               wcexInput.hCursor = NULL;
+               wcexInput.hbrBackground = nullBrush;
+               wcexInput.lpszClassName = TEXT("MissTosca_Input");
+
+               return
+                       RegisterClassEx(&wcexUI) &&
+                       RegisterClassEx(&wcexConsole) &&
+                       RegisterClassEx(&wcexDisplay) &&
+                       RegisterClassEx(&wcexInput);
+       }
+
+       static void Shutdown()
+       {
+               // TODO
+       }
+
+       /*
+               This is the main UI window. It's the direct child of the control
+               window, it fills its whole extent and it contains the scrollbars.
+               When activated, it will move keyboard focus to the input window
+       */
+private:
+       HWND m_uiWindow;
+       LONG m_scrollHPos;
+       LONG m_scrollVPos;
+
+       LRESULT UIWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+       {
+               switch(uMsg)
+               {
+                       // Keep the keyboard focus on the input window
+               case WM_ACTIVATE:
+                       switch(LOWORD(wParam))
+                       {
+                       case WA_INACTIVE:
+                               break;
+
+                       case WA_ACTIVE:
+                       case WA_CLICKACTIVE:
+                               if(!HIWORD(wParam))
+                                       SetFocus(m_inputWindow);
+                       }
+
+                       return 0;
+
+                       // Resized: rearrange children windows, adjust scrollbars
+               case WM_SIZE:
+                       {
+                               if(IsIconic(m_uiWindow))
+                                       break;
+
+                               RECT rcClient;
+                               GetWindowRect(m_uiWindow, &rcClient);
+
+                               if(m_smartSizing)
+                               {
+                                       // we are not supposed to maintain aspect ratio. Container has to do that
+                                       m_consoleX = 0;
+                                       m_consoleY = 0;
+                                       m_consoleWidth = rcClient.right;
+                                       m_consoleHeight = rcClient.bottom;
+                               }
+                               else
+                               {
+                                       // center horizontally, no horizontal scrollbar
+                                       if(rcClient.right >= m_consoleWidth)
+                                               m_consoleX = (m_consoleWidth - rcClient.right) / 2;
+
+                                       // center vertically, no vertical scrollbar
+                                       if(rcClient.bottom >= m_consoleHeight)
+                                               m_consoleY = (m_consoleHeight - rcClient.right) / 2;
+                               }
+
+                               SCROLLINFO scroll = { sizeof(scroll), SIF_ALL, 0 };
+
+                               // update the horizontal scrollbar
+                               scroll.nMax = m_consoleWidth;
+                               scroll.nPage = rcClient.right;
+                               scroll.nPos = 0 - m_consoleX;
+                               SetScrollInfo(m_uiWindow, SB_HORZ, &scroll, TRUE);
+
+                               // update the vertical scrollbar
+                               scroll.nMax = m_consoleHeight;
+                               scroll.nPage = rcClient.bottom;
+                               scroll.nPos = 0 - m_consoleY;
+                               SetScrollInfo(m_uiWindow, SB_VERT, &scroll, TRUE);
+
+                               // move/resize the console window
+                               MoveWindow(m_consoleWindow, m_consoleX, m_consoleY, m_consoleWidth, m_consoleHeight, TRUE);
+                       }
+
+                       return 0;
+
+               case WM_HSCROLL:
+                       {
+                               SCROLLINFO scroll = { sizeof(scroll), SIF_TRACKPOS };
+                               GetScrollInfo(m_uiWindow, SB_HORZ, &scroll);
+                               m_consoleX = - scroll.nTrackPos;
+                               MoveWindow(m_consoleWindow, m_consoleX, m_consoleY, m_consoleWidth, m_consoleHeight, TRUE);
+                       }
+
+                       return 0;
+
+               case WM_VSCROLL:
+                       {
+                               SCROLLINFO scroll = { sizeof(scroll), SIF_TRACKPOS };
+                               GetScrollInfo(m_uiWindow, SB_VERT, &scroll);
+                               m_consoleY = - scroll.nTrackPos;
+                               MoveWindow(m_consoleWindow, m_consoleX, m_consoleY, m_consoleWidth, m_consoleHeight, TRUE);
+                       }
+
+                       return 0;
+
+               default:
+                       break;
+               }
+
+               return DefWindowProc(m_uiWindow, uMsg, wParam, lParam);
+       }
+
+       /*
+               This is the full-screen title bar. It's displayed at the top of the
+               main UI window while in full-screen mode, and it contains two toolbars
+               with the pin, minimize, restore and close buttons
+       */
+       HWND m_fullScreenBarWindow;
+
+       /*
+               This is the console window. It has the same extent as the display on
+               the remote computer, or it fills the UI window in smart resizing mode,
+               and it contains the input and display windows
+       */
+private:
+       HWND m_consoleWindow;
+       LONG m_consoleX;
+       LONG m_consoleY;
+       LONG m_consoleWidth;
+       LONG m_consoleHeight;
+       bool m_smartSizing;
+
+       LRESULT ConsoleWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+       {
+               switch(uMsg)
+               {
+               case WM_SIZE:
+                       {
+                               RECT rcClient;
+                               GetClientRect(m_consoleWindow, &rcClient);
+
+                               MoveWindow(m_inputWindow, 0, 0, rcClient.right, rcClient.bottom, TRUE);
+                               MoveWindow(m_displayWindow, 0, 0, rcClient.right, rcClient.bottom, TRUE);
+                       }
+
+                       return 0;
+
+               default:
+                       break;
+               }
+
+               return DefWindowProc(m_consoleWindow, uMsg, wParam, lParam);
+       }
+
+       /*
+               This is the display window. It represents the virtual display of the
+               remote computer. It completely fills its parent, the console window,
+               and it runs in its own thread for performance reasons
+       */
+private:
+       HWND m_displayWindow;
+       LONG m_displayBufferWidth;
+       LONG m_displayBufferHeight;
+       HDC m_displayBuffer;
+       void * m_displayBufferRaw;
+       int m_displayBufferSave;
+       int m_displayBufferBitDepth;
+       int m_displayBufferByteDepth;
+       int m_displayBufferStride;
+       RECT m_displayBufferClip;
+       CRITICAL_SECTION m_displayBufferMutex;
+
+       LRESULT DisplayWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+       {
+               switch(uMsg)
+               {
+               case WM_DESTROY:
+                       PostQuitMessage(0);
+                       return 0;
+
+               case WM_PRINTCLIENT:
+                       if(wParam == 0)
+                               break;
+
+               case WM_PAINT:
+                       {
+                               HDC hdc = (HDC)wParam;
+
+                               EnterCriticalSection(&m_displayBufferMutex);
+
+                               if(hdc)
+                               {
+                                       RECT rc;
+                                       GetClientRect(m_displayWindow, &rc);
+                                       BitBlt(hdc, 0, 0, rc.right, rc.bottom, m_displayBuffer, 0, 0, SRCCOPY);
+                               }
+                               else
+                               {
+                                       PAINTSTRUCT ps;
+                                       hdc = BeginPaint(m_displayWindow, &ps);
+
+                                       if(!m_smartSizing)
+                                       {
+                                               BitBlt
+                                               (
+                                                       hdc,
+                                                       ps.rcPaint.left,
+                                                       ps.rcPaint.top,
+                                                       ps.rcPaint.right - ps.rcPaint.left,
+                                                       ps.rcPaint.bottom - ps.rcPaint.top,
+                                                       m_displayBuffer,
+                                                       ps.rcPaint.left,
+                                                       ps.rcPaint.top,
+                                                       SRCCOPY
+                                               );
+                                       }
+                                       else
+                                       {
+                                               // bleh. There has to be a better way
+                                               SetStretchBltMode(hdc, HALFTONE);
+
+                                               StretchBlt
+                                               (
+                                                       hdc,
+                                                       0,
+                                                       0,
+                                                       m_consoleWidth,
+                                                       m_consoleHeight,
+                                                       m_displayBuffer,
+                                                       0,
+                                                       0,
+                                                       m_displayBufferWidth,
+                                                       m_displayBufferHeight,
+                                                       SRCCOPY
+                                               );
+                                       }
+
+                                       EndPaint(m_displayWindow, &ps);
+                               }
+
+                               LeaveCriticalSection(&m_displayBufferMutex);
+                       }
+
+                       return 0;
+
+               default:
+                       break;
+               }
+
+               return DefWindowProc(m_displayWindow, uMsg, wParam, lParam);
+       }
+
+       /* Screen repainting */
+       void Display_RepaintRect(const RECT * lprc)
+       {
+               if(m_smartSizing)
+                       return Display_RepaintAll();
+
+               RECT rcDamage;
+               IntersectRect(&rcDamage, lprc, &m_displayBufferClip);
+               InvalidateRect(m_displayWindow, &rcDamage, FALSE);
+       }
+
+       void Display_RepaintArea(int x, int y, int cx, int cy)
+       {
+               if(m_smartSizing)
+                       return Display_RepaintAll();
+
+               RECT rcDamage;
+               rcDamage.left = x;
+               rcDamage.top = y;
+               rcDamage.right = x + cx;
+               rcDamage.bottom = y + cy;
+               Display_RepaintRect(&rcDamage);
+       }
+
+       void Display_RepaintPolygon(POINT * point, int npoints, int linewidth)
+       {
+               if(m_smartSizing)
+                       return Display_RepaintAll();
+
+               RECT rcDamage;
+
+               rcDamage.left = MAXLONG;
+               rcDamage.top = MAXLONG;
+               rcDamage.right = 0;
+               rcDamage.bottom = 0;
+
+               for(int i = 0; i < npoints; ++ i)
+               {
+                       if(point[i].x < rcDamage.left)
+                               rcDamage.left = point[i].x;
+
+                       if(point[i].y < rcDamage.top)
+                               rcDamage.top = point[i].y;
+
+                       if(point[i].x > rcDamage.right)
+                               rcDamage.right = point[i].x;
+
+                       if(point[i].y > rcDamage.bottom)
+                               rcDamage.bottom = point[i].y;
+               }
+
+               InflateRect(&rcDamage, linewidth, linewidth);
+               Display_RepaintRect(&rcDamage);
+       }
+
+       void Display_RepaintAll()
+       {
+               InvalidateRgn(m_displayWindow, NULL, FALSE);
+       }
+
+public:
+       void Display_SetClip(int x, int y, int cx, int cy)
+       {
+               m_displayBufferClip.left = x;
+               m_displayBufferClip.top = y;
+               m_displayBufferClip.right = x + cx + 1;
+               m_displayBufferClip.bottom = y + cy + 1;
+
+               HRGN hrgn = CreateRectRgnIndirect(&m_displayBufferClip);
+               SelectClipRgn(m_displayBuffer, hrgn);
+               DeleteObject(hrgn);
+       }
+
+       void Display_ResetClip()
+       {
+               m_displayBufferClip.left = 0;
+               m_displayBufferClip.top = 0;
+               m_displayBufferClip.right = m_displayBufferWidth;
+               m_displayBufferClip.bottom = m_displayBufferHeight;
+               SelectClipRgn(m_displayBuffer, NULL);
+       }
+
+       void Display_PaintBitmap(int x, int y, int cx, int cy, int width, int height, uint8 * data)
+       {
+               GdiFlush();
+
+               int fromstride = alignup(width * m_displayBufferByteDepth, 4);
+               int sizex = cx * m_displayBufferByteDepth;
+
+               const uint8 * src = data;
+
+               uint8 * dst =
+                       (uint8 *)m_displayBufferRaw +
+                       (m_displayBufferHeight - y - cy) * m_displayBufferStride +
+                       x * m_displayBufferByteDepth;
+
+               for(int i = 0; i < cy; ++ i)
+               {
+                       memcpy(dst, src, sizex);
+                       src += fromstride;
+                       dst += m_displayBufferStride;
+               }
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_DestBlt(uint8 opcode, int x, int y, int cx, int cy)
+       {
+               int dcsave = SaveDC(m_displayBuffer);
+               SelectObject(m_displayBuffer, GetStockObject(BLACK_BRUSH));
+               PatBlt(m_displayBuffer, x, y, cx, cy, MAKELONG(0, opcode));
+               RestoreDC(m_displayBuffer, dcsave);
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_PatBlt(uint8 opcode, int x, int y, int cx, int cy, BRUSH * brush, int bgcolour, int fgcolour)
+       {
+               HBRUSH hbr = win32_create_brush(brush, fgcolour);
+
+               int dcsave = SaveDC(m_displayBuffer);
+
+               SetBkColor(m_displayBuffer, bgcolour);
+               SetTextColor(m_displayBuffer, fgcolour);
+               SetBrushOrgEx(m_displayBuffer, brush->xorigin, brush->yorigin, NULL);
+               SelectObject(m_displayBuffer, hbr);
+
+               PatBlt(m_displayBuffer, x, y, cx, cy, MAKELONG(0, opcode));
+
+               RestoreDC(m_displayBuffer, dcsave);
+
+               DeleteObject(hbr);
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_ScreenBlt(uint8 opcode, int x, int y, int cx, int cy, int srcx, int srcy)
+       {
+               BitBlt(m_displayBuffer, x, y, cx, cy, m_displayBuffer, srcx, srcy, MAKELONG(0, opcode));
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_MemBlt(uint8 opcode, int x, int y, int cx, int cy, HBITMAP src, int srcx, int srcy)
+       {
+               HDC hdcSrc = CreateCompatibleDC(m_displayBuffer);
+               HGDIOBJ hOld = SelectObject(hdcSrc, src);
+
+               BitBlt(m_displayBuffer, x, y, cx, cy, hdcSrc, srcx, srcy, MAKELONG(0, opcode));
+
+               SelectObject(hdcSrc, hOld);
+               DeleteDC(hdcSrc);
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_TriBlt(uint8 opcode, int x, int y, int cx, int cy, HBITMAP src, int srcx, int srcy, BRUSH * brush, int bgcolour, int fgcolour)
+       {
+               // TODO
+               HDC hdcSrc = CreateCompatibleDC(m_displayBuffer);
+               HGDIOBJ hOld = SelectObject(hdcSrc, src);
+
+               //SELECT_BRUSH(brush, bgcolour, fgcolour);
+
+               BitBlt(m_displayBuffer, x, y, cx, cy, hdcSrc, srcx, srcy, MAKELONG(0, opcode));
+
+               //RESET_BRUSH();
+
+               SelectObject(hdcSrc, hOld);
+               DeleteDC(hdcSrc);
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_Line(uint8 opcode, int startx, int starty, int endx, int endy, PEN * pen)
+       {
+               HPEN hpen = CreatePen(pen->style, pen->width, pen->colour);
+
+               int dcsave = SaveDC(m_displayBuffer);
+
+               SetROP2(m_displayBuffer, opcode);
+               SelectObject(m_displayBuffer, hpen);
+               MoveToEx(m_displayBuffer, startx, starty, NULL);
+
+               LineTo(m_displayBuffer, endx, endy);
+
+               RestoreDC(m_displayBuffer, dcsave);
+
+               DeleteObject(hpen);
+
+               RECT rcDamage;
+
+               if(startx < endx)
+               {
+                       rcDamage.left = startx;
+                       rcDamage.right = endx;
+               }
+               else
+               {
+                       rcDamage.left = endx;
+                       rcDamage.right = startx;
+               }
+
+               if(starty < endy)
+               {
+                       rcDamage.top = starty;
+                       rcDamage.bottom = endy;
+               }
+               else
+               {
+                       rcDamage.top = endy;
+                       rcDamage.bottom = starty;
+               }
+
+               InflateRect(&rcDamage, pen->width, pen->width);
+               Display_RepaintRect(&rcDamage);
+       }
+
+       void Display_Rect(int x, int y, int cx, int cy, int colour)
+       {
+               HBRUSH hbr = CreateSolidBrush(colour);
+
+               int dcsave = SaveDC(m_displayBuffer);
+
+               SelectObject(m_displayBuffer, hbr);
+               SelectObject(m_displayBuffer, GetStockObject(NULL_PEN));
+
+               Rectangle(m_displayBuffer, x, y, x + cx + 1, y + cy + 1);
+
+               RestoreDC(m_displayBuffer, dcsave);
+
+               DeleteObject(hbr);
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_Polygon(uint8 opcode, uint8 fillmode, POINT * point, int npoints, BRUSH * brush, int bgcolour, int fgcolour)
+       {
+               HBRUSH hbr = win32_create_brush(brush, fgcolour);
+
+               int dcsave = SaveDC(m_displayBuffer);
+
+               SetBkColor(m_displayBuffer, bgcolour);
+               SetTextColor(m_displayBuffer, fgcolour);
+               SetPolyFillMode(m_displayBuffer, fillmode);
+               SelectObject(m_displayBuffer, hbr);
+
+               Polygon(m_displayBuffer, point, npoints);
+
+               RestoreDC(m_displayBuffer, dcsave);
+
+               Display_RepaintPolygon(point, npoints, 0);
+       }
+
+       void Display_Polyline(uint8 opcode, POINT * points, int npoints, PEN * pen)
+       {
+               POINT last = points[0];
+
+               for(int i = 1; i < npoints; ++ i)
+               {
+                       points[i].x += last.x;
+                       points[i].y += last.y;
+                       last = points[i];
+               }
+
+               HPEN hpen = CreatePen(pen->style, pen->width, pen->colour);
+
+               int dcsave = SaveDC(m_displayBuffer);
+
+               SetROP2(m_displayBuffer, opcode);
+               SelectObject(m_displayBuffer, hpen);
+
+               Polyline(m_displayBuffer, points, npoints);
+
+               RestoreDC(m_displayBuffer, dcsave);
+
+               DeleteObject(hpen);
+
+               Display_RepaintPolygon(points, npoints, pen->width);
+       }
+
+       void Display_Ellypse(uint8 opcode, uint8 fillmode, int x, int y, int cx, int cy, BRUSH * brush, int bgcolour, int fgcolour)
+       {
+               // TODO
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       // TBD: optimize text drawing
+       void Display_DrawGlyph(int mixmode, int x, int y, int cx, int cy, HGLYPH glyph, int srcx, int srcy, int bgcolour, int fgcolour)
+       {
+               HBITMAP hbmGlyph = (HBITMAP)glyph;
+               HDC hdcGlyph = CreateCompatibleDC(m_displayBuffer);
+               HGDIOBJ hOld = SelectObject(hdcGlyph, hbmGlyph);
+
+               int dcsave = SaveDC(m_displayBuffer);
+
+               switch(mixmode)
+               {
+               case MIX_TRANSPARENT:
+                       {
+                               /*
+                                       ROP is DSPDxax:
+                                        - where the glyph (S) is white, D is set to the foreground color (P)
+                                        - where the glyph (S) is black, D is left untouched
+
+                                       This paints a transparent glyph in the specified color
+                               */
+                               HBRUSH hbr = CreateSolidBrush(fgcolour);
+                               SelectObject(m_displayBuffer, hbr);
+                               BitBlt(m_displayBuffer, x, y, cx, cy, hdcGlyph, srcx, srcy, MAKELONG(0, 0xe2));
+                               DeleteObject(hbr);
+                       }
+
+                       break;
+
+               case MIX_OPAQUE:
+                       {
+                               /* Curiously, glyphs are inverted (white-on-black) */
+                               SetBkColor(m_displayBuffer, fgcolour);
+                               SetTextColor(m_displayBuffer, bgcolour);
+                               BitBlt(m_displayBuffer, x, y, cx, cy, hdcGlyph, srcx, srcy, SRCCOPY);
+                       }
+
+                       break;
+               }
+
+               RestoreDC(m_displayBuffer, dcsave);
+
+               SelectObject(hdcGlyph, hOld);
+               DeleteDC(hdcGlyph);
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+       void Display_DoGlyph(uint8 font, uint8 flags, int mixmode, int& x, int& y, int bgcolour, int fgcolour, const uint8 * ttext, int& idx)
+       {
+               FONTGLYPH * glyph;
+
+               glyph = cache_get_font(/*This*/NULL, font, ttext[idx]);
+
+               if(!(flags & TEXT2_IMPLICIT_X))
+               {
+                       int xyoffset = ttext[++ idx];
+
+                       if((xyoffset & 0x80))
+                       {
+                               if (flags & TEXT2_VERTICAL)
+                                       y += ttext[idx + 1] | (ttext[idx + 2] << 8);
+                               else
+                                       x += ttext[idx + 1] | (ttext[idx + 2] << 8);
+
+                         idx += 2;
+                       }
+                       else
+                       {
+                               if (flags & TEXT2_VERTICAL)
+                                       y += xyoffset;
+                               else
+                                       x += xyoffset;
+                       }
+               }
+
+               if(glyph)
+               {
+                       Display_DrawGlyph
+                       (
+                               mixmode,
+                               x + (short)glyph->offset,
+                               y + (short)glyph->baseline,
+                               glyph->width,
+                               glyph->height,
+                               glyph->pixmap,
+                               0,
+                               0,
+                               bgcolour,
+                               fgcolour
+                       );
+
+                       if(flags & TEXT2_IMPLICIT_X)
+                               x += glyph->width;
+               }
+       }
+
+       void Display_DrawText
+       (
+               uint8 font,
+               uint8 flags,
+               uint8 opcode,
+               int mixmode,
+               int x,
+               int y,
+               int clipx,
+               int clipy,
+               int clipcx,
+               int clipcy,
+               int boxx,
+               int boxy,
+               int boxcx,
+               int boxcy,
+               BRUSH * brush,
+               int bgcolour,
+               int fgcolour,
+               uint8 * text,
+               uint8 length
+       )
+       {
+               int i, j;
+               DATABLOB *entry;
+
+               HBRUSH hbr = CreateSolidBrush(bgcolour);
+               HGDIOBJ holdbrush = SelectObject(m_displayBuffer, hbr);
+               HGDIOBJ holdpen = SelectObject(m_displayBuffer, GetStockObject(NULL_PEN));
+
+               if (boxcx > 1)
+                       Rectangle(m_displayBuffer, boxx, boxy, boxx + boxcx + 1, boxy + boxcy + 1);
+               else if (mixmode == MIX_OPAQUE)
+                       Rectangle(m_displayBuffer, clipx, clipy, clipx + clipcx + 1, clipy + clipcy + 1);
+
+               SelectObject(m_displayBuffer, holdpen);
+               SelectObject(m_displayBuffer, holdbrush);
+
+               DeleteObject(hbr);
+
+               if(boxcx > 1)
+                       Display_RepaintArea(boxx, boxy, boxcx, boxcy);
+               else
+                       Display_RepaintArea(clipx, clipy, clipcx, clipcy);
+
+               /* Paint text, character by character */
+               for (i = 0; i < length;)
+               {
+                       switch (text[i])
+                       {
+                               case 0xff:
+                                       /* At least two bytes needs to follow */
+                                       if (i + 3 > length)
+                                       {
+                                               warning("Skipping short 0xff command:");
+                                               for (j = 0; j < length; j++)
+                                                       fprintf(stderr, "%02x ", text[j]);
+                                               fprintf(stderr, "\n");
+                                               i = length = 0;
+                                               break;
+                                       }
+                                       cache_put_text(NULL /* TODO */, text[i + 1], text, text[i + 2]);
+                                       i += 3;
+                                       length -= i;
+                                       /* this will move pointer from start to first character after FF command */
+                                       text = &(text[i]);
+                                       i = 0;
+                                       break;
+
+                               case 0xfe:
+                                       /* At least one byte needs to follow */
+                                       if (i + 2 > length)
+                                       {
+                                               warning("Skipping short 0xfe command:");
+                                               for (j = 0; j < length; j++)
+                                                       fprintf(stderr, "%02x ", text[j]);
+                                               fprintf(stderr, "\n");
+                                               i = length = 0;
+                                               break;
+                                       }
+                                       entry = cache_get_text(/*This*/NULL, text[i + 1]);
+                                       if (entry->data != NULL)
+                                       {
+                                               if ((((uint8 *) (entry->data))[1] == 0)
+                                                       && (!(flags & TEXT2_IMPLICIT_X)) && (i + 2 < length))
+                                               {
+                                                       if (flags & TEXT2_VERTICAL)
+                                                               y += text[i + 2];
+                                                       else
+                                                               x += text[i + 2];
+                                               }
+                                               for (j = 0; j < entry->size; j++)
+                                                       Display_DoGlyph(font, flags, mixmode, x, y, bgcolour, fgcolour, ((uint8 *) (entry->data)), j);
+                                       }
+                                       if (i + 2 < length)
+                                               i += 3;
+                                       else
+                                               i += 2;
+                                       length -= i;
+                                       /* this will move pointer from start to first character after FE command */
+                                       text = &(text[i]);
+                                       i = 0;
+                                       break;
+
+                               default:
+                                       Display_DoGlyph(font, flags, mixmode, x, y, bgcolour, fgcolour, text, i);
+                                       i++;
+                                       break;
+                       }
+               }
+       }
+
+       void Display_SaveDesktop(uint32 offset, int x, int y, int cx, int cy)
+       {
+               GdiFlush();
+
+               uint8 * data =
+                       (uint8 *)m_displayBufferRaw +
+                       x * m_displayBufferByteDepth +
+                       (m_displayBufferHeight - y - cy) * m_displayBufferStride;
+
+               cache_put_desktop
+               (
+                       /*This*/NULL,
+                       offset * m_displayBufferByteDepth,
+                       cx,
+                       cy,
+                       m_displayBufferStride,
+                       m_displayBufferByteDepth,
+                       data
+               );
+       }
+
+       void Display_RestoreDesktop(uint32 offset, int x, int y, int cx, int cy)
+       {
+               int fromstride = cx * m_displayBufferByteDepth;
+
+               const uint8 * src = cache_get_desktop(/*This*/NULL, offset, cx, cy, m_displayBufferByteDepth);
+
+               uint8 * dst =
+                       (uint8 *)m_displayBufferRaw +
+                       x * m_displayBufferByteDepth +
+                       (m_displayBufferHeight - y - cy) * m_displayBufferStride;
+
+               GdiFlush();
+
+               for(int i = 0; i < cy; ++ i)
+               {
+                       memcpy(dst, src, fromstride);
+                       src += fromstride;
+                       dst += m_displayBufferStride;
+               }
+
+               Display_RepaintArea(x, y, cx, cy);
+       }
+
+
+       void Display_BeginUpdate()
+       {
+               EnterCriticalSection(&m_displayBufferMutex);
+               m_displayBufferSave = SaveDC(m_displayBuffer);
+       }
+
+       void Display_EndUpdate()
+       {
+               RestoreDC(m_displayBuffer, m_displayBufferSave);
+               LeaveCriticalSection(&m_displayBufferMutex);
+       }
+
+       /*
+               This is the input window. It receives the keyboard and mouse input from
+               the user, and it's the only window that can receive the keyboard focus.
+               It completely fills its parent, the console window, and it runs in its
+               own thread for performance reasons and because of technical reasons
+               involving keyboard hooks in full-screen mode
+       */
+       HWND m_inputWindow;
+       HCURSOR m_inputCursor;
+
+       LRESULT InputWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+       {
+               switch(uMsg)
+               {
+               case WM_DESTROY:
+                       PostQuitMessage(0);
+                       return 0;
+
+                       /* Keyboard stuff */
+                       // TODO: we need a good way to post output cross-thread
+               case WM_SYSKEYDOWN:
+               case WM_KEYDOWN:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_SCANCODE, RDP_KEYPRESS | (lparam & 0x1000000 ? KBD_FLAG_EXT : 0), LOBYTE(HIWORD(lparam)), 0);
+                       break;
+
+               case WM_SYSKEYUP:
+               case WM_KEYUP:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_SCANCODE, RDP_KEYRELEASE | (lparam & 0x1000000 ? KBD_FLAG_EXT : 0), LOBYTE(HIWORD(lparam)), 0);
+                       break;
+
+                       /* Mouse stuff */
+                       // Cursor shape
+               case WM_SETCURSOR:
+                       if(LOWORD(lParam) == HTCLIENT)
+                       {
+                               SetCursor(m_inputCursor);
+                               return TRUE;
+                       }
+
+                       break;
+
+                       // Movement
+               case WM_MOUSEMOVE:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_MOVE, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+                       // Buttons
+                       // TODO: X buttons
+               case WM_LBUTTONDOWN:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON1 | MOUSE_FLAG_DOWN, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+               case WM_RBUTTONDOWN:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON2 | MOUSE_FLAG_DOWN, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+               case WM_MBUTTONDOWN:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON3 | MOUSE_FLAG_DOWN, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+               case WM_LBUTTONUP:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON1, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+               case WM_RBUTTONUP:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON2, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+               case WM_MBUTTONUP:
+                       //rdp_send_input(This, GetMessageTime(), RDP_INPUT_MOUSE, MOUSE_FLAG_BUTTON3, LOWORD(lparam), HIWORD(lparam));
+                       break;
+
+                       // Wheel
+               case WM_MOUSEWHEEL:
+                       //mstsc_mousewheel(This, (SHORT)HIWORD(wparam), lparam);
+                       break;
+               }
+
+               return DefWindowProc(m_inputWindow, uMsg, wParam, lParam);
+       }
+
+public:
+};
+
+#pragma warning(push)
+#pragma warning(disable: 4584)
+
+/* The ActiveX control */
+class RdpClient SEALED_:
+       /* COM basics */
+       public IUnknown,
+       public IDispatch,
+
+       /* ActiveX stuff */
+       public IConnectionPointContainer,
+       public IDataObject,
+       public IObjectSafety,
+       public IOleControl,
+       public IOleInPlaceActiveObject,
+       public IOleInPlaceObject,
+       public IOleObject,
+       public IOleWindow,
+       public IPersist,
+       public IPersistPropertyBag,
+       public IPersistStorage,
+       public IPersistStreamInit,
+       public IProvideClassInfo,
+       public IProvideClassInfo2,
+       public IQuickActivate,
+       public IViewObject,
+       public IViewObject2,
+
+       // NOTE: the original has a vestigial, non-functional implementation of this, which we omit
+       // ISpecifyPropertyPages
+
+       // Hidden interfaces, not available through QueryInterface
+       public IConnectionPoint,
+
+       /* RDP client interface */
+       public MSTSCLib::IMsRdpClient4,
+       public MSTSCLib::IMsRdpClientNonScriptable2
+
+       // NOTE: implemented by inner classes due to requiring distinct IDispatch implementations
+       // IMsRdpClientAdvancedSettings4
+       // IMsRdpClientSecuredSettings
+{
+private:
+       /* An endless amount of COM glue */
+       // Reference counting
+       LONG m_refCount;
+
+#ifdef _DEBUG
+       DWORD m_apartmentThreadId;
+
+       bool InsideApartment() const
+       {
+               return GetCurrentThreadId() == m_apartmentThreadId;
+       }
+#endif
+
+       // Aggregation support
+       IUnknown * m_punkOuter;
+
+       class RdpClientInner: public IUnknown
+       {
+       private:
+               RdpClient * Outer()
+               {
+                       return InnerToOuter(this);
+               }
+
+       public:
+               virtual STDMETHODIMP IUnknown::QueryInterface(REFIID riid, void ** ppvObject)
+               {
+                       return Outer()->queryInterface(riid, ppvObject);
+               }
+
+               virtual STDMETHODIMP_(ULONG) IUnknown::AddRef()
+               {
+                       return Outer()->addRef();
+               }
+
+               virtual STDMETHODIMP_(ULONG) IUnknown::Release()
+               {
+                       return Outer()->release();
+               }
+
+       }
+       m_inner;
+
+       // Persistence support
+       CLSID m_classId;
+
+       // Late binding support
+       unsigned m_typeLibIndex;
+       ITypeLib * m_typeLib;
+       ITypeInfo * m_dispTypeInfo;
+
+       // Event sinks
+       size_t m_EventSinksCount;
+
+       union
+       {
+               MSTSCLib::IMsTscAxEvents * m_EventSinksStatic[1];
+               MSTSCLib::IMsTscAxEvents ** m_EventSinks;
+       };
+
+       // OLE control glue
+       HWND m_controlWindow;
+       IOleClientSite * m_clientSite;
+       IOleInPlaceSite * m_inPlaceSite;
+       IOleAdviseHolder * m_adviseHolder;
+       LONG m_freezeEvents;
+       bool m_uiActive;
+
+       // UrlMon security
+       DWORD m_SafetyOptions;
+
+       bool IsSafeForScripting() const
+       {
+               return m_SafetyOptions & INTERFACESAFE_FOR_UNTRUSTED_CALLER;
+       }
+
+       /* Glue to interface to rdesktop-core */
+       RdpClientUI * m_clientUI;
+       RDPCLIENT m_protocolState;
+       HANDLE m_protocolThread;
+       HANDLE m_protocolThreadWaitingReconnection;
+       bool m_reconnectAborted;
+       bool m_actuallyConnected;
+       bool m_loggedIn;
+
+       /* Properties */
+       // Storage fields
+       // NOTE: keep sorted by alignment (pointers and handles, integers, enumerations, booleans)
+       BSTR m_Domain;
+       BSTR m_UserName;
+       BSTR m_DisconnectedText;
+       BSTR m_ConnectingText;
+       BSTR m_FullScreenTitle;
+       BSTR m_StartProgram;
+       BSTR m_WorkDir;
+       BSTR m_ConnectedStatusText;
+       BSTR m_ClearTextPassword; // FIXME! dangerous, shouldn't store in cleartext!
+       BSTR m_RdpdrLocalPrintingDocName;
+       BSTR m_RdpdrClipCleanTempDirString;
+       BSTR m_RdpdrClipPasteInfoString;
+       BSTR m_KeyboardLayoutString;
+       LPSTR m_Server;
+       LPSTR m_LoadBalanceInfo;
+       // TODO: plugin DLLs
+       HWND m_UIParentWindowHandle;
+       long m_DesktopWidth;
+       long m_DesktopHeight;
+       long m_StartConnected;
+       long m_ColorDepth;
+       long m_KeyboardHookMode;
+       long m_AudioRedirectionMode;
+       long m_TransportType;
+       long m_SasSequence;
+       long m_RDPPort;
+       long m_HotKeyFullScreen;
+       long m_HotKeyAltEsc;
+       long m_HotKeyAltShiftTab;
+       long m_HotKeyAltSpace;
+       long m_HotKeyAltTab;
+       long m_HotKeyCtrlAltDel;
+       long m_HotKeyCtrlEsc;
+       long m_orderDrawThresold;
+       long m_BitmapCacheSize;
+       long m_BitmapVirtualCacheSize;
+       long m_NumBitmapCaches;
+       long m_brushSupportLevel;
+       long m_minInputSendInterval;
+       long m_InputEventsAtOnce;
+       long m_maxEventCount;
+       long m_keepAliveInternal;
+       long m_shutdownTimeout;
+       long m_overallConnectionTimeout;
+       long m_singleConnectionTimeout;
+       long m_MinutesToIdleTimeout;
+       long m_BitmapVirtualCache16BppSize;
+       long m_BitmapVirtualCache24BppSize;
+       long m_PerformanceFlags;
+       long m_MaxReconnectAttempts;
+       unsigned int m_AuthenticationLevel;
+
+       MSTSCLib::ExtendedDisconnectReasonCode m_ExtendedDisconnectReason;
+
+       bool m_Connected;
+       bool m_Compress;
+       bool m_BitmapPersistence;
+       bool m_allowBackgroundInput;
+       bool m_ContainerHandledFullScreen;
+       bool m_DisableRdpdr;
+       bool m_SecuredSettingsEnabled;
+       bool m_FullScreen;
+       bool m_AcceleratorPassthrough;
+       bool m_ShadowBitmap;
+       bool m_EncryptionEnabled;
+       bool m_DedicatedTerminal;
+       bool m_DisableCtrlAltDel;
+       bool m_EnableWindowsKey;
+       bool m_DoubleClickDetect;
+       bool m_MaximizeShell;
+       bool m_ScaleBitmapCachesByBpp;
+       bool m_CachePersistenceActive;
+       bool m_ConnectToServerConsole;
+       bool m_SmartSizing; // FIXME: this can be set while the control is connected
+       bool m_DisplayConnectionBar;
+       bool m_PinConnectionBar;
+       bool m_GrabFocusOnConnect;
+       bool m_RedirectDrives;
+       bool m_RedirectPrinters;
+       bool m_RedirectPorts;
+       bool m_RedirectSmartCards;
+       bool m_NotifyTSPublicKey;
+       bool m_CanAutoReconnect;
+       bool m_EnableAutoReconnect;
+       bool m_ConnectionBarShowMinimizeButton;
+       bool m_ConnectionBarShowRestoreButton;
+
+       // Generic getters/setters
+       HRESULT GetProperty(BSTR& prop, BSTR * retVal) const
+       {
+               assert(InsideApartment());
+
+               if(retVal == NULL)
+                       return E_POINTER;
+
+               *retVal = SysAllocStringLen(prop, SysStringLen(prop));
+
+               if(*retVal == NULL)
+                       return E_OUTOFMEMORY;
+
+               return S_OK;
+       }
+
+       HRESULT GetProperty(LPSTR& prop, BSTR * retVal) const
+       {
+               assert(InsideApartment());
+
+               if(retVal == NULL)
+                       return E_POINTER;
+
+               *retVal = LpszToBstr(prop);
+
+               if(*retVal == NULL)
+                       return E_OUTOFMEMORY;
+
+               return S_OK;
+       }
+
+       HRESULT SetProperty(BSTR& prop, BSTR newValue)
+       {
+               assert(InsideApartment());
+
+               if(m_Connected)
+                       return E_FAIL;
+
+               SysFreeString(prop);
+
+               UINT len = SysStringLen(newValue);
+
+               if(len)
+               {
+                       // no embedded NULs, please
+                       if(len != lstrlenW(newValue))
+                               return E_INVALIDARG;
+
+                       prop = SysAllocStringLen(newValue, len);
+
+                       if(prop == NULL)
+                               return E_OUTOFMEMORY;
+               }
+               else
+                       prop = NULL;
+
+               return S_OK;
+       }
+
+       HRESULT ReplaceProperty(BSTR& prop, BSTR newValue)
+       {
+               assert(InsideApartment());
+               assert((prop == NULL && newValue == NULL) || prop != newValue);
+
+               SysFreeString(prop);
+               prop = newValue;
+               return S_OK;
+       }
+
+       HRESULT SetProperty(LPSTR& prop, BSTR newValue)
+       {
+               assert(InsideApartment());
+
+               if(m_Connected)
+                       return E_FAIL;
+
+               delete[] prop;
+
+               if(SysStringLen(newValue))
+               {
+                       prop = BstrToLpsz(newValue);
+
+                       if(prop == NULL)
+                               return E_OUTOFMEMORY;
+               }
+               else
+                       prop = NULL;
+
+               return S_OK;
+       }
+
+       HRESULT ReplaceProperty(LPSTR& prop, LPSTR newValue)
+       {
+               assert(InsideApartment());
+               assert((prop == NULL && newValue == NULL) || prop != newValue);
+
+               if(prop)
+                       delete[] prop;
+
+               prop = newValue;
+               return S_OK;
+       }
+
+       template<class Type> HRESULT SetProperty(bool& prop, const Type& newValue)
+       {
+               assert(InsideApartment());
+
+               if(m_Connected)
+                       return E_FAIL;
+
+               prop = !!newValue;
+               return S_OK;
+       }
+
+       template<class Type> HRESULT SetProperty(Type& prop, const Type& newValue)
+       {
+               assert(InsideApartment());
+
+               if(m_Connected)
+                       return E_FAIL;
+
+               prop = newValue;
+               return S_OK;
+       }
+
+       template<class Type> HRESULT GetProperty(const bool& prop, Type * retVal) const
+       {
+               assert(InsideApartment());
+
+               if(retVal == NULL)
+                       return E_POINTER;
+
+               *retVal = prop ? VARIANT_TRUE : VARIANT_FALSE;
+               return S_OK;
+       }
+
+       template<class Type> HRESULT GetProperty(const Type& prop, Type * retVal) const
+       {
+               assert(InsideApartment());
+
+               if(retVal == NULL)
+                       return E_POINTER;
+
+               *retVal = prop;
+               return S_OK;
+       }
+
+       /* Events */
+       MSTSCLib::IMsTscAxEvents ** GetSinks() const
+       {
+               if(m_EventSinksCount > 1)
+                       return m_EventSinks;
+               else
+                       return const_cast<MSTSCLib::IMsTscAxEvents **>(m_EventSinksStatic);
+       }
+
+       // Event freezing
+       void UnfreezeEvents()
+       {
+               // Just in case
+       }
+
+       // Generic event riser & helpers
+       void InvokeSinks(DISPID eventId, VARIANTARG rgvarg[], unsigned int cArgs, VARIANTARG * retval)
+       {
+               assert(InsideApartment());
+
+               DISPPARAMS params;
+
+               params.rgvarg = rgvarg;
+               params.rgdispidNamedArgs = NULL;
+               params.cArgs = cArgs;
+               params.cNamedArgs = 0;
+
+               MSTSCLib::IMsTscAxEvents ** sinks = GetSinks();
+
+               for(size_t i = 0; i < m_EventSinksCount; ++ i)
+                       sinks[i]->Invoke(eventId, IID_NULL, 0, DISPATCH_METHOD, &params, retval, NULL, NULL);
+       }
+
+       typedef void (RdpClient::* AsyncEventCallback)
+       (
+               DISPID eventId,
+               VARIANTARG * rgvarg,
+               unsigned int cArgs,
+               VARIANTARG * retVal
+       );
+
+       void CleanupEventArgumentsCallback
+       (
+               DISPID eventId,
+               VARIANTARG * rgvarg,
+               unsigned int cArgs,
+               VARIANTARG * retVal
+       )
+       {
+               assert((rgvarg == NULL) == (cArgs == 0));
+
+               for(unsigned int i = 0; i < cArgs; ++ i)
+                       VariantClear(&rgvarg[i]);
+
+               if(retVal)
+                       VariantClear(retVal);
+       }
+
+       // synchronous call from inside the apartment that owns the object
+       void FireEventInsideApartment
+       (
+               DISPID eventId,
+               VARIANTARG * rgvarg = NULL,
+               unsigned int cArgs = 0,
+               VARIANTARG * retval = NULL,
+               AsyncEventCallback callback = NULL
+       )
+       {
+               assert(InsideApartment());
+
+               if(retval == NULL && callback)
+               {
+                       VARIANTARG localRetval = { };
+                       retval = &localRetval;
+               }
+
+               InvokeSinks(eventId, rgvarg, cArgs, retval);
+
+               if(callback)
+                       (this->*callback)(eventId, rgvarg, cArgs, retval);
+       }
+
+       struct EventArguments
+       {
+               DISPID eventId;
+               VARIANTARG * rgvarg;
+               unsigned int cArgs;
+               VARIANTARG * retval;
+               AsyncEventCallback callback;
+       };
+
+       struct RedirectArguments
+       {
+               uint32 flags;
+               uint32 server_len;
+               wchar_t * server;
+               uint32 cookie_len;
+               char * cookie;
+               uint32 username_len;
+               wchar_t * username;
+               uint32 domain_len;
+               wchar_t * domain;
+               uint32 password_len;
+               wchar_t * password;
+       };
+
+       enum
+       {
+               RDPC_WM_ = WM_USER,
+               RDPC_WM_SYNC_EVENT,
+               RDPC_WM_ASYNC_EVENT,
+               RDPC_WM_DISCONNECT,
+               RDPC_WM_REQUEST_CLOSE,
+               RDPC_WM_REDIRECT,
+       };
+
+       static VOID CALLBACK DisconnectAPC(ULONG_PTR)
+       {
+               // no need to do anything. The interruption will be enough
+       }
+
+       bool HandleEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result)
+       {
+               result = 0;
+
+               switch(uMsg)
+               {
+                       /* Regular event to be dispatched to the container's sink */
+               case RDPC_WM_SYNC_EVENT:
+                       assert(InSendMessage());
+
+               case RDPC_WM_ASYNC_EVENT:
+                       {
+                               const EventArguments * eventArgs = reinterpret_cast<EventArguments *>(lParam);
+                               assert(eventArgs);
+
+                               FireEventInsideApartment
+                               (
+                                       eventArgs->eventId,
+                                       eventArgs->rgvarg,
+                                       eventArgs->cArgs,
+                                       eventArgs->retval,
+                                       eventArgs->callback
+                               );
+
+                               if(uMsg == RDPC_WM_ASYNC_EVENT)
+                                       delete eventArgs;
+                       }
+
+                       break;
+
+                       /* The protocol thread is about to die: prepare for disconnection */
+               case RDPC_WM_DISCONNECT:
+                       {
+                               assert(m_Connected);
+                               assert(InsideApartment());
+                               assert(InSendMessage());
+
+                               // Unblock the protocol thread and wait for it to terminate
+                               ReplyMessage(0);
+                               JoinProtocolThread();
+
+                               // Finish disconnecting
+                               PerformDisconnect(static_cast<long>(wParam));
+                       }
+
+                       break;
+
+               case RDPC_WM_REDIRECT:
+                       {
+                               assert(InSendMessage());
+                               assert(lParam);
+                               assert(m_Connected);
+                               assert(m_protocolState.redirect);
+
+                               RedirectArguments * redirectArgs = reinterpret_cast<RedirectArguments *>(lParam);
+
+                               // BUGBUG: this is extremely messy and more prone to out-of-memory than it should be
+                               LPSTR lpszNewServer = NULL;
+                               LPSTR lpszNewCookie = NULL;
+                               BSTR strNewUsername = NULL;
+                               BSTR strNewDomain = NULL;
+                               BSTR strNewPassword = NULL;
+                               HRESULT hr = S_OK;
+
+                               for(;;)
+                               {
+                                       // Allocate the new properties
+                                       hr = E_OUTOFMEMORY;
+
+                                       // FIXME: convert the hostname to Punycode, not the ANSI codepage
+                                       lpszNewServer = AllocLpsz(redirectArgs->server, redirectArgs->server_len / sizeof(OLECHAR));
+
+                                       if(lpszNewServer == NULL && redirectArgs->server_len)
+                                               break;
+
+                                       lpszNewCookie = AllocLpsz(redirectArgs->cookie, redirectArgs->cookie_len);
+
+                                       if(lpszNewCookie == NULL && redirectArgs->cookie_len)
+                                               break;
+
+                                       strNewUsername = SysAllocStringLen(redirectArgs->username, redirectArgs->username_len / sizeof(OLECHAR));
+
+                                       if(strNewUsername == NULL && redirectArgs->username_len)
+                                               break;
+
+                                       strNewDomain = SysAllocStringLen(redirectArgs->domain, redirectArgs->domain_len / sizeof(OLECHAR));
+
+                                       if(strNewDomain == NULL && redirectArgs->domain_len)
+                                               break;
+
+                                       strNewPassword = SysAllocStringLen(redirectArgs->password, redirectArgs->password_len / sizeof(OLECHAR));
+
+                                       if(strNewPassword == NULL && redirectArgs->password_len)
+                                               break;
+
+                                       hr = S_OK;
+                                       break;
+                               }
+
+                               // Success
+                               if(SUCCEEDED(hr))
+                               {
+                                       // set the new properties
+                                       ReplaceProperty(m_Server, lpszNewServer);
+                                       ReplaceProperty(m_LoadBalanceInfo, lpszNewCookie);
+                                       ReplaceProperty(m_UserName, strNewUsername);
+                                       ReplaceProperty(m_Domain, strNewDomain);
+                                       ReplaceProperty(m_ClearTextPassword, strNewPassword);
+                               }
+                               // Failure
+                               else
+                               {
+                                       // free the buffers
+                                       FreeLpsz(lpszNewServer);
+                                       FreeLpsz(lpszNewCookie);
+                                       SysFreeString(strNewUsername);
+                                       SysFreeString(strNewDomain);
+                                       SysFreeString(strNewPassword);
+
+                                       // signal the error
+                                       m_protocolState.disconnect_reason = 262;
+                                       m_protocolState.redirect = False;
+                                       result = -1;
+                               }
+                       }
+
+                       break;
+
+                       // BUGBUG: this could potentially disconnect an unrelated connection established later...
+               case RDPC_WM_REQUEST_CLOSE:
+                       {
+                               assert(!InSendMessage());
+
+                               if(m_Connected)
+                               {
+                                       // Ask confirmation to the container in case we are logged in
+                                       if(m_loggedIn && !FireConfirmClose())
+                                               break;
+
+                                       // For reentrancy (OnConfirmClose could deviously call Disconnect)
+                                       if(m_protocolThread == NULL)
+                                               break;
+
+                                       // Terminate the protocol thread. It will fire the Disconnected event on exit
+                                       TerminateProtocolThread();
+                               }
+                       }
+
+                       break;
+
+               default:
+                       return false;
+               }
+
+               // If the calling thread is blocked, unblock it ASAP
+               if(InSendMessage())
+                       ReplyMessage(result);
+
+               return true;
+       }
+
+       // synchronous call from outside the apartment
+       void FireEventOutsideApartment
+       (
+               DISPID eventId,
+               VARIANTARG * rgvarg = NULL,
+               unsigned int cArgs = 0,
+               VARIANTARG * retval = NULL,
+               AsyncEventCallback callback = NULL
+       )
+       {
+               assert(!InsideApartment());
+               EventArguments syncEvent = { eventId, rgvarg, cArgs, retval, callback };
+               SendMessage(m_controlWindow, RDPC_WM_SYNC_EVENT, 0, reinterpret_cast<LPARAM>(&syncEvent));
+       }
+
+       // asynchronous call from outside the apartment
+       HRESULT FireEventOutsideApartmentAsync
+       (
+               DISPID eventId,
+               VARIANTARG * rgvarg = NULL,
+               unsigned int cArgs = 0,
+               VARIANTARG * retval = NULL,
+               AsyncEventCallback callback = NULL
+       )
+       {
+               assert(!InsideApartment());
+
+               EventArguments * asyncEvent = new EventArguments();
+
+               if(asyncEvent == NULL)
+                       return E_OUTOFMEMORY;
+
+               asyncEvent->eventId = eventId;
+               asyncEvent->rgvarg = rgvarg;
+               asyncEvent->cArgs = cArgs;
+               asyncEvent->retval = NULL;
+
+               if(!PostMessage(m_controlWindow, RDPC_WM_ASYNC_EVENT, 0, reinterpret_cast<LPARAM>(asyncEvent)))
+               {
+                       delete asyncEvent;
+                       return HRESULT_FROM_WIN32(GetLastError());
+               }
+
+               return S_OK;
+       }
+
+       // Specific events
+       void FireConnecting()
+       {
+               // Source: protocol
+               FireEventOutsideApartment(1);
+       }
+
+       void FireConnected()
+       {
+               // Source: protocol
+               FireEventOutsideApartment(2);
+       }
+
+       void FireLoginComplete()
+       {
+               // Source: protocol
+               FireEventOutsideApartment(3);
+       }
+
+       void FireDisconnected(long reason)
+       {
+               // Source: protocol. Special handling
+               SendMessage(m_controlWindow, RDPC_WM_DISCONNECT, reason, 0);
+       }
+
+       void FireEnterFullScreenMode()
+       {
+               // Source: UI window
+               FireEventInsideApartment(5);
+       }
+
+       void FireLeaveFullScreenMode()
+       {
+               // Source: UI window
+               FireEventInsideApartment(6);
+       }
+
+       HRESULT FireChannelReceivedData(char (& chanName)[CHANNEL_NAME_LEN + 1], void * chanData, unsigned int chanDataSize)
+       {
+               // BUGBUG: what to do when we run out of memory?
+
+               OLECHAR wchanName[ARRAYSIZE(chanName)];
+               std::copy(chanName + 0, chanName + ARRAYSIZE(chanName), wchanName);
+
+               BSTR bstrChanName = SysAllocString(wchanName);
+
+               if(bstrChanName == NULL)
+                       return E_OUTOFMEMORY;
+
+               BSTR bstrChanData = SysAllocStringByteLen(NULL, chanDataSize);
+
+               if(bstrChanData == NULL)
+               {
+                       SysFreeString(bstrChanName);
+                       return E_OUTOFMEMORY;
+               }
+
+               CopyMemory(bstrChanData, chanData, chanDataSize);
+
+               VARIANTARG args[2] = { };
+
+               args[1].vt = VT_BSTR;
+               args[1].bstrVal = bstrChanName;
+
+               args[0].vt = VT_BSTR;
+               args[0].bstrVal = bstrChanData;
+
+               // Source: protocol
+               HRESULT hr = FireEventOutsideApartmentAsync(7, args, ARRAYSIZE(args), NULL, &RdpClient::CleanupEventArgumentsCallback);
+
+               if(FAILED(hr))
+                       CleanupEventArgumentsCallback(7, args, ARRAYSIZE(args), NULL);
+
+               return hr;
+       }
+
+       void FireRequestGoFullScreen()
+       {
+               // Source: UI window
+               FireEventInsideApartment(8);
+       }
+
+       void FireRequestLeaveFullScreen()
+       {
+               // Source: UI window
+               FireEventInsideApartment(9);
+       }
+
+       void FireFatalError(long errorCode)
+       {
+               VARIANTARG arg = { };
+
+               arg.vt = VT_I4;
+               arg.lVal = errorCode;
+
+               // Source: protocol
+               FireEventOutsideApartment(10, &arg, 1);
+       }
+
+       void FireFatalErrorFromApartment(long errorCode)
+       {
+               VARIANTARG arg = { };
+
+               arg.vt = VT_I4;
+               arg.lVal = errorCode;
+
+               // Source: control
+               FireEventInsideApartment(10, &arg, 1);
+       }
+
+       void FireWarning(long warningCode)
+       {
+               VARIANTARG arg = { };
+
+               arg.vt = VT_I4;
+               arg.lVal = warningCode;
+
+               // Source: protocol
+               FireEventOutsideApartment(11, &arg, 1);
+       }
+
+       void FireRemoteDesktopSizeChange(long width, long height)
+       {
+               VARIANTARG args[2] = { };
+
+               args[1].vt = VT_I4;
+               args[1].lVal = width;
+
+               args[0].vt = VT_I4;
+               args[0].lVal = height;
+
+               // Source: UI window
+               FireEventInsideApartment(12, args, ARRAYSIZE(args));
+       }
+
+       void FireIdleTimeoutNotification()
+       {
+               // Source: input thread
+               FireEventOutsideApartment(13);
+       }
+
+       void FireRequestContainerMinimize()
+       {
+               // Source: UI window
+               FireEventInsideApartment(14);
+       }
+
+       bool FireConfirmClose()
+       {
+               VARIANTARG retval = { };
+               VARIANT_BOOL allowClose = VARIANT_TRUE;
+
+               retval.vt = VT_BYREF | VT_BOOL;
+               retval.pboolVal = &allowClose;
+
+               // Source: control
+               FireEventInsideApartment(15, NULL, 0, &retval);
+
+               return allowClose != VARIANT_FALSE;
+       }
+
+    HRESULT FireReceivedTSPublicKey(void * publicKey, unsigned int publicKeyLength)
+       {
+               assert(m_Connected);
+
+               if(!m_NotifyTSPublicKey)
+                       return S_OK;
+
+               BSTR bstrPublicKey = SysAllocStringByteLen(NULL, publicKeyLength);
+
+               if(bstrPublicKey == NULL)
+                       return E_OUTOFMEMORY;
+
+               CopyMemory(bstrPublicKey, publicKey, publicKeyLength);
+
+               VARIANT_BOOL continueLogon = VARIANT_TRUE;
+               VARIANTARG arg = { };
+               VARIANTARG retval = { };
+
+               arg.vt = VT_BSTR;
+               arg.bstrVal = bstrPublicKey;
+
+               retval.vt = VT_BYREF | VT_BOOL;
+               retval.pboolVal = &continueLogon;
+
+               // Source: protocol
+               FireEventOutsideApartment(16, &arg, 1, &retval);
+
+               return continueLogon ? S_OK : S_FALSE;
+       }
+
+       LONG FireAutoReconnecting(long disconnectReason, long attemptCount)
+       {
+               LONG continueStatus = MSTSCLib::autoReconnectContinueAutomatic;
+               VARIANTARG args[2] = { };
+               VARIANTARG retval = { };
+
+               args[1].vt = VT_I4;
+               args[1].lVal = disconnectReason;
+
+               args[0].vt = VT_I4;
+               args[0].lVal = attemptCount;
+
+               retval.vt = VT_BYREF | VT_I4;
+               retval.plVal = &continueStatus;
+
+               // Source: protocol
+               FireEventOutsideApartment(17, args, ARRAYSIZE(args), &retval);
+
+               return continueStatus;
+       }
+
+    void FireAuthenticationWarningDisplayed()
+       {
+               // Source: protocol
+               FireEventOutsideApartment(18);
+       }
+
+    void FireAuthenticationWarningDismissed()
+       {
+               // Source: protocol
+               FireEventOutsideApartment(19);
+       }
+
+       /* Actual IUnknown implementation */
+       HRESULT queryInterface(REFIID riid, void ** ppvObject)
+       {
+               IUnknown * pvObject = NULL;
+
+               using namespace MSTSCLib;
+
+               if(riid == IID_IUnknown)
+                       pvObject = static_cast<IUnknown *>(&m_inner);
+               else if(riid == IID_IConnectionPointContainer)
+                       pvObject = static_cast<IConnectionPointContainer *>(this);
+               else if(riid == IID_IDataObject)
+                       pvObject = static_cast<IDataObject *>(this);
+               else if(riid == IID_IObjectSafety)
+                       pvObject = static_cast<IObjectSafety *>(this);
+               else if(riid == IID_IOleControl)
+                       pvObject = static_cast<IOleControl *>(this);
+               else if(riid == IID_IOleInPlaceActiveObject)
+                       pvObject = static_cast<IOleInPlaceActiveObject *>(this);
+               else if(riid == IID_IOleInPlaceObject)
+                       pvObject = static_cast<IOleInPlaceObject *>(this);
+               else if(riid == IID_IOleObject)
+                       pvObject = static_cast<IOleObject *>(this);
+               else if(riid == IID_IOleWindow)
+                       pvObject = static_cast<IOleWindow *>(this);
+               else if(riid == IID_IPersist)
+                       pvObject = static_cast<IPersist *>(this);
+               else if(riid == IID_IPersistPropertyBag)
+                       pvObject = static_cast<IPersistPropertyBag *>(this);
+               else if(riid == IID_IPersistStorage)
+                       pvObject = static_cast<IPersistStorage *>(this);
+               else if(riid == IID_IPersistStreamInit)
+                       pvObject = static_cast<IPersistStreamInit *>(this);
+               else if(riid == IID_IQuickActivate)
+                       pvObject = static_cast<IQuickActivate *>(this);
+               else if(riid == IID_IViewObject)
+                       pvObject = static_cast<IViewObject *>(this);
+               else if(riid == IID_IViewObject2)
+                       pvObject = static_cast<IViewObject2 *>(this);
+               else if(riid == IID_IMsTscAx || riid == MSTSCLib_Redist::IID_IMsTscAx)
+                       pvObject = static_cast<IMsTscAx *>(this);
+               else if(riid == IID_IMsRdpClient)
+                       pvObject = static_cast<IMsRdpClient *>(this);
+               else if(riid == IID_IMsRdpClient2)
+                       pvObject = static_cast<IMsRdpClient2 *>(this);
+               else if(riid == IID_IMsRdpClient3)
+                       pvObject = static_cast<IMsRdpClient3 *>(this);
+               else if(riid == IID_IMsRdpClient4)
+                       pvObject = static_cast<IMsRdpClient4 *>(this);
+               else if(riid == IID_IMsTscNonScriptable)
+                       pvObject = static_cast<IMsTscNonScriptable *>(this);
+               else if(riid == IID_IMsRdpClientNonScriptable)
+                       pvObject = static_cast<IMsRdpClientNonScriptable *>(this);
+               else if(riid == IID_IMsRdpClientNonScriptable2)
+                       pvObject = static_cast<IMsRdpClientNonScriptable2 *>(this);
+
+               *ppvObject = pvObject;
+
+               if(pvObject)
+               {
+                       pvObject->AddRef();
+                       return S_OK;
+               }
+
+               return E_NOINTERFACE;
+       }
+
+       ULONG addRef()
+       {
+               return InterlockedIncrement(&m_refCount);
+       }
+
+       ULONG release()
+       {
+               LONG n = InterlockedDecrement(&m_refCount);
+
+               if(n == 0)
+                       delete this;
+
+               return n;
+       }
+
+       /* Constructor */
+       RdpClient(REFCLSID classId, unsigned libIndex, IUnknown * punkOuter):
+               // COM/OLE internals
+               m_refCount(0),
+               m_punkOuter(punkOuter),
+               m_classId(classId),
+               m_typeLibIndex(libIndex),
+               m_typeLib(),
+               m_dispTypeInfo(),
+               m_controlWindow(NULL),
+               m_clientSite(),
+               m_inPlaceSite(),
+               m_adviseHolder(),
+               m_freezeEvents(0),
+               m_uiActive(false),
+               m_SafetyOptions(),
+
+#ifdef _DEBUG
+               m_apartmentThreadId(GetCurrentThreadId()),
+#endif
+
+               // rdesktop-core interface
+               m_protocolState(),
+               m_protocolThread(),
+
+               // Properties
+               m_Server(),
+               m_Domain(),
+               m_UserName(),
+               m_DisconnectedText(),
+               m_ConnectingText(),
+               m_FullScreenTitle(),
+               m_StartProgram(),
+               m_WorkDir(),
+               m_LoadBalanceInfo(),
+               m_ConnectedStatusText(),
+               m_ClearTextPassword(),
+               m_RdpdrLocalPrintingDocName(),
+               m_RdpdrClipCleanTempDirString(),
+               m_RdpdrClipPasteInfoString(),
+               m_UIParentWindowHandle(),
+               m_DesktopWidth(),
+               m_DesktopHeight(),
+               m_StartConnected(),
+               m_ColorDepth(16),
+               m_KeyboardHookMode(2),
+               m_AudioRedirectionMode(0),
+               m_TransportType(1), // BUGBUG: ??? what's this ???
+               m_SasSequence(0xAA03), // BUGBUG: ??? what's this ???
+               m_RDPPort(3389),
+               m_HotKeyFullScreen(VK_CANCEL),
+               m_HotKeyAltEsc(VK_INSERT),
+               m_HotKeyAltShiftTab(VK_NEXT),
+               m_HotKeyAltSpace(VK_DELETE),
+               m_HotKeyAltTab(VK_PRIOR),
+               m_HotKeyCtrlAltDel(VK_END),
+               m_HotKeyCtrlEsc(VK_HOME),
+               m_orderDrawThresold(0),
+               m_BitmapCacheSize(1500),
+               m_BitmapVirtualCacheSize(10),
+               m_brushSupportLevel(),
+               m_minInputSendInterval(),
+               m_InputEventsAtOnce(),
+               m_maxEventCount(),
+               m_keepAliveInternal(0),
+               m_shutdownTimeout(10),
+               m_overallConnectionTimeout(120),
+               m_singleConnectionTimeout(30),
+               m_MinutesToIdleTimeout(0),
+               m_BitmapVirtualCache16BppSize(20),
+               m_BitmapVirtualCache24BppSize(30),
+               m_PerformanceFlags(),
+               m_MaxReconnectAttempts(20),
+               m_AuthenticationLevel(0),
+               m_ExtendedDisconnectReason(MSTSCLib::exDiscReasonNoInfo),
+               m_Connected(false),
+               m_Compress(true),
+               m_BitmapPersistence(true),
+               m_allowBackgroundInput(false),
+               m_ContainerHandledFullScreen(false),
+               m_DisableRdpdr(false),
+               m_SecuredSettingsEnabled(true),
+               m_FullScreen(false),
+               m_AcceleratorPassthrough(true),
+               m_ShadowBitmap(true),
+               m_EncryptionEnabled(true),
+               m_DedicatedTerminal(false),
+               m_DisableCtrlAltDel(true),
+               m_EnableWindowsKey(true),
+               m_DoubleClickDetect(false),
+               m_MaximizeShell(true),
+               m_ScaleBitmapCachesByBpp(false),
+               m_CachePersistenceActive(false),
+               m_ConnectToServerConsole(false),
+               m_SmartSizing(false),
+               m_DisplayConnectionBar(true),
+               m_PinConnectionBar(true),
+               m_GrabFocusOnConnect(true),
+               m_RedirectDrives(false),
+               m_RedirectPrinters(false),
+               m_RedirectPorts(false),
+               m_RedirectSmartCards(false),
+               m_NotifyTSPublicKey(false),
+               m_CanAutoReconnect(false),
+               m_EnableAutoReconnect(true),
+               m_ConnectionBarShowMinimizeButton(true),
+               m_ConnectionBarShowRestoreButton(true)
+       {
+               if(m_punkOuter == NULL)
+                       m_punkOuter = &m_inner;
+       }
+
+       /* Destructor */
+       ~RdpClient()
+       {
+               assert(m_refCount == 0);
+
+               if(m_Connected)
+               {
+                       // Terminate the protocol thread
+                       TerminateProtocolThread();
+
+                       // Dispatch the RDPC_WM_DISCONNECT message sent by the dying thread
+                       MSG msg;
+                       PeekMessage(&msg, m_controlWindow, 0, 0, PM_NOREMOVE);
+
+                       assert(!m_Connected);
+               }
+
+               DestroyControlWindow();
+
+               if(m_typeLib)
+                       m_typeLib->Release();
+
+               if(m_dispTypeInfo)
+                       m_dispTypeInfo->Release();
+
+               MSTSCLib::IMsTscAxEvents ** sinks = GetSinks();
+
+               for(size_t i = 0; i < m_EventSinksCount; ++ i)
+                       sinks[i]->Release();
+
+               if(m_EventSinksCount > 1)
+                       delete[] m_EventSinks;
+
+               if(m_clientSite)
+                       m_clientSite->Release();
+
+               if(m_inPlaceSite)
+                       m_inPlaceSite->Release();
+
+               if(m_adviseHolder)
+                       m_adviseHolder->Release();
+
+               SysFreeString(m_Domain);
+               SysFreeString(m_UserName);
+               SysFreeString(m_DisconnectedText);
+               SysFreeString(m_DisconnectedText);
+               SysFreeString(m_FullScreenTitle);
+               SysFreeString(m_StartProgram);
+               SysFreeString(m_WorkDir);
+               SysFreeString(m_ConnectedStatusText);
+               SysFreeString(m_ClearTextPassword);
+               SysFreeString(m_RdpdrLocalPrintingDocName);
+               SysFreeString(m_RdpdrClipCleanTempDirString);
+               SysFreeString(m_RdpdrClipPasteInfoString);
+
+               FreeLpsz(m_LoadBalanceInfo);
+               FreeLpsz(m_Server);
+
+               unlockServer();
+       }
+
+       /* Advanced settings wrapper */
+       friend class AdvancedSettings;
+
+       class AdvancedSettings SEALED_: public MSTSCLib::IMsRdpClientAdvancedSettings4
+       {
+       private:
+               RdpClient * Outer()
+               {
+                       return InnerToOuter(this);
+               }
+
+               const RdpClient * Outer() const
+               {
+                       return InnerToOuter(this);
+               }
+
+               /* IDispatch type information */
+               ITypeInfo * m_dispTypeInfo;
+
+               HRESULT LoadDispTypeInfo()
+               {
+                       if(m_dispTypeInfo)
+                               return S_OK;
+
+                       HRESULT hr = Outer()->LoadTypeLibrary();
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       assert(MSTSCLib::IID_IMsRdpClientAdvancedSettings4 == MSTSCLib_Redist::IID_IMsRdpClientAdvancedSettings4);
+
+                       hr = Outer()->m_typeLib->GetTypeInfoOfGuid(MSTSCLib::IID_IMsRdpClientAdvancedSettings4, &m_dispTypeInfo);
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       assert(m_dispTypeInfo);
+                       return S_OK;
+               }
+
+               HRESULT AcquireDispTypeInfo(ITypeInfo ** ppTI)
+               {
+                       HRESULT hr = LoadDispTypeInfo();
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       m_dispTypeInfo->AddRef();
+                       *ppTI = m_dispTypeInfo;
+                       return S_OK;
+               }
+
+       public:
+               ~AdvancedSettings()
+               {
+                       if(m_dispTypeInfo)
+                               m_dispTypeInfo->Release();
+               }
+
+               /* IUnknown */
+               virtual STDMETHODIMP IUnknown::QueryInterface(REFIID riid, void ** ppvObject)
+               {
+                       using namespace MSTSCLib;
+
+                       if
+                       (
+                               riid == IID_IUnknown ||
+                               riid == IID_IDispatch ||
+                               riid == IID_IMsTscAdvancedSettings ||
+                               riid == IID_IMsRdpClientAdvancedSettings ||
+                               riid == IID_IMsRdpClientAdvancedSettings2 ||
+                               riid == IID_IMsRdpClientAdvancedSettings3 ||
+                               riid == IID_IMsRdpClientAdvancedSettings4
+                       )
+                       {
+                               *ppvObject = this;
+                               Outer()->addRef();
+                               return S_OK;
+                       }
+                       else
+                       {
+                               *ppvObject = NULL;
+                               return E_NOINTERFACE;
+                       }
+               }
+
+               virtual STDMETHODIMP_(ULONG) IUnknown::AddRef()
+               {
+                       return Outer()->addRef();
+               }
+
+               virtual STDMETHODIMP_(ULONG) IUnknown::Release()
+               {
+                       return Outer()->release();
+               }
+
+               /* IDispatch */
+               virtual STDMETHODIMP IDispatch::GetTypeInfoCount(UINT * pctinfo)
+               {
+                       *pctinfo = 1;
+                       return S_OK;
+               }
+
+               virtual STDMETHODIMP IDispatch::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo)
+               {
+                       if(iTInfo != 0)
+                               return DISP_E_BADINDEX;
+
+                       return AcquireDispTypeInfo(ppTInfo);
+               }
+
+               virtual STDMETHODIMP IDispatch::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId)
+               {
+                       HRESULT hr = LoadDispTypeInfo();
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       return m_dispTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
+               }
+
+               virtual STDMETHODIMP IDispatch::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr)
+               {
+                       return m_dispTypeInfo->Invoke
+                       (
+                               static_cast<MSTSCLib::IMsRdpClientAdvancedSettings4 *>(this),
+                               dispIdMember,
+                               wFlags,
+                               pDispParams,
+                               pVarResult,
+                               pExcepInfo,
+                               puArgErr
+                       );
+               }
+
+               /* IMsTscAdvancedSettings */
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_Compress(long pcompress)
+               {
+                       return Outer()->SetProperty(Outer()->m_Compress, pcompress);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::get_Compress(long * pcompress) const
+               {
+                       return Outer()->GetProperty(Outer()->m_Compress, pcompress);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_BitmapPeristence(long pbitmapPeristence)
+               {
+                       return Outer()->SetProperty(Outer()->m_BitmapPersistence, pbitmapPeristence);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::get_BitmapPeristence(long * pbitmapPeristence) const
+               {
+                       return Outer()->GetProperty(Outer()->m_BitmapPersistence, pbitmapPeristence);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_allowBackgroundInput(long pallowBackgroundInput)
+               {
+                       if(Outer()->IsSafeForScripting())
+                               return S_FALSE;
+
+                       return Outer()->SetProperty(Outer()->m_allowBackgroundInput, pallowBackgroundInput);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::get_allowBackgroundInput(long * pallowBackgroundInput) const
+               {
+                       return Outer()->GetProperty(Outer()->m_allowBackgroundInput, pallowBackgroundInput);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_KeyBoardLayoutStr(BSTR rhs)
+               {
+                       return Outer()->SetProperty(Outer()->m_KeyboardLayoutString, rhs);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_PluginDlls(BSTR rhs)
+               {
+                       // TODO: split rhs into an array
+
+                       // Control marked safe for scripting: only allow filenames
+                       if(Outer()->IsSafeForScripting())
+                       {
+                               // TODO: validate entries
+                               // TODO: replace each entry with a full path based on the Virtual Channel DLL path
+                       }
+
+                       return E_NOTIMPL; // TODO
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_IconFile(BSTR rhs)
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_IconIndex(long rhs)
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_ContainerHandledFullScreen(long pContainerHandledFullScreen)
+               {
+                       if(Outer()->IsSafeForScripting())
+                               return S_FALSE;
+
+                       return Outer()->SetProperty(Outer()->m_ContainerHandledFullScreen, pContainerHandledFullScreen);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::get_ContainerHandledFullScreen(long * pContainerHandledFullScreen) const
+               {
+                       return Outer()->GetProperty(Outer()->m_ContainerHandledFullScreen, pContainerHandledFullScreen);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::put_DisableRdpdr(long pDisableRdpdr)
+               {
+                       return Outer()->SetProperty(Outer()->m_DisableRdpdr, pDisableRdpdr);
+               }
+
+               virtual STDMETHODIMP IMsTscAdvancedSettings::get_DisableRdpdr(long * pDisableRdpdr) const
+               {
+                       return Outer()->GetProperty(Outer()->m_DisableRdpdr, pDisableRdpdr);
+               }
+
+               /* IMsRdpClientAdvancedSettings */
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_SmoothScroll(long psmoothScroll)
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_SmoothScroll(long * psmoothScroll) const
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_AcceleratorPassthrough(long pacceleratorPassthrough)
+               {
+                       return Outer()->SetProperty(Outer()->m_AcceleratorPassthrough, pacceleratorPassthrough);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_AcceleratorPassthrough(long * pacceleratorPassthrough) const
+               {
+                       return Outer()->GetProperty(Outer()->m_AcceleratorPassthrough, pacceleratorPassthrough);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_ShadowBitmap(long pshadowBitmap)
+               {
+                       return Outer()->SetProperty(Outer()->m_ShadowBitmap, pshadowBitmap);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_ShadowBitmap(long * pshadowBitmap) const
+               {
+                       return Outer()->GetProperty(Outer()->m_ShadowBitmap, pshadowBitmap);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_TransportType(long ptransportType)
+               {
+                       // Reserved
+                       return Outer()->SetProperty(Outer()->m_TransportType, ptransportType);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_TransportType(long * ptransportType) const
+               {
+                       // Reserved
+                       return Outer()->GetProperty(Outer()->m_TransportType, ptransportType);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_SasSequence(long psasSequence)
+               {
+                       // Reserved
+                       return Outer()->SetProperty(Outer()->m_SasSequence, psasSequence);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_SasSequence(long * psasSequence) const
+               {
+                       // Reserved
+                       return Outer()->GetProperty(Outer()->m_SasSequence, psasSequence);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_EncryptionEnabled(long pencryptionEnabled)
+               {
+                       return Outer()->SetProperty(Outer()->m_EncryptionEnabled, pencryptionEnabled);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_EncryptionEnabled(long * pencryptionEnabled) const
+               {
+                       return Outer()->GetProperty(Outer()->m_EncryptionEnabled, pencryptionEnabled);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_DedicatedTerminal(long pdedicatedTerminal)
+               {
+                       return Outer()->SetProperty(Outer()->m_DedicatedTerminal, pdedicatedTerminal);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_DedicatedTerminal(long * pdedicatedTerminal) const
+               {
+                       return Outer()->GetProperty(Outer()->m_DedicatedTerminal, pdedicatedTerminal);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RDPPort(long prdpPort)
+               {
+                       if(prdpPort == 0 || prdpPort > 65535)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_RDPPort, prdpPort);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RDPPort(long * prdpPort) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RDPPort, prdpPort);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_EnableMouse(long penableMouse)
+               {
+                       return S_FALSE; // TBD? implement?
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_EnableMouse(long * penableMouse) const
+               {
+                       return S_FALSE; // TBD? implement?
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_DisableCtrlAltDel(long pdisableCtrlAltDel)
+               {
+                       return Outer()->SetProperty(Outer()->m_DisableCtrlAltDel, pdisableCtrlAltDel);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_DisableCtrlAltDel(long * pdisableCtrlAltDel) const
+               {
+                       return Outer()->GetProperty(Outer()->m_DisableCtrlAltDel, pdisableCtrlAltDel);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_EnableWindowsKey(long penableWindowsKey)
+               {
+                       return Outer()->SetProperty(Outer()->m_EnableWindowsKey, penableWindowsKey);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_EnableWindowsKey(long * penableWindowsKey) const
+               {
+                       return Outer()->GetProperty(Outer()->m_EnableWindowsKey, penableWindowsKey);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_DoubleClickDetect(long pdoubleClickDetect)
+               {
+                       return Outer()->SetProperty(Outer()->m_DoubleClickDetect, pdoubleClickDetect);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_DoubleClickDetect(long * pdoubleClickDetect) const
+               {
+                       return Outer()->GetProperty(Outer()->m_DoubleClickDetect, pdoubleClickDetect);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_MaximizeShell(long pmaximizeShell)
+               {
+                       return Outer()->SetProperty(Outer()->m_MaximizeShell, pmaximizeShell);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_MaximizeShell(long * pmaximizeShell) const
+               {
+                       return Outer()->GetProperty(Outer()->m_MaximizeShell, pmaximizeShell);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyFullScreen(long photKeyFullScreen)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyFullScreen, photKeyFullScreen);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyFullScreen(long * photKeyFullScreen) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyFullScreen, photKeyFullScreen);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyCtrlEsc(long photKeyCtrlEsc)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyCtrlEsc, photKeyCtrlEsc);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyCtrlEsc(long * photKeyCtrlEsc) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyCtrlEsc, photKeyCtrlEsc);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyAltEsc(long photKeyAltEsc)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyAltEsc, photKeyAltEsc);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyAltEsc(long * photKeyAltEsc) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyAltEsc, photKeyAltEsc);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyAltTab(long photKeyAltTab)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyAltTab, photKeyAltTab);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyAltTab(long * photKeyAltTab) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyAltTab, photKeyAltTab);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyAltShiftTab(long photKeyAltShiftTab)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyAltShiftTab, photKeyAltShiftTab);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyAltShiftTab(long * photKeyAltShiftTab) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyAltShiftTab, photKeyAltShiftTab);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyAltSpace(long photKeyAltSpace)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyAltSpace, photKeyAltSpace);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyAltSpace(long * photKeyAltSpace) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyAltSpace, photKeyAltSpace);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_HotKeyCtrlAltDel(long photKeyCtrlAltDel)
+               {
+                       return Outer()->SetProperty(Outer()->m_HotKeyCtrlAltDel, photKeyCtrlAltDel);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_HotKeyCtrlAltDel(long * photKeyCtrlAltDel) const
+               {
+                       return Outer()->GetProperty(Outer()->m_HotKeyCtrlAltDel, photKeyCtrlAltDel);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_orderDrawThreshold(long porderDrawThreshold)
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_orderDrawThreshold(long * porderDrawThreshold) const
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_BitmapCacheSize(long pbitmapCacheSize)
+               {
+                       // NOTE: the upper bound of "32" for a field with a default value of 1500 seems to be a bug
+                       if(pbitmapCacheSize < 0 || pbitmapCacheSize > 32)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_BitmapCacheSize, pbitmapCacheSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_BitmapCacheSize(long * pbitmapCacheSize) const
+               {
+                       return Outer()->GetProperty(Outer()->m_BitmapCacheSize, pbitmapCacheSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_BitmapVirtualCacheSize(long pbitmapVirtualCacheSize)
+               {
+                       if(pbitmapVirtualCacheSize < 0 || pbitmapVirtualCacheSize > 32)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_BitmapVirtualCacheSize, pbitmapVirtualCacheSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_BitmapVirtualCacheSize(long * pbitmapVirtualCacheSize) const
+               {
+                       return Outer()->GetProperty(Outer()->m_BitmapVirtualCacheSize, pbitmapVirtualCacheSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_ScaleBitmapCachesByBPP(long pbScale)
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_ScaleBitmapCachesByBPP(long * pbScale) const
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_NumBitmapCaches(long pnumBitmapCaches)
+               {
+                       return Outer()->SetProperty(Outer()->m_NumBitmapCaches, pnumBitmapCaches);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_NumBitmapCaches(long * pnumBitmapCaches) const
+               {
+                       return Outer()->GetProperty(Outer()->m_NumBitmapCaches, pnumBitmapCaches);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_CachePersistenceActive(long pcachePersistenceActive)
+               {
+                       return Outer()->SetProperty(Outer()->m_CachePersistenceActive, pcachePersistenceActive);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_CachePersistenceActive(long * pcachePersistenceActive) const
+               {
+                       return Outer()->GetProperty(Outer()->m_CachePersistenceActive, pcachePersistenceActive);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_PersistCacheDirectory(BSTR rhs)
+               {
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_brushSupportLevel(long pbrushSupportLevel)
+               {
+                       return Outer()->SetProperty(Outer()->m_brushSupportLevel, pbrushSupportLevel);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_brushSupportLevel(long * pbrushSupportLevel) const
+               {
+                       return Outer()->GetProperty(Outer()->m_brushSupportLevel, pbrushSupportLevel);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_minInputSendInterval(long pminInputSendInterval)
+               {
+                       // TODO
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_minInputSendInterval(long * pminInputSendInterval) const
+               {
+                       // TODO
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_InputEventsAtOnce(long pinputEventsAtOnce)
+               {
+                       // TODO
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_InputEventsAtOnce(long * pinputEventsAtOnce) const
+               {
+                       // TODO
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_maxEventCount(long pmaxEventCount)
+               {
+                       // TODO
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_maxEventCount(long * pmaxEventCount) const
+               {
+                       // TODO
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_keepAliveInterval(long pkeepAliveInterval)
+               {
+                       if(pkeepAliveInterval && pkeepAliveInterval < 10)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_keepAliveInternal, pkeepAliveInterval);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_keepAliveInterval(long * pkeepAliveInterval) const
+               {
+                       return Outer()->GetProperty(Outer()->m_keepAliveInternal, pkeepAliveInterval);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_shutdownTimeout(long pshutdownTimeout)
+               {
+                       if(pshutdownTimeout >= 600)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_shutdownTimeout, pshutdownTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_shutdownTimeout(long * pshutdownTimeout) const
+               {
+                       return Outer()->GetProperty(Outer()->m_shutdownTimeout, pshutdownTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_overallConnectionTimeout(long poverallConnectionTimeout)
+               {
+                       if(poverallConnectionTimeout < 0 || poverallConnectionTimeout >= 600)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_overallConnectionTimeout, poverallConnectionTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_overallConnectionTimeout(long * poverallConnectionTimeout) const
+               {
+                       return Outer()->GetProperty(Outer()->m_overallConnectionTimeout, poverallConnectionTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_singleConnectionTimeout(long psingleConnectionTimeout)
+               {
+                       if(psingleConnectionTimeout >= 600)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_singleConnectionTimeout, psingleConnectionTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_singleConnectionTimeout(long * psingleConnectionTimeout) const
+               {
+                       return Outer()->GetProperty(Outer()->m_singleConnectionTimeout, psingleConnectionTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_KeyboardType(long pkeyboardType)
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_KeyboardType(long * pkeyboardType) const
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_KeyboardSubType(long pkeyboardSubType)
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_KeyboardSubType(long * pkeyboardSubType) const
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_KeyboardFunctionKey(long pkeyboardFunctionKey)
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_KeyboardFunctionKey(long * pkeyboardFunctionKey) const
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_WinceFixedPalette(long pwinceFixedPalette)
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_WinceFixedPalette(long * pwinceFixedPalette) const
+               {
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_ConnectToServerConsole(VARIANT_BOOL pConnectToConsole)
+               {
+                       return Outer()->SetProperty(Outer()->m_ConnectToServerConsole, pConnectToConsole);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_ConnectToServerConsole(VARIANT_BOOL * pConnectToConsole) const
+               {
+                       return Outer()->GetProperty(Outer()->m_ConnectToServerConsole, pConnectToConsole);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_BitmapPersistence(long pbitmapPersistence)
+               {
+                       return put_BitmapPeristence(pbitmapPersistence);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_BitmapPersistence(long * pbitmapPersistence) const
+               {
+                       return get_BitmapPeristence(pbitmapPersistence);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_MinutesToIdleTimeout(long pminutesToIdleTimeout)
+               {
+                       if(pminutesToIdleTimeout > 240)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_MinutesToIdleTimeout, pminutesToIdleTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_MinutesToIdleTimeout(long * pminutesToIdleTimeout) const
+               {
+                       return Outer()->GetProperty(Outer()->m_MinutesToIdleTimeout, pminutesToIdleTimeout);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_SmartSizing(VARIANT_BOOL pfSmartSizing)
+               {
+                       return Outer()->SetProperty(Outer()->m_SmartSizing, pfSmartSizing);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_SmartSizing(VARIANT_BOOL * pfSmartSizing) const
+               {
+                       return Outer()->GetProperty(Outer()->m_SmartSizing, pfSmartSizing);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RdpdrLocalPrintingDocName(BSTR pLocalPrintingDocName)
+               {
+                       return Outer()->SetProperty(Outer()->m_RdpdrLocalPrintingDocName, pLocalPrintingDocName);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RdpdrLocalPrintingDocName(BSTR * pLocalPrintingDocName) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RdpdrLocalPrintingDocName, pLocalPrintingDocName);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RdpdrClipCleanTempDirString(BSTR clipCleanTempDirString)
+               {
+                       return Outer()->SetProperty(Outer()->m_RdpdrClipCleanTempDirString, clipCleanTempDirString);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RdpdrClipCleanTempDirString(BSTR * clipCleanTempDirString) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RdpdrClipCleanTempDirString, clipCleanTempDirString);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RdpdrClipPasteInfoString(BSTR clipPasteInfoString)
+               {
+                       return Outer()->SetProperty(Outer()->m_RdpdrClipPasteInfoString, clipPasteInfoString);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RdpdrClipPasteInfoString(BSTR * clipPasteInfoString) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RdpdrClipPasteInfoString, clipPasteInfoString);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_ClearTextPassword(BSTR rhs)
+               {
+                       return Outer()->put_ClearTextPassword(rhs);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_DisplayConnectionBar(VARIANT_BOOL pDisplayConnectionBar)
+               {
+                       if(!pDisplayConnectionBar && Outer()->IsSafeForScripting())
+                               return E_FAIL;
+
+                       return Outer()->SetProperty(Outer()->m_DisplayConnectionBar, pDisplayConnectionBar);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_DisplayConnectionBar(VARIANT_BOOL * pDisplayConnectionBar) const
+               {
+                       return Outer()->GetProperty(Outer()->m_DisplayConnectionBar, pDisplayConnectionBar);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_PinConnectionBar(VARIANT_BOOL pPinConnectionBar)
+               {
+                       if(Outer()->IsSafeForScripting())
+                               return E_NOTIMPL;
+
+                       return Outer()->SetProperty(Outer()->m_PinConnectionBar, pPinConnectionBar);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_PinConnectionBar(VARIANT_BOOL * pPinConnectionBar) const
+               {
+                       return Outer()->GetProperty(Outer()->m_PinConnectionBar, pPinConnectionBar);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_GrabFocusOnConnect(VARIANT_BOOL pfGrabFocusOnConnect)
+               {
+                       return Outer()->SetProperty(Outer()->m_GrabFocusOnConnect, pfGrabFocusOnConnect);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_GrabFocusOnConnect(VARIANT_BOOL * pfGrabFocusOnConnect) const
+               {
+                       return Outer()->GetProperty(Outer()->m_GrabFocusOnConnect, pfGrabFocusOnConnect);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_LoadBalanceInfo(BSTR pLBInfo)
+               {
+                       return Outer()->SetProperty(Outer()->m_LoadBalanceInfo, pLBInfo);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_LoadBalanceInfo(BSTR * pLBInfo) const
+               {
+                       return Outer()->GetProperty(Outer()->m_LoadBalanceInfo, pLBInfo);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RedirectDrives(VARIANT_BOOL pRedirectDrives)
+               {
+                       return Outer()->SetProperty(Outer()->m_RedirectDrives, pRedirectDrives);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RedirectDrives(VARIANT_BOOL * pRedirectDrives) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RedirectDrives, pRedirectDrives);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RedirectPrinters(VARIANT_BOOL pRedirectPrinters)
+               {
+                       return Outer()->SetProperty(Outer()->m_RedirectPrinters, pRedirectPrinters);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RedirectPrinters(VARIANT_BOOL * pRedirectPrinters) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RedirectPrinters, pRedirectPrinters);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RedirectPorts(VARIANT_BOOL pRedirectPorts)
+               {
+                       return Outer()->SetProperty(Outer()->m_RedirectPorts, pRedirectPorts);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RedirectPorts(VARIANT_BOOL * pRedirectPorts) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RedirectPorts, pRedirectPorts);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_RedirectSmartCards(VARIANT_BOOL pRedirectSmartCards)
+               {
+                       return Outer()->SetProperty(Outer()->m_RedirectSmartCards, pRedirectSmartCards);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_RedirectSmartCards(VARIANT_BOOL * pRedirectSmartCards) const
+               {
+                       return Outer()->GetProperty(Outer()->m_RedirectSmartCards, pRedirectSmartCards);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_BitmapVirtualCache16BppSize(long pBitmapVirtualCache16BppSize)
+               {
+                       if(pBitmapVirtualCache16BppSize < 0 || pBitmapVirtualCache16BppSize > 32)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_BitmapVirtualCache16BppSize, pBitmapVirtualCache16BppSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_BitmapVirtualCache16BppSize(long * pBitmapVirtualCache16BppSize) const
+               {
+                       return Outer()->GetProperty(Outer()->m_BitmapVirtualCache16BppSize, pBitmapVirtualCache16BppSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_BitmapVirtualCache24BppSize(long pBitmapVirtualCache24BppSize)
+               {
+                       if(pBitmapVirtualCache24BppSize < 0 || pBitmapVirtualCache24BppSize > 32)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_BitmapVirtualCache24BppSize, pBitmapVirtualCache24BppSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_BitmapVirtualCache24BppSize(long * pBitmapVirtualCache24BppSize) const
+               {
+                       return Outer()->GetProperty(Outer()->m_BitmapVirtualCache24BppSize, pBitmapVirtualCache24BppSize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_PerformanceFlags(long pDisableList)
+               {
+                       return Outer()->SetProperty(Outer()->m_PerformanceFlags, pDisableList);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_PerformanceFlags(long * pDisableList) const
+               {
+                       return Outer()->GetProperty(Outer()->m_PerformanceFlags, pDisableList);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_ConnectWithEndpoint(VARIANT * rhs)
+               {
+                       // TBD? the Microsoft client implements this, but what does it mean?
+                       return E_NOTIMPL;
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::put_NotifyTSPublicKey(VARIANT_BOOL pfNotify)
+               {
+                       return Outer()->SetProperty(Outer()->m_NotifyTSPublicKey, pfNotify);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings::get_NotifyTSPublicKey(VARIANT_BOOL * pfNotify) const
+               {
+                       return Outer()->GetProperty(Outer()->m_NotifyTSPublicKey, pfNotify);
+               }
+
+               /* IMsRdpClientAdvancedSettings2 */
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings2::get_CanAutoReconnect(VARIANT_BOOL * pfCanAutoReconnect) const
+               {
+                       return E_NOTIMPL; // TODO
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings2::put_EnableAutoReconnect(VARIANT_BOOL pfEnableAutoReconnect)
+               {
+                       return Outer()->SetProperty(Outer()->m_EnableAutoReconnect, pfEnableAutoReconnect);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings2::get_EnableAutoReconnect(VARIANT_BOOL * pfEnableAutoReconnect) const
+               {
+                       return Outer()->GetProperty(Outer()->m_EnableAutoReconnect, pfEnableAutoReconnect);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings2::put_MaxReconnectAttempts(long pMaxReconnectAttempts)
+               {
+                       if(pMaxReconnectAttempts < 0 || pMaxReconnectAttempts > 200)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_MaxReconnectAttempts, pMaxReconnectAttempts);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings2::get_MaxReconnectAttempts(long * pMaxReconnectAttempts) const
+               {
+                       return Outer()->GetProperty(Outer()->m_MaxReconnectAttempts, pMaxReconnectAttempts);
+               }
+
+               /* IMsRdpClientAdvancedSettings3 */
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings3::put_ConnectionBarShowMinimizeButton(VARIANT_BOOL pfShowMinimize)
+               {
+                       return Outer()->SetProperty(Outer()->m_ConnectionBarShowMinimizeButton, pfShowMinimize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings3::get_ConnectionBarShowMinimizeButton(VARIANT_BOOL * pfShowMinimize) const
+               {
+                       return Outer()->GetProperty(Outer()->m_ConnectionBarShowMinimizeButton, pfShowMinimize);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings3::put_ConnectionBarShowRestoreButton(VARIANT_BOOL pfShowRestore)
+               {
+                       return Outer()->SetProperty(Outer()->m_ConnectionBarShowRestoreButton, pfShowRestore);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientAdvancedSettings3::get_ConnectionBarShowRestoreButton(VARIANT_BOOL * pfShowRestore) const
+               {
+                       return Outer()->GetProperty(Outer()->m_ConnectionBarShowRestoreButton, pfShowRestore);
+               }
+
+               /* IMsRdpClientAdvancedSettings4 */
+        virtual STDMETHODIMP IMsRdpClientAdvancedSettings4::put_AuthenticationLevel(unsigned int puiAuthLevel)
+               {
+                       // TODO: this isn't implemented in rdesktop yet...
+                       return Outer()->SetProperty(Outer()->m_AuthenticationLevel, puiAuthLevel);
+               }
+
+        virtual STDMETHODIMP IMsRdpClientAdvancedSettings4::get_AuthenticationLevel(unsigned int * puiAuthLevel) const
+               {
+                       return Outer()->GetProperty(Outer()->m_AuthenticationLevel, puiAuthLevel);
+               }
+       }
+       m_advancedSettings;
+
+       template<class Interface> HRESULT GetAdvancedSettings(Interface ** ppAdvSettings)
+       {
+               addRef();
+               *ppAdvSettings = &m_advancedSettings;
+               return S_OK;
+       }
+
+       /* Secured settings wrapper */
+       friend class SecuredSettings;
+
+       class SecuredSettings SEALED_: public MSTSCLib::IMsRdpClientSecuredSettings
+       {
+       private:
+               RdpClient * Outer()
+               {
+                       return InnerToOuter(this);
+               }
+
+               const RdpClient * Outer() const
+               {
+                       return InnerToOuter(this);
+               }
+
+               /* IDispatch type information */
+               ITypeInfo * m_dispTypeInfo;
+
+               HRESULT LoadDispTypeInfo()
+               {
+                       if(m_dispTypeInfo)
+                               return S_OK;
+
+                       HRESULT hr = Outer()->LoadTypeLibrary();
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       assert(MSTSCLib::IID_IMsRdpClientSecuredSettings == MSTSCLib_Redist::IID_IMsRdpClientSecuredSettings);
+
+                       hr = Outer()->m_typeLib->GetTypeInfoOfGuid(MSTSCLib::IID_IMsRdpClientSecuredSettings, &m_dispTypeInfo);
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       assert(m_dispTypeInfo);
+                       return S_OK;
+               }
+
+               HRESULT AcquireDispTypeInfo(ITypeInfo ** ppTI)
+               {
+                       HRESULT hr = LoadDispTypeInfo();
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       m_dispTypeInfo->AddRef();
+                       *ppTI = m_dispTypeInfo;
+                       return S_OK;
+               }
+
+       public:
+               ~SecuredSettings()
+               {
+                       if(m_dispTypeInfo)
+                               m_dispTypeInfo->Release();
+               }
+
+               /* IUnknown */
+               virtual STDMETHODIMP IUnknown::QueryInterface(REFIID riid, void ** ppvObject)
+               {
+                       using namespace MSTSCLib;
+
+                       if
+                       (
+                               riid == IID_IUnknown ||
+                               riid == IID_IDispatch ||
+                               riid == IID_IMsTscSecuredSettings ||
+                               riid == IID_IMsRdpClientSecuredSettings
+                       )
+                       {
+                               *ppvObject = this;
+                               Outer()->addRef();
+                               return S_OK;
+                       }
+                       else
+                       {
+                               *ppvObject = NULL;
+                               return E_NOINTERFACE;
+                       }
+               }
+
+               virtual STDMETHODIMP_(ULONG) IUnknown::AddRef()
+               {
+                       return Outer()->addRef();
+               }
+
+               virtual STDMETHODIMP_(ULONG) IUnknown::Release()
+               {
+                       return Outer()->release();
+               }
+
+               /* IDispatch */
+               virtual STDMETHODIMP IDispatch::GetTypeInfoCount(UINT * pctinfo)
+               {
+                       *pctinfo = 1;
+                       return S_OK;
+               }
+
+               virtual STDMETHODIMP IDispatch::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo)
+               {
+                       if(iTInfo != 0)
+                               return DISP_E_BADINDEX;
+
+                       return AcquireDispTypeInfo(ppTInfo);
+               }
+
+               virtual STDMETHODIMP IDispatch::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId)
+               {
+                       HRESULT hr = LoadDispTypeInfo();
+
+                       if(FAILED(hr))
+                               return hr;
+
+                       return m_dispTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
+               }
+
+               virtual STDMETHODIMP IDispatch::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr)
+               {
+                       return m_dispTypeInfo->Invoke
+                       (
+                               static_cast<MSTSCLib::IMsRdpClientSecuredSettings *>(this),
+                               dispIdMember,
+                               wFlags,
+                               pDispParams,
+                               pVarResult,
+                               pExcepInfo,
+                               puArgErr
+                       );
+               }
+
+               /* IMsTscSecuredSettings */
+               virtual STDMETHODIMP IMsTscSecuredSettings::put_StartProgram(BSTR pStartProgram)
+               {
+                       return Outer()->SetProperty(Outer()->m_StartProgram, pStartProgram);
+               }
+
+               virtual STDMETHODIMP IMsTscSecuredSettings::get_StartProgram(BSTR * pStartProgram) const
+               {
+                       return Outer()->GetProperty(Outer()->m_StartProgram, pStartProgram);
+               }
+
+               virtual STDMETHODIMP IMsTscSecuredSettings::put_WorkDir(BSTR pWorkDir)
+               {
+                       return Outer()->SetProperty(Outer()->m_WorkDir, pWorkDir);
+               }
+
+               virtual STDMETHODIMP IMsTscSecuredSettings::get_WorkDir(BSTR * pWorkDir) const
+               {
+                       return Outer()->GetProperty(Outer()->m_WorkDir, pWorkDir);
+               }
+
+               virtual STDMETHODIMP IMsTscSecuredSettings::put_FullScreen(long pfFullScreen)
+               {
+                       return Outer()->put_FullScreen(!!pfFullScreen);
+               }
+
+               virtual STDMETHODIMP IMsTscSecuredSettings::get_FullScreen(long * pfFullScreen) const
+               {
+                       return Outer()->GetProperty(Outer()->m_FullScreen, pfFullScreen);
+               }
+
+               /* IMsRdpClientSecuredSettings */
+               virtual STDMETHODIMP IMsRdpClientSecuredSettings::put_KeyboardHookMode(long pkeyboardHookMode)
+               {
+                       if(pkeyboardHookMode < 0 || pkeyboardHookMode > 2)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_KeyboardHookMode, pkeyboardHookMode);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientSecuredSettings::get_KeyboardHookMode(long * pkeyboardHookMode) const
+               {
+                       return Outer()->GetProperty(Outer()->m_KeyboardHookMode, pkeyboardHookMode);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientSecuredSettings::put_AudioRedirectionMode(long pAudioRedirectionMode)
+               {
+                       if(pAudioRedirectionMode < 0 || pAudioRedirectionMode > 2)
+                               return E_INVALIDARG;
+
+                       return Outer()->SetProperty(Outer()->m_AudioRedirectionMode, pAudioRedirectionMode);
+               }
+
+               virtual STDMETHODIMP IMsRdpClientSecuredSettings::get_AudioRedirectionMode(long * pAudioRedirectionMode) const
+               {
+                       return Outer()->GetProperty(Outer()->m_AudioRedirectionMode, pAudioRedirectionMode);
+               }
+       }
+       m_securedSettings;
+
+       template<class Interface> HRESULT GetSecuredSettings(Interface ** ppSecuredSettings)
+       {
+               if(!m_SecuredSettingsEnabled)
+                       return E_FAIL;
+
+               addRef();
+               *ppSecuredSettings = &m_securedSettings;
+               return S_OK;
+       }
+
+       /* Type library loading */
+       HRESULT LoadTypeLibrary()
+       {
+               if(m_typeLib)
+                       return S_OK;
+
+               // Get the DLL name of the ActiveX control
+               WCHAR szPath[MAX_PATH + 1];
+               DWORD cchPathLen = GetModuleFileNameW(GetCurrentModule(), szPath, ARRAYSIZE(szPath) - 1);
+
+               if(cchPathLen == 0)
+                       return HRESULT_FROM_WIN32(GetLastError());
+
+               if(cchPathLen > ((ARRAYSIZE(szPath) - 1) - 2))
+                       return E_FAIL;
+
+               // Append the resource id of the type library
+               assert(m_typeLibIndex < 10);
+
+               szPath[cchPathLen + 0] = L'\\';
+               szPath[cchPathLen + 1] = static_cast<WCHAR>(L'0' + m_typeLibIndex);
+               szPath[cchPathLen + 2] = 0;
+
+               // Load the type library
+               HRESULT hr = LoadTypeLibEx(szPath, REGKIND_NONE, &m_typeLib);
+
+               if(FAILED(hr))
+                       return hr;
+
+               assert(m_typeLib);
+               return S_OK;
+       }
+
+       /* IDispatch type information */
+       HRESULT LoadDispTypeInfo()
+       {
+               if(m_dispTypeInfo)
+                       return S_OK;
+
+               HRESULT hr = LoadTypeLibrary();
+
+               if(FAILED(hr))
+                       return hr;
+
+               assert(MSTSCLib::IID_IMsRdpClient4 == MSTSCLib_Redist::IID_IMsRdpClient4);
+
+               hr = m_typeLib->GetTypeInfoOfGuid(MSTSCLib::IID_IMsRdpClient4, &m_dispTypeInfo);
+
+               if(FAILED(hr))
+                       return hr;
+
+               assert(m_dispTypeInfo);
+               return S_OK;
+       }
+
+       HRESULT AcquireDispTypeInfo(ITypeInfo ** ppTI)
+       {
+               HRESULT hr = LoadDispTypeInfo();
+
+               if(FAILED(hr))
+                       return hr;
+
+               m_dispTypeInfo->AddRef();
+               *ppTI = m_dispTypeInfo;
+               return S_OK;
+       }
+
+public:
+       /* Helpers for our various embedded children */
+       static RdpClient * InnerToOuter(RdpClientInner * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_inner);
+       }
+
+       static RdpClient * InnerToOuter(AdvancedSettings * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_advancedSettings);
+       }
+
+       static RdpClient * InnerToOuter(SecuredSettings * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_securedSettings);
+       }
+
+       static RdpClient * InnerToOuter(RDPCLIENT * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_protocolState);
+       }
+
+       static const RdpClient * InnerToOuter(const RdpClientInner * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_inner);
+       }
+
+       static const RdpClient * InnerToOuter(const AdvancedSettings * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_advancedSettings);
+       }
+
+       static const RdpClient * InnerToOuter(const SecuredSettings * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_securedSettings);
+       }
+
+       static const RdpClient * InnerToOuter(const RDPCLIENT * innerThis)
+       {
+               return CONTAINING_RECORD(innerThis, RdpClient, m_protocolState);
+       }
+
+       RdpClientUI * GetUI() const
+       {
+               assert(m_clientUI);
+               return m_clientUI;
+       }
+
+       /* Glue for rdesktop-core */
+public:
+       static bool OnPublicKey(RDPCLIENT * This, unsigned char * key, unsigned int key_size)
+       {
+               return InnerToOuter(This)->OnPublicKey(key, key_size);
+       }
+
+       static void OnLogon(RDPCLIENT * This)
+       {
+               return InnerToOuter(This)->OnLogon();
+       }
+
+       static bool OnRedirect
+       (
+               RDPCLIENT * This,
+               uint32 flags,
+               uint32 server_len,
+               wchar_t * server,
+               uint32 cookie_len,
+               char * cookie,
+               uint32 username_len,
+               wchar_t * username,
+               uint32 domain_len,
+               wchar_t * domain,
+               uint32 password_len,
+               wchar_t * password
+       )
+       {
+               return InnerToOuter(This)->OnRedirect
+               (
+                       flags,
+                       server_len,
+                       server,
+                       cookie_len,
+                       cookie,
+                       username_len,
+                       username,
+                       domain_len,
+                       domain,
+                       password_len,
+                       password
+               );
+       }
+
+private:
+       bool OnPublicKey(unsigned char * key, unsigned int key_size)
+       {
+               HRESULT hr = FireReceivedTSPublicKey(key, key_size);
+
+               if(FAILED(hr))
+               {
+                       m_protocolState.disconnect_reason = 262;
+                       return false;
+               }
+
+               return hr == S_OK;
+       }
+
+       void OnLogon()
+       {
+               m_loggedIn = true;
+               FireLoginComplete();
+       }
+
+       bool OnRedirect
+       (
+               uint32 flags,
+               uint32 server_len,
+               wchar_t * server,
+               uint32 cookie_len,
+               char * cookie,
+               uint32 username_len,
+               wchar_t * username,
+               uint32 domain_len,
+               wchar_t * domain,
+               uint32 password_len,
+               wchar_t * password
+       )
+       {
+               assert(m_Connected);
+               assert(!InsideApartment());
+               assert(IsWindow(m_controlWindow));
+
+               RedirectArguments redirectArgs =
+               {
+                       flags,
+                       server_len,
+                       server,
+                       cookie_len,
+                       cookie,
+                       username_len,
+                       username,
+                       domain_len,
+                       domain,
+                       password_len,
+                       password
+               };
+
+               return SendMessage(m_controlWindow, RDPC_WM_REDIRECT, 0, reinterpret_cast<LPARAM>(&redirectArgs)) == 0;
+       }
+
+private:
+       static DWORD WINAPI ProtocolLoopThreadProc(LPVOID lpParam)
+       {
+               static_cast<RdpClient *>(lpParam)->ProtocolLoop();
+               return 0;
+       }
+
+       static VOID CALLBACK ConnectionTimerAPC(LPVOID, DWORD, DWORD)
+       {
+       }
+
+       // FIXME: various potential inconsistencies due to lack of detailed documentation of expected semantics
+       void ProtocolLoop()
+       {
+               HANDLE waitingReconnection = NULL;
+
+               // Retrieve the local hostname to be passed to the server
+               WCHAR hostname[MAX_COMPUTERNAME_LENGTH + 1];
+               DWORD hostnameLen = ARRAYSIZE(hostname);
+
+               if(!GetComputerNameW(hostname, &hostnameLen))
+                       hostname[0] = 0;
+
+               // Set some connection flags
+               uint32 flags = RDP_LOGON_NORMAL;
+
+               if(m_Compress)
+                       flags |= RDP_LOGON_COMPRESSION | RDP_LOGON_COMPRESSION2;
+
+               if(m_AudioRedirectionMode == 1)
+                       flags |= RDP_LOGON_LEAVE_AUDIO;
+
+               if(m_ClearTextPassword)
+                       flags |= RDP_LOGON_AUTO;
+
+               // Notify the container that the connection process is beginning now
+               FireConnecting();
+
+               // Set the overall connection timer, if a timeout is set
+               // BUGBUG: the timeout semantics are ambiguous and have been most probably misinterpreted
+               HANDLE overallConnectionTimer = NULL;
+               LARGE_INTEGER overallTimeout;
+
+               if(m_overallConnectionTimeout)
+               {
+                       overallTimeout.QuadPart = - ((m_overallConnectionTimeout * 1000 * 1000 * 1000) / 100);
+
+                       overallConnectionTimer = CreateWaitableTimer(NULL, FALSE, NULL);
+
+                       if(overallConnectionTimer == NULL)
+                               goto l_Disconnect;
+               }
+
+               if(overallConnectionTimer)
+                       SetWaitableTimer(overallConnectionTimer, &overallTimeout, 0, ConnectionTimerAPC, NULL, FALSE);
+
+               // Initial connection
+               BOOL disconnected = rdp_connect
+               (
+                       &m_protocolState,
+                       m_Server,
+                       flags,
+                       m_UserName,
+                       m_Domain,
+                       m_ClearTextPassword,
+                       m_StartProgram,
+                       m_WorkDir,
+                       hostname,
+                       m_LoadBalanceInfo
+               );
+
+               if(overallConnectionTimer)
+                       CancelWaitableTimer(overallConnectionTimer);
+
+               if(disconnected)
+                       goto l_Disconnect;
+
+               // TODO: set the disconnect reason for every instance in which we abort the loop
+               for(;;)
+               {
+                       BOOL deactivated = False;
+                       uint32 extendedDisconnectReason = 0;
+
+                       m_actuallyConnected = true;
+
+                       // Notify the container of the successful connection
+                       FireConnected();
+
+                       // Main protocol loop
+                       m_loggedIn = false;
+                       rdp_main_loop(&m_protocolState, &deactivated, &extendedDisconnectReason);
+                       rdp_disconnect(&m_protocolState);
+
+                       m_actuallyConnected = false;
+
+                       // Redirection
+                       // BUGBUG: redirection is very messy and probably this implementation is not "canonical"
+                       if(m_protocolState.redirect)
+                       {
+                               m_protocolState.redirect = False;
+                               rdp_reset_state(&m_protocolState);
+
+                               // TODO: reset connection parameters
+                               // This has to be done in the main thread, so use SendMessage on the control window
+
+                               flags |= RDP_LOGON_AUTO;
+
+                               // retry
+                               continue;
+                       }
+
+                       // Disconnection
+                       m_ExtendedDisconnectReason = static_cast<MSTSCLib::ExtendedDisconnectReasonCode>(extendedDisconnectReason);
+
+                       // Clean disconnection
+                       if(deactivated)
+                               break;
+
+                       BOOL success;
+
+                       long autoReconnections = 0;
+                       long totalReconnections = 0;
+
+                       // Reconnection
+                       // BUGBUG: reconnection semantics may not be entirely accurate
+                       do
+                       {
+                               ++ totalReconnections;
+
+                               // ask the container whether we should reconnect
+                               long reconnectMode = FireAutoReconnecting(m_protocolState.disconnect_reason, totalReconnections);
+
+                               // do not reconnect
+                               if(reconnectMode == MSTSCLib::autoReconnectContinueStop)
+                                       goto l_Disconnect;
+
+                               // the container will reconnect or abort manually
+                               if(reconnectMode == MSTSCLib::autoReconnectContinueManual)
+                               {
+                                       assert(!m_reconnectAborted);
+                                       assert(m_protocolThreadWaitingReconnection == NULL);
+
+                                       if(waitingReconnection == NULL)
+                                       {
+                                               waitingReconnection = CreateEvent(NULL, TRUE, FALSE, NULL);
+
+                                               if(waitingReconnection == NULL)
+                                                       // TODO: fatal error
+                                                       goto l_Disconnect;
+                                       }
+
+                                       m_protocolThreadWaitingReconnection = waitingReconnection;
+
+                                       WaitForSingleObject(waitingReconnection, INFINITE);
+
+                                       m_protocolThreadWaitingReconnection = NULL;
+
+                                       if(m_reconnectAborted)
+                                       {
+                                               // FIXME? do we set the disconnection status here?
+                                               goto l_Disconnect;
+                                       }
+                               }
+                               // reconnect automatically
+                               else
+                               {
+                                       // automatic reconnection is disabled
+                                       if(m_EnableAutoReconnect)
+                                               break;
+
+                                       // too many consecutive automatic reconnections
+                                       if(autoReconnections == m_MaxReconnectAttempts)
+                                               break;
+
+                                       ++ autoReconnections;
+                               }
+
+                               if(overallConnectionTimer)
+                                       SetWaitableTimer(overallConnectionTimer, &overallTimeout, 0, ConnectionTimerAPC, NULL, FALSE);
+
+                               // Reconnection
+                               success = rdp_reconnect
+                               (
+                                       &m_protocolState,
+                                       m_Server,
+                                       flags,
+                                       m_UserName,
+                                       m_Domain,
+                                       m_ClearTextPassword,
+                                       m_StartProgram,
+                                       m_WorkDir,
+                                       hostname,
+                                       m_LoadBalanceInfo
+                               );
+
+                               if(overallConnectionTimer)
+                                       CancelWaitableTimer(overallConnectionTimer);
+                       }
+                       while(!success);
+               }
+
+l_Disconnect:
+               // Disconnected
+               FireDisconnected(m_protocolState.disconnect_reason);
+
+               if(overallConnectionTimer)
+                       CloseHandle(overallConnectionTimer);
+       }
+
+       void JoinProtocolThread()
+       {
+               assert(m_protocolThread);
+               WaitForSingleObject(m_protocolThread, INFINITE);
+               CloseHandle(m_protocolThread);
+               m_protocolThread = NULL;
+       }
+
+       void TerminateProtocolThread()
+       {
+               assert(m_protocolThread);
+
+               // wake it up if it's waiting for a manual reconnection
+               if(m_protocolThreadWaitingReconnection)
+               {
+                       assert(!m_reconnectAborted);
+                       m_reconnectAborted = true;
+                       SetEvent(m_protocolThreadWaitingReconnection);
+               }
+               // otherwise, attempt to interrupt any current blocking operation
+               else
+               {
+                       // shutdown(m_protocolState.tcp.sock, SD_BOTH); // TBD: maybe in the future?
+                       QueueUserAPC(DisconnectAPC, m_protocolThread, 0);
+               }
+
+               assert(m_protocolThreadWaitingReconnection == NULL);
+       }
+
+       void PerformDisconnect(long reason)
+       {
+               assert(InsideApartment());
+               assert(m_Connected);
+
+               // TODO: notify virtual channels
+
+               // TODO: do any other disconnection work here...
+
+               // Put the control in the disconnected state
+               m_Connected = false;
+               m_loggedIn = false;
+
+               // Notify the container
+               VARIANTARG arg = { };
+
+               arg.vt = VT_I4;
+               arg.lVal = reason;
+
+               FireEventInsideApartment(4, &arg, 1);
+       }
+
+public:
+       /* Startup initialization */
+       static BOOL Startup()
+       {
+               if(!RdpClientUI::Startup())
+                       return FALSE;
+
+               WNDCLASSEX wcex = { sizeof(wcex) };
+
+               wcex.style = CS_HREDRAW | CS_VREDRAW;
+               wcex.lpfnWndProc = ControlWindowProc;
+               wcex.hInstance = GetCurrentModule();
+               wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+               wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+               wcex.lpszClassName = TEXT("MissTosca_Control");
+
+               return RegisterClassEx(&wcex);
+       }
+
+       static void Shutdown()
+       {
+               UnregisterClass(TEXT("MissTosca_Control"), GetCurrentModule());
+       }
+
+       /* Class factory */
+       static HRESULT CreateInstance(REFCLSID rclsid, unsigned libIndex, IUnknown * punkOuter, REFIID riid, void ** ppObj)
+       {
+               RdpClient * obj = new RdpClient(rclsid, libIndex, punkOuter);
+
+               if(obj == NULL)
+                       return E_OUTOFMEMORY;
+
+               HRESULT hr = obj->m_inner.QueryInterface(riid, ppObj);
+
+               if(FAILED(hr))
+               {
+                       delete obj;
+                       return hr;
+               }
+
+               assert(obj->m_refCount == 1);
+               assert(*ppObj != NULL);
+
+               return S_OK;
+       }
+
+private:
+       /* Connection point enumerator */
+       class CEnumConnectionPoints: public IEnumConnectionPoints
+       {
+       private:
+               LONG m_refCount;
+               IConnectionPoint * m_cp;
+               bool m_done;
+
+       public:
+               CEnumConnectionPoints(IConnectionPoint * cp): m_refCount(1), m_cp(cp), m_done(false)
+               {
+                       assert(m_cp);
+                       m_cp->AddRef();
+               }
+
+               CEnumConnectionPoints(const CEnumConnectionPoints& ecp): m_refCount(1), m_cp(ecp.m_cp), m_done(ecp.m_done)
+               {
+                       assert(m_cp);
+                       m_cp->AddRef();
+               }
+
+               ~CEnumConnectionPoints()
+               {
+                       assert(m_cp);
+                       m_cp->Release();
+               }
+
+               virtual STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObject)
+               {
+                       if(riid == IID_IUnknown || riid == IID_IEnumConnectionPoints)
+                       {
+                               *ppvObject = this;
+                               return S_OK;
+                       }
+                       else
+                       {
+                               *ppvObject = NULL;
+                               return E_NOINTERFACE;
+                       }
+               }
+
+               virtual STDMETHODIMP_(ULONG) AddRef()
+               {
+                       return InterlockedIncrement(&m_refCount);
+               }
+
+               virtual STDMETHODIMP_(ULONG) Release()
+               {
+                       LONG n = InterlockedDecrement(&m_refCount);
+
+                       if(n == 0)
+                               delete this;
+
+                       return n;
+               }
+
+               virtual STDMETHODIMP Next(ULONG cConnections, LPCONNECTIONPOINT * ppCP, ULONG * pcFetched)
+               {
+                       if(cConnections == 0 || m_done)
+                               return S_FALSE;
+
+                       m_done = true;
+                       m_cp->AddRef();
+                       *ppCP = m_cp;
+                       *pcFetched = 1;
+
+                       return S_OK;
+               }
+
+               virtual STDMETHODIMP Skip(ULONG cConnections)
+               {
+                       if(cConnections == 0)
+                               return S_OK;
+
+                       if(cConnections == 1 && !m_done)
+                       {
+                               m_done = true;
+                               return S_OK;
+                       }
+
+                       assert(cConnections > 1 || m_done);
+
+                       return S_FALSE;
+               }
+
+               virtual STDMETHODIMP Reset()
+               {
+                       m_done = false;
+                       return S_OK;
+               }
+
+               virtual STDMETHODIMP Clone(IEnumConnectionPoints ** ppEnum)
+               {
+                       if(ppEnum == NULL)
+                               return E_POINTER;
+
+                       *ppEnum = new CEnumConnectionPoints(*this);
+
+                       if(*ppEnum == NULL)
+                               return E_OUTOFMEMORY;
+
+                       return S_OK;
+               }
+       };
+
+       /* Pay no attention, ActiveX glue... */
+       LRESULT ControlWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
+       {
+               switch(uMsg)
+               {
+               case WM_SIZE:
+                       {
+                               // TODO: resize UI
+                       }
+
+                       return 0;
+
+               case WM_PAINT:
+                       {
+                               LPCWSTR text = NULL;
+
+                               if(!m_Connected)
+                                       text = m_DisconnectedText;
+                               else if(m_actuallyConnected)
+                                       text = m_ConnectedStatusText;
+                               else
+                                       text = m_ConnectingText;
+
+                               RECT clientRect;
+                               GetClientRect(m_controlWindow, &clientRect);
+
+                               PAINTSTRUCT ps;
+                               HDC hdc = BeginPaint(m_controlWindow, &ps);
+
+                               SetBkColor(hdc, GetSysColor(COLOR_WINDOW));
+                               SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
+                               SelectObject(hdc, GetStockObject(SYSTEM_FONT));
+
+                               RECT textRect = clientRect;
+
+                               DrawTextW
+                               (
+                                       hdc,
+                                       text,
+                                       -1,
+                                       &textRect,
+                                       DT_CENTER | DT_EDITCONTROL | DT_END_ELLIPSIS | DT_NOPREFIX | DT_WORDBREAK | DT_CALCRECT
+                               );
+
+                               if(textRect.right > clientRect.right)
+                                       textRect.right = clientRect.right;
+
+                               if(textRect.bottom > clientRect.bottom)
+                                       textRect.bottom = clientRect.bottom;
+
+                               textRect.left = (clientRect.right - textRect.right) / 2;
+                               textRect.right += textRect.left;
+                               textRect.top = (clientRect.bottom - textRect.bottom) / 2;
+                               textRect.bottom += textRect.top;
+
+                               DrawTextW
+                               (
+                                       hdc,
+                                       text,
+                                       -1,
+                                       &textRect,
+                                       DT_CENTER | DT_EDITCONTROL | DT_END_ELLIPSIS | DT_NOPREFIX | DT_WORDBREAK
+                               );
+
+                               EndPaint(m_controlWindow, &ps);
+                       }
+
+                       return 0;
+
+               default:
+                       {
+                               LRESULT result;
+
+                               if(HandleEvent(uMsg, wParam, lParam, result))
+                                       return result;
+                       }
+
+                       break;
+               }
+
+               return DefWindowProc(m_controlWindow, uMsg, wParam, lParam);
+       }
+
+       static LRESULT CALLBACK ControlWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+       {
+               if(uMsg == WM_CREATE)
+               {
+                       SetWindowLongPtr
+                       (
+                               hwnd,
+                               GWLP_USERDATA,
+                               (LONG_PTR)reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams
+                       );
+               }
+
+               RdpClient * Self = reinterpret_cast<RdpClient *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
+               assert(Self);
+
+               return Self->ControlWindowProc(uMsg, wParam, lParam);
+       }
+
+       HRESULT CreateControlWindow(HWND hwndParent)
+       {
+               m_controlWindow = CreateWindow
+               (
+                       TEXT("MissTosca_Control"),
+                       NULL,
+                       WS_CHILD | WS_CLIPCHILDREN,
+                       CW_USEDEFAULT,
+                       CW_USEDEFAULT,
+                       CW_USEDEFAULT,
+                       CW_USEDEFAULT,
+                       hwndParent,
+                       NULL,
+                       GetCurrentModule(),
+                       this
+               );
+
+               if(m_controlWindow == NULL)
+                       return HRESULT_FROM_WIN32(GetLastError());
+
+               m_UIParentWindowHandle = m_controlWindow;
+               return S_OK;
+       }
+
+       HRESULT DestroyControlWindow()
+       {
+               if(m_controlWindow == NULL)
+                       return S_FALSE;
+
+               HWND controlWindow = NULL;
+               std::swap(controlWindow, m_controlWindow);
+               DestroyWindow(controlWindow);
+               return S_OK;
+       }
+
+       HRESULT Activate(LONG iVerb, IOleClientSite * pActiveSite, HWND hwndParent, LPCRECT lprcPosRect)
+       {
+               if(pActiveSite == NULL)
+                       pActiveSite = m_clientSite;
+
+               if(pActiveSite == NULL)
+                       return E_FAIL;
+
+               // TODO: store this until we are closed or deactivated
+               IOleInPlaceSite * site;
+
+               HRESULT hr = pActiveSite->QueryInterface(&site);
+
+               if(FAILED(hr))
+                       return hr;
+
+               IOleInPlaceFrame * frame = NULL;
+               IOleInPlaceUIWindow * uiWindow = NULL;
+
+               for(;;)
+               {
+                       hr = site->CanInPlaceActivate();
+
+                       if(hr == S_FALSE)
+                               hr = E_FAIL;
+
+                       if(FAILED(hr))
+                               break;
+
+                       site->OnInPlaceActivate();
+
+                       if(hwndParent == NULL)
+                       {
+                               hr = site->GetWindow(&hwndParent);
+
+                               if(FAILED(hr))
+                                       break;
+                       }
+
+                       RECT rcPos;
+                       RECT rcClip;
+                       OLEINPLACEFRAMEINFO frameInfo = { sizeof(frameInfo) };
+
+                       site->GetWindowContext(&frame, &uiWindow, &rcPos, &rcClip, &frameInfo);
+
+                       if(lprcPosRect == NULL)
+                               lprcPosRect = &rcPos;
+
+                       if(m_controlWindow)
+                               ShowWindow(m_controlWindow, SW_SHOW);
+                       else
+                       {
+                               hr = CreateControlWindow(hwndParent);
+
+                               if(FAILED(hr))
+                                       break;
+                       }
+
+                       SetObjectRects(lprcPosRect, &rcClip);
+
+                       // UI activation
+                       if((iVerb == OLEIVERB_PRIMARY || iVerb == OLEIVERB_UIACTIVATE) && !m_uiActive)
+                       {
+                               m_uiActive = true;
+
+                               hr = site->OnUIActivate();
+
+                               if(FAILED(hr))
+                                       break;
+
+                               SetWindowPos
+                               (
+                                       m_controlWindow,
+                                       NULL,
+                                       lprcPosRect->left,
+                                       lprcPosRect->top,
+                                       lprcPosRect->right - lprcPosRect->left,
+                                       lprcPosRect->bottom - lprcPosRect->top,
+                                       SWP_SHOWWINDOW
+                               );
+
+                               if(frame)
+                               {
+                                       frame->SetActiveObject(this, NULL);
+                                       frame->SetBorderSpace(NULL);
+                               }
+
+                               if(uiWindow)
+                               {
+                                       uiWindow->SetActiveObject(this, NULL);
+                                       uiWindow->SetBorderSpace(NULL);
+                               }
+                       }
+
+                       break;
+               }
+
+               if(uiWindow)
+                       uiWindow->Release();
+
+               if(frame)
+                       frame->Release();
+
+               site->Release();
+
+               if(SUCCEEDED(hr))
+                       pActiveSite->ShowObject();
+
+               return hr;
+       }
+
+public:
+       /* IUnknown */
+       /*
+               NOTE: this is the delegating implementation, to support aggregation. The actual
+               implementation is RdpClientInner, above
+       */
+       virtual STDMETHODIMP IUnknown::QueryInterface(REFIID riid, void ** ppvObject)
+       {
+               return m_punkOuter->QueryInterface(riid, ppvObject);
+       }
+
+       virtual STDMETHODIMP_(ULONG) IUnknown::AddRef()
+       {
+               return m_punkOuter->AddRef();
+       }
+
+       virtual STDMETHODIMP_(ULONG) IUnknown::Release()
+       {
+               return m_punkOuter->Release();
+       }
+
+       /* IDispatch */
+       virtual STDMETHODIMP IDispatch::GetTypeInfoCount(UINT * pctinfo)
+       {
+               *pctinfo = 1;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IDispatch::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo)
+       {
+               if(iTInfo != 0)
+                       return DISP_E_BADINDEX;
+
+               return AcquireDispTypeInfo(ppTInfo);
+       }
+
+       virtual STDMETHODIMP IDispatch::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId)
+       {
+               HRESULT hr = LoadDispTypeInfo();
+
+               if(FAILED(hr))
+                       return hr;
+
+               return m_dispTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId);
+       }
+
+       virtual STDMETHODIMP IDispatch::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr)
+       {
+               HRESULT hr = LoadDispTypeInfo();
+
+               if(FAILED(hr))
+                       return hr;
+
+               return m_dispTypeInfo->Invoke
+               (
+                       static_cast<MSTSCLib::IMsRdpClient4 *>(this),
+                       dispIdMember,
+                       wFlags,
+                       pDispParams,
+                       pVarResult,
+                       pExcepInfo,
+                       puArgErr
+               );
+       }
+
+       /* IConnectionPoint */
+       virtual STDMETHODIMP GetConnectionInterface(IID * pIID)
+       {
+               if(pIID == NULL)
+                       return E_POINTER;
+
+               *pIID = MSTSCLib::DIID_IMsTscAxEvents;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP GetConnectionPointContainer(IConnectionPointContainer ** ppCPC)
+       {
+               if(ppCPC == NULL)
+                       return E_POINTER;
+
+               addRef();
+               *ppCPC = this;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP Advise(IUnknown * pUnkSink, DWORD * pdwCookie)
+       {
+               MSTSCLib::IMsTscAxEvents * sink;
+
+               if(FAILED(pUnkSink->QueryInterface(&sink)))
+                       return CONNECT_E_CANNOTCONNECT;
+
+               MSTSCLib::IMsTscAxEvents ** sinks = GetSinks();
+               DWORD cookie = 0;
+
+               if(m_EventSinksCount)
+               {
+                       bool found = false;
+
+                       for(size_t i = 0; i < m_EventSinksCount; ++ i)
+                       {
+                               found = (sinks[i] == NULL);
+
+                               if(found)
+                               {
+                                       cookie = static_cast<DWORD>(i);
+                                       break;
+                               }
+                       }
+
+                       if(!found)
+                       {
+                               MSTSCLib::IMsTscAxEvents ** newSinks = new MSTSCLib::IMsTscAxEvents *[m_EventSinksCount + 1];
+
+                               if(newSinks == NULL)
+                               {
+                                       sink->Release();
+                                       return E_OUTOFMEMORY;
+                               }
+
+                               std::copy(sinks, sinks + m_EventSinksCount, newSinks);
+
+                               m_EventSinks = newSinks;
+                               sinks = newSinks;
+
+                               cookie = static_cast<DWORD>(m_EventSinksCount);
+                       }
+               }
+
+               sinks[cookie] = sink;
+               *pdwCookie = cookie;
+
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP Unadvise(DWORD dwCookie)
+       {
+               MSTSCLib::IMsTscAxEvents ** sinks = GetSinks();
+
+               if(dwCookie >= m_EventSinksCount || sinks[dwCookie] == NULL)
+                       return CONNECT_E_NOCONNECTION;
+
+               sinks[dwCookie]->Release();
+               sinks[dwCookie] = NULL;
+
+               // BUGBUG: the array currently grows forever. Trim it whenever possible
+
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP EnumConnections(IEnumConnections ** ppEnum)
+       {
+               // I see no real value in this
+               return E_NOTIMPL;
+       }
+
+       /* IConnectionPointContainer */
+       virtual STDMETHODIMP IConnectionPointContainer::EnumConnectionPoints(IEnumConnectionPoints ** ppEnum)
+       {
+               *ppEnum = new CEnumConnectionPoints(this);
+
+               if(*ppEnum == NULL)
+                       return E_OUTOFMEMORY;
+
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IConnectionPointContainer::FindConnectionPoint(REFIID riid, IConnectionPoint ** ppCP)
+       {
+               if(riid != MSTSCLib::DIID_IMsTscAxEvents)
+                       return CONNECT_E_NOCONNECTION;
+
+               addRef();
+               *ppCP = this;
+
+               return S_OK;
+       }
+
+       /* IDataObject */ // 0/9
+       virtual STDMETHODIMP IDataObject::GetData(FORMATETC * pformatetcIn, STGMEDIUM * pmedium)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::GetDataHere(FORMATETC * pformatetc, STGMEDIUM * pmedium)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::QueryGetData(FORMATETC * pformatetc)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::GetCanonicalFormatEtc(FORMATETC * pformatectIn, FORMATETC * pformatetcOut)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::SetData(FORMATETC * pformatetc, STGMEDIUM * pmedium, BOOL fRelease)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC ** ppenumFormatEtc)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::DAdvise(FORMATETC * pformatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD * pdwConnection)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::DUnadvise(DWORD dwConnection)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IDataObject::EnumDAdvise(IEnumSTATDATA ** ppenumAdvise)
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IObjectSafety */
+       virtual STDMETHODIMP IObjectSafety::GetInterfaceSafetyOptions(REFIID riid, DWORD * pdwSupportedOptions, DWORD * pdwEnabledOptions)
+       {
+               if(pdwSupportedOptions == NULL || pdwEnabledOptions == NULL)
+                       return E_POINTER;
+
+               if(riid != IID_IDispatch)
+                       return E_NOINTERFACE;
+
+               *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER;
+               *pdwEnabledOptions = m_SafetyOptions;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IObjectSafety::SetInterfaceSafetyOptions(REFIID riid, DWORD dwOptionSetMask, DWORD dwEnabledOptions)
+       {
+               if(riid != IID_IDispatch)
+                       return E_NOINTERFACE;
+
+               m_SafetyOptions = dwEnabledOptions & (dwOptionSetMask & INTERFACESAFE_FOR_UNTRUSTED_CALLER);
+               return S_OK;
+       }
+
+       /* IOleControl */ // 3/4
+       virtual STDMETHODIMP IOleControl::GetControlInfo(CONTROLINFO * pCI)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleControl::OnMnemonic(MSG * pMsg)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleControl::OnAmbientPropertyChange(DISPID dispID)
+       {
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleControl::FreezeEvents(BOOL bFreeze)
+       {
+               if(bFreeze)
+                       InterlockedIncrement(&m_freezeEvents);
+               else if(InterlockedDecrement(&m_freezeEvents) == 0)
+                       UnfreezeEvents();
+
+               return S_OK;
+       }
+
+       /* IOleInPlaceActiveObject */ // 3/5
+       virtual STDMETHODIMP IOleInPlaceActiveObject::TranslateAccelerator(LPMSG lpmsg)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceActiveObject::OnFrameWindowActivate(BOOL fActivate)
+       {
+               // TODO
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceActiveObject::OnDocWindowActivate(BOOL fActivate)
+       {
+               // TODO
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceActiveObject::ResizeBorder(LPCRECT prcBorder, IOleInPlaceUIWindow * pUIWindow, BOOL fFrameWindow)
+       {
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceActiveObject::EnableModeless(BOOL fEnable)
+       {
+               return S_OK;
+       }
+
+       /* IOleInPlaceObject */ // 1/4
+       virtual STDMETHODIMP IOleInPlaceObject::InPlaceDeactivate()
+       {
+               // TODO: UIDeactivate, destroy window, inplacesite->OnInPlaceDeactivate
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceObject::UIDeactivate()
+       {
+               // TODO
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceObject::SetObjectRects(LPCRECT lprcPosRect, LPCRECT lprcClipRect)
+       {
+               if(m_controlWindow == NULL)
+                       return E_FAIL;
+
+               MoveWindow
+               (
+                       m_controlWindow,
+                       lprcPosRect->left,
+                       lprcPosRect->top,
+                       lprcPosRect->right - lprcPosRect->left,
+                       lprcPosRect->bottom - lprcPosRect->top,
+                       TRUE
+               );
+
+               SetWindowRgn(m_controlWindow, CreateRectRgnIndirect(lprcClipRect), TRUE);
+
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleInPlaceObject::ReactivateAndUndo()
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IOleObject */ // 18/21
+       virtual STDMETHODIMP IOleObject::SetClientSite(IOleClientSite * pClientSite)
+       {
+               if(m_clientSite)
+                       m_clientSite->Release();
+
+               m_clientSite = pClientSite;
+
+               if(m_clientSite)
+                       m_clientSite->AddRef();
+
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleObject::GetClientSite(IOleClientSite ** ppClientSite)
+       {
+               if(ppClientSite == NULL)
+                       return E_POINTER;
+
+               if(m_clientSite)
+                       m_clientSite->AddRef();
+
+               *ppClientSite = m_clientSite;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleObject::SetHostNames(LPCOLESTR szContainerApp, LPCOLESTR szContainerObj)
+       {
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleObject::Close(DWORD dwSaveOption)
+       {
+               // TODO: deactivate, destroy window, release in-place site, release advise sink
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IOleObject::SetMoniker(DWORD dwWhichMoniker, IMoniker * pmk)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleObject::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker ** ppmk)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleObject::InitFromData(IDataObject * pDataObject, BOOL fCreation, DWORD dwReserved)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleObject::GetClipboardData(DWORD dwReserved, IDataObject ** ppDataObject)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleObject::DoVerb(LONG iVerb, LPMSG lpmsg, IOleClientSite * pActiveSite, LONG lindex, HWND hwndParent, LPCRECT lprcPosRect)
+       {
+               HRESULT hr;
+
+               switch(iVerb)
+               {
+               case OLEIVERB_PRIMARY:
+               case OLEIVERB_SHOW:
+               case OLEIVERB_UIACTIVATE:
+               case OLEIVERB_INPLACEACTIVATE:
+                       hr = S_OK;
+                       break;
+
+               default:
+                       if(iVerb > 0)
+                               hr = OLEOBJ_S_INVALIDVERB;
+                       else
+                               hr = E_NOTIMPL;
+               }
+
+               if(FAILED(hr))
+                       return hr;
+
+               HRESULT hrActivate = Activate(iVerb, pActiveSite, hwndParent, lprcPosRect);
+
+               if(FAILED(hrActivate))
+                       hr = hrActivate;
+
+               return hr;
+       }
+
+       virtual STDMETHODIMP IOleObject::EnumVerbs(IEnumOLEVERB ** ppEnumOleVerb)
+       {
+               return OleRegEnumVerbs(m_classId, ppEnumOleVerb);
+       }
+
+       virtual STDMETHODIMP IOleObject::Update()
+       {
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleObject::IsUpToDate()
+       {
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleObject::GetUserClassID(CLSID * pClsid)
+       {
+               *pClsid = m_classId;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleObject::GetUserType(DWORD dwFormOfType, LPOLESTR * pszUserType)
+       {
+               return OleRegGetUserType(m_classId, dwFormOfType, pszUserType);
+       }
+
+       virtual STDMETHODIMP IOleObject::SetExtent(DWORD dwDrawAspect, SIZEL * psizel)
+       {
+               // TODO: resize
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IOleObject::GetExtent(DWORD dwDrawAspect, SIZEL * psizel)
+       {
+               // TODO: return size
+               return E_NOTIMPL;
+       }
+
+       HRESULT NeedAdviseHolder()
+       {
+               if(m_adviseHolder)
+                       return S_OK;
+
+               return CreateOleAdviseHolder(&m_adviseHolder);
+       }
+
+       virtual STDMETHODIMP IOleObject::Advise(IAdviseSink * pAdvSink, DWORD * pdwConnection)
+       {
+               HRESULT hr = NeedAdviseHolder();
+
+               if(FAILED(hr))
+                       return hr;
+
+               return m_adviseHolder->Advise(pAdvSink, pdwConnection);
+       }
+
+       virtual STDMETHODIMP IOleObject::Unadvise(DWORD dwConnection)
+       {
+               HRESULT hr = NeedAdviseHolder();
+
+               if(FAILED(hr))
+                       return hr;
+
+               return m_adviseHolder->Unadvise(dwConnection);
+       }
+
+       virtual STDMETHODIMP IOleObject::EnumAdvise(IEnumSTATDATA ** ppenumAdvise)
+       {
+               HRESULT hr = NeedAdviseHolder();
+
+               if(FAILED(hr))
+                       return hr;
+
+               return m_adviseHolder->EnumAdvise(ppenumAdvise);
+       }
+
+       virtual STDMETHODIMP IOleObject::GetMiscStatus(DWORD dwAspect, DWORD * pdwStatus)
+       {
+               return OleRegGetMiscStatus(m_classId, dwAspect, pdwStatus);
+       }
+
+       virtual STDMETHODIMP IOleObject::SetColorScheme(LOGPALETTE * pLogpal)
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IOleWindow */
+       virtual STDMETHODIMP IOleWindow::GetWindow(HWND * phwnd)
+       {
+               if(phwnd == NULL)
+                       return E_POINTER;
+
+               if(m_controlWindow == NULL)
+                       return E_FAIL;
+
+               *phwnd = m_controlWindow;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IOleWindow::ContextSensitiveHelp(BOOL fEnterMode)
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IPersist */
+       virtual STDMETHODIMP IPersist::GetClassID(CLSID * pClassID)
+       {
+               *pClassID = m_classId;
+               return S_OK;
+       }
+
+       /* IPersistPropertyBag */ // 0/3
+       virtual STDMETHODIMP IPersistPropertyBag::InitNew()
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistPropertyBag::Load(IPropertyBag * pPropBag, IErrorLog * pErrorLog)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistPropertyBag::Save(IPropertyBag * pPropBag, BOOL fClearDirty, BOOL fSaveAllProperties)
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IPersistStorage */ // 0/6
+       virtual STDMETHODIMP IPersistStorage::IsDirty()
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStorage::InitNew(IStorage * pStg)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStorage::Load(IStorage * pStg)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStorage::Save(IStorage * pStgSave, BOOL fSameAsLoad)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStorage::SaveCompleted(IStorage * pStgNew)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStorage::HandsOffStorage()
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IPersistStreamInit */ // 0/5
+       virtual STDMETHODIMP IPersistStreamInit::IsDirty()
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStreamInit::Load(LPSTREAM pStm)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStreamInit::Save(LPSTREAM pStm, BOOL fClearDirty)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStreamInit::GetSizeMax(ULARGE_INTEGER * pCbSize)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IPersistStreamInit::InitNew()
+       {
+               return E_NOTIMPL;
+       }
+
+       /* IProvideClassInfo */
+       virtual STDMETHODIMP IProvideClassInfo::GetClassInfo(ITypeInfo ** ppTI)
+       {
+               HRESULT hr = LoadTypeLibrary();
+
+               if(FAILED(hr))
+                       return hr;
+
+               return m_typeLib->GetTypeInfoOfGuid(m_classId, ppTI);
+       }
+
+       /* IProvideClassInfo2 */
+       virtual STDMETHODIMP IProvideClassInfo2::GetGUID(DWORD dwGuidKind, GUID * pGUID)
+       {
+               if(dwGuidKind != GUIDKIND_DEFAULT_SOURCE_DISP_IID)
+                       return E_INVALIDARG;
+
+               *pGUID = MSTSCLib::DIID_IMsTscAxEvents;
+               return S_OK;
+       }
+
+       /* IQuickActivate */
+       virtual STDMETHODIMP IQuickActivate::QuickActivate(QACONTAINER * pQaContainer, QACONTROL * pQaControl)
+       {
+               if(pQaContainer == NULL || pQaControl == NULL)
+                       return E_POINTER;
+
+               if(pQaContainer->cbSize < sizeof(*pQaContainer) || pQaControl->cbSize < sizeof(*pQaControl))
+                       return E_INVALIDARG;
+
+               ULONG cb = pQaControl->cbSize;
+               ZeroMemory(pQaControl, cb);
+               pQaControl->cbSize = cb;
+
+               SetClientSite(pQaContainer->pClientSite);
+
+               if(pQaContainer->pAdviseSink)
+                       SetAdvise(DVASPECT_CONTENT, 0, pQaContainer->pAdviseSink);
+
+               if(pQaContainer->pUnkEventSink)
+                       Advise(pQaContainer->pUnkEventSink, &pQaControl->dwEventCookie);
+
+               GetMiscStatus(DVASPECT_CONTENT, &pQaControl->dwMiscStatus);
+
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IQuickActivate::SetContentExtent(LPSIZEL pSizel)
+       {
+               return SetExtent(DVASPECT_CONTENT, pSizel);
+       }
+
+       virtual STDMETHODIMP IQuickActivate::GetContentExtent(LPSIZEL pSizel)
+       {
+               return GetExtent(DVASPECT_CONTENT, pSizel);
+       }
+
+       /* IViewObject */ // 3/6
+       virtual STDMETHODIMP IViewObject::Draw(DWORD dwDrawAspect, LONG lindex, void * pvAspect, DVTARGETDEVICE * ptd, HDC hdcTargetDev, HDC hdcDraw, LPCRECTL lprcBounds, LPCRECTL lprcWBounds, BOOL (STDMETHODCALLTYPE * pfnContinue)(ULONG_PTR dwContinue), ULONG_PTR dwContinue)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IViewObject::GetColorSet(DWORD dwDrawAspect, LONG lindex, void * pvAspect, DVTARGETDEVICE * ptd, HDC hicTargetDev, LOGPALETTE ** ppColorSet)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IViewObject::Freeze(DWORD dwDrawAspect, LONG lindex, void * pvAspect, DWORD * pdwFreeze)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IViewObject::Unfreeze(DWORD dwFreeze)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IViewObject::SetAdvise(DWORD aspects, DWORD advf, IAdviseSink * pAdvSink)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IViewObject::GetAdvise(DWORD * pAspects, DWORD * pAdvf, IAdviseSink ** ppAdvSink)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       /* IViewObject2 */ // 0/1
+       virtual STDMETHODIMP IViewObject2::GetExtent(DWORD dwDrawAspect, LONG lindex, DVTARGETDEVICE * ptd, LPSIZEL lpsizel)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       /* IMsTscAx */ // 23/30
+       virtual STDMETHODIMP IMsTscAx::put_Server(BSTR pServer)
+       {
+               // FIXME: convert the hostname to Punycode, not the ANSI codepage
+               return SetProperty(m_Server, pServer);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_Server(BSTR * pServer) const
+       {
+               return GetProperty(m_Server, pServer);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_Domain(BSTR pDomain)
+       {
+               return SetProperty(m_Domain, pDomain);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_Domain(BSTR * pDomain) const
+       {
+               return GetProperty(m_Domain, pDomain);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_UserName(BSTR pUserName)
+       {
+               return SetProperty(m_UserName, pUserName);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_UserName(BSTR * pUserName) const
+       {
+               return GetProperty(m_UserName, pUserName);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_DisconnectedText(BSTR pDisconnectedText)
+       {
+               return SetProperty(m_DisconnectedText, pDisconnectedText);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_DisconnectedText(BSTR * pDisconnectedText) const
+       {
+               return GetProperty(m_DisconnectedText, pDisconnectedText);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_ConnectingText(BSTR pConnectingText)
+       {
+               return SetProperty(m_ConnectingText, pConnectingText);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_ConnectingText(BSTR * pConnectingText) const
+       {
+               return GetProperty(m_ConnectingText, pConnectingText);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_Connected(short * pIsConnected) const
+       {
+               return GetProperty(m_Connected, pIsConnected);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_DesktopWidth(long pVal)
+       {
+               if(pVal < 200 || pVal > 1600)
+                       return E_INVALIDARG;
+
+               return SetProperty(m_DesktopWidth, pVal);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_DesktopWidth(long * pVal) const
+       {
+               return GetProperty(m_DesktopWidth, pVal);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_DesktopHeight(long pVal)
+       {
+               if(pVal < 200 || pVal > 1200)
+                       return E_INVALIDARG;
+
+               return SetProperty(m_DesktopHeight, pVal);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_DesktopHeight(long * pVal) const
+       {
+               return GetProperty(m_DesktopHeight, pVal);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_StartConnected(long pfStartConnected)
+       {
+               return SetProperty(m_StartConnected, pfStartConnected);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_StartConnected(long * pfStartConnected) const
+       {
+               return GetProperty(m_StartConnected, pfStartConnected);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_HorizontalScrollBarVisible(long * pfHScrollVisible) const
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_VerticalScrollBarVisible(long * pfVScrollVisible) const
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IMsTscAx::put_FullScreenTitle(BSTR rhs)
+       {
+               // TODO
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_CipherStrength(long * pCipherStrength) const
+       {
+               if(pCipherStrength == NULL)
+                       return E_INVALIDARG;
+
+               *pCipherStrength = 128; // BUGBUG: a later version may change this. Use a compile-time constant
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_Version(BSTR * pVersion) const
+       {
+               if(pVersion == NULL)
+                       return E_INVALIDARG;
+
+               BSTR version = SysAllocString(L"5.2.3790.1830"); // BUGBUG: don't use hardcoded string
+
+               if(version == NULL)
+                       return E_OUTOFMEMORY;
+
+               *pVersion = version;
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_SecuredSettingsEnabled(long * pSecuredSettingsEnabled) const
+       {
+               // TODO: initialize m_SecuredSettingsEnabled as soon as we have an OLE client site
+               return GetProperty(m_SecuredSettingsEnabled, pSecuredSettingsEnabled);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_SecuredSettings(MSTSCLib::IMsTscSecuredSettings ** ppSecuredSettings) const
+       {
+               return GetSecuredSettings(ppSecuredSettings);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_AdvancedSettings(MSTSCLib::IMsTscAdvancedSettings ** ppAdvSettings) const
+       {
+               return GetAdvancedSettings(ppAdvSettings);
+       }
+
+       virtual STDMETHODIMP IMsTscAx::get_Debugger(MSTSCLib::IMsTscDebug ** ppDebugger) const
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscAx::Connect()
+       {
+               if(m_Connected)
+               {
+                       // Protocol thread waiting for a manual reconnection: wake it up
+                       if(m_protocolThreadWaitingReconnection)
+                       {
+                               SetEvent(m_protocolThreadWaitingReconnection);
+                               return S_OK;
+                       }
+
+                       return E_FAIL;
+               }
+
+               m_Connected = true;
+
+               HRESULT hr;
+
+               if(m_controlWindow == NULL)
+               {
+                       hr = CreateControlWindow(NULL);
+
+                       if(FAILED(hr))
+                               return hr;
+               }
+
+               for(;;)
+               {
+                       // TODO: initialize plugin DLLs/channels
+
+                       m_clientUI = new RdpClientUI();
+
+                       if(m_clientUI == NULL)
+                       {
+                               hr = E_OUTOFMEMORY;
+                               break;
+                       }
+
+                       m_clientUI->Initialize(m_controlWindow);
+
+                       m_protocolState.licence_username = BstrToLpsz(m_UserName);
+
+                       if(m_protocolState.licence_username == NULL)
+                       {
+                               hr = E_OUTOFMEMORY;
+                               break;
+                       }
+
+                       DWORD dwSize = ARRAYSIZE(m_protocolState.licence_hostname);
+
+                       if(!GetComputerNameA(m_protocolState.licence_hostname, &dwSize))
+                       {
+                               hr = HRESULT_FROM_WIN32(GetLastError());
+                               break;
+                       }
+
+                       // Keyboard layout
+                       // BUGBUG: not too sure about the semantics
+                       long keyboardLayout = -1;
+                       WCHAR * endPtr = NULL;
+
+                       if(m_KeyboardLayoutString)
+                               keyboardLayout = wcstol(m_KeyboardLayoutString, &endPtr, 0);
+
+                       // no keyboard layout specified or invalid keyboard layout: use current keyboard layout
+                       if(endPtr == NULL || *endPtr == 0 || keyboardLayout == -1)
+                               keyboardLayout = PtrToLong(GetKeyboardLayout(0)); // FIXME? use LOWORD()?
+
+                       m_protocolState.keylayout = keyboardLayout;
+
+                       // in case of failure, assume English (US)
+                       if(m_protocolState.keylayout == 0)
+                               m_protocolState.keylayout = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
+
+                       // Physical keyboard information
+                       m_protocolState.keyboard_type = GetKeyboardType(0);
+                       m_protocolState.keyboard_subtype = GetKeyboardType(1);
+                       m_protocolState.keyboard_functionkeys = GetKeyboardType(2);
+
+                       // in case of failure, assume an IBM Enhanced keyboard with 12 function keys
+                       if(m_protocolState.keyboard_type == 0 || m_protocolState.keyboard_functionkeys == 0)
+                       {
+                               m_protocolState.keyboard_type = 4;
+                               m_protocolState.keyboard_subtype = 0;
+                               m_protocolState.keyboard_functionkeys = 12;
+                       }
+
+                       // More initialization
+                       m_protocolState.width = m_DesktopWidth;
+                       m_protocolState.height = m_DesktopHeight;
+                       m_protocolState.server_depth = m_ColorDepth;
+                       m_protocolState.bitmap_compression = m_Compress ? TRUE : FALSE;
+                       m_protocolState.bitmap_cache = True; // TODO
+                       m_protocolState.bitmap_cache_persist_enable = False; // TODO
+                       m_protocolState.bitmap_cache_precache = True; // FIXME?
+                       m_protocolState.encryption = m_EncryptionEnabled ? TRUE : FALSE; // TBD: detect automatically
+                       m_protocolState.packet_encryption = m_EncryptionEnabled ? TRUE : FALSE;
+                       m_protocolState.desktop_save = True; // FIXME? tie to bitmap cache setting?
+                       m_protocolState.polygon_ellipse_orders = True;
+                       m_protocolState.use_rdp5 = True; // TBD: detect automatically
+                       m_protocolState.console_session = m_ConnectToServerConsole ? TRUE : FALSE;
+                       m_protocolState.rdp5_performanceflags = m_PerformanceFlags;
+                       m_protocolState.tcp_port_rdp = m_RDPPort;
+                       m_protocolState.rdp.current_status = 1;
+
+                       // TODO: cache tuning based on the provided parameters
+
+                       m_protocolState.cache.bmpcache_lru[0] = -1;
+                       m_protocolState.cache.bmpcache_lru[1] = -1;
+                       m_protocolState.cache.bmpcache_lru[2] = -1;
+                       m_protocolState.cache.bmpcache_mru[0] = -1;
+                       m_protocolState.cache.bmpcache_mru[1] = -1;
+                       m_protocolState.cache.bmpcache_mru[2] = -1;
+
+                       DWORD dwIgnore;
+                       m_protocolThread = CreateThread(NULL, 0, ProtocolLoopThreadProc, this, 0, &dwIgnore);
+
+                       hr = S_OK;
+                       break;
+               }
+
+               if(FAILED(hr))
+                       m_Connected = false;
+
+               return hr;
+       }
+
+       virtual STDMETHODIMP IMsTscAx::Disconnect()
+       {
+               if(!m_Connected)
+                       return E_FAIL;
+
+               // Terminate the protocol thread. On exit, it will fire the Disconnected event
+               TerminateProtocolThread();
+               return S_OK;
+       }
+
+       virtual STDMETHODIMP IMsTscAx::CreateVirtualChannels(BSTR newVal)
+       {
+               UINT strLength = SysStringLen(newVal);
+
+               if(strLength < 1 || strLength > 300)
+                       return E_INVALIDARG;
+
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IMsTscAx::SendOnVirtualChannel(BSTR chanName, BSTR ChanData)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       /* IMsRdpClient */ // 6/10
+       virtual STDMETHODIMP IMsRdpClient::put_ColorDepth(long pcolorDepth)
+       {
+               switch(pcolorDepth)
+               {
+               case 8:
+               case 15:
+               case 16:
+               case 24:
+               case 32:
+                       break;
+
+               default:
+                       return E_INVALIDARG;
+               }
+
+               return SetProperty(m_ColorDepth, pcolorDepth);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::get_ColorDepth(long * pcolorDepth) const
+       {
+               return GetProperty(m_ColorDepth, pcolorDepth);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::get_AdvancedSettings2(MSTSCLib::IMsRdpClientAdvancedSettings ** ppAdvSettings) const
+       {
+               return GetAdvancedSettings(ppAdvSettings);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::get_SecuredSettings2(MSTSCLib::IMsRdpClientSecuredSettings ** ppSecuredSettings) const
+       {
+               return GetSecuredSettings(ppSecuredSettings);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::get_ExtendedDisconnectReason(MSTSCLib::ExtendedDisconnectReasonCode * pExtendedDisconnectReason) const
+       {
+               return GetProperty(m_ExtendedDisconnectReason, pExtendedDisconnectReason);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::put_FullScreen(VARIANT_BOOL pfFullScreen)
+       {
+               if(!m_Connected)
+                       return E_FAIL;
+
+               if(pfFullScreen && !m_SecuredSettingsEnabled)
+                       return E_FAIL;
+
+               // TODO
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::get_FullScreen(VARIANT_BOOL * pfFullScreen) const
+       {
+               return GetProperty(m_FullScreen, pfFullScreen);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::SetVirtualChannelOptions(BSTR chanName, long chanOptions)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::GetVirtualChannelOptions(BSTR chanName, long * pChanOptions)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IMsRdpClient::RequestClose(MSTSCLib::ControlCloseStatus * pCloseStatus)
+       {
+               if(pCloseStatus == NULL)
+                       return E_POINTER;
+
+               if(!m_Connected)
+               {
+                       *pCloseStatus = MSTSCLib::controlCloseCanProceed;
+                       return S_OK;
+               }
+
+               *pCloseStatus = MSTSCLib::controlCloseWaitForEvents;
+
+               if(!PostMessage(m_controlWindow, RDPC_WM_REQUEST_CLOSE, 0, 0))
+                       return HRESULT_FROM_WIN32(GetLastError());
+
+               return S_OK;
+       }
+
+       /* IMsRdpClient2 */
+       virtual STDMETHODIMP IMsRdpClient2::get_AdvancedSettings3(MSTSCLib::IMsRdpClientAdvancedSettings2 ** ppAdvSettings) const
+       {
+               return GetAdvancedSettings(ppAdvSettings);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient2::put_ConnectedStatusText(BSTR pConnectedStatusText)
+       {
+               return SetProperty(m_ConnectedStatusText, pConnectedStatusText);
+       }
+
+       virtual STDMETHODIMP IMsRdpClient2::get_ConnectedStatusText(BSTR * pConnectedStatusText) const
+       {
+               return GetProperty(m_ConnectedStatusText, pConnectedStatusText);
+       }
+
+       /* IMsRdpClient3 */
+       virtual STDMETHODIMP IMsRdpClient3::get_AdvancedSettings4(MSTSCLib::IMsRdpClientAdvancedSettings3 ** ppAdvSettings) const
+       {
+               return GetAdvancedSettings(ppAdvSettings);
+       }
+
+       /* IMsRdpClient4 */
+    virtual STDMETHODIMP IMsRdpClient4::get_AdvancedSettings5(MSTSCLib::IMsRdpClientAdvancedSettings4 ** ppAdvSettings5) const
+       {
+               return GetAdvancedSettings(ppAdvSettings5);
+       }
+
+       /* IMsTscNonScriptable */
+       virtual STDMETHODIMP IMsTscNonScriptable::put_ClearTextPassword(BSTR rhs)
+       {
+               return SetProperty(m_ClearTextPassword, rhs);
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::put_PortablePassword(BSTR pPortablePass)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::get_PortablePassword(BSTR * pPortablePass) const
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::put_PortableSalt(BSTR pPortableSalt)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::get_PortableSalt(BSTR * pPortableSalt) const
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::put_BinaryPassword(BSTR pBinaryPassword)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::get_BinaryPassword(BSTR * pBinaryPassword) const
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::put_BinarySalt(BSTR pSalt)
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::get_BinarySalt(BSTR * pSalt) const
+       {
+               return E_NOTIMPL;
+       }
+
+       virtual STDMETHODIMP IMsTscNonScriptable::ResetPassword()
+       {
+               return SetProperty(m_ClearTextPassword, NULL);
+       }
+
+       /* IMsRdpClientNonScriptable */ // 0/2
+       virtual STDMETHODIMP IMsRdpClientNonScriptable::NotifyRedirectDeviceChange(MSTSCLib::UINT_PTR wParam, MSTSCLib::LONG_PTR lParam)
+       {
+               return E_NOTIMPL; // TODO
+       }
+
+       virtual STDMETHODIMP IMsRdpClientNonScriptable::SendKeys(long numKeys, VARIANT_BOOL * pbArrayKeyUp, long * plKeyData)
+       {
+               // NOTE: the keys must be sent in a single, atomic sequence
+               // TODO: acquire the write lock
+               return E_NOTIMPL; // TODO
+       }
+
+       /* IMsRdpClientNonScriptable2 */
+       virtual STDMETHODIMP IMsRdpClientNonScriptable2::put_UIParentWindowHandle(HWND phwndUIParentWindowHandle)
+       {
+               return SetProperty(m_UIParentWindowHandle, phwndUIParentWindowHandle);
+       }
+
+       virtual STDMETHODIMP IMsRdpClientNonScriptable2::get_UIParentWindowHandle(HWND * phwndUIParentWindowHandle) const
+       {
+               return GetProperty(m_UIParentWindowHandle, phwndUIParentWindowHandle);
+       }
+};
+
+#pragma warning(pop)
+
+/* More glue to interface to the rdesktop code */
+extern "C"
+{
+
+/* Orders */
+/* support routines */
+void ui_begin_update(RDPCLIENT * This)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_BeginUpdate();
+}
+
+void ui_end_update(RDPCLIENT * This)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_EndUpdate();
+}
+
+void ui_set_clip(RDPCLIENT * This, int x, int y, int cx, int cy)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_SetClip(x, y, cx, cy);
+}
+
+void ui_reset_clip(RDPCLIENT * This)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_ResetClip();
+}
+
+/* blits */
+void ui_destblt(RDPCLIENT * This, uint8 opcode, int x, int y, int cx, int cy)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_DestBlt(opcode, x, y, cx, cy);
+}
+
+void ui_memblt(RDPCLIENT * This, uint8 opcode, int x, int y, int cx, int cy, HBITMAP src, int srcx, int srcy)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_MemBlt(opcode, x, y, cx, cy, src, srcx, srcy);
+}
+
+void ui_patblt(RDPCLIENT * This, uint8 opcode, int x, int y, int cx, int cy, BRUSH * brush, int bgcolour, int fgcolour)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_PatBlt(opcode, x, y, cx, cy, brush, bgcolour, fgcolour);
+}
+
+void ui_screenblt(RDPCLIENT * This, uint8 opcode, int x, int y, int cx, int cy, int srcx, int srcy)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_ScreenBlt(opcode, x, y, cx, cy, srcx, srcy);
+}
+
+void ui_triblt(RDPCLIENT * This, uint8 opcode, int x, int y, int cx, int cy, HBITMAP src, int srcx, int srcy, BRUSH * brush, int bgcolour, int fgcolour)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_TriBlt(opcode, x, y, cx, cy, src, srcx, srcy, brush, bgcolour, fgcolour);
+}
+
+void ui_paint_bitmap(RDPCLIENT * This, int x, int y, int cx, int cy, int width, int height, uint8 * data)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_PaintBitmap(x, y, cx, cy, width, height, data);
+}
+
+/* shapes */
+void ui_ellipse(RDPCLIENT * This, uint8 opcode, uint8 fillmode, int x, int y, int cx, int cy, BRUSH * brush, int bgcolour, int fgcolour)
+{
+       // TODO
+//     RdpClient::InnerToOuter(This)->GetUI()->Display_Ellipse(opcode, fillmode, x, y, cx, cy, brush, bgcolour, fgcolour);
+}
+
+void ui_line(RDPCLIENT * This, uint8 opcode, int startx, int starty, int endx, int endy, PEN * pen)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_Line(opcode, startx, starty, endx, endy, pen);
+}
+
+void ui_polygon(RDPCLIENT * This, uint8 opcode, uint8 fillmode, POINT * point, int npoints, BRUSH * brush, int bgcolour, int fgcolour)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_Polygon(opcode, fillmode, point, npoints, brush, bgcolour, fgcolour);
+}
+
+void ui_polyline(RDPCLIENT * This, uint8 opcode, POINT * points, int npoints, PEN * pen)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_Polyline(opcode, points, npoints, pen);
+}
+
+void ui_rect(RDPCLIENT * This, int x, int y, int cx, int cy, int colour)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_Rect(x, y, cx, cy, colour);
+}
+
+/* text */
+void ui_draw_text
+(
+       RDPCLIENT * This,
+       uint8 font,
+       uint8 flags,
+       uint8 opcode,
+       int mixmode,
+       int x,
+       int y,
+       int clipx,
+       int clipy,
+       int clipcx,
+       int clipcy,
+       int boxx,
+       int boxy,
+       int boxcx,
+       int boxcy,
+       BRUSH * brush,
+       int bgcolour,
+       int fgcolour,
+       uint8 * text,
+       uint8 length
+)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_DrawText
+       (
+               font,
+               flags,
+               opcode,
+               mixmode,
+               x,
+               y,
+               clipx,
+               clipy,
+               clipcx,
+               clipcy,
+               boxx,
+               boxy,
+               boxcx,
+               boxcy,
+               brush,
+               bgcolour,
+               fgcolour,
+               text,
+               length
+       );
+}
+
+/* desktop save/restore */
+void ui_desktop_save(RDPCLIENT * This, uint32 offset, int x, int y, int cx, int cy)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_SaveDesktop(offset, x, y, cx, cy);
+}
+
+void ui_desktop_restore(RDPCLIENT * This, uint32 offset, int x, int y, int cx, int cy)
+{
+       RdpClient::InnerToOuter(This)->GetUI()->Display_RestoreDesktop(offset, x, y, cx, cy);
+}
+
+/* Resources */
+/* bitmaps */
+HBITMAP ui_create_bitmap(RDPCLIENT * This, int width, int height, uint8 * data)
+{
+       return win32_create_dib(width, height, This->server_depth, data);
+}
+
+void ui_destroy_bitmap(RDPCLIENT *, HBITMAP bmp)
+{
+       DeleteObject(bmp);
+}
+
+/* palettes */
+HCOLOURMAP ui_create_colourmap(RDPCLIENT *, COLOURMAP * colours)
+{
+       return NULL;
+}
+
+void ui_set_colourmap(RDPCLIENT * This, HCOLOURMAP map)
+{
+       // TODO
+}
+
+/* cursors */
+HCURSOR ui_create_cursor(RDPCLIENT * This, unsigned int x, unsigned int y, int width, int height, uint8 * andmask, uint8 * xormask)
+{
+       uint8 * andbuf = NULL;
+       uint8 * xorbuf = NULL;
+
+       uint8 * andbits = win32_convert_scanlines(width, - height, 1, 2, 4, andmask, &andbuf);
+       uint8 * xorbits = win32_convert_scanlines(width, height, 24, 2, 4, xormask, &xorbuf);
+
+       HBITMAP hbmMask = CreateBitmap(width, height, 1, 1, andbits);
+       HBITMAP hbmColor = win32_create_dib(width, height, 24, xorbits);
+
+       ICONINFO iconinfo;
+       iconinfo.fIcon = FALSE;
+       iconinfo.xHotspot = x;
+       iconinfo.yHotspot = y;
+       iconinfo.hbmMask = hbmMask;
+       iconinfo.hbmColor = hbmColor;
+
+       HICON icon = CreateIconIndirect(&iconinfo);
+
+       if(icon == NULL)
+               error("CreateIconIndirect %dx%d failed\n", width, height);
+
+       if(andbuf)
+               delete[] andbuf;
+
+       if(xorbuf)
+               delete[] xorbuf;
+
+       DeleteObject(hbmMask);
+       DeleteObject(hbmColor);
+
+       return icon;
+}
+
+void ui_destroy_cursor(RDPCLIENT *, HCURSOR cursor)
+{
+       DestroyIcon(cursor);
+}
+
+/* glyphs */
+HGLYPH ui_create_glyph(RDPCLIENT * This, int width, int height, const uint8 * data)
+{
+       uint8 * databuf = NULL;
+       uint8 * databits = win32_convert_scanlines(width, height, 1, 1, 2, data, &databuf);
+
+       HBITMAP hbm = CreateBitmap(width, height, 1, 1, databits);
+
+       if(databuf)
+               delete[] databuf;
+
+       const uint8 * p = data;
+       int stride = alignup(alignup(width, 8) / 8, 1);
+
+#ifdef _DEBUG
+       printf("glyph %p\n", hbm);
+
+       for(int i = 0; i < height; ++ i, p += stride)
+       {
+               for(int j = 0; j < width; ++ j)
+               {
+                       int B = p[j / 8];
+                       int b = 8 - j % 8 - 1;
+
+                       if(B & (1 << b))
+                               fputs("##", stdout);
+                       else
+                               fputs("..", stdout);
+               }
+
+               fputc('\n', stdout);
+       }
+
+       fputc('\n', stdout);
+#endif
+
+       return hbm;
+}
+
+void ui_destroy_glyph(RDPCLIENT *, HGLYPH glyph)
+{
+       DeleteObject(glyph);
+}
+
+/* Input window */
+void ui_move_pointer(RDPCLIENT * This, int x, int y)
+{
+       // TODO
+}
+
+void ui_set_cursor(RDPCLIENT * This, HCURSOR cursor)
+{
+       // TODO
+}
+
+void ui_set_null_cursor(RDPCLIENT * This)
+{
+       // TODO
+}
+
+/* Miscellaneous */
+void ui_resize_window(RDPCLIENT * This)
+{
+}
+
+void ui_bell(RDPCLIENT *)
+{
+       MessageBeep(0);
+}
+
+int ui_select(RDPCLIENT * This, SOCKET rdp_socket)
+{
+       return SleepEx(0, TRUE) == WAIT_IO_COMPLETION;
+}
+
+/* Events */
+BOOL event_pubkey(RDPCLIENT * This, unsigned char * key, unsigned int key_size)
+{
+       if(!RdpClient::OnPublicKey(This, key, key_size))
+               return FALSE;
+       else
+               return TRUE;
+}
+
+void event_logon(RDPCLIENT * This)
+{
+       RdpClient::OnLogon(This);
+}
+
+BOOL event_redirect(RDPCLIENT * This, uint32 flags, uint32 server_len, wchar_t * server, uint32 cookie_len, char * cookie, uint32 username_len, wchar_t * username, uint32 domain_len, wchar_t * domain, uint32 password_len, wchar_t * password)
+{
+       if
+       (
+               !RdpClient::OnRedirect
+               (
+                       This,
+                       flags,
+                       server_len,
+                       server,
+                       cookie_len,
+                       cookie,
+                       username_len,
+                       username,
+                       domain_len,
+                       domain,
+                       password_len,
+                       password
+               )
+       )
+               return FALSE;
+       else
+               return TRUE;
+}
+
+}
+
+class ClassFactory: public IClassFactory
+{
+private:
+       LONG m_refCount;
+       CLSID m_classId;
+       unsigned m_libIndex;
+
+public:
+       ClassFactory(REFCLSID rclsid, unsigned libIndex):
+               m_refCount(1),
+               m_classId(rclsid)
+       {
+               lockServer();
+       }
+
+       ~ClassFactory()
+       {
+               unlockServer();
+       }
+
+       virtual STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObject)
+       {
+               if(riid == IID_IUnknown || riid == IID_IClassFactory)
+               {
+                       *ppvObject = this;
+                       return S_OK;
+               }
+               else
+               {
+                       *ppvObject = NULL;
+                       return E_NOINTERFACE;
+               }
+       }
+
+       virtual STDMETHODIMP_(ULONG) AddRef()
+       {
+               return InterlockedIncrement(&m_refCount);
+       }
+
+       virtual STDMETHODIMP_(ULONG) Release()
+       {
+               LONG n = InterlockedDecrement(&m_refCount);
+
+               if(n == 0)
+                       delete this;
+
+               return n;
+       }
+
+       virtual STDMETHODIMP CreateInstance(IUnknown * pUnkOuter, REFIID riid, void ** ppvObject)
+       {
+               if(pUnkOuter && riid != IID_IUnknown)
+                       return CLASS_E_NOAGGREGATION;
+
+               return RdpClient::CreateInstance(m_classId, m_libIndex, pUnkOuter, riid, ppvObject);
+       }
+
+       virtual STDMETHODIMP LockServer(BOOL fLock)
+       {
+               if(fLock)
+                       lockServer();
+               else
+                       unlockServer();
+
+               return S_OK;
+       }
+};
+
+extern "C"
+{
+
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID * ppv)
+{
+       unsigned libindex;
+
+       if
+       (
+               rclsid == MSTSCLib::CLSID_MsTscAx ||
+               rclsid == MSTSCLib::CLSID_MsRdpClient ||
+               rclsid == MSTSCLib::CLSID_MsRdpClient2 ||
+               rclsid == MSTSCLib::CLSID_MsRdpClient3 ||
+               rclsid == MSTSCLib::CLSID_MsRdpClient4
+       )
+               libindex = 1;
+       else if
+       (
+               rclsid == MSTSCLib_Redist::CLSID_MsTscAx ||
+               rclsid == MSTSCLib_Redist::CLSID_MsRdpClient ||
+               rclsid == MSTSCLib_Redist::CLSID_MsRdpClient2 ||
+               rclsid == MSTSCLib_Redist::CLSID_MsRdpClient3 // ||
+               // rclsid != MSTSCLib::CLSID_MsRdpClient4
+       )
+               libindex = 2;
+       else
+               return CLASS_E_CLASSNOTAVAILABLE;
+
+       ClassFactory * p = new ClassFactory(rclsid, libindex);
+
+       if(p == NULL)
+               return E_OUTOFMEMORY;
+
+       HRESULT hr = p->QueryInterface(riid, ppv);
+
+       p->Release();
+
+       if(FAILED(hr))
+               return hr;
+
+       return S_OK;
+}
+
+STDAPI DllCanUnloadNow(void)
+{
+       return canUnloadServer() ? S_OK : S_FALSE;
+}
+
+STDAPI_(ULONG) DllGetTscCtlVer(void)
+{
+       // BUGBUG: don't hardcode this
+       return 0x05020ECE; // 5.2.3790
+}
+
+DWORD WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpvReserved)
+{
+       assert(hInstance == GetCurrentModule());
+
+       switch(fdwReason)
+       {
+       case DLL_PROCESS_ATTACH:
+               {
+                       DisableThreadLibraryCalls(hInstance);
+
+                       if(!RdpClient::Startup())
+                               return FALSE;
+               }
+
+               break;
+
+       case DLL_PROCESS_DETACH:
+               {
+                       // Process is terminating, no need to clean up
+                       if(lpvReserved)
+                               break;
+
+                       RdpClient::Shutdown();
+               }
+
+               break;
+       }
+
+       return TRUE;
+}
+
+}
+
+// EOF