Migrating to new directory structure adopted from the RealVNC's source tree. More changes will follow.

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@591 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/win/rfb_win32/AboutDialog.cxx b/win/rfb_win32/AboutDialog.cxx
new file mode 100644
index 0000000..030be1b
--- /dev/null
+++ b/win/rfb_win32/AboutDialog.cxx
@@ -0,0 +1,49 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/AboutDialog.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/TCharArray.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("AboutDialog");
+
+AboutDialog AboutDialog::instance;
+
+
+AboutDialog::AboutDialog() : Dialog(GetModuleHandle(0)) {
+}
+
+bool AboutDialog::showDialog() {
+  return Dialog::showDialog(MAKEINTRESOURCE(DialogId));
+}
+
+void AboutDialog::initDialog() {
+  // Set the build time field
+  SetWindowText(GetDlgItem(handle, BuildTime), TStr(buildTime));
+
+  // Get our executable's version info
+  FileVersionInfo verInfo;
+
+  SetWindowText(GetDlgItem(handle, Version), verInfo.getVerString(_T("ProductVersion")));
+  SetWindowText(GetDlgItem(handle, Copyright), verInfo.getVerString(_T("LegalCopyright")));
+  SetWindowText(GetDlgItem(handle, Description), verInfo.getVerString(_T("ProductName")));
+}
diff --git a/win/rfb_win32/AboutDialog.h b/win/rfb_win32/AboutDialog.h
new file mode 100644
index 0000000..0dd9d49
--- /dev/null
+++ b/win/rfb_win32/AboutDialog.h
@@ -0,0 +1,55 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- AboutDialog.h
+
+#ifndef __RFB_WIN32_ABOUT_DIALOG_H__
+#define __RFB_WIN32_ABOUT_DIALOG_H__
+
+#include <rfb_win32/Dialog.h>
+#include <rfb/util.h>
+
+extern const char* buildTime;
+
+namespace rfb {
+
+  namespace win32 {
+
+    class AboutDialog : Dialog {
+    public:
+      AboutDialog();
+      virtual bool showDialog();
+      virtual void initDialog();
+
+      static AboutDialog instance;
+
+      typedef WORD LabelId;
+      static const LabelId DialogId;    // Resource ID of the About dialog
+      static const LabelId BuildTime;   // Resource ID of the BuildTime label in the dialog
+      static const LabelId Version;     // etc...
+      static const LabelId Copyright;
+      static const LabelId Description;
+    protected:
+      WORD dialogId;
+    };
+
+  };
+
+};
+
+#endif
diff --git a/win/rfb_win32/BitmapInfo.h b/win/rfb_win32/BitmapInfo.h
new file mode 100644
index 0000000..6a6f0d2
--- /dev/null
+++ b/win/rfb_win32/BitmapInfo.h
@@ -0,0 +1,48 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_BITMAP_INFO_H__
+#define __RFB_WIN32_BITMAP_INFO_H__
+
+#include <windows.h>
+#include <rdr/types.h>
+
+namespace rfb {
+  namespace win32 {
+
+    struct BitmapInfo {
+      BITMAPINFOHEADER bmiHeader;
+      union {
+        struct {
+          DWORD red;
+          DWORD green;
+          DWORD blue;
+        } mask;
+        RGBQUAD color[256];
+      };
+    };
+
+    inline void initMaxAndShift(DWORD mask, int* max, int* shift) {
+      for ((*shift) = 0; (mask & 1) == 0; (*shift)++) mask >>= 1;
+        (*max) = (rdr::U16)mask;
+    }
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/CKeyboard.cxx b/win/rfb_win32/CKeyboard.cxx
new file mode 100644
index 0000000..28aceab
--- /dev/null
+++ b/win/rfb_win32/CKeyboard.cxx
@@ -0,0 +1,258 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <map>
+
+#define XK_MISCELLANY
+#define XK_LATIN1
+#define XK_CURRENCY
+#include <rfb/keysymdef.h>
+
+#include <rfb_win32/CKeyboard.h>
+#include <rfb/LogWriter.h>
+#include <rfb_win32/OSVersion.h>
+#include "keymap.h"
+
+using namespace rfb;
+
+static LogWriter vlog("CKeyboard");
+
+
+// Client-side RFB keyboard event sythesis
+
+class CKeymapper {
+
+public:
+  CKeymapper()
+  {
+    for (int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) {
+      int extendedVkey = keymap[i].vk + (keymap[i].extended ? 256 : 0);
+      if (keysymMap.find(extendedVkey) == keysymMap.end()) {
+        keysymMap[extendedVkey] = keymap[i].keysym;
+      }
+    }
+  }
+
+  // lookup() tries to find a match for vkey with the extended flag.  We check
+  // first for an exact match including the extended flag, then try without the
+  // extended flag.
+  rdr::U32 lookup(int extendedVkey) {
+    if (keysymMap.find(extendedVkey) != keysymMap.end())
+      return keysymMap[extendedVkey];
+    if (keysymMap.find(extendedVkey ^ 256) != keysymMap.end())
+      return keysymMap[extendedVkey ^ 256];
+    return 0;
+  }
+
+private:
+  std::map<int,rdr::U32> keysymMap;
+} ckeymapper;
+
+
+class ModifierKeyReleaser {
+public:
+  ModifierKeyReleaser(InputHandler* writer_, int vkCode, bool extended)
+    : writer(writer_), extendedVkey(vkCode + (extended ? 256 : 0)),
+      keysym(0)
+  {}
+  void release(std::map<int,rdr::U32>* downKeysym) {
+    if (downKeysym->find(extendedVkey) != downKeysym->end()) {
+      keysym = (*downKeysym)[extendedVkey];
+      vlog.debug("fake release extendedVkey 0x%x, keysym 0x%x",
+                 extendedVkey, keysym);
+      writer->keyEvent(keysym, false);
+    }
+  }
+  ~ModifierKeyReleaser() {
+    if (keysym) {
+      vlog.debug("fake press extendedVkey 0x%x, keysym 0x%x",
+                 extendedVkey, keysym);
+      writer->keyEvent(keysym, true);
+    }
+  }
+  InputHandler* writer;
+  int extendedVkey;
+  rdr::U32 keysym;
+};
+
+// IS_PRINTABLE_LATIN1 tests if a character is either a printable latin1
+// character, or 128, which is the Euro symbol on Windows.
+#define IS_PRINTABLE_LATIN1(c) (((c) >= 32 && (c) <= 126) || (c) == 128 || \
+                                ((c) >= 160 && (c) <= 255))
+
+void win32::CKeyboard::keyEvent(InputHandler* writer, rdr::U8 vkey,
+                                rdr::U32 flags, bool down)
+{
+  bool extended = (flags & 0x1000000);
+  int extendedVkey = vkey + (extended ? 256 : 0);
+
+  // If it's a release, just release whichever keysym corresponded to the same
+  // key being pressed, regardless of how it would be interpreted in the
+  // current keyboard state.
+  if (!down) {
+    releaseKey(writer, extendedVkey);
+    return;
+  }
+
+  // We should always pass every down event to ToAscii() otherwise it can get
+  // out of sync.
+
+  // XXX should we pass CapsLock, ScrollLock or NumLock to ToAscii - they
+  // actually alter the lock state on the keyboard?
+
+  BYTE keystate[256];
+  GetKeyboardState(keystate);
+  rdr::U8 chars[2];
+
+  int nchars = ToAscii(vkey, 0, keystate, (WORD*)&chars, 0);
+
+  // See if it's in the Windows VK code -> X keysym map.  We do this before
+  // looking at the result of ToAscii so that e.g. we recognise that it's
+  // XK_KP_Add rather than '+'.
+
+  rdr::U32 keysym = ckeymapper.lookup(extendedVkey);
+  if (keysym) {
+    vlog.debug("mapped key: extendedVkey 0x%x", extendedVkey);
+    pressKey(writer, extendedVkey, keysym);
+    return;
+  }
+
+  if (nchars < 0) {
+    // Dead key - the next call to ToAscii() will give us either the accented
+    // character or two characters.
+    vlog.debug("ToAscii dead key (1): extendedVkey 0x%x", extendedVkey);
+    return;
+  }
+
+  if (nchars > 0 && IS_PRINTABLE_LATIN1(chars[0])) {
+    // Got a printable latin1 character.  We must release Control and Alt
+    // (AltGr) if they were both pressed, so that the latin1 character is seen
+    // without them by the VNC server.
+    ModifierKeyReleaser lctrl(writer, VK_CONTROL, 0);
+    ModifierKeyReleaser rctrl(writer, VK_CONTROL, 1);
+    ModifierKeyReleaser lalt(writer, VK_MENU, 0);
+    ModifierKeyReleaser ralt(writer, VK_MENU, 1);
+
+    if ((keystate[VK_CONTROL] & 0x80) && (keystate[VK_MENU] & 0x80)) {
+      lctrl.release(&downKeysym);
+      rctrl.release(&downKeysym);
+      lalt.release(&downKeysym);
+      ralt.release(&downKeysym);
+    }
+
+    for (int i = 0; i < nchars; i++) {
+      vlog.debug("ToAscii key (1): extendedVkey 0x%x", extendedVkey);
+      if (chars[i] == 128) { // special hack for euro!
+        pressKey(writer, extendedVkey, XK_EuroSign);
+      } else {
+        pressKey(writer, extendedVkey, chars[i]);
+      }
+    }
+    return;
+  }
+
+  // Either no chars were generated, or something outside the printable
+  // character range.  Try ToAscii() without the Control and Alt keys down to
+  // see if that yields an ordinary character.
+
+  keystate[VK_CONTROL] = keystate[VK_LCONTROL] = keystate[VK_RCONTROL] = 0;
+  keystate[VK_MENU] = keystate[VK_LMENU] = keystate[VK_RMENU] = 0;
+
+  nchars = ToAscii(vkey, 0, keystate, (WORD*)&chars, 0);
+
+  if (nchars < 0) {
+    // So it would be a dead key if neither control nor alt were pressed.
+    // However, we know that at least one of control and alt must be pressed.
+    // We can't leave it at this stage otherwise the next call to ToAscii()
+    // with a valid character will get wrongly interpreted in the context of
+    // this bogus dead key.  Working on the assumption that a dead key followed
+    // by space usually returns the dead character itself, try calling ToAscii
+    // with VK_SPACE.
+    vlog.debug("ToAscii dead key (2): extendedVkey 0x%x", extendedVkey);
+    nchars = ToAscii(VK_SPACE, 0, keystate, (WORD*)&chars, 0);
+    if (nchars < 0) {
+      vlog.debug("ToAscii dead key (3): extendedVkey 0x%x - giving up!",
+                 extendedVkey);
+      return;
+    }
+  }
+
+  if (nchars > 0 && IS_PRINTABLE_LATIN1(chars[0])) {
+    for (int i = 0; i < nchars; i++) {
+      vlog.debug("ToAscii key (2) (no ctrl/alt): extendedVkey 0x%x",
+                 extendedVkey);
+      if (chars[i] == 128) { // special hack for euro!
+        pressKey(writer, extendedVkey, XK_EuroSign);
+      } else {
+        pressKey(writer, extendedVkey, chars[i]);
+      }
+    }
+    return;
+  }
+
+  vlog.debug("no chars regardless of control and alt: extendedVkey 0x%x",
+             extendedVkey);
+}
+
+// releaseAllKeys() - write key release events to the server for all keys
+// that are currently regarded as being down.
+void win32::CKeyboard::releaseAllKeys(InputHandler* writer) {
+  std::map<int,rdr::U32>::iterator i, next_i;
+  for (i=downKeysym.begin(); i!=downKeysym.end(); i=next_i) {
+    next_i = i; next_i++;
+    writer->keyEvent((*i).second, false);
+    downKeysym.erase(i);
+  }
+}
+
+// releaseKey() - write a key up event to the server, but only if we've
+// actually sent a key down event for the given key.  The key up event always
+// contains the same keysym we used in the key down event, regardless of what
+// it would look up as using the current keyboard state.
+void win32::CKeyboard::releaseKey(InputHandler* writer, int extendedVkey)
+{
+  if (downKeysym.find(extendedVkey) != downKeysym.end()) {
+    vlog.debug("release extendedVkey 0x%x, keysym 0x%x",
+               extendedVkey, downKeysym[extendedVkey]);
+    writer->keyEvent(downKeysym[extendedVkey], false);
+    downKeysym.erase(extendedVkey);
+  }
+}
+
+// pressKey() - write a key down event to the server, and record which keysym
+// was sent as corresponding to the given extendedVkey.  The only tricky bit is
+// that if we are trying to press an extendedVkey which is already marked as
+// down but with a different keysym, then we need to release the old keysym
+// first.  This can happen in two cases: (a) when a single key press results in
+// more than one character, and (b) when shift is released while another key is
+// autorepeating.
+void win32::CKeyboard::pressKey(InputHandler* writer, int extendedVkey,
+                                rdr::U32 keysym)
+{
+  if (downKeysym.find(extendedVkey) != downKeysym.end()) {
+    if (downKeysym[extendedVkey] != keysym) {
+      vlog.debug("release extendedVkey 0x%x, keysym 0x%x",
+                 extendedVkey, downKeysym[extendedVkey]);
+      writer->keyEvent(downKeysym[extendedVkey], false);
+    }
+  }
+  vlog.debug("press   extendedVkey 0x%x, keysym 0x%x",
+             extendedVkey, keysym);
+  writer->keyEvent(keysym, true);
+  downKeysym[extendedVkey] = keysym;
+}
diff --git a/win/rfb_win32/CKeyboard.h b/win/rfb_win32/CKeyboard.h
new file mode 100644
index 0000000..666ebce
--- /dev/null
+++ b/win/rfb_win32/CKeyboard.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- CKeyboard.h
+//
+// Client-side keyboard handling for Win32
+
+#ifndef __RFB_WIN32_CKEYBOARD_H__
+#define __RFB_WIN32_CKEYBOARD_H__
+
+#include <rfb/InputHandler.h>
+#include <map>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class CKeyboard {
+    public:
+      void keyEvent(InputHandler* writer, rdr::U8 vkey, rdr::U32 flags,
+                    bool down);
+      void releaseAllKeys(InputHandler* writer);
+      const std::map<int,rdr::U32>& pressedKeys() const {return downKeysym;};
+      bool keyPressed(int k) const {return downKeysym.find(k)!=downKeysym.end();}
+    private:
+      void win32::CKeyboard::releaseKey(InputHandler* writer, int extendedVkey);
+      void win32::CKeyboard::pressKey(InputHandler* writer, int extendedVkey,
+                                      rdr::U32 keysym);
+      std::map<int,rdr::U32> downKeysym;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_CKEYBOARD_H__
diff --git a/win/rfb_win32/CPointer.cxx b/win/rfb_win32/CPointer.cxx
new file mode 100644
index 0000000..3d0d934
--- /dev/null
+++ b/win/rfb_win32/CPointer.cxx
@@ -0,0 +1,186 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <windows.h>
+#include <rfb/LogWriter.h>
+#include <rfb_win32/CPointer.h>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("CPointer");
+
+
+CPointer::CPointer() : currButtonMask(0), intervalQueued(false), threeEmulating(false) {
+}
+
+CPointer::~CPointer() {
+  intervalTimer.stop();
+  threeTimer.stop();
+}
+
+
+void CPointer::pointerEvent(InputHandler* writer, const Point& pos, int buttonMask) {
+  //
+  // - Duplicate Event Filtering
+  //
+
+  bool maskChanged = buttonMask != currButtonMask;
+  bool posChanged = !pos.equals(currPos);
+  if (!(posChanged || maskChanged))
+    return;
+
+  // Pass on the event to the event-interval handler
+  threePointerEvent(writer, pos, buttonMask);
+
+  // Save the position and mask
+  currPos = pos;
+  currButtonMask = buttonMask;
+}
+
+
+inline abs(int x) {return x>0 ? x : 0;}
+
+int emulate3Mask(int buttonMask) {
+  // - Release left & right and press middle
+  vlog.debug("emulate3: active");
+  buttonMask &= ~5;
+  buttonMask |= 2;
+  return buttonMask;
+}
+
+void CPointer::threePointerEvent(InputHandler* writer, const Point& pos, int buttonMask) {
+  //
+  // - 3-Button Mouse Emulation
+  //
+
+  if (emulate3) {
+
+    bool leftChanged = (buttonMask & 1) != (currButtonMask & 1);
+    bool rightChanged = (buttonMask & 4) != (currButtonMask & 4);
+
+    if (leftChanged || rightChanged) {
+      // - One of left or right have changed
+
+      if ((buttonMask & 5) == 1 || (buttonMask & 5) == 4) {
+        // - One is up, one is down.  Start a timer, so that if it
+        //   expires then we know we should actually send this event
+        vlog.debug("emulate3: start timer");
+        threeTimer.start(100);
+        threePos = pos;
+        threeMask = buttonMask;
+        return;
+
+      } else if (threeTimer.isActive()) {
+        // - Both are up or both are down, and we were timing for an emulation event
+        //   Stop the timer and flush the stored event
+        vlog.debug("emulate3: stop timer (state)");
+        threeTimer.stop();
+        if (threeEmulating == ((buttonMask & 5) == 5))
+          intervalPointerEvent(writer, threePos, threeMask);
+        else
+          threeEmulating = ((buttonMask & 5) == 5);
+      }
+
+    } else {
+    
+      if (threeTimer.isActive()) {
+        // - We are timing for an emulation event
+
+        if (abs(threePos.x - pos.x) <= 4 || abs(threePos.y - pos.y) <= 4) {
+          //   If the mouse has moved too far since the button-change event then flush
+          vlog.debug("emulate3: stop timer (moved)");
+          threeTimer.stop();
+          intervalPointerEvent(writer, threePos, threeMask);
+
+        } else {
+          //   Otherwise, we ignore the new event
+          return;
+        }
+      }
+
+    }
+
+    // - If neither left nor right are down, stop emulating
+    if ((buttonMask & 5) == 0)
+      threeEmulating = false;
+
+    // - If emulating, release left & right and press middle
+    if (threeEmulating)
+      buttonMask = emulate3Mask(buttonMask);
+
+  }
+
+  // - Let the event pass through to the next stage of processing
+  intervalPointerEvent(writer, pos, buttonMask);
+}
+
+void CPointer::intervalPointerEvent(InputHandler* writer, const Point& pos, int buttonMask) {
+  //
+  // - Pointer Event Interval
+  //
+  vlog.write(101, "ptrEvent: %d,%d (%lx)", pos.x, pos.y, buttonMask);
+
+  // Send the event immediately if we haven't sent one for a while
+  bool sendNow = !intervalTimer.isActive();
+
+  if (intervalMask != buttonMask) {
+    // If the buttons have changed then flush queued events and send now
+    sendNow = true;
+    if (intervalQueued)
+      writer->pointerEvent(intervalPos, intervalMask);
+    intervalQueued = false;
+  }
+
+  if (!sendNow) {
+    // If we're not sending now then just queue the event
+    intervalQueued = true;
+    intervalPos = pos;
+    intervalMask = buttonMask;
+  } else {
+    // Start the interval timer if required, and send the event
+    intervalQueued = false;
+    intervalMask = buttonMask;
+    if (pointerEventInterval)
+      intervalTimer.start(pointerEventInterval);
+    writer->pointerEvent(pos, buttonMask);
+  }
+}
+
+void CPointer::handleTimer(InputHandler* writer, int timerId) {
+  if (timerId == intervalTimer.getId()) {
+    // Pointer interval has expired - send any queued events
+    if (intervalQueued) {
+      writer->pointerEvent(intervalPos, intervalMask);
+      intervalQueued = false;
+    } else {
+      intervalTimer.stop();
+    }
+
+  } else if (timerId = threeTimer.getId()) {
+    // 3-Button emulation timer has expired - send what we've got
+    vlog.debug("emulate3: timeout");
+    threeTimer.stop();
+
+    // If emulating, release left & right and press middle
+    if (threeEmulating)
+      threeMask = emulate3Mask(threeMask);
+
+    intervalPointerEvent(writer, threePos, threeMask);
+  }
+}
diff --git a/win/rfb_win32/CPointer.h b/win/rfb_win32/CPointer.h
new file mode 100644
index 0000000..b591601
--- /dev/null
+++ b/win/rfb_win32/CPointer.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- CPointer.h
+//
+// Client-side pointer event handling for Win32
+
+#ifndef __RFB_WIN32_CPOINTER_H__
+#define __RFB_WIN32_CPOINTER_H__
+
+#include <rdr/Exception.h>
+#include <rfb/Configuration.h>
+#include <rfb/InputHandler.h>
+#include <rfb/Rect.h>
+#include <rfb_win32/IntervalTimer.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class CPointer {
+    public:
+      CPointer();
+      ~CPointer();
+
+      void pointerEvent(InputHandler* writer, const Point& pos, int buttonMask);
+      void handleTimer(InputHandler* writer, int timerId);
+
+      void setHWND(HWND w) {intervalTimer.setHWND(w); threeTimer.setHWND(w);}
+      void setIntervalTimerId(int id) {intervalTimer.setId(id);}
+      void set3ButtonTimerId(int id) {threeTimer.setId(id);}
+
+      void enableEmulate3(bool enable) {emulate3 = enable;}
+      void enableInterval(int millis) {pointerEventInterval = millis;}
+    private:
+      Point currPos;
+      int currButtonMask;
+
+      bool emulate3;
+      int pointerEventInterval;
+
+      void intervalPointerEvent(InputHandler* writer, const Point& pos, int buttonMask);
+      IntervalTimer intervalTimer;
+      bool intervalQueued;
+      Point intervalPos;
+      int intervalMask;
+
+      void threePointerEvent(InputHandler* writer, const Point& pos, int buttonMask);
+      IntervalTimer threeTimer;
+      Point threePos;
+      int threeMask;
+      bool threeEmulating;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_CPOINTER_H__
diff --git a/win/rfb_win32/CleanDesktop.cxx b/win/rfb_win32/CleanDesktop.cxx
new file mode 100644
index 0000000..39cca11
--- /dev/null
+++ b/win/rfb_win32/CleanDesktop.cxx
@@ -0,0 +1,321 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- CleanDesktop.cxx
+
+#include <windows.h>
+#include <wininet.h>
+#include <shlobj.h>
+#include <rfb_win32/CleanDesktop.h>
+#include <rfb_win32/CurrentUser.h>
+#include <rfb_win32/Registry.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb/LogWriter.h>
+#include <rdr/Exception.h>
+#include <set>
+
+#ifdef SPI_GETUIEFFECTS
+#define RFB_HAVE_SPI_UIEFFECTS
+#else
+#pragma message("  NOTE: Not building Get/Set UI Effects support.")
+#endif
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("CleanDesktop");
+
+
+struct ActiveDesktop {
+  ActiveDesktop() : handle(0) {
+    // - Contact Active Desktop
+    HRESULT result = CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER,
+                                      IID_IActiveDesktop, (PVOID*)&handle);
+    if (result != S_OK)
+      throw rdr::SystemException("failed to contact Active Desktop", result);
+  }
+  ~ActiveDesktop() {
+    if (handle)
+      handle->Release();
+  }
+
+  // enableItem
+  //   enables or disables the Nth Active Desktop item
+  bool enableItem(int i, bool enable_) {
+    COMPONENT item;
+    memset(&item, 0, sizeof(item));
+    item.dwSize = sizeof(item);
+
+    HRESULT hr = handle->GetDesktopItem(i, &item, 0);
+    if (hr != S_OK) {
+      vlog.error("unable to GetDesktopItem %d: %ld", i, hr);
+      return false;
+    }
+    item.fChecked = enable_;
+    vlog.debug("%sbling %d: \"%s\"", enable_ ? "ena" : "disa", i, (const char*)CStr(item.wszFriendlyName));
+
+    hr = handle->ModifyDesktopItem(&item, COMP_ELEM_CHECKED);
+    return hr == S_OK;
+  }
+  
+  // enable
+  //   Attempts to enable/disable Active Desktop, returns true if the setting changed,
+  //   false otherwise.
+  //   If Active Desktop *can* be enabled/disabled then that is done.
+  //   If Active Desktop is always on (XP/2K3) then instead the individual items are
+  //   disabled, and true is returned to indicate that they need to be restored later.
+  bool enable(bool enable_) {
+    bool modifyComponents = false;
+
+    vlog.debug("ActiveDesktop::enable");
+
+    // - Firstly, try to disable Active Desktop entirely
+    HRESULT hr;
+    COMPONENTSOPT adOptions;
+    memset(&adOptions, 0, sizeof(adOptions));
+    adOptions.dwSize = sizeof(adOptions);
+
+    // Attempt to actually disable/enable AD
+    hr = handle->GetDesktopItemOptions(&adOptions, 0);
+    if (hr == S_OK) {
+      // If Active Desktop is already in the desired state then return false (no change)
+      // NB: If AD is enabled AND restoreItems is set then we regard it as disabled...
+      if (((adOptions.fActiveDesktop==0) && restoreItems.empty()) == (enable_==false))
+        return false;
+      adOptions.fActiveDesktop = enable_;
+      hr = handle->SetDesktopItemOptions(&adOptions, 0);
+    }
+    // Apply the change, then test whether it actually took effect
+    if (hr == S_OK)
+      hr = handle->ApplyChanges(AD_APPLY_REFRESH);
+    if (hr == S_OK)
+      hr = handle->GetDesktopItemOptions(&adOptions, 0);
+    if (hr == S_OK)
+      modifyComponents = (adOptions.fActiveDesktop==0) != (enable_==false);
+    if (hr != S_OK) {
+      vlog.error("failed to get/set Active Desktop options: %ld", hr);
+      return false;
+    }
+
+    if (enable_) {
+      // - We are re-enabling Active Desktop.  If there are components in restoreItems
+      //   then restore them!
+      std::set<int>::const_iterator i;
+      for (i=restoreItems.begin(); i!=restoreItems.end(); i++) {
+        enableItem(*i, true);
+      }
+      restoreItems.clear();
+    } else if (modifyComponents) {
+      // - Disable all currently enabled items, and add the disabled ones to restoreItems
+      int itemCount = 0;
+      hr = handle->GetDesktopItemCount(&itemCount, 0);
+      if (hr != S_OK) {
+        vlog.error("failed to get desktop item count: %ld", hr);
+        return false;
+      }
+      for (unsigned int i=0; i<itemCount; i++) {
+        if (enableItem(i, false))
+          restoreItems.insert(i);
+      }
+    }
+
+    // - Apply whatever changes we have made, but DON'T save them!
+    hr = handle->ApplyChanges(AD_APPLY_REFRESH);
+    return hr == S_OK;
+  }
+  IActiveDesktop* handle;
+  std::set<int> restoreItems;
+};
+
+
+DWORD SysParamsInfo(UINT action, UINT param, PVOID ptr, UINT ini) {
+  DWORD r = ERROR_SUCCESS;
+  if (!SystemParametersInfo(action, param, ptr, ini)) {
+    r = GetLastError();
+    vlog.info("SPI error: %d", r);
+  }
+  return r;
+}
+
+
+CleanDesktop::CleanDesktop() : restoreActiveDesktop(false), restoreWallpaper(false),
+                               restorePattern(false), restoreEffects(false) {
+  CoInitialize(0);
+}
+
+CleanDesktop::~CleanDesktop() {
+  enableEffects();
+  enablePattern();
+  enableWallpaper();
+  CoUninitialize();
+}
+
+void CleanDesktop::disableWallpaper() {
+  try {
+    ImpersonateCurrentUser icu;
+
+    vlog.debug("disable desktop wallpaper/Active Desktop");
+
+    // -=- First attempt to remove the wallpaper using Active Desktop
+    try {
+      ActiveDesktop ad;
+      if (ad.enable(false))
+        restoreActiveDesktop = true;
+    } catch (rdr::Exception& e) {
+      vlog.error(e.str());
+    }
+
+    // -=- Switch of normal wallpaper and notify apps
+    SysParamsInfo(SPI_SETDESKWALLPAPER, 0, "", SPIF_SENDCHANGE);
+    restoreWallpaper = true;
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
+
+void CleanDesktop::enableWallpaper() {
+  try {
+    ImpersonateCurrentUser icu;
+
+    if (restoreActiveDesktop) {
+      vlog.debug("restore Active Desktop");
+
+      // -=- First attempt to re-enable Active Desktop
+      try {
+        ActiveDesktop ad;
+        ad.enable(true);
+        restoreActiveDesktop = false;
+      } catch (rdr::Exception& e) {
+        vlog.error(e.str());
+      }
+    }
+
+    if (restoreWallpaper) {
+      vlog.debug("restore desktop wallpaper");
+
+      // -=- Then restore the standard wallpaper if required
+	    SysParamsInfo(SPI_SETDESKWALLPAPER, 0, NULL, SPIF_SENDCHANGE);
+      restoreWallpaper = false;
+    }
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
+
+
+void CleanDesktop::disablePattern() {
+  try {
+    ImpersonateCurrentUser icu;
+
+    vlog.debug("disable desktop pattern");
+    SysParamsInfo(SPI_SETDESKPATTERN, 0, "", SPIF_SENDCHANGE);
+    restorePattern = true;
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
+
+void CleanDesktop::enablePattern() {
+  try {
+    if (restorePattern) {
+      ImpersonateCurrentUser icu;
+
+      vlog.debug("restoring pattern...");
+
+      TCharArray pattern;
+      if (osVersion.isPlatformWindows) {
+        RegKey cfgKey;
+        cfgKey.openKey(HKEY_CURRENT_USER, _T("Control Panel\\Desktop"));
+        pattern.buf = cfgKey.getString(_T("Pattern"));
+      }
+      SysParamsInfo(SPI_SETDESKPATTERN, 0, pattern.buf, SPIF_SENDCHANGE);
+      restorePattern = false;
+    }
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
+
+
+void CleanDesktop::disableEffects() {
+  try {
+    ImpersonateCurrentUser icu;
+
+    vlog.debug("disable desktop effects");
+
+    SysParamsInfo(SPI_SETFONTSMOOTHING, FALSE, 0, SPIF_SENDCHANGE);
+#ifdef RFB_HAVE_SPI_UIEFFECTS
+    if (SysParamsInfo(SPI_GETUIEFFECTS, 0, &uiEffects, 0) == ERROR_CALL_NOT_IMPLEMENTED) {
+      SysParamsInfo(SPI_GETCOMBOBOXANIMATION, 0, &comboBoxAnim, 0);
+      SysParamsInfo(SPI_GETGRADIENTCAPTIONS, 0, &gradientCaptions, 0);
+      SysParamsInfo(SPI_GETHOTTRACKING, 0, &hotTracking, 0);
+      SysParamsInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &listBoxSmoothScroll, 0);
+      SysParamsInfo(SPI_GETMENUANIMATION, 0, &menuAnim, 0);
+      SysParamsInfo(SPI_SETCOMBOBOXANIMATION, 0, FALSE, SPIF_SENDCHANGE);
+      SysParamsInfo(SPI_SETGRADIENTCAPTIONS, 0, FALSE, SPIF_SENDCHANGE);
+      SysParamsInfo(SPI_SETHOTTRACKING, 0, FALSE, SPIF_SENDCHANGE);
+      SysParamsInfo(SPI_SETLISTBOXSMOOTHSCROLLING, 0, FALSE, SPIF_SENDCHANGE);
+      SysParamsInfo(SPI_SETMENUANIMATION, 0, FALSE, SPIF_SENDCHANGE);
+    } else {
+      SysParamsInfo(SPI_SETUIEFFECTS, 0, FALSE, SPIF_SENDCHANGE);
+
+      // We *always* restore UI effects overall, since there is no Windows GUI to do it
+      uiEffects = TRUE;
+    }
+#else
+    vlog.debug("  not supported");
+#endif
+    restoreEffects = true;
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
+
+void CleanDesktop::enableEffects() {
+  try {
+    if (restoreEffects) {
+      ImpersonateCurrentUser icu;
+
+      vlog.debug("restore desktop effects");
+
+      RegKey desktopCfg;
+      desktopCfg.openKey(HKEY_CURRENT_USER, _T("Control Panel\\Desktop"));
+      SysParamsInfo(SPI_SETFONTSMOOTHING, desktopCfg.getInt(_T("FontSmoothing"), 0) != 0, 0, SPIF_SENDCHANGE);
+#ifdef RFB_HAVE_SPI_UIEFFECTS
+      if (SysParamsInfo(SPI_SETUIEFFECTS, 0, (void*)uiEffects, SPIF_SENDCHANGE) == ERROR_CALL_NOT_IMPLEMENTED) {
+        SysParamsInfo(SPI_SETCOMBOBOXANIMATION, 0, (void*)comboBoxAnim, SPIF_SENDCHANGE);
+        SysParamsInfo(SPI_SETGRADIENTCAPTIONS, 0, (void*)gradientCaptions, SPIF_SENDCHANGE);
+        SysParamsInfo(SPI_SETHOTTRACKING, 0, (void*)hotTracking, SPIF_SENDCHANGE);
+        SysParamsInfo(SPI_SETLISTBOXSMOOTHSCROLLING, 0, (void*)listBoxSmoothScroll, SPIF_SENDCHANGE);
+        SysParamsInfo(SPI_SETMENUANIMATION, 0, (void*)menuAnim, SPIF_SENDCHANGE);
+      }
+      restoreEffects = false;
+#else
+      vlog.info("  not supported");
+#endif
+    }
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
diff --git a/win/rfb_win32/CleanDesktop.h b/win/rfb_win32/CleanDesktop.h
new file mode 100644
index 0000000..22e246f
--- /dev/null
+++ b/win/rfb_win32/CleanDesktop.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- CleanDesktop.h
+
+#ifndef __RFB_WIN32_CLEANDESKTOP_H__
+#define __RFB_WIN32_CLEANDESKTOP_H__
+
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class CleanDesktop {
+    public:
+      CleanDesktop();
+      ~CleanDesktop();
+
+      void disableWallpaper();
+      void enableWallpaper();
+
+      void disablePattern();
+      void enablePattern();
+
+      void disableEffects();
+      void enableEffects();
+
+    private:
+      bool restoreActiveDesktop;
+      bool restoreWallpaper;
+      bool restorePattern;
+      bool restoreEffects;
+      BOOL uiEffects;
+      BOOL comboBoxAnim, gradientCaptions, hotTracking, listBoxSmoothScroll, menuAnim;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_CLEANDESKTOP_H__
diff --git a/win/rfb_win32/Clipboard.cxx b/win/rfb_win32/Clipboard.cxx
new file mode 100644
index 0000000..a4c43f0
--- /dev/null
+++ b/win/rfb_win32/Clipboard.cxx
@@ -0,0 +1,200 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Clipboard.cxx
+
+#include <rfb_win32/Clipboard.h>
+#include <rfb_win32/WMShatter.h>
+#include <rfb/util.h>
+
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("Clipboard");
+
+
+//
+// -=- CR/LF handlers
+//
+
+char*
+dos2unix(const char* text) {
+  int len = strlen(text)+1;
+  char* unix = new char[strlen(text)+1];
+  int i, j=0;
+  for (i=0; i<len; i++) {
+    if (text[i] != '\x0d')
+      unix[j++] = text[i];
+  }
+  return unix;
+}
+
+char*
+unix2dos(const char* text) {
+  int len = strlen(text)+1;
+  char* dos = new char[strlen(text)*2+1];
+  int i, j=0;
+  for (i=0; i<len; i++) {
+    if (text[i] == '\x0a')
+      dos[j++] = '\x0d';
+    dos[j++] = text[i];
+  }
+  return dos;
+}
+
+
+//
+// -=- ISO-8859-1 (Latin 1) filter (in-place)
+//
+
+void
+removeNonISOLatin1Chars(char* text) {
+  int len = strlen(text);
+  int i=0, j=0;
+  for (; i<len; i++) {
+    if (((text[i] >= 1) && (text[i] <= 127)) ||
+        ((text[i] >= 160) && (text[i] <= 255)))
+      text[j++] = text[i];
+  }
+  text[j] = 0;
+}
+
+//
+// -=- Clipboard object
+//
+
+Clipboard::Clipboard()
+  : MsgWindow(_T("Clipboard")), notifier(0), next_window(0) {
+  next_window = SetClipboardViewer(getHandle());
+  vlog.debug("registered clipboard handler");
+}
+
+Clipboard::~Clipboard() {
+  vlog.debug("removing %x from chain (next is %x)", getHandle(), next_window);
+  ChangeClipboardChain(getHandle(), next_window);
+}
+
+LRESULT
+Clipboard::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  switch (msg) {
+
+  case WM_CHANGECBCHAIN:
+    vlog.debug("change clipboard chain (%x, %x)", wParam, lParam);
+    if ((HWND) wParam == next_window)
+      next_window = (HWND) lParam;
+    else if (next_window != 0)
+      SendMessage(next_window, msg, wParam, lParam);
+    else
+      vlog.error("bad clipboard chain change!");
+    break;
+
+  case WM_DRAWCLIPBOARD:
+    {
+      HWND owner = GetClipboardOwner();
+      if (owner == getHandle()) {
+        vlog.debug("local clipboard changed by me");
+      } else {
+        vlog.debug("local clipboard changed by %x", owner);
+
+			  // Open the clipboard
+			  if (OpenClipboard(getHandle())) {
+				  // Get the clipboard data
+				  HGLOBAL cliphandle = GetClipboardData(CF_TEXT);
+				  if (cliphandle) {
+					  char* clipdata = (char*) GlobalLock(cliphandle);
+
+            // Notify clients
+            if (notifier) {
+              if (!clipdata) {
+                notifier->notifyClipboardChanged(0, 0);
+              } else {
+                CharArray unix_text;
+                unix_text.buf = dos2unix(clipdata);
+                removeNonISOLatin1Chars(unix_text.buf);
+                notifier->notifyClipboardChanged(unix_text.buf, strlen(unix_text.buf));
+              }
+            } else {
+              vlog.debug("no clipboard notifier registered");
+            }
+
+					  // Release the buffer and close the clipboard
+					  GlobalUnlock(cliphandle);
+				  }
+
+				  CloseClipboard();
+        }
+			}
+    }
+    if (next_window)
+		  SendMessage(next_window, msg, wParam, lParam);
+    return 0;
+
+  };
+  return MsgWindow::processMessage(msg, wParam, lParam);
+};
+
+void
+Clipboard::setClipText(const char* text) {
+  HANDLE clip_handle = 0;
+
+  try {
+
+    // - Firstly, we must open the clipboard
+    if (!OpenClipboard(getHandle()))
+      throw rdr::SystemException("unable to open Win32 clipboard", GetLastError());
+
+    // - Pre-process the supplied clipboard text into DOS format
+    CharArray dos_text;
+    dos_text.buf = unix2dos(text);
+    removeNonISOLatin1Chars(dos_text.buf);
+    int dos_text_len = strlen(dos_text.buf);
+
+    // - Allocate global memory for the data
+    clip_handle = ::GlobalAlloc(GMEM_MOVEABLE, dos_text_len+1);
+
+    char* data = (char*) GlobalLock(clip_handle);
+    memcpy(data, dos_text.buf, dos_text_len+1);
+    data[dos_text_len] = 0;
+    GlobalUnlock(clip_handle);
+
+    // - Next, we must clear out any existing data
+    if (!EmptyClipboard())
+      throw rdr::SystemException("unable to empty Win32 clipboard", GetLastError());
+
+    // - Set the new clipboard data
+    if (!SetClipboardData(CF_TEXT, clip_handle))
+      throw rdr::SystemException("unable to set Win32 clipboard", GetLastError());
+    clip_handle = 0;
+
+    vlog.debug("set clipboard");
+  } catch (rdr::Exception& e) {
+    vlog.debug(e.str());
+  }
+
+  // - Close the clipboard
+  if (!CloseClipboard())
+    vlog.debug("unable to close Win32 clipboard: %u", GetLastError());
+  else
+    vlog.debug("closed clipboard");
+  if (clip_handle) {
+    vlog.debug("freeing clipboard handle");
+    GlobalFree(clip_handle);
+  }
+}
diff --git a/win/rfb_win32/Clipboard.h b/win/rfb_win32/Clipboard.h
new file mode 100644
index 0000000..b79768f
--- /dev/null
+++ b/win/rfb_win32/Clipboard.h
@@ -0,0 +1,66 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Clipboard.h
+//
+// The Clipboard is used to set the system clipboard, and to get callbacks
+// when the system clipboard has changed.
+
+#ifndef __RFB_WIN32_CLIPBOARD_H__
+#define __RFB_WIN32_CLIPBOARD_H__
+
+#include <rfb/SDesktop.h>
+#include <rfb/Threading.h>
+#include <rfb_win32/MsgWindow.h>
+#include <rfb_win32/DeviceFrameBuffer.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class Clipboard : MsgWindow {
+    public:
+
+      // -=- Abstract base class for callback recipients
+      class Notifier {
+      public:
+        virtual void notifyClipboardChanged(const char* text, int len) = 0;
+      };
+
+      Clipboard();
+      ~Clipboard();
+
+      // - Set the notifier to use
+      void setNotifier(Notifier* cbn) {notifier = cbn;}
+
+      // - Set the clipboard contents
+      void setClipText(const char* text);
+
+    protected:
+      // - Internal MsgWindow callback
+      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+      Notifier* notifier;
+      HWND next_window;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_CLIPBOARD_H__
diff --git a/win/rfb_win32/CompatibleBitmap.h b/win/rfb_win32/CompatibleBitmap.h
new file mode 100644
index 0000000..4beed8d
--- /dev/null
+++ b/win/rfb_win32/CompatibleBitmap.h
@@ -0,0 +1,46 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_COMPAT_BITMAP_H__
+#define __RFB_WIN32_COMPAT_BITMAP_H__
+
+#include <windows.h>
+#include <rdr/Exception.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class CompatibleBitmap {
+    public:
+      CompatibleBitmap(HDC hdc, int width, int height) {
+        hbmp = CreateCompatibleBitmap(hdc, width, height);
+        if (!hbmp)
+          throw rdr::SystemException("CreateCompatibleBitmap() failed", GetLastError());
+      }
+      virtual ~CompatibleBitmap() {
+        if (hbmp) DeleteObject(hbmp);
+      }
+      operator HBITMAP() const {return hbmp;}
+    protected:
+      HBITMAP hbmp;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/ComputerName.h b/win/rfb_win32/ComputerName.h
new file mode 100644
index 0000000..110caa5
--- /dev/null
+++ b/win/rfb_win32/ComputerName.h
@@ -0,0 +1,40 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_COMPUTERNAME_H__
+#define __RFB_WIN32_COMPUTERNAME_H__
+
+#include <windows.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+  namespace win32 {
+
+    // Get the computer name
+    struct ComputerName : TCharArray {
+      ComputerName() : TCharArray(MAX_COMPUTERNAME_LENGTH+1) {
+        ULONG namelength = MAX_COMPUTERNAME_LENGTH+1;
+        if (!GetComputerName(buf, &namelength))
+          _tcscpy(buf, _T(""));
+      }
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/CurrentUser.cxx b/win/rfb_win32/CurrentUser.cxx
new file mode 100644
index 0000000..7562d29
--- /dev/null
+++ b/win/rfb_win32/CurrentUser.cxx
@@ -0,0 +1,152 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Currentuser.cxx
+
+#include <stdlib.h>
+#include <rfb/LogWriter.h>
+#include <rfb_win32/CurrentUser.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/OSVersion.h>
+#include <lmcons.h>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("CurrentUser");
+
+
+const TCHAR* shellIconClass = _T("Shell_TrayWnd");
+
+BOOL CALLBACK enumWindows(HWND hwnd, LPARAM lParam) {
+  TCHAR className[16];
+  if (GetClassName(hwnd, className, sizeof(className)) &&
+      (_tcscmp(className, shellIconClass) == 0)) {
+    vlog.debug("located tray icon window (%s)", (const char*)CStr(className));
+    DWORD processId = 0;
+    GetWindowThreadProcessId(hwnd, &processId);
+    if (!processId)
+      return TRUE;
+    Handle process = OpenProcess(MAXIMUM_ALLOWED, FALSE, processId);
+    if (!process.h)
+      return TRUE;
+    if (!OpenProcessToken(process, MAXIMUM_ALLOWED, (HANDLE*)lParam))
+      return TRUE;
+    vlog.debug("obtained user token");
+    return FALSE;
+  }
+  return TRUE;
+}
+
+BOOL CALLBACK enumDesktops(LPTSTR lpszDesktop, LPARAM lParam) {
+  HDESK desktop = OpenDesktop(lpszDesktop, 0, FALSE, DESKTOP_ENUMERATE);
+  vlog.debug("opening \"%s\"", lpszDesktop);
+  if (!desktop) {
+    vlog.info("desktop \"%s\" inaccessible", (const char*)CStr(lpszDesktop));
+    return TRUE;
+  }
+  BOOL result = EnumDesktopWindows(desktop, enumWindows, lParam);
+  if (!CloseDesktop(desktop))
+    vlog.info("unable to close desktop: %ld", GetLastError());
+  return result;
+}
+
+
+CurrentUserToken::CurrentUserToken() : isSafe_(false) {
+  if (isServiceProcess()) {
+    // If the platform is Windows 95/98/Me then we must fake the token's presence
+    if (osVersion.isPlatformWindows) {
+      try {
+        UserName un;
+        h = INVALID_HANDLE_VALUE;
+      } catch (rdr::SystemException& e) {
+        if (e.err != ERROR_NOT_LOGGED_ON)
+          throw;
+        if (FindWindow(shellIconClass, 0))
+          h = INVALID_HANDLE_VALUE;
+      }
+      isSafe_ = (h != 0);
+      return;
+    }
+
+    // Try to get the user token using the Terminal Services APIs
+    //   NB: This will only work under XP/2003 and later
+    typedef BOOL (WINAPI *WTSQueryUserToken_proto)(ULONG, PHANDLE);
+    DynamicFn<WTSQueryUserToken_proto> _WTSQueryUserToken(_T("wtsapi32.dll"), "WTSQueryUserToken");
+    if (_WTSQueryUserToken.isValid()) {
+      (*_WTSQueryUserToken)(-1, &h);
+      isSafe_ = true;
+      return;
+    }
+
+    // Try to find the Shell Tray Icon window and take its token
+    //   NB: This will only work under NT/2K (and later, but they're dealt with above)
+    //   NB: If the shell is not running then this will return an Unsafe Null token.
+    EnumDesktops(GetProcessWindowStation(), enumDesktops, (LONG)&h);
+    isSafe_ = (h != 0);
+  } else {
+    // Try to open the security token for the User-Mode process
+    if (!OpenProcessToken(GetCurrentProcess(), GENERIC_ALL, &h)) {
+      DWORD err = GetLastError();
+      if (err != ERROR_CALL_NOT_IMPLEMENTED)
+        throw rdr::SystemException("OpenProcessToken failed", err);
+      // Under Windows 95/98/Me, we fake the handle value...
+      h = INVALID_HANDLE_VALUE;
+    }
+    isSafe_ = true;
+  }
+}
+
+
+ImpersonateCurrentUser::ImpersonateCurrentUser() {
+  RegCloseKey(HKEY_CURRENT_USER);
+  if (!isServiceProcess())
+    return;
+  if (!token.canImpersonate())
+    throw rdr::Exception("Cannot impersonate unsafe or null token");
+  if (!ImpersonateLoggedOnUser(token)) {
+    DWORD err = GetLastError();
+    if (err != ERROR_CALL_NOT_IMPLEMENTED)
+      throw rdr::SystemException("Failed to impersonate user", GetLastError());
+  }
+}
+
+ImpersonateCurrentUser::~ImpersonateCurrentUser() {
+  if (!RevertToSelf()) {
+    DWORD err = GetLastError();
+    if (err != ERROR_CALL_NOT_IMPLEMENTED)
+      exit(err);
+  }
+  RegCloseKey(HKEY_CURRENT_USER);
+}
+
+
+UserName::UserName() : TCharArray(UNLEN+1) {
+  DWORD len = UNLEN+1;
+  if (!GetUserName(buf, &len))
+    throw rdr::SystemException("GetUserName failed", GetLastError());
+}
+
+
+UserSID::UserSID() {
+  CurrentUserToken token;
+  if (!token.canImpersonate())
+    return;
+  setSID(Sid::FromToken(token.h));
+}
diff --git a/win/rfb_win32/CurrentUser.h b/win/rfb_win32/CurrentUser.h
new file mode 100644
index 0000000..794f27c
--- /dev/null
+++ b/win/rfb_win32/CurrentUser.h
@@ -0,0 +1,100 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// CurrentUser.h
+
+// Helper class providing the session's logged on username, if
+// a user is logged on.  Also allows processes running under
+// XP/2K3 etc to masquerade as the logged on user for security
+// purposes
+
+#ifndef __RFB_WIN32_CURRENT_USER_H__
+#define __RFB_WIN32_CURRENT_USER_H__
+
+#include <rfb_win32/Handle.h>
+#include <rfb_win32/Security.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // CurrentUserToken
+    //   CurrentUserToken is a Handle containing the security token
+    //   for the currently logged-on user, or null if no user is
+    //   logged on.
+    //
+    //   Under Windows 95/98/Me, which don't support security tokens,
+    //   the token will be INVALID_HANDLE_VALUE if a user is logged on.
+    //
+    //   Under Windows NT/2K, it may be the case that the token is
+    //   null even when a user *is* logged on, because we use some hacks
+    //   to detect the user's token and sometimes they fail.  On these
+    //   platforms, isSafe() will return False if the token is null.
+    //
+    //   Under Windows XP, etc, isSafe() will always be True, and the token
+    //   will always be set to the currently logged on user's token.
+    //
+    //   canImpersonate() tests whether there is a user token that is safe
+    //   to impersonate.
+    //
+    //   noUserLoggedOn() tests whether there is *definitely* no user logged on.
+
+    struct CurrentUserToken : public Handle {
+      CurrentUserToken();
+      bool isSafe() const { return isSafe_; };
+      bool canImpersonate() const { return h && isSafe(); }
+      bool noUserLoggedOn() const { return !h && isSafe(); }
+    private:
+      bool isSafe_;
+    };
+
+    // ImpersonateCurrentUser
+    //   Throws an exception on failure.
+    //   Succeeds (trivially) if process is not running as service.
+    //   Fails if CurrentUserToken is not valid.
+    //   Fails if platform is NT AND cannot impersonate token.
+    //   Succeeds otherwise.
+
+    struct ImpersonateCurrentUser {
+      ImpersonateCurrentUser();
+      ~ImpersonateCurrentUser();
+      CurrentUserToken token;
+    };
+
+    // UserName
+    //   Returns the name of the user the thread is currently running as.
+    //   Raises a SystemException in case of error.
+    //   NB: Raises a SystemException with err == ERROR_NOT_LOGGED_ON if
+    //       running under Windows 9x/95/Me and no user is logged on.
+
+    struct UserName : public TCharArray {
+      UserName();
+    };
+
+    // UserSID
+    //   Returns the SID of the currently logged-on user (i.e. the session user)
+
+    struct UserSID : public Sid {
+      UserSID();
+    };
+
+  }
+
+}
+
+#endif
diff --git a/win/rfb_win32/DIBSectionBuffer.cxx b/win/rfb_win32/DIBSectionBuffer.cxx
new file mode 100644
index 0000000..18ce3ea
--- /dev/null
+++ b/win/rfb_win32/DIBSectionBuffer.cxx
@@ -0,0 +1,222 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/DIBSectionBuffer.h>
+#include <rfb_win32/DeviceContext.h>
+#include <rfb_win32/BitmapInfo.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("DIBSectionBuffer");
+
+
+DIBSectionBuffer::DIBSectionBuffer(HWND window_)
+  : bitmap(0), device(0), window(window_) {
+  memset(&format, 0, sizeof(format));
+  memset(palette, 0, sizeof(palette));
+}
+
+DIBSectionBuffer::DIBSectionBuffer(HDC device_)
+  : bitmap(0), window(0), device(device_) {
+  memset(&format, 0, sizeof(format));
+  memset(palette, 0, sizeof(palette));
+}
+
+DIBSectionBuffer::~DIBSectionBuffer() {
+  if (bitmap)
+    DeleteObject(bitmap);
+}
+
+
+void DIBSectionBuffer::setPF(const PixelFormat& pf) {
+  if (memcmp(&getPF(), &pf, sizeof(pf)) == 0) {
+    vlog.debug("pixel format unchanged by setPF()");
+    return;
+  }
+  format = pf;
+  recreateBuffer();
+  if ((pf.bpp <= 8) && pf.trueColour) {
+    vlog.info("creating %d-bit TrueColour palette", pf.depth);
+    for (int i=0; i < (1<<(pf.depth)); i++) {
+      palette[i].b = ((((i >> pf.blueShift) & pf.blueMax) * 65535) + pf.blueMax/2) / pf.blueMax;
+      palette[i].g = ((((i >> pf.greenShift) & pf.greenMax) * 65535) + pf.greenMax/2) / pf.greenMax;
+      palette[i].r = ((((i >> pf.redShift) & pf.redMax) * 65535) + pf.redMax/2) / pf.redMax;
+    }
+    refreshPalette();
+  }
+}
+
+void DIBSectionBuffer::setSize(int w, int h) {
+  if (width_ == w && height_ == h) {
+    vlog.debug("size unchanged by setSize()");
+    return;
+  }
+  width_ = w;
+  height_ = h;
+  recreateBuffer();
+}
+
+
+// * copyPaletteToDIB MUST NEVER be called on a truecolour DIB! *
+
+void copyPaletteToDIB(Colour palette[256], HDC wndDC, HBITMAP dib) {
+  BitmapDC dibDC(wndDC, dib);
+  RGBQUAD rgb[256];
+  for (unsigned int i=0;i<256;i++) {
+    rgb[i].rgbRed = palette[i].r >> 8;
+    rgb[i].rgbGreen = palette[i].g >> 8;
+    rgb[i].rgbBlue = palette[i].b >> 8;
+  }
+  if (!SetDIBColorTable(dibDC, 0, 256, (RGBQUAD*) rgb))
+    throw rdr::SystemException("unable to SetDIBColorTable", GetLastError());
+}
+
+inline void initMaxAndShift(DWORD mask, int* max, int* shift) {
+  for ((*shift) = 0; (mask & 1) == 0; (*shift)++) mask >>= 1;
+  (*max) = (rdr::U16)mask;
+}
+
+void DIBSectionBuffer::recreateBuffer() {
+  HBITMAP new_bitmap = 0;
+  rdr::U8* new_data = 0;
+
+  if (width_ && height_ && (format.depth != 0)) {
+    BitmapInfo bi;
+    memset(&bi, 0, sizeof(bi));
+    // *** wrong?
+    UINT iUsage = format.trueColour ? DIB_RGB_COLORS : DIB_PAL_COLORS;
+    // ***
+    bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+    bi.bmiHeader.biBitCount = format.bpp;
+    bi.bmiHeader.biSizeImage = (format.bpp / 8) * width_ * height_;
+    bi.bmiHeader.biPlanes = 1;
+    bi.bmiHeader.biWidth = width_;
+    bi.bmiHeader.biHeight = -height_;
+    bi.bmiHeader.biCompression = (format.bpp > 8) ? BI_BITFIELDS : BI_RGB;
+    bi.mask.red = format.redMax << format.redShift;
+    bi.mask.green = format.greenMax << format.greenShift;
+    bi.mask.blue = format.blueMax << format.blueShift;
+
+    // Create a DIBSection to draw into
+    if (device)
+      new_bitmap = ::CreateDIBSection(device, (BITMAPINFO*)&bi.bmiHeader, iUsage,
+                                      (void**)&new_data, NULL, 0);
+    else
+      new_bitmap = ::CreateDIBSection(WindowDC(window), (BITMAPINFO*)&bi.bmiHeader, iUsage,
+                                      (void**)&new_data, NULL, 0);
+
+    if (!new_bitmap) {
+      int err = GetLastError();
+      throw rdr::SystemException("unable to create DIB section", err);
+    }
+
+    vlog.debug("recreateBuffer()");
+  } else {
+    vlog.debug("one of area or format not set");
+  }
+
+  if (new_bitmap && bitmap) {
+    vlog.debug("preserving bitmap contents");
+
+    // Copy the contents across
+    if (device) {
+      if (format.bpp <= 8)
+        copyPaletteToDIB(palette, device, new_bitmap);
+      BitmapDC src_dev(device, bitmap);
+      BitmapDC dest_dev(device, new_bitmap);
+      BitBlt(dest_dev, 0, 0, width_, height_, src_dev, 0, 0, SRCCOPY);
+    } else {
+      WindowDC wndDC(window);
+      if (format.bpp <= 8)
+        copyPaletteToDIB(palette, wndDC, new_bitmap);
+      BitmapDC src_dev(wndDC, bitmap);
+      BitmapDC dest_dev(wndDC, new_bitmap);
+      BitBlt(dest_dev, 0, 0, width_, height_, src_dev, 0, 0, SRCCOPY);
+    }
+  }
+  
+  if (bitmap) {
+    // Delete the old bitmap
+    DeleteObject(bitmap);
+    bitmap = 0;
+    data = 0;
+  }
+
+  if (new_bitmap) {
+    // Set up the new bitmap
+    bitmap = new_bitmap;
+    data = new_data;
+
+    // Determine the *actual* DIBSection format
+    DIBSECTION ds;
+    if (!GetObject(bitmap, sizeof(ds), &ds))
+      throw rdr::SystemException("GetObject", GetLastError());
+
+    // Correct the "stride" of the DIB
+    // *** This code DWORD aligns each row - is that right???
+    stride = width_;
+    int bytesPerRow = stride * format.bpp/8;
+    if (bytesPerRow % 4) {
+      bytesPerRow += 4 - (bytesPerRow % 4);
+      stride = (bytesPerRow * 8) / format.bpp;
+      vlog.info("adjusting DIB stride: %d to %d", width_, stride);
+    }
+
+    // Calculate the PixelFormat for the DIB
+    format.bigEndian = 0;
+    format.bpp = format.depth = ds.dsBm.bmBitsPixel;
+    format.trueColour = format.trueColour || format.bpp > 8;
+    if (format.bpp > 8) {
+
+      // Get the truecolour format used by the DIBSection
+      initMaxAndShift(ds.dsBitfields[0], &format.redMax, &format.redShift);
+      initMaxAndShift(ds.dsBitfields[1], &format.greenMax, &format.greenShift);
+      initMaxAndShift(ds.dsBitfields[2], &format.blueMax, &format.blueShift);
+
+      // Calculate the effective depth
+      format.depth = 0;
+      Pixel bits = ds.dsBitfields[0] | ds.dsBitfields[1] | ds.dsBitfields[2];
+      while (bits) {
+        format.depth++;
+        bits = bits >> 1;
+      }
+      if (format.depth > format.bpp)
+        throw Exception("Bad DIBSection format (depth exceeds bpp)");
+    } else {
+      // Set the DIBSection's palette
+      refreshPalette();
+    }
+
+  }
+}
+
+void DIBSectionBuffer::refreshPalette() {
+  if (format.bpp > 8) {
+    vlog.error("refresh palette called for truecolor DIB");
+    return;
+  }
+  vlog.debug("refreshing palette");
+  if (device)
+    copyPaletteToDIB(palette, device, bitmap);
+  else
+    copyPaletteToDIB(palette, WindowDC(window), bitmap);
+}
+
+
diff --git a/win/rfb_win32/DIBSectionBuffer.h b/win/rfb_win32/DIBSectionBuffer.h
new file mode 100644
index 0000000..ad1a310
--- /dev/null
+++ b/win/rfb_win32/DIBSectionBuffer.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- DIBSectionBuffer.h
+
+// A DIBSectionBuffer acts much like a standard PixelBuffer, but is associated
+// with a particular window on-screen and can be drawn into that window if
+// required, using the standard Win32 drawing operations.
+
+#ifndef __RFB_WIN32_DIB_SECTION_BUFFER_H__
+#define __RFB_WIN32_DIB_SECTION_BUFFER_H__
+
+#include <windows.h>
+#include <rfb/PixelBuffer.h>
+#include <rfb/Region.h>
+#include <rfb/ColourMap.h>
+#include <rfb/Exception.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    //
+    // -=- DIBSectionBuffer
+    //
+
+    class DIBSectionBuffer : public FullFramePixelBuffer, ColourMap {
+    public:
+      DIBSectionBuffer(HWND window);
+      DIBSectionBuffer(HDC device);
+      virtual ~DIBSectionBuffer();
+
+      virtual void setPF(const PixelFormat &pf);
+      virtual void setSize(int w, int h);
+
+      virtual int getStride() const {return stride;}
+
+      virtual ColourMap* getColourMap() const {return (ColourMap*)this;}
+
+      // - ColourMap interface
+      virtual void lookup(int index, int* r, int *g, int* b) {
+        *r = palette[index].r;
+        *g = palette[index].g;
+        *b = palette[index].b;
+      }
+  
+      // Custom colourmap interface
+      void setColour(int index, int r, int g, int b) {
+        palette[index].r = r;
+        palette[index].g = g;
+        palette[index].b = b;
+      }
+      void refreshPalette();
+
+      // *** virtual void copyRect(const Rect &dest, const Point &move_by_delta);
+    public:
+      HBITMAP bitmap;
+    protected:
+      void recreateBuffer();
+      Colour palette[256];
+      int stride;
+      HWND window;
+      HDC device;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_DIB_SECTION_BUFFER_H__
diff --git a/win/rfb_win32/DeviceContext.cxx b/win/rfb_win32/DeviceContext.cxx
new file mode 100644
index 0000000..4f70a1b
--- /dev/null
+++ b/win/rfb_win32/DeviceContext.cxx
@@ -0,0 +1,188 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/DeviceContext.h>
+#include <rfb_win32/CompatibleBitmap.h>
+#include <rfb_win32/BitmapInfo.h>
+#include <rdr/Exception.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace win32;
+
+
+static LogWriter vlog("DeviceContext");
+
+PixelFormat DeviceContext::getPF() const {
+  return getPF(dc);
+}
+
+PixelFormat DeviceContext::getPF(HDC dc) {
+  PixelFormat format;
+  CompatibleBitmap bitmap(dc, 1, 1);
+
+  // -=- Get the bitmap format information
+  BitmapInfo bi;
+  memset(&bi, 0, sizeof(bi));
+  bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+  bi.bmiHeader.biBitCount = 0;
+  if (!::GetDIBits(dc, bitmap, 0, 1, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS)) {
+    throw rdr::SystemException("unable to determine device pixel format", GetLastError());
+  }
+  if (!::GetDIBits(dc, bitmap, 0, 1, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS)) {
+    throw rdr::SystemException("unable to determine pixel shifts/palette", GetLastError());
+  }
+
+  // Set the initial format information
+  format.trueColour = bi.bmiHeader.biBitCount > 8;
+  format.bigEndian = 0;
+  format.bpp = bi.bmiHeader.biBitCount;
+
+  if (format.trueColour) {
+    DWORD rMask=0, gMask=0, bMask=0;
+
+    // Which true colour format is the DIB section using?
+    switch (bi.bmiHeader.biCompression) {
+    case BI_RGB:
+      // Default RGB layout
+      switch (bi.bmiHeader.biBitCount) {
+      case 16:
+        // RGB 555 - High Colour
+        vlog.info("16-bit High Colour");
+        rMask = 0x7c00;
+        bMask = 0x001f;
+        gMask = 0x03e0;
+        break;
+      case 24:
+      case 32:
+        // RGB 888 - True Colour
+        vlog.info("24/32-bit High Colour");
+        rMask = 0xff0000;
+        gMask = 0x00ff00;
+        bMask = 0x0000ff;
+        break;
+      default:
+        vlog.error("bits per pixel %u not supported", bi.bmiHeader.biBitCount);
+        throw rdr::Exception("unknown bits per pixel specified");
+      };
+      break;
+    case BI_BITFIELDS:
+      // Custom RGB layout
+      rMask = bi.mask.red;
+      gMask = bi.mask.green;
+      bMask = bi.mask.blue;
+      vlog.info("%lu-bit BitFields: (%lx, %lx, %lx)",
+                 bi.bmiHeader.biBitCount, rMask, gMask, bMask);
+      break;
+    };
+
+    // Convert the data we just retrieved
+    initMaxAndShift(rMask, &format.redMax, &format.redShift);
+    initMaxAndShift(gMask, &format.greenMax, &format.greenShift);
+    initMaxAndShift(bMask, &format.blueMax, &format.blueShift);
+
+    // Calculate the depth from the colour shifts
+    format.depth = 0;
+    Pixel bits = rMask | gMask | bMask;
+    while (bits) {
+      format.depth++;
+      bits = bits >> 1;
+    }
+
+    // Check that the depth & bpp are valid
+    if (format.depth > format.bpp) {
+      vlog.error("depth exceeds bits per pixel!");
+      format.bpp = format.depth;
+    }
+
+    // Correct the bits-per-pixel to something we're happy with
+    if (format.bpp <= 16)
+      format.bpp = 16;
+    else if (format.bpp <= 32)
+      format.bpp = 32;
+  } else {
+    // Palettised format - depth reflects number of colours,
+    // but bits-per-pixel is ALWAYS 8
+    format.depth = format.bpp;
+    if (format.bpp < 8)
+      format.bpp = 8;
+    vlog.info("%d-colour palettised", 1<<format.depth);
+  }
+
+  return format;
+}
+
+Rect DeviceContext::getClipBox() const {
+  return getClipBox(dc);
+}
+
+Rect DeviceContext::getClipBox(HDC dc) {
+  // Get the display dimensions
+  RECT cr;
+  if (!GetClipBox(dc, &cr))
+    throw rdr::SystemException("GetClipBox", GetLastError());
+  return Rect(cr.left, cr.top, cr.right, cr.bottom);
+}
+
+
+DeviceDC::DeviceDC(const TCHAR* deviceName) {
+  dc = ::CreateDC(_T("DISPLAY"), deviceName, NULL, NULL);
+  if (!dc)
+    throw rdr::SystemException("failed to create DeviceDC", GetLastError());
+}
+
+DeviceDC::~DeviceDC() {
+  if (dc)
+    DeleteDC(dc);
+}
+
+
+WindowDC::WindowDC(HWND wnd) : hwnd(wnd) {
+  dc = GetDC(wnd);
+  if (!dc)
+    throw rdr::SystemException("GetDC failed", GetLastError());
+}
+
+WindowDC::~WindowDC() {
+  if (dc)
+    ReleaseDC(hwnd, dc);
+}
+
+
+CompatibleDC::CompatibleDC(HDC existing) {
+  dc = CreateCompatibleDC(existing);
+  if (!dc)
+    throw rdr::SystemException("CreateCompatibleDC failed", GetLastError());
+}
+
+CompatibleDC::~CompatibleDC() {
+  if (dc)
+    DeleteDC(dc);
+}
+
+
+BitmapDC::BitmapDC(HDC hdc, HBITMAP hbitmap) : CompatibleDC(hdc){
+  oldBitmap = (HBITMAP)SelectObject(dc, hbitmap);
+  if (!oldBitmap)
+    throw rdr::SystemException("SelectObject to CompatibleDC failed",
+    GetLastError());
+}
+
+BitmapDC::~BitmapDC() {
+  SelectObject(dc, oldBitmap);
+}
diff --git a/win/rfb_win32/DeviceContext.h b/win/rfb_win32/DeviceContext.h
new file mode 100644
index 0000000..9d91cec
--- /dev/null
+++ b/win/rfb_win32/DeviceContext.h
@@ -0,0 +1,86 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// DeviceContext base class, wrapping Windows HDC, plus some
+// helper classes tailored to particular types of DC, such as
+// window and device DCs.
+
+#ifndef __RFB_WIN32_DEVICECONTEXT_H__
+#define __RFB_WIN32_DEVICECONTEXT_H__
+
+#include <windows.h>
+#include <rfb/PixelFormat.h>
+#include <rfb/Rect.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // Base class, providing methods to get the bounding (clip) box,
+    // and the pixel format, and access to the HDC itself.
+    class DeviceContext {
+    public:
+      DeviceContext() : dc(0) {}
+      virtual ~DeviceContext() {}
+      operator HDC() const {return dc;}
+      PixelFormat getPF() const;
+      static PixelFormat getPF(HDC dc);
+      Rect getClipBox() const;
+      static Rect getClipBox(HDC dc);
+    protected:
+      HDC dc;
+    };
+
+    // -=- DeviceContext that opens a specific display device
+    class DeviceDC : public DeviceContext {
+    public:
+      DeviceDC(const TCHAR* deviceName);
+      ~DeviceDC();
+    };
+
+    // Get a DC for a particular window's client area.
+    class WindowDC : public DeviceContext {
+    public:
+      WindowDC(HWND wnd);
+      virtual ~WindowDC();
+    protected:
+      HWND hwnd;
+    };
+
+    // Create a new DC, compatible with an existing one.
+    class CompatibleDC : public DeviceContext {
+    public:
+      CompatibleDC(HDC existing);
+      virtual ~CompatibleDC();
+    };
+
+    // Create a new DC, compatible with an existing one, and
+    // select the specified bitmap into it.
+    class BitmapDC : public CompatibleDC {
+    public:
+      BitmapDC(HDC hdc, HBITMAP hbitmap);
+      ~BitmapDC();
+    protected:
+      HBITMAP oldBitmap;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/DeviceFrameBuffer.cxx b/win/rfb_win32/DeviceFrameBuffer.cxx
new file mode 100644
index 0000000..8da894e
--- /dev/null
+++ b/win/rfb_win32/DeviceFrameBuffer.cxx
@@ -0,0 +1,289 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- DeviceFrameBuffer.cxx
+//
+// The DeviceFrameBuffer class encapsulates the pixel data of the system
+// display.
+
+#include <vector>
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <rfb_win32/DeviceContext.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb_win32/IconInfo.h>
+#include <rfb/VNCServer.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("DeviceFrameBuffer");
+
+BoolParameter DeviceFrameBuffer::useCaptureBlt("UseCaptureBlt",
+  "Use a slower capture method that ensures that alpha blended windows appear correctly",
+  true);
+
+
+// -=- DeviceFrameBuffer class
+
+DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect)
+  : DIBSectionBuffer(deviceContext), device(deviceContext), cursorBm(deviceContext),
+    ignoreGrabErrors(false)
+{
+
+  // -=- Firstly, let's check that the device has suitable capabilities
+
+  int capabilities = GetDeviceCaps(device, RASTERCAPS);
+  if (!(capabilities & RC_BITBLT)) {
+    throw Exception("device does not support BitBlt");
+  }
+  if (!(capabilities & RC_DI_BITMAP)) {
+    throw Exception("device does not support GetDIBits");
+  }
+  /*
+  if (GetDeviceCaps(device, PLANES) != 1) {
+    throw Exception("device does not support planar displays");
+  }
+  */
+
+  // -=- Get the display dimensions and pixel format
+
+  // Get the display dimensions
+  deviceCoords = DeviceContext::getClipBox(device);
+  if (!wRect.is_empty())
+    deviceCoords = wRect.translate(deviceCoords.tl);
+  int w = deviceCoords.width();
+  int h = deviceCoords.height();
+
+  // We can't handle uneven widths :(
+  if (w % 2) w--;
+
+  // Configure the underlying DIB to match the device
+  DIBSectionBuffer::setPF(DeviceContext::getPF(device));
+  DIBSectionBuffer::setSize(w, h);
+
+  // Configure the cursor buffer
+  cursorBm.setPF(format);
+
+  // Set up a palette if required
+  if (!format.trueColour)
+    updateColourMap();
+}
+
+DeviceFrameBuffer::~DeviceFrameBuffer() {
+}
+
+
+void
+DeviceFrameBuffer::setPF(const PixelFormat &pf) {
+  throw Exception("setPF not supported");
+}
+
+void
+DeviceFrameBuffer::setSize(int w, int h) {
+  throw Exception("setSize not supported");
+}
+
+
+#ifndef CAPTUREBLT
+#define CAPTUREBLT 0x40000000
+#endif
+
+void
+DeviceFrameBuffer::grabRect(const Rect &rect) {
+  BitmapDC tmpDC(device, bitmap);
+
+  // Map the rectangle coords from VNC Desktop-relative to device relative - usually (0,0)
+  Point src = desktopToDevice(rect.tl);
+
+  // Note: Microsoft's documentation lies directly about CAPTUREBLT and claims it works on 98/ME
+  //       If you try CAPTUREBLT on 98 then you get blank output...
+  if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y, rect.width(), rect.height(), device, src.x, src.y,
+    (osVersion.isPlatformNT && useCaptureBlt) ? (CAPTUREBLT | SRCCOPY) : SRCCOPY)) {
+    if (ignoreGrabErrors)
+      vlog.error("BitBlt failed:%ld", GetLastError());
+    else
+      throw rdr::SystemException("BitBlt failed", GetLastError());
+  }
+}
+
+void
+DeviceFrameBuffer::grabRegion(const Region &rgn) {
+  std::vector<Rect> rects;
+  std::vector<Rect>::const_iterator i;
+  rgn.get_rects(&rects);
+  for(i=rects.begin(); i!=rects.end(); i++) {
+    grabRect(*i);
+  }
+  ::GdiFlush();
+}
+
+
+void copyDevicePaletteToDIB(HDC dc, DIBSectionBuffer* dib) {
+  // - Fetch the system palette for the framebuffer
+  PALETTEENTRY syspalette[256];
+  UINT entries = ::GetSystemPaletteEntries(dc, 0, 256, syspalette);
+
+  if (entries == 0) {
+    vlog.info("resorting to standard 16 color palette");
+    for (unsigned int i=0;i<256;i++) {
+      int v = (i%16) >= 8 ? 127 : 255;
+      syspalette[i].peRed = i & 1 ? v : 0;
+      syspalette[i].peGreen = i & 2 ? v : 0;
+      syspalette[i].peBlue = i & 4 ? v : 0;
+    }
+  } else {
+    vlog.info("framebuffer has %u palette entries", entries);
+  }
+
+  // - Update the bitmap's stored copy of the palette
+  for (unsigned int i=0;i<256;i++) {
+    int r, g, b;
+    r = (syspalette[i].peRed << 8) + 0x80;
+    g = (syspalette[i].peGreen << 8) + 0x80;
+    b = (syspalette[i].peBlue << 8) + 0x80;
+    dib->setColour(i, r, g, b);
+  }
+
+  // - Update the DIB section to use the palette
+  dib->refreshPalette();
+}
+
+
+void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server)
+{
+  // - If hCursor is null then there is no cursor - clear the old one
+
+  if (hCursor == 0) {
+    server->setCursor(0, 0, Point(), 0, 0);
+    return;
+  }
+
+  try {
+
+    // - Get the size and other details about the cursor.
+
+    IconInfo iconInfo((HICON)hCursor);
+
+    BITMAP maskInfo;
+    if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo))
+      throw rdr::SystemException("GetObject() failed", GetLastError());
+    if (maskInfo.bmPlanes != 1)
+      throw rdr::Exception("unsupported multi-plane cursor");
+    if (maskInfo.bmBitsPixel != 1)
+      throw rdr::Exception("unsupported cursor mask format");
+
+    // - Create the cursor pixel buffer and mask storage
+    //   NB: The cursor pixel buffer is NOT used here.  Instead, we
+    //   pass the cursorBm.data pointer directly, to save overhead.
+
+    cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight);
+    cursor.setPF(format);
+    cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot);
+
+    // - Get the AND and XOR masks.  There is only an XOR mask if this is not a
+    // colour cursor.
+
+    if (!iconInfo.hbmColor)
+      cursor.setSize(cursor.width(), cursor.height() / 2);
+    rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight);
+    rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes;
+
+    if (!GetBitmapBits(iconInfo.hbmMask,
+                       maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf))
+      throw rdr::SystemException("GetBitmapBits failed", GetLastError());
+
+    // Configure the cursor bitmap
+    cursorBm.setSize(cursor.width(), cursor.height());
+
+    // Copy the palette into it if required
+    if (format.bpp <= 8)
+      copyDevicePaletteToDIB(device, &cursorBm);
+
+    // Draw the cursor into the bitmap
+    BitmapDC dc(device, cursorBm.bitmap);
+    if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT))
+      throw rdr::SystemException("unable to render cursor", GetLastError());
+
+    // Replace any XORed pixels with xorColour, because RFB doesn't support
+    // XORing of cursors.  XORing is used for the I-beam cursor, which is most
+    // often used over a white background, but also sometimes over a black
+    // background.  We set the XOR'd pixels to black, then draw a white outline
+    // around the whole cursor.
+
+    // *** should we replace any pixels not set in mask to zero, to ensure
+    // that irrelevant data doesn't screw compression?
+
+    bool doOutline = false;
+    if (!iconInfo.hbmColor) {
+      Pixel xorColour = format.pixelFromRGB(0, 0, 0, cursorBm.getColourMap());
+      for (int y = 0; y < cursor.height(); y++) {
+        bool first = true;
+        for (int x = 0; x < cursor.width(); x++) {
+          int byte = y * maskInfo.bmWidthBytes + x / 8;
+          int bit = 7 - x % 8;
+          if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit)))
+          {
+            mask.buf[byte] &= ~(1 << bit);
+
+            switch (format.bpp) {
+            case 8:
+              ((rdr::U8*)cursorBm.data)[y * cursor.width() + x] = xorColour;  break;
+            case 16:
+              ((rdr::U16*)cursorBm.data)[y * cursor.width() + x] = xorColour; break;
+            case 32:
+              ((rdr::U32*)cursorBm.data)[y * cursor.width() + x] = xorColour; break;
+            }
+
+            doOutline = true;
+          }
+        }
+      }
+    }
+
+    // Finally invert the AND mask so it's suitable for RFB and pack it into
+    // the minimum number of bytes per row.
+
+    int maskBytesPerRow = (cursor.width() + 7) / 8;
+
+    for (int j = 0; j < cursor.height(); j++) {
+      for (int i = 0; i < maskBytesPerRow; i++)
+        cursor.mask.buf[j * maskBytesPerRow + i]
+          = ~mask.buf[j * maskInfo.bmWidthBytes + i];
+    }
+
+    if (doOutline) {
+      vlog.debug("drawing cursor outline!");
+      memcpy(cursor.data, cursorBm.data, cursor.dataLen());
+      cursor.drawOutline(format.pixelFromRGB(0xffff, 0xffff, 0xffff, cursorBm.getColourMap()));
+      memcpy(cursorBm.data, cursor.data, cursor.dataLen());
+    }
+
+    server->setCursor(cursor.width(), cursor.height(), cursor.hotspot,
+                      cursorBm.data, cursor.mask.buf);
+  } catch (rdr::Exception& e) {
+    vlog.error(e.str());
+  }
+}
+
+
+void
+DeviceFrameBuffer::updateColourMap() {
+  if (!format.trueColour)
+    copyDevicePaletteToDIB(device, this);
+}
diff --git a/win/rfb_win32/DeviceFrameBuffer.h b/win/rfb_win32/DeviceFrameBuffer.h
new file mode 100644
index 0000000..7718c33
--- /dev/null
+++ b/win/rfb_win32/DeviceFrameBuffer.h
@@ -0,0 +1,106 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- DeviceFrameBuffer.h
+//
+// The DeviceFrameBuffer class encapsulates the pixel data of a supplied
+// Device Context Handle (HDC)
+
+// *** THIS INTERFACE NEEDS TIDYING TO SEPARATE COORDINATE SYSTEMS BETTER ***
+
+#ifndef __RFB_WIN32_DEVICE_FRAME_BUFFER_H__
+#define __RFB_WIN32_DEVICE_FRAME_BUFFER_H__
+
+#include <windows.h>
+#include <rfb_win32/DIBSectionBuffer.h>
+#include <rfb/Cursor.h>
+#include <rfb/Region.h>
+#include <rfb/Exception.h>
+#include <rfb/Configuration.h>
+
+namespace rfb {
+
+  class VNCServer;
+
+  namespace win32 {
+
+    // -=- DeviceFrameBuffer interface
+
+    // DeviceFrameBuffer is passed an HDC referring to a window or to
+    // the entire display.  It may also be passed a rectangle specifying
+    // the Device-relative coordinates of the actual rectangle to treat
+    // as the desktop.
+
+    // Coordinate systems start getting really annoying here.  There are
+    // three different "origins" to which coordinates might be relative:
+    //
+    // Desktop - VNC coordinates, top-left always (0,0)
+    // Device - DC coordinates.  Top-left *usually (0,0) but could be other.
+    // Window - coordinates relative to the specified sub-rectangle within
+    //          the supplied DC.
+    // Screen - Coordinates relative to the entire Windows virtual screen.
+    //          The virtual screen includes all monitors that are part of
+    //          the Windows desktop.
+
+    // The data member is made to point to an internal mirror of the
+    // current display data.  Individual rectangles or regions of the
+    // buffer can be brought up to date by calling the grab functions.
+
+    class DeviceFrameBuffer : public DIBSectionBuffer {
+    public:
+      DeviceFrameBuffer(HDC deviceContext, const Rect& area_=Rect());
+      virtual ~DeviceFrameBuffer();
+
+      // - FrameBuffer overrides
+
+      virtual void grabRect(const Rect &rect);
+      virtual void grabRegion(const Region &region);
+
+      // - DIBSectionBuffer overrides
+      
+      virtual void setPF(const PixelFormat& pf);
+      virtual void setSize(int w, int h);
+      
+      // - DeviceFrameBuffer specific methods
+
+      void setCursor(HCURSOR c, VNCServer* server);
+      void updateColourMap();
+
+      // Set whether grabRect should ignore errors or throw exceptions
+      // Only set this if you are sure you'll capture the errors some other way!
+      void setIgnoreGrabErrors(bool ie) {ignoreGrabErrors=ie;}
+      
+      static BoolParameter useCaptureBlt;
+
+    protected:
+      // Translate supplied Desktop coordinates into Device-relative coordinates
+      // This translation may have been affected at start-time by the supplied sub-rect.
+      Point desktopToDevice(const Point p) const {return p.translate(deviceCoords.tl);}
+
+      HDC device;
+      DIBSectionBuffer cursorBm;
+      Cursor cursor;
+      Rect deviceCoords;
+      bool ignoreGrabErrors;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_DEVICE_FRAME_BUFFER_H__
diff --git a/win/rfb_win32/Dialog.cxx b/win/rfb_win32/Dialog.cxx
new file mode 100644
index 0000000..398334f
--- /dev/null
+++ b/win/rfb_win32/Dialog.cxx
@@ -0,0 +1,391 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Dialog.cxx
+
+// Base-class for any Dialog classes we might require
+
+#include <rfb_win32/Dialog.h>
+#include <rfb_win32/TCharArray.h>
+#include <rfb/LogWriter.h>
+#include <rdr/Exception.h>
+#include <rfb_win32/Win32Util.h>
+
+#ifdef _DIALOG_CAPTURE
+#ifdef PropSheet_IndexToId
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <extra/LoadBMP.cxx>
+#else
+#undef _DIALOG_CAPTURE
+#pragma message("  NOTE: Not building Dialog Capture support.")
+#endif
+#endif
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter dlog("Dialog");
+static LogWriter plog("PropSheet");
+
+
+Dialog::Dialog(HINSTANCE inst_)
+: inst(inst_), alreadyShowing(false), handle(0)
+{
+}
+
+Dialog::~Dialog()
+{
+}
+
+
+bool Dialog::showDialog(const TCHAR* resource, HWND owner)
+{
+  if (alreadyShowing) return false;
+  handle = 0;
+  alreadyShowing = true;
+  INT_PTR result = DialogBoxParam(inst, resource, owner,
+                                  staticDialogProc, (LPARAM)this);
+  if (result<0)
+    throw rdr::SystemException("DialogBoxParam failed", GetLastError());
+  alreadyShowing = false;
+  return (result == 1);
+}
+
+
+bool Dialog::isItemChecked(int id) {
+  return SendMessage(GetDlgItem(handle, id), BM_GETCHECK, 0, 0) == BST_CHECKED;
+}
+int Dialog::getItemInt(int id) {
+  BOOL trans;
+  int result = GetDlgItemInt(handle, id, &trans, TRUE);
+  if (!trans)
+    throw rdr::Exception("unable to read dialog Int");
+  return result;
+}
+TCHAR* Dialog::getItemString(int id) {
+  TCharArray tmp(256);
+  if (!GetDlgItemText(handle, id, tmp.buf, 256))
+    tmp.buf[0] = 0;
+  return tmp.takeBuf();
+}
+
+void Dialog::setItemChecked(int id, bool state) {
+  dlog.debug("bool[%d]=%d", id, (int)state);
+  SendMessage(GetDlgItem(handle, id), BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
+}
+void Dialog::setItemInt(int id, int value) {
+  dlog.debug("int[%d]=%d", id, value);
+  SetDlgItemInt(handle, id, value, TRUE);
+}
+void Dialog::setItemString(int id, const TCHAR* s) {
+  dlog.debug("string[%d]=%s", id, (const char*)CStr(s));
+  SetDlgItemText(handle, id, s);
+}
+
+
+void Dialog::enableItem(int id, bool state) {
+  dlog.debug("enable[%d]=%d", id, (int)state);
+  EnableWindow(GetDlgItem(handle, id), state);
+}
+
+
+
+
+BOOL CALLBACK Dialog::staticDialogProc(HWND hwnd, UINT msg,
+				       WPARAM wParam, LPARAM lParam)
+{
+  if (msg == WM_INITDIALOG)
+    SetWindowLong(hwnd, GWL_USERDATA, (LONG)lParam);
+
+  LONG self = GetWindowLong(hwnd, GWL_USERDATA);
+  if (!self) return FALSE;
+
+  return ((Dialog*)self)->dialogProc(hwnd, msg, wParam, lParam);
+}
+
+BOOL Dialog::dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+  switch (msg) {
+
+  case WM_INITDIALOG:
+    handle = hwnd;
+    initDialog();
+    return TRUE;
+
+  case WM_COMMAND:
+    switch (LOWORD(wParam)) {
+    case IDOK:
+      if (onOk()) {
+        EndDialog(hwnd, 1);
+        return TRUE;
+      }
+      return FALSE;
+    case IDCANCEL:
+      EndDialog(hwnd, 0);
+      return TRUE;
+    default:
+      return onCommand(LOWORD(wParam), HIWORD(wParam));
+    };
+
+  case WM_HELP:
+    return onHelp(((HELPINFO*)lParam)->iCtrlId);
+
+  }
+
+  return FALSE;
+}
+
+
+PropSheetPage::PropSheetPage(HINSTANCE inst, const TCHAR* id) : Dialog(inst), propSheet(0) {
+  page.dwSize = sizeof(page);
+  page.dwFlags = 0; // PSP_USECALLBACK;
+  page.hInstance = inst;
+  page.pszTemplate = id;
+  page.pfnDlgProc = staticPageProc;
+  page.lParam = (LPARAM)this;
+  page.pfnCallback = 0; // staticPageProc;
+}
+
+PropSheetPage::~PropSheetPage() {
+}
+
+
+BOOL CALLBACK PropSheetPage::staticPageProc(HWND hwnd, UINT msg,
+				       WPARAM wParam, LPARAM lParam)
+{
+  if (msg == WM_INITDIALOG)
+    SetWindowLong(hwnd, GWL_USERDATA, ((PROPSHEETPAGE*)lParam)->lParam);
+
+  LONG self = GetWindowLong(hwnd, GWL_USERDATA);
+  if (!self) return FALSE;
+
+  return ((PropSheetPage*)self)->dialogProc(hwnd, msg, wParam, lParam);
+}
+
+BOOL PropSheetPage::dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+  switch (msg) {
+
+  case WM_INITDIALOG:
+    handle = hwnd;
+    initDialog();
+    return TRUE;
+
+  case WM_NOTIFY:
+    switch (((NMHDR*)lParam)->code) {
+    case PSN_APPLY:
+      onOk();
+      return FALSE;
+    };
+    return FALSE;
+
+  case WM_COMMAND:
+    return onCommand(LOWORD(wParam), HIWORD(wParam));
+
+  case WM_HELP:
+    return onHelp(((HELPINFO*)lParam)->iCtrlId);
+
+  }
+
+  return FALSE;
+}
+
+
+PropSheet::PropSheet(HINSTANCE inst_, const TCHAR* title_, std::list<PropSheetPage*> pages_, HICON icon_)
+: title(tstrDup(title_)), inst(inst_), pages(pages_), alreadyShowing(0), handle(0), icon(icon_) {
+}
+
+PropSheet::~PropSheet() {
+}
+
+
+// For some reason, DLGTEMPLATEEX isn't defined in the Windows headers - go figure...
+struct DLGTEMPLATEEX {
+  WORD dlgVer;
+  WORD signature;
+  DWORD helpID;
+  DWORD exStyle;
+  DWORD style;
+  WORD cDlgItems;
+  short x;
+  short y;
+  short cx;
+  short cy;
+};
+
+static int CALLBACK removeCtxtHelp(HWND hwnd, UINT message, LPARAM lParam) {
+  if (message == PSCB_PRECREATE) {
+    // Remove the context-help style, to remove the titlebar ? button
+    // *** Nasty hack to cope with new & old dialog template formats...
+    if (((DLGTEMPLATEEX*)lParam)->signature == 0xffff)
+      ((DLGTEMPLATEEX*)lParam)->style &= ~DS_CONTEXTHELP;
+    else
+      ((LPDLGTEMPLATE)lParam)->style &= ~DS_CONTEXTHELP;
+  }
+  return TRUE;
+}
+
+
+bool PropSheet::showPropSheet(HWND owner, bool showApply, bool showCtxtHelp, bool capture) {
+  if (alreadyShowing) return false;
+  alreadyShowing = true;
+  int count = pages.size();
+
+  HPROPSHEETPAGE* hpages = new HPROPSHEETPAGE[count];
+  try {
+    // Create the PropertSheet page GDI objects.
+    std::list<PropSheetPage*>::iterator pspi;
+    int i = 0;
+    for (pspi=pages.begin(); pspi!=pages.end(); pspi++) {
+      hpages[i] = CreatePropertySheetPage(&((*pspi)->page));
+      (*pspi)->setPropSheet(this);
+      i++;
+    }
+ 
+    // Initialise and create the PropertySheet itself
+    PROPSHEETHEADER header;
+    header.dwSize = PROPSHEETHEADER_V1_SIZE;
+    header.dwFlags = PSH_MODELESS | (showApply ? 0 : PSH_NOAPPLYNOW) | (showCtxtHelp ? 0 : PSH_USECALLBACK);
+    header.pfnCallback = removeCtxtHelp;
+    header.hwndParent = owner;
+    header.hInstance = inst;
+    header.pszCaption = title.buf;
+    header.nPages = count;
+    header.nStartPage = 0;
+    header.phpage = hpages;
+    if (icon) {
+      header.hIcon = icon;
+      header.dwFlags |= PSH_USEHICON;
+    }
+
+    handle = (HWND)PropertySheet(&header);
+    if ((handle == 0) || (handle == (HWND)-1))
+      throw rdr::SystemException("PropertySheet failed", GetLastError());
+    centerWindow(handle, owner);
+    plog.info("created %lx", handle);
+
+#ifdef _DIALOG_CAPTURE
+    if (capture) {
+      plog.info("capturing \"%s\"", (const char*)CStr(title.buf));
+      char* tmpdir = getenv("TEMP");
+      HDC dc = GetWindowDC(handle);
+      DeviceFrameBuffer fb(dc);
+      int i=0;
+      while (true) {
+        int id = PropSheet_IndexToId(handle, i);
+        if (!id) break;
+        PropSheet_SetCurSelByID(handle, id);
+        MSG msg;
+        while (PeekMessage(&msg, handle, 0, 0, PM_REMOVE)) {
+          if (!PropSheet_IsDialogMessage(handle, &msg))
+            DispatchMessage(&msg);
+        }
+        fb.grabRect(fb.getRect());
+        TCHAR title[128];
+        if (!GetWindowText(PropSheet_GetCurrentPageHwnd(handle), title, sizeof(title)))
+          _stprintf(title, _T("capture%d"), i);
+        CharArray pageTitle(strDup(title));
+        for (int j=0; j<strlen(pageTitle.buf); j++) {
+          if (pageTitle.buf[j] == '/' || pageTitle.buf[j] == '\\' || pageTitle.buf[j] == ':')
+            pageTitle.buf[j] = '-';
+        }
+        char filename[256];
+        sprintf(filename, "%s\\%s.bmp", tmpdir, pageTitle.buf);
+        vlog.debug("writing to %s", filename);
+        saveBMP(filename, &fb);
+        i++;
+      }
+      ReleaseDC(handle, dc);
+    } else {
+#endif
+      try {
+        if (owner)
+          EnableWindow(owner, FALSE);
+        // Run the PropertySheet
+        MSG msg;
+        while (GetMessage(&msg, 0, 0, 0)) {
+          if (!PropSheet_IsDialogMessage(handle, &msg))
+            DispatchMessage(&msg);
+          if (!PropSheet_GetCurrentPageHwnd(handle))
+            break;
+        }
+        if (owner)
+          EnableWindow(owner, TRUE);
+      } catch (...) {
+        if (owner)
+          EnableWindow(owner, TRUE);
+        throw;
+      }
+#ifdef _DIALOG_CAPTURE
+    }
+#endif
+
+    plog.info("finished %lx", handle);
+
+    DestroyWindow(handle);
+    handle = 0;
+    alreadyShowing = false;
+
+    // Clear up the pages' GDI objects
+    for (pspi=pages.begin(); pspi!=pages.end(); pspi++)
+      (*pspi)->setPropSheet(0);
+    delete [] hpages; hpages = 0;
+
+    return true;
+  } catch (rdr::Exception) {
+    alreadyShowing = false;
+
+    std::list<PropSheetPage*>::iterator pspi;
+    for (pspi=pages.begin(); pspi!=pages.end(); pspi++)
+      (*pspi)->setPropSheet(0);
+    delete [] hpages; hpages = 0;
+
+    throw;
+  }
+}
+
+void PropSheet::reInitPages() {
+  plog.debug("reInitPages %lx", handle);
+  std::list<PropSheetPage*>::iterator pspi;
+  for (pspi=pages.begin(); pspi!=pages.end(); pspi++) {
+    if ((*pspi)->handle)
+      (*pspi)->initDialog();
+  }
+}
+
+bool PropSheet::commitPages() {
+  plog.debug("commitPages %lx", handle);
+  bool result = true;
+  std::list<PropSheetPage*>::iterator pspi;
+  for (pspi=pages.begin(); pspi!=pages.end(); pspi++) {
+    if ((*pspi)->handle)
+      result = result && (*pspi)->onOk();
+  }
+  return result;
+}
+
+
+void PropSheetPage::setChanged(bool changed) {
+  if (propSheet) {
+    plog.debug("setChanged[%lx(%lx)]=%d", handle, propSheet->handle, (int)changed);
+    if (changed)
+      PropSheet_Changed(propSheet->handle, handle);
+    else
+      PropSheet_UnChanged(propSheet->handle, handle);
+  }
+}
diff --git a/win/rfb_win32/Dialog.h b/win/rfb_win32/Dialog.h
new file mode 100644
index 0000000..9784ba4
--- /dev/null
+++ b/win/rfb_win32/Dialog.h
@@ -0,0 +1,158 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- RegConfig.h
+
+// Class which monitors the registry and reads in the registry settings
+// whenever they change, or are added or removed.
+
+#ifndef __RFB_WIN32_DIALOG_H__
+#define __RFB_WIN32_DIALOG_H__
+
+#include <windows.h>
+#include <prsht.h>
+#include <list>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // Dialog - A simple Win32 Dialog box.  A derived class of Dialog overrides the
+    // initDialog(), command() and ok() methods to take appropriate action.  A
+    // simple dialog box can be displayed by creating a Dialog object and calling
+    // show().
+
+    class Dialog {
+    public:
+
+      Dialog(HINSTANCE inst);
+      virtual ~Dialog();
+
+      // showDialog() displays the dialog box.  It returns when it has been dismissed,
+      // returning true if "OK" was pressed, false otherwise.  The resource
+      // argument identifies the dialog resource (often a MAKEINTRESOURCE macro
+      // expansion), and owner is an optional window handle - the corresponding
+      // window is disabled while the dialog box is displayed.
+
+      bool showDialog(const TCHAR* resource, HWND owner=0);
+
+      // initDialog() is called upon receipt of the WM_INITDIALOG message.
+
+      virtual void initDialog() {}
+
+      // onCommand() is called upon receipt of a WM_COMMAND message item other than IDOK
+      // or IDCANCEL.  It should return true if the command has been handled.
+
+      virtual bool onCommand(int item, int cmd) { return false; }
+
+      // onHelp() is called upon receipt of a WM_MENU message.  This indicates that
+      // context-specific help should be displayed, for a dialog control, for example.
+      // It should return true if the command has been handled.
+
+      virtual bool onHelp(int item) { return false; }
+
+      // onOk() is called when the OK button is pressed.  The hwnd argument is the
+      // dialog box's window handle.
+
+      virtual bool onOk() { return true; }
+
+      // Read the states of items
+      bool isItemChecked(int id);
+      int getItemInt(int id);
+      TCHAR* getItemString(int id); // Recipient owns string storage
+      
+      // Set the states of items
+      void setItemChecked(int id, bool state);
+      void setItemInt(int id, int value);
+      void setItemString(int id, const TCHAR* s);
+
+      // enableItem is used to grey out an item, making it inaccessible, or to
+      // re-enable it.
+      void enableItem(int id, bool state);
+
+    protected:
+      static BOOL CALLBACK staticDialogProc(HWND hwnd, UINT msg,
+			      WPARAM wParam, LPARAM lParam);
+      virtual BOOL dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+      HINSTANCE inst;
+      HWND handle;
+      bool alreadyShowing;
+    };
+
+    // PropertySheetPage 
+    // Class used to define property pages within a PropertySheet.
+    // Each page is associated with a particular dialog resource, indicated by
+    // the "id" parameter supplied to the constructor.
+
+    class PropSheetPage;
+
+    class PropSheet {
+    public:
+      PropSheet(HINSTANCE inst, const TCHAR* title, std::list<PropSheetPage*> pages, HICON icon=0);
+      virtual ~PropSheet();
+
+      // Display the PropertySheet
+      bool showPropSheet(HWND owner, bool showApply = false, bool showCtxtHelp = false, bool capture=false);
+      
+      // Calls initDialog again for each page that has already had it called.
+      // Note: If a page hasn't been seen yet, it won't have been called.
+      // Note: This must only be called while the property sheet is visible.
+      void reInitPages();
+
+      // Calls onOk for each page that has had initDialog called, and returns
+      // false if any one of them returns false, or true otherwise.  ALL the
+      // onOk() methods will be called, even if one of them fails.
+      // Note: If a page hasn't been seen yet, it won't have been called.
+      // Note: This must only be called while the property sheet is visible.
+      bool commitPages();
+
+      friend class PropSheetPage;
+
+    protected:
+      HWND owner;
+      HICON icon;
+      std::list<PropSheetPage*> pages;
+      HINSTANCE inst;
+      TCharArray title;
+      HWND handle;
+      bool alreadyShowing;
+    };
+
+    class PropSheetPage : public Dialog {
+    public:
+      PropSheetPage(HINSTANCE inst, const TCHAR* id);
+      virtual ~PropSheetPage();
+
+      void setChanged(bool changed);
+
+      friend class PropSheet;
+
+    protected:
+      void setPropSheet(PropSheet* ps) {propSheet = ps;};
+      static BOOL CALLBACK staticPageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+      virtual BOOL dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+      PROPSHEETPAGE page;
+      PropSheet* propSheet;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_DIALOG_H__
diff --git a/win/rfb_win32/DynamicFn.cxx b/win/rfb_win32/DynamicFn.cxx
new file mode 100644
index 0000000..e933f24
--- /dev/null
+++ b/win/rfb_win32/DynamicFn.cxx
@@ -0,0 +1,45 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/TCharArray.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("DynamicFn");
+
+
+DynamicFnBase::DynamicFnBase(const TCHAR* dllName, const char* fnName) : dllHandle(0), fnPtr(0) {
+  dllHandle = LoadLibrary(dllName);
+  if (!dllHandle) {
+    vlog.info("DLL %s not found (%d)", (const char*)CStr(dllName), GetLastError());
+    return;
+  }
+  fnPtr = GetProcAddress(dllHandle, fnName);
+  if (!fnPtr)
+    vlog.info("proc %s not found in %s (%d)", fnName, (const char*)CStr(dllName), GetLastError());
+}
+
+DynamicFnBase::~DynamicFnBase() {
+  if (dllHandle)
+    FreeLibrary(dllHandle);
+}
+
+
diff --git a/win/rfb_win32/DynamicFn.h b/win/rfb_win32/DynamicFn.h
new file mode 100644
index 0000000..57fdbec
--- /dev/null
+++ b/win/rfb_win32/DynamicFn.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// Helper class managing dynamic linkage to DLL functions.
+
+#ifndef __RFB_WIN32_DYNAMICFN_H__
+#define __RFB_WIN32_DYNAMICFN_H__
+
+#include <windows.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class DynamicFnBase {
+    public:
+      DynamicFnBase(const TCHAR* dllName, const char* fnName);
+      ~DynamicFnBase();
+      bool isValid() const {return fnPtr != 0;}
+    protected:
+      void* fnPtr;
+      HMODULE dllHandle;
+    private:
+      DynamicFnBase(const DynamicFnBase&);
+      DynamicFnBase operator=(const DynamicFnBase&);
+    };
+
+    template<class T> class DynamicFn : public DynamicFnBase {
+    public:
+      DynamicFn(const TCHAR* dllName, const char* fnName) : DynamicFnBase(dllName, fnName) {}
+      T operator *() const {return (T)fnPtr;};
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/EventManager.cxx b/win/rfb_win32/EventManager.cxx
new file mode 100644
index 0000000..0f9993b
--- /dev/null
+++ b/win/rfb_win32/EventManager.cxx
@@ -0,0 +1,103 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/EventManager.h>
+#include <rdr/Exception.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("EventManager");
+
+
+EventManager::EventManager() : eventCount(0) {
+}
+
+EventManager::~EventManager() {
+}
+
+
+bool EventManager::addEvent(HANDLE event, EventHandler* ecb) {
+  if (eventCount >= MAXIMUM_WAIT_OBJECTS-1)
+    return false;
+  events[eventCount] = event;
+  handlers[eventCount] = ecb;
+  eventCount++;
+  return true;
+}
+
+void EventManager::removeEvent(HANDLE event) {
+  for (int i=0; i<eventCount; i++) {
+    if (events[i] == event) {
+      for (int j=i; j<eventCount-1; j++) {
+        events[j] = events[j+1];
+        handlers[j] = handlers[j+1];
+      }
+      eventCount--;
+      return;
+    }
+  }
+  throw rdr::Exception("Event not registered");
+}
+
+
+int EventManager::checkTimeouts() {
+  return 0;
+}
+
+BOOL EventManager::getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg) {
+  while (true) {
+    // - Process any pending timeouts
+    DWORD timeout = checkTimeouts();
+    if (timeout == 0)
+      timeout = INFINITE;
+
+    // - Events take precedence over messages
+    DWORD result;
+    if (eventCount) {
+      // - Check whether any events are set
+      result = WaitForMultipleObjects(eventCount, events, FALSE, 0);
+      if (result == WAIT_TIMEOUT) {
+        // - No events are set, so check for messages
+        if (PeekMessage(msg, hwnd, minMsg, maxMsg, PM_REMOVE)) 
+          return msg->message != WM_QUIT;
+
+        // - Block waiting for an event to be set, or a message
+        result = MsgWaitForMultipleObjects(eventCount, events, FALSE, timeout,
+                                           QS_ALLINPUT);
+        if (result == WAIT_OBJECT_0 + eventCount) {
+          // - Return the message, if any
+          if (PeekMessage(msg, hwnd, minMsg, maxMsg, PM_REMOVE)) 
+            return msg->message != WM_QUIT;
+          continue;
+        }
+      }
+    } else
+      return GetMessage(msg, hwnd, minMsg, maxMsg);
+
+    if ((result >= WAIT_OBJECT_0) && (result < (WAIT_OBJECT_0 + eventCount))) {
+      // - An event was set - call the handler
+      int index = result - WAIT_OBJECT_0;
+      handlers[index]->processEvent(events[index]);
+    } else if (result == WAIT_FAILED) {
+      // - An error has occurred, so return the error status code
+      return -1;
+    }
+  }
+}
diff --git a/win/rfb_win32/EventManager.h b/win/rfb_win32/EventManager.h
new file mode 100644
index 0000000..bb66e34
--- /dev/null
+++ b/win/rfb_win32/EventManager.h
@@ -0,0 +1,77 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- EventManager.h
+
+// Win32 event manager.  Caller supplies event & handler pairs and
+// then uses getMessage() in place of ::GetMessage() in the main
+// loop.  EventManager calls the event handler whenever the event
+// is set.
+// Ownership of events remains with the caller.
+// It is the responsibility of handlers to reset events.
+
+#ifndef __RFB_WIN32_EVENT_MGR_H__
+#define __RFB_WIN32_EVENT_MGR_H__
+
+#include <rfb_win32/Win32Util.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class EventHandler {
+    public:
+      virtual ~EventHandler() {}
+      virtual void processEvent(HANDLE event) = 0;
+    };
+
+    class EventManager {
+    public:
+      EventManager();
+      virtual ~EventManager();
+
+      // Add a Win32 event & handler for it
+      //   NB: The handler must call ResetEvent on the event.
+      //   NB: The caller retains ownership of the event.
+      virtual bool addEvent(HANDLE event, EventHandler* ecb);
+
+      // Remove a Win32 event
+      virtual void removeEvent(HANDLE event);
+
+      // getMessage
+      //   Waits for a message to become available on the thread's message queue,
+      //   and returns it.  If any registered events become set while waiting then
+      //   their handlers are called before returning.
+      //   Returns zero if the message is WM_QUIT, -1 in case of error, >0 otherwise.
+      virtual BOOL getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg);
+
+    protected:
+      // checkTimeouts
+      //   Derived classes should override this to perform any extra processing,
+      //   returning the maximum number of milliseconds after which the callback
+      //   should be called again.
+      virtual int checkTimeouts();
+
+      HANDLE events[MAXIMUM_WAIT_OBJECTS];
+      EventHandler* handlers[MAXIMUM_WAIT_OBJECTS-1];
+      int eventCount;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/FolderManager.cxx b/win/rfb_win32/FolderManager.cxx
new file mode 100644
index 0000000..a2fa08d
--- /dev/null
+++ b/win/rfb_win32/FolderManager.cxx
@@ -0,0 +1,279 @@
+/* Copyright (C) 2005 TightVNC Team.  All Rights Reserved.
+ * 
+ * Developed by Dennis Syrovatsky.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- FolderManager.cxx
+
+#include <rfb_win32/FolderManager.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+FolderManager::FolderManager()
+{
+
+}
+
+FolderManager::~FolderManager()
+{
+
+}
+      
+bool 
+FolderManager::createDir(char *pFullPath)
+{
+  if (CreateDirectory(pFullPath, NULL) == 0) return false;
+
+  return true;
+}
+
+bool
+FolderManager::renameIt(char *pPath, char *pOldName, char *pNewName)
+{
+  char fullOldName[FT_FILENAME_SIZE];
+  char fullNewName[FT_FILENAME_SIZE];
+
+  sprintf(fullOldName, "%s\\%s", pPath, pOldName);
+  sprintf(fullNewName, "%s\\%s", pPath, pNewName);
+
+  return renameIt(fullOldName, fullNewName);
+}
+
+bool 
+FolderManager::renameIt(char *pOldName, char *pNewName)
+{
+  if (MoveFile(pOldName, pNewName)) return true;
+
+  return false;
+}
+
+bool 
+FolderManager::deleteIt(char *pFullPath)
+{
+  FileInfo fileInfo;
+
+  FILEINFO FIStruct;
+  if (!getInfo(pFullPath, &FIStruct)) return false;
+
+  fileInfo.add(&FIStruct);
+
+  return deleteIt(&fileInfo);
+}
+
+bool
+FolderManager::deleteIt(char *pPrefix, FileInfo *pFI)
+{
+  char buf[FT_FILENAME_SIZE];
+  for (unsigned int i = 0; i < pFI->getNumEntries(); i++) {
+    sprintf(buf, "%s\\%s", pPrefix, pFI->getNameAt(i));
+    pFI->setNameAt(i,buf);
+  }
+  return deleteIt(pFI);
+}
+
+bool
+FolderManager::deleteIt(FileInfo *pFI)
+{
+  unsigned int num = pFI->getNumEntries();
+  unsigned int last = num - 1;
+
+  while (num > 0) {
+    if (pFI->getFlagsAt(last) & FT_ATTR_DIR) {
+      if (RemoveDirectory(pFI->getNameAt(last)) == 0) {
+        if (GetLastError() == ERROR_DIR_NOT_EMPTY) {
+          if (!getFolderInfoWithPrefix(pFI->getNameAt(last), pFI)) {
+            pFI->free();
+            return false;
+          }
+        }
+      } else {
+        pFI->deleteAt(last);
+      }
+    } else {
+      if (DeleteFile(pFI->getNameAt(last)) == 0) {
+        pFI->free();
+        return false;
+      } else {
+        pFI->deleteAt(last);
+      }
+    }
+    num = pFI->getNumEntries();
+    last = num - 1;
+  }
+
+  return true;
+}
+
+bool 
+FolderManager::getFolderInfoWithPrefix(char *pPrefix, FileInfo *pFileInfo)
+{
+  char prefix[FT_FILENAME_SIZE];
+  strcpy(prefix, pPrefix);
+
+  FileInfo tmpFileInfo;
+  if (!getDirInfo(prefix, &tmpFileInfo, 0)) {
+    tmpFileInfo.free();
+    return false;
+  } else {
+    char buf[FT_FILENAME_SIZE];
+    for (unsigned int i = 0; i < tmpFileInfo.getNumEntries(); i++) {
+      sprintf(buf, "%s\\%s", prefix, tmpFileInfo.getNameAt(i));
+      pFileInfo->add(buf, tmpFileInfo.getSizeAt(i), tmpFileInfo.getDataAt(i), tmpFileInfo.getFlagsAt(i));
+    }
+  }
+  tmpFileInfo.free();
+  return true;
+}
+    
+bool 
+FolderManager::getDirInfo(char *pPath, FileInfo *pFileInfo, unsigned int dirOnly)
+{
+  if (strlen(pPath) == 0) return getDrivesInfo(pFileInfo);
+  
+  char path[FT_FILENAME_SIZE];
+  sprintf(path, "%s\\*", pPath);
+  
+  WIN32_FIND_DATA FindFileData;
+  SetErrorMode(SEM_FAILCRITICALERRORS);
+  HANDLE handle = FindFirstFile(path, &FindFileData);
+  DWORD lastError = GetLastError();
+  SetErrorMode(0);
+  
+  if (handle != INVALID_HANDLE_VALUE) {
+    do {
+      if (strcmp(FindFileData.cFileName, ".") != 0 &&
+        strcmp(FindFileData.cFileName, "..") != 0) {
+        unsigned int lastWriteTime = getTime70(FindFileData.ftLastWriteTime);
+        if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {	
+          pFileInfo->add(FindFileData.cFileName, 0, lastWriteTime, FT_ATTR_DIR);
+        } else {
+          if (!dirOnly)
+            pFileInfo->add(FindFileData.cFileName, FindFileData.nFileSizeLow, lastWriteTime, FT_ATTR_FILE);
+        }
+      }
+      
+    } while (FindNextFile(handle, &FindFileData));
+  } else {
+    return false;
+  }
+  FindClose(handle);
+  return true;
+}
+
+bool 
+FolderManager::getDrivesInfo(FileInfo *pFileInfo)
+{
+  TCHAR szDrivesList[256];
+  if (GetLogicalDriveStrings(255, szDrivesList) == 0)
+    return false;
+  
+  int i = 0;
+  while (szDrivesList[i] != '\0') {
+    char *drive = strdup(&szDrivesList[i]);
+    char *backslash = strrchr(drive, '\\');
+    if (backslash != NULL)
+      *backslash = '\0';
+    pFileInfo->add(drive, 0, 0, FT_ATTR_DIR);
+    free(drive);
+    i += strcspn(&szDrivesList[i], "\0") + 1;
+  }
+  return true;
+}
+
+bool
+FolderManager::getInfo(char *pFullPath, FILEINFO *pFIStruct)
+{
+  WIN32_FIND_DATA FindFileData;
+  SetErrorMode(SEM_FAILCRITICALERRORS);
+  HANDLE hFile = FindFirstFile(pFullPath, &FindFileData);
+  DWORD lastError = GetLastError();
+  SetErrorMode(0);
+  if (hFile != INVALID_HANDLE_VALUE) {
+    FindClose(hFile);
+    strcpy(pFIStruct->name, pFullPath);
+    pFIStruct->info.data = getTime70(FindFileData.ftLastWriteTime);
+    if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {	
+      pFIStruct->info.size = 0;
+      pFIStruct->info.flags = FT_ATTR_DIR;
+      return true;
+    } else {
+      pFIStruct->info.size = FindFileData.nFileSizeLow;
+      pFIStruct->info.flags = FT_ATTR_FILE;
+      return true;
+    }
+  }
+  return false;
+}
+
+unsigned int 
+FolderManager::getTime70(FILETIME ftime)
+{
+  LARGE_INTEGER uli;
+  uli.LowPart = ftime.dwLowDateTime;
+  uli.HighPart = ftime.dwHighDateTime;
+  uli.QuadPart = (uli.QuadPart - 116444736000000000) / 10000000;
+  return uli.LowPart;
+}
+
+void 
+FolderManager::getFiletime(unsigned int time70, FILETIME *pftime)
+{
+  LONGLONG ll = Int32x32To64(time70, 10000000) + 116444736000000000;
+  pftime->dwLowDateTime = (DWORD) ll;
+  pftime->dwHighDateTime = (DWORD) (ll >> 32);
+}
+
+bool
+FolderManager::getDirSize(char *pFullPath, DWORD64 *dirSize)
+{
+  char fullPath[FT_FILENAME_SIZE];
+  FileInfo fi;
+  fi.add(pFullPath, 0, 0, FT_ATTR_DIR);
+  DWORD64 dirFileSize64 = 0;
+  do {
+    sprintf(fullPath, "%s\\*", fi.getNameAt(0));
+    WIN32_FIND_DATA FindFileData;
+    SetErrorMode(SEM_FAILCRITICALERRORS);
+    HANDLE hFile = FindFirstFile(fullPath, &FindFileData);
+    SetErrorMode(0);
+    
+    if (hFile != INVALID_HANDLE_VALUE) {
+      do {
+        if (strcmp(FindFileData.cFileName, ".") != 0 &&
+          strcmp(FindFileData.cFileName, "..") != 0) {
+          char buff[MAX_PATH];
+          if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {	
+            sprintf(buff, "%s\\%s", fi.getNameAt(0), FindFileData.cFileName);
+            fi.add(buff, 0, 0, FT_ATTR_DIR);
+          } else {
+            dirFileSize64 += FindFileData.nFileSizeLow;
+          }
+        }
+      } while (FindNextFile(hFile, &FindFileData));
+      FindClose(hFile);
+    }
+    fi.deleteAt(0);
+  } while (fi.getNumEntries() > 0);
+  
+  *dirSize = dirFileSize64;
+  return true;
+}
\ No newline at end of file
diff --git a/win/rfb_win32/FolderManager.h b/win/rfb_win32/FolderManager.h
new file mode 100644
index 0000000..24923dd
--- /dev/null
+++ b/win/rfb_win32/FolderManager.h
@@ -0,0 +1,66 @@
+/* Copyright (C) 2005 TightVNC Team.  All Rights Reserved.
+ *
+ * Developed by Dennis Syrovatsky.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- FolderManager.h
+
+#ifndef __RFB_WIN32_FOLDERMANAGER_H__
+#define __RFB_WIN32_FOLDERMANAGER_H__
+
+#include <windows.h>
+
+#include <rfb/FileInfo.h>
+#include <rfb/DirManager.h>
+
+namespace rfb {
+  namespace win32{
+    class FolderManager : public DirManager {
+    public:
+      FolderManager();
+      ~FolderManager();
+      
+      bool createDir(char *pFullPath);
+
+      bool renameIt(char *pOldName, char *pNewName);
+      bool renameIt(char *pPath, char *pOldName, char *pNewName);
+      
+      bool deleteIt(char *pPrefix, FileInfo *pFI);
+      bool deleteIt(char *pFullPath);
+      bool deleteIt(FileInfo *pFI);
+
+      bool getInfo(char *pFullPath, FILEINFO *pFIStruct);
+      
+      bool getDirInfo(char *pPath, FileInfo *pFileInfo, unsigned int dirOnly);
+      bool getDrivesInfo(FileInfo *pFI);
+
+      unsigned int getTime70(FILETIME ftime);
+      void getFiletime(unsigned int time70, FILETIME *pftime);
+
+      bool getDirSize(char *pFullPath, DWORD64 *dirSize);
+
+    private:
+      bool getFolderInfoWithPrefix(char *pPrefix, FileInfo *pFileInfo);
+    };
+  }
+}
+
+#endif // __RFB_WIN32_FOLDERMANAGER_H__
\ No newline at end of file
diff --git a/win/rfb_win32/Handle.h b/win/rfb_win32/Handle.h
new file mode 100644
index 0000000..d3baa58
--- /dev/null
+++ b/win/rfb_win32/Handle.h
@@ -0,0 +1,43 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// Wrapper for Win32 HANDLEs that can/must be CloseHandle()d.
+
+#ifndef __RFB_WIN32_HANDLE_H__
+#define __RFB_WIN32_HANDLE_H__
+
+#include <windows.h>
+
+namespace rfb {
+  namespace win32 {
+
+
+    class Handle {
+    public:
+      Handle(HANDLE h_=0) : h(h_) {}
+      ~Handle() {
+        if (h) CloseHandle(h);
+      }
+      operator HANDLE() {return h;}
+      HANDLE h;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/IconInfo.h b/win/rfb_win32/IconInfo.h
new file mode 100644
index 0000000..cb33a42
--- /dev/null
+++ b/win/rfb_win32/IconInfo.h
@@ -0,0 +1,44 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_ICONINFO_H__
+#define __RFB_WIN32_ICONINFO_H__
+
+#include <windows.h>
+#include <rdr/Exception.h>
+
+namespace rfb {
+  namespace win32 {
+
+    struct IconInfo : public ICONINFO {
+      IconInfo(HICON icon) {
+        if (!GetIconInfo(icon, this))
+          throw rdr::SystemException("GetIconInfo() failed", GetLastError());
+      }
+      ~IconInfo() {
+        if (hbmColor)
+          DeleteObject(hbmColor);
+        if (hbmMask)
+          DeleteObject(hbmMask);
+      }
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/IntervalTimer.h b/win/rfb_win32/IntervalTimer.h
new file mode 100644
index 0000000..ddfae49
--- /dev/null
+++ b/win/rfb_win32/IntervalTimer.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- IntervalTimer.h
+//
+// Simple wrapper for standard Win32 timers
+
+#ifndef __RFB_WIN32_INTERVAL_TIMER_H__
+#define __RFB_WIN32_INTERVAL_TIMER_H__
+
+namespace rfb {
+
+  namespace win32 {
+
+    struct IntervalTimer {
+      IntervalTimer(HWND hwnd_, int id_)
+        : active(false), hwnd(hwnd_), id(id_) {
+      }
+      IntervalTimer() : active(false), hwnd(0), id(0) {
+      }
+      ~IntervalTimer() {
+        stop();
+      }
+
+      void start(int interval_) {
+        if (!active || interval_ != interval) {
+          interval = interval_;
+          if (!SetTimer(hwnd, id, interval, 0))
+            throw rdr::SystemException("SetTimer", GetLastError());
+          active = true;
+        }
+      }
+      void stop() {
+        if (active)
+          KillTimer(hwnd, id);
+        active = false;
+      }
+
+      void setHWND(HWND hwnd_) {hwnd=hwnd_;}
+      void setId(int id_) {id = id_;}
+      int getId() const {return id;}
+      bool isActive() const {return active;}
+
+    private:
+      HWND hwnd;
+      int id;
+      bool active;
+      int interval;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_INTERVAL_TIMER_H__
diff --git a/win/rfb_win32/LaunchProcess.cxx b/win/rfb_win32/LaunchProcess.cxx
new file mode 100644
index 0000000..56a712e
--- /dev/null
+++ b/win/rfb_win32/LaunchProcess.cxx
@@ -0,0 +1,103 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- LaunchProcess.cxx
+
+#include <rfb_win32/LaunchProcess.h>
+#include <rfb_win32/ModuleFileName.h>
+#include <rfb_win32/Win32Util.h>
+#include <rdr/Exception.h>
+#include <stdio.h>
+
+using namespace rfb;
+using namespace win32;
+
+
+LaunchProcess::LaunchProcess(const TCHAR* exeName_, const TCHAR* params_)
+: exeName(tstrDup(exeName_)), params(tstrDup(params_)) {
+  memset(&procInfo, 0, sizeof(procInfo));
+}
+
+LaunchProcess::~LaunchProcess() {
+  await();
+}
+
+
+void LaunchProcess::start(HANDLE userToken, bool createConsole) {
+  if (procInfo.hProcess && (WaitForSingleObject(procInfo.hProcess, 0) != WAIT_OBJECT_0))
+    return;
+  await();
+  returnCode = STILL_ACTIVE;
+
+  // - Create storage for the process startup information
+  STARTUPINFO sinfo;
+  memset(&sinfo, 0, sizeof(sinfo));
+  sinfo.cb = sizeof(sinfo);
+
+  // - Concoct a suitable command-line
+  TCharArray exePath;
+  if (!tstrContains(exeName.buf, _T('\\'))) {
+    ModuleFileName filename;
+    TCharArray path; splitPath(filename.buf, &path.buf, 0);
+    exePath.buf = new TCHAR[_tcslen(path.buf) + _tcslen(exeName.buf) + 2];
+    _stprintf(exePath.buf, _T("%s\\%s"), path.buf, exeName.buf);
+  } else {
+    exePath.buf = tstrDup(exeName.buf);
+  }
+
+  // - Start the process
+  // Note: We specify the exe's precise path in the ApplicationName parameter,
+  //       AND include the name as the first part of the CommandLine parameter,
+  //       because CreateProcess doesn't make ApplicationName argv[0] in C programs.
+  TCharArray cmdLine(_tcslen(exeName.buf) + 3 + _tcslen(params.buf) + 1);
+  _stprintf(cmdLine.buf, _T("\"%s\" %s"), exeName.buf, params.buf);
+  DWORD flags = createConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW;
+  BOOL success;
+  if (userToken != INVALID_HANDLE_VALUE)
+    success = CreateProcessAsUser(userToken, exePath.buf, cmdLine.buf, 0, 0, FALSE, flags, 0, 0, &sinfo, &procInfo);
+  else
+    success = CreateProcess(exePath.buf, cmdLine.buf, 0, 0, FALSE, flags, 0, 0, &sinfo, &procInfo);
+  if (!success)
+    throw rdr::SystemException("unable to launch process", GetLastError());
+
+  // Wait for it to finish initialising
+  WaitForInputIdle(procInfo.hProcess, 15000);
+}
+
+void LaunchProcess::detach()
+{
+  if (!procInfo.hProcess)
+    return;
+  CloseHandle(procInfo.hProcess);
+  CloseHandle(procInfo.hThread);
+  memset(&procInfo, 0, sizeof(procInfo));
+}
+
+bool LaunchProcess::await(DWORD timeoutMs) {
+  if (!procInfo.hProcess)
+    return true;
+  DWORD result = WaitForSingleObject(procInfo.hProcess, timeoutMs);
+  if (result == WAIT_OBJECT_0) {
+    GetExitCodeProcess(procInfo.hProcess, &returnCode);
+    detach();
+    return true;
+  } else if (result == WAIT_FAILED) {
+    throw rdr::SystemException("await() failed", GetLastError());
+  }
+  return false;
+}
diff --git a/win/rfb_win32/LaunchProcess.h b/win/rfb_win32/LaunchProcess.h
new file mode 100644
index 0000000..38521dc
--- /dev/null
+++ b/win/rfb_win32/LaunchProcess.h
@@ -0,0 +1,71 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- LaunchProcess.h
+
+// Helper class to launch a names process from the same directory as
+// the current process executable resides in.
+
+#ifndef __RFB_WIN32_LAUNCHPROCESS_H__
+#define __RFB_WIN32_LAUNCHPROCESS_H__
+
+#include <windows.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class LaunchProcess {
+    public:
+      LaunchProcess(const TCHAR* exeName_, const TCHAR* params);
+      ~LaunchProcess();
+
+      // start() starts the specified process with the supplied
+      //   command-line.
+      //   If userToken is INVALID_HANDLE_VALUE then starts the process
+      //   as the current user, otherwise as the specified user.
+      //   If createConsole is true then CREATE_CONSOLE_WINDOW is passed
+      //   as an extra flag to the process creation call.
+      void start(HANDLE userToken, bool createConsole=false);
+
+      // Detatch from the child process. After detatching from a child
+      //   process, no other methods should be called on the object
+      //   that started it
+      void detach();
+
+      // Wait for the process to quit, up to the specified timeout, and
+      //   close the handles to it once it has quit.
+      //   If the process quits within the timeout then true is returned
+      //   and returnCode is set. If it has not quit then false is returned.
+      //   If an error occurs then an exception will be thrown.
+      bool await(DWORD timeoutMs=INFINITE);
+
+      PROCESS_INFORMATION procInfo;
+      DWORD returnCode;
+    protected:
+      TCharArray exeName;
+      TCharArray params;
+    };
+
+
+  };
+
+};
+
+#endif
diff --git a/win/rfb_win32/ListViewControl.cxx b/win/rfb_win32/ListViewControl.cxx
new file mode 100644
index 0000000..12e0400
--- /dev/null
+++ b/win/rfb_win32/ListViewControl.cxx
@@ -0,0 +1,103 @@
+// ListViewControl.cxx: implementation of the ListViewControl class.
+//
+//////////////////////////////////////////////////////////////////////
+#include <tchar.h>
+#include "ListViewControl.h"
+#include "commctrl.h"
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+using namespace rfb;
+using namespace rfb::win32;
+
+ListViewControl::ListViewControl()
+{
+}
+
+bool ListViewControl::IsSelectedLVItem(DWORD idListView,
+                                      HWND hDlg, int numberItem)
+{	
+  return (ListView_GetItemState(GetDlgItem(hDlg, idListView),
+    numberItem, LVIS_SELECTED) == LVIS_SELECTED);
+}
+
+void ListViewControl::SelectLVItem(DWORD idListView, HWND hDlg, int numberItem)
+{
+  ListView_SetItemState(GetDlgItem(hDlg, idListView),
+    numberItem, LVIS_SELECTED, LVIS_SELECTED);
+}
+
+BOOL ListViewControl::InitLVColumns(DWORD idListView, HWND hDlg, int width, int columns,
+                                    TCHAR *title[], DWORD mask, DWORD LVStyle, DWORD format)
+{
+  ListView_SetExtendedListViewStyle(GetDlgItem(hDlg, idListView), LVStyle);
+  TCHAR szText[256];      
+  LVCOLUMN lvc; 
+  int iCol;
+  
+  lvc.mask = mask; 
+  
+  for (iCol = 0; iCol < columns; iCol++) { 
+    lvc.iSubItem = iCol;
+    lvc.pszText = szText;	
+    lvc.cx = width;           
+    lvc.fmt = format;
+    
+    _tcscpy(szText, title[iCol]); 
+    if (ListView_InsertColumn(GetDlgItem(hDlg, idListView), iCol, &lvc) == -1) 
+      return FALSE; 
+  } 
+  return TRUE; 
+}
+
+BOOL ListViewControl::InsertLVItem(DWORD idListView, HWND hDlg, int number,  TCHAR * texts[],
+                                   int columns)
+{
+  int i;
+  LVITEM lvI;
+  lvI.mask = LVIF_TEXT| LVIF_STATE; 
+  lvI.state = 0; 
+  lvI.stateMask = 0; 
+  lvI.iItem = number; 
+  lvI.iSubItem = 0; 
+  lvI.pszText = texts[0]; 									  
+  
+  if(ListView_InsertItem(GetDlgItem(hDlg, idListView), &lvI) == -1)
+    return NULL;
+  
+  for (i =1; i < columns; i++) {	
+    SetLVItemText(
+      idListView, hDlg, 
+      number, i, texts[i]);
+  }
+  return TRUE;
+}
+
+void ListViewControl::SetLVItemText(DWORD idListView, HWND hDlg, int numberItem,
+                                    int namberColumn, TCHAR * text)
+{
+  ListView_SetItemText(
+    GetDlgItem(hDlg, idListView), 
+    numberItem, namberColumn, text);
+}
+
+void ListViewControl::GetLVItemText(DWORD idListView, HWND hDlg, int numberItem,
+                                    int namberColumn, TCHAR * text)
+{
+	 ListView_GetItemText(GetDlgItem(hDlg, idListView), numberItem,
+							namberColumn, text, 256);
+}
+
+void ListViewControl::DeleteLVItem(DWORD idListView, HWND hDlg, int number)
+{
+  ListView_DeleteItem(GetDlgItem(hDlg, idListView), number);
+}
+
+void ListViewControl::DeleteAllLVItem(DWORD idListView, HWND hDlg)
+{
+  ListView_DeleteAllItems(GetDlgItem(hDlg, idListView));
+}
+
+ListViewControl::~ListViewControl()
+{
+}
diff --git a/win/rfb_win32/ListViewControl.h b/win/rfb_win32/ListViewControl.h
new file mode 100644
index 0000000..8a16373
--- /dev/null
+++ b/win/rfb_win32/ListViewControl.h
@@ -0,0 +1,35 @@
+// ListViewControl.h: interface for the ListViewControl class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#ifndef AFX_LISTVIEWCONTROL_H__
+#define AFX_LISTVIEWCONTROL_H__
+
+#include <windows.h>
+#include "commctrl.h"
+
+namespace rfb {
+  
+  namespace win32 {
+    class ListViewControl  
+    {
+    public:
+      ListViewControl();
+      bool IsSelectedLVItem(DWORD idListView, HWND hDlg, int numberItem);
+      void SelectLVItem(DWORD idListView, HWND hDlg, int numberItem);
+      BOOL InitLVColumns(DWORD idListView, HWND hDlg, int width, int columns,
+        TCHAR * title[], DWORD mask, DWORD style, DWORD format);
+      BOOL InsertLVItem(DWORD idListView, HWND hDlg, int number,  TCHAR * texts[],
+        int columns);
+      void SetLVItemText(DWORD idListView, HWND hDlg, int numberItem,
+        int namberColumn, TCHAR * text);
+      void GetLVItemText(DWORD idListView, HWND hDlg, int numberItem,
+        int namberColumn, TCHAR * text);
+      void DeleteLVItem(DWORD idListView, HWND hDlg, int number);
+      void DeleteAllLVItem(DWORD idListView, HWND hDlg);
+      virtual ~ListViewControl();	
+    };
+  };
+};
+
+#endif;
\ No newline at end of file
diff --git a/win/rfb_win32/LocalMem.h b/win/rfb_win32/LocalMem.h
new file mode 100644
index 0000000..a99d324
--- /dev/null
+++ b/win/rfb_win32/LocalMem.h
@@ -0,0 +1,45 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_LOCALMEM_H__
+#define __RFB_WIN32_LOCALMEM_H__
+
+#include <windows.h>
+#include <rdr/Exception.h>
+
+namespace rfb {
+  namespace win32 {
+
+    // Allocate and/or manage LocalAlloc memory.
+    struct LocalMem {
+      LocalMem(int size) : ptr(LocalAlloc(LMEM_FIXED, size)) {
+        if (!ptr) throw rdr::SystemException("LocalAlloc", GetLastError());
+      }
+      LocalMem(void* p) : ptr(p) {}
+      ~LocalMem() {LocalFree(ptr);}
+      operator void*() {return ptr;}
+      void* takePtr() {
+        void* t = ptr; ptr = 0; return t;
+      }
+      void* ptr;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/LogicalPalette.h b/win/rfb_win32/LogicalPalette.h
new file mode 100644
index 0000000..204f108
--- /dev/null
+++ b/win/rfb_win32/LogicalPalette.h
@@ -0,0 +1,90 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_LOGPALETTE_H__
+#define __RFB_WIN32_LOGPALETTE_H__
+
+#include <windows.h>
+#include <rdr/Exception.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class LogicalPalette {
+    public:
+      LogicalPalette() {
+        BYTE buf[sizeof(LOGPALETTE)+256*sizeof(PALETTEENTRY)];
+        LOGPALETTE* logpal = (LOGPALETTE*)buf;
+        logpal->palVersion = 0x300;
+        logpal->palNumEntries = 256;
+        for (int i=0; i<256;i++) {
+          logpal->palPalEntry[i].peRed = 0;
+          logpal->palPalEntry[i].peGreen = 0;
+          logpal->palPalEntry[i].peBlue = 0;
+          logpal->palPalEntry[i].peFlags = 0;
+        }
+        palette = CreatePalette(logpal);
+        if (!palette)
+          throw rdr::SystemException("failed to CreatePalette", GetLastError());
+      }
+      ~LogicalPalette() {
+        if (palette && !DeleteObject(palette))
+          throw rdr::SystemException("del palette failed", GetLastError());
+      }
+      void setEntries(int start, int count, const Colour* cols) {
+        if (numEntries < count) {
+          ResizePalette(palette, start+count);
+          numEntries = start+count;
+        }
+        PALETTEENTRY* logpal = new PALETTEENTRY[count];
+        for (int i=0; i<count; i++) {
+          logpal[i].peRed = cols[i].r >> 8;
+          logpal[i].peGreen = cols[i].g >> 8;
+          logpal[i].peBlue = cols[i].b >> 8;
+          logpal[i].peFlags = 0;
+        }
+        UnrealizeObject(palette);
+        SetPaletteEntries(palette, start, count, logpal);
+        delete [] logpal;
+      }
+      HPALETTE getHandle() {return palette;}
+    protected:
+      HPALETTE palette;
+      int numEntries;
+    };
+
+    class PaletteSelector {
+    public:
+      PaletteSelector(HDC dc, HPALETTE pal) : device(dc), redrawRequired(false) {
+        oldPal = SelectPalette(dc, pal, FALSE);
+        redrawRequired = RealizePalette(dc) > 0;
+      }
+      ~PaletteSelector() {
+        if (oldPal) SelectPalette(device, oldPal, TRUE);
+      }
+      bool isRedrawRequired() {return redrawRequired;}
+    protected:
+      HPALETTE oldPal;
+      HDC device;
+      bool redrawRequired;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/LowLevelKeyEvents.cxx b/win/rfb_win32/LowLevelKeyEvents.cxx
new file mode 100644
index 0000000..322d1f4
--- /dev/null
+++ b/win/rfb_win32/LowLevelKeyEvents.cxx
@@ -0,0 +1,96 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <windows.h>
+#include <rfb_win32/LowLevelKeyEvents.h>
+#include <rfb/Threading.h>
+#include <rfb/LogWriter.h>
+#include <list>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("LowLevelKeyEvents");
+
+
+HHOOK hook = 0;
+std::list<HWND> windows;
+Mutex windowLock;
+
+
+static bool filterKeyEvent(int vkCode) {
+  switch (vkCode) {
+  case VK_LWIN:
+  case VK_RWIN:
+  case VK_SNAPSHOT:
+    return true;
+  case VK_TAB:
+    if (GetAsyncKeyState(VK_MENU) & 0x8000)
+      return true;
+  case VK_ESCAPE:
+    if (GetAsyncKeyState(VK_MENU) & 0x8000)
+      return true;
+    if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
+      return true;
+  }
+  return false;
+}
+
+LRESULT CALLBACK LowLevelKeyEventProc(int nCode,
+                                      WPARAM wParam,
+                                      LPARAM lParam) {
+  if (nCode >= 0) {
+    Lock l(windowLock);
+    HWND foreground = GetForegroundWindow();
+    std::list<HWND>::iterator i;
+    for (i=windows.begin(); i!=windows.end(); i++) {
+      if (*i == foreground) {
+        UINT msgType = wParam;
+        KBDLLHOOKSTRUCT* msgInfo = (KBDLLHOOKSTRUCT*)lParam;
+        if (filterKeyEvent(msgInfo->vkCode)) {
+          vlog.debug("filtered event %lx(%lu) %lu", msgInfo->vkCode, msgInfo->vkCode, wParam);
+          PostMessage(*i, wParam, msgInfo->vkCode, (msgInfo->scanCode & 0xff) << 16);
+          return 1;
+        }
+      }
+    }
+  }
+  return CallNextHookEx(hook, nCode, wParam, lParam);
+}
+
+
+bool rfb::win32::enableLowLevelKeyEvents(HWND hwnd) {
+// ***  return false; // *** THIS CODE IS EXPERIMENTAL, SO DISABLED BY DEFAULT!
+  Lock l(windowLock);
+  if (windows.empty() && !hook)
+    hook = SetWindowsHookEx(WH_KEYBOARD_LL, &LowLevelKeyEventProc, GetModuleHandle(0), 0);
+  if (hook)
+    windows.push_back(hwnd);
+  vlog.debug("enable %p -> %s", hwnd, hook ? "success" : "failure");
+  return hook != 0;
+}
+
+void rfb::win32::disableLowLevelKeyEvents(HWND hwnd) {
+  vlog.debug("disable %p", hwnd);
+  Lock l(windowLock);
+  windows.remove(hwnd);
+  if (windows.empty() && hook) {
+    UnhookWindowsHookEx(hook);
+    hook = 0;
+  }
+}
diff --git a/win/rfb_win32/LowLevelKeyEvents.h b/win/rfb_win32/LowLevelKeyEvents.h
new file mode 100644
index 0000000..40d2ecf
--- /dev/null
+++ b/win/rfb_win32/LowLevelKeyEvents.h
@@ -0,0 +1,49 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- LowLevelKeyEvents.h
+//
+// This interface allows keyboard events destined for a particular window
+// to be intercepted early in the keyboard message queue and posted directly
+// to the window.  This is used to avoid having the operating system process
+// keys such as VK_LWIN, VK_RWIN, etc.
+//
+
+#ifndef __RFB_WIN32_LOW_LEVEL_KEY_EVENTS_H__
+#define __RFB_WIN32_LOW_LEVEL_KEY_EVENTS_H__
+
+namespace rfb {
+
+  namespace win32 {
+
+    // enableLowLevelKeyEvents
+    //   Specifies that keyboard events destined for the specified window should
+    //   be posted directly to the window, rather than being passed via the normal
+    //   Windows keyboard message queue.
+    bool enableLowLevelKeyEvents(HWND hwnd);
+
+    // disableLowLevelKeyEvents
+    //   Causes the specified window to revert to the normal Windows keyboard
+    //   event processing mechanism.
+    void disableLowLevelKeyEvents(HWND hwnd);
+
+  };
+
+};
+
+#endif // __RFB_WIN32_LOW_LEVEL_KEY_EVENTS_H__
diff --git a/win/rfb_win32/ModuleFileName.h b/win/rfb_win32/ModuleFileName.h
new file mode 100644
index 0000000..2264e89
--- /dev/null
+++ b/win/rfb_win32/ModuleFileName.h
@@ -0,0 +1,40 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_MODULE_FILENAME_H__
+#define __RFB_WIN32_MODULE_FILENAME_H__
+
+#include <windows.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+  namespace win32 {
+
+    struct ModuleFileName : public TCharArray {
+      ModuleFileName(HMODULE module=0) : TCharArray(MAX_PATH) {
+        if (!module)
+          module = GetModuleHandle(0);
+        if (!GetModuleFileName(module, buf, MAX_PATH))
+          buf[0] = 0;
+      }
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/MonitorInfo.cxx b/win/rfb_win32/MonitorInfo.cxx
new file mode 100644
index 0000000..03772e9
--- /dev/null
+++ b/win/rfb_win32/MonitorInfo.cxx
@@ -0,0 +1,205 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/MonitorInfo.h>
+#include <rfb_win32/Win32Util.h>
+#include <rdr/Exception.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("MonitorInfo");
+
+
+// If we are building in multi-monitor support (i.e. the headers support it)
+//   then do dynamic imports of the required system calls, and provide any
+//   other code that wouldn't otherwise compile.
+#ifdef RFB_HAVE_MONITORINFO
+#include <tchar.h>
+typedef HMONITOR (WINAPI *_MonitorFromWindow_proto)(HWND,DWORD);
+static rfb::win32::DynamicFn<_MonitorFromWindow_proto> _MonitorFromWindow(_T("user32.dll"), "MonitorFromWindow");
+typedef HMONITOR (WINAPI *_MonitorFromRect_proto)(LPCRECT,DWORD);
+static rfb::win32::DynamicFn<_MonitorFromRect_proto> _MonitorFromRect(_T("user32.dll"), "MonitorFromRect");
+typedef BOOL (WINAPI *_GetMonitorInfo_proto)(HMONITOR,LPMONITORINFO);
+static rfb::win32::DynamicFn<_GetMonitorInfo_proto> _GetMonitorInfo(_T("user32.dll"), "GetMonitorInfoA");
+typedef BOOL (WINAPI *_EnumDisplayMonitors_proto)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);
+static rfb::win32::DynamicFn<_EnumDisplayMonitors_proto> _EnumDisplayMonitors(_T("user32.dll"), "EnumDisplayMonitors");
+static void fillMonitorInfo(HMONITOR monitor, MonitorInfo* mi) {
+  vlog.debug("monitor=%lx", monitor);
+  if (!_GetMonitorInfo.isValid())
+    throw rdr::Exception("no GetMonitorInfo");
+  memset(mi, 0, sizeof(MONITORINFOEXA));
+  mi->cbSize = sizeof(MONITORINFOEXA);
+  if (!(*_GetMonitorInfo)(monitor, mi))
+    throw rdr::SystemException("failed to GetMonitorInfo", GetLastError());
+  vlog.debug("monitor is %d,%d-%d,%d", mi->rcMonitor.left, mi->rcMonitor.top, mi->rcMonitor.right, mi->rcMonitor.bottom);
+  vlog.debug("work area is %d,%d-%d,%d", mi->rcWork.left, mi->rcWork.top, mi->rcWork.right, mi->rcWork.bottom);
+  vlog.debug("device is \"%s\"", mi->szDevice);
+}
+#else
+#pragma message("  NOTE: Not building Multi-Monitor support.")
+#endif
+
+
+MonitorInfo::MonitorInfo(HWND window) {
+  cbSize = sizeof(MonitorInfo);
+  szDevice[0] = 0;
+
+#ifdef RFB_HAVE_MONITORINFO
+  try {
+    if (_MonitorFromWindow.isValid()) {
+      HMONITOR monitor = (*_MonitorFromWindow)(window, MONITOR_DEFAULTTONEAREST);
+      if (!monitor)
+        throw rdr::SystemException("failed to get monitor", GetLastError());
+      fillMonitorInfo(monitor, this);
+      return;
+    }
+  } catch (rdr::Exception& e) {
+    vlog.error(e.str());
+  }
+#endif
+
+  // Legacy fallbacks - just return the desktop settings
+  vlog.debug("using legacy fall-backs");
+  HWND desktop = GetDesktopWindow();
+  GetWindowRect(desktop, &rcMonitor);
+  SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0);
+  dwFlags = 0;
+}
+
+MonitorInfo::MonitorInfo(const RECT& r) {
+  cbSize = sizeof(MonitorInfo);
+  szDevice[0] = 0;
+
+#ifdef RFB_HAVE_MONITORINFO
+  try {
+    if (_MonitorFromRect.isValid()) {
+      HMONITOR monitor = (*_MonitorFromRect)(&r, MONITOR_DEFAULTTONEAREST);
+      if (!monitor)
+        throw rdr::SystemException("failed to get monitor", GetLastError());
+      fillMonitorInfo(monitor, this);
+      return;
+    }
+  } catch (rdr::Exception& e) {
+    vlog.error(e.str());
+  }
+#endif
+
+  // Legacy fallbacks - just return the desktop settings
+  vlog.debug("using legacy fall-backs");
+  HWND desktop = GetDesktopWindow();
+  GetWindowRect(desktop, &rcMonitor);
+  SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0);
+  dwFlags = 0;
+}
+
+
+#ifdef RFB_HAVE_MONITORINFO
+
+struct monitorByNameData {
+  MonitorInfo* info;
+  const char* monitorName;
+};
+
+static BOOL CALLBACK monitorByNameEnumProc(HMONITOR monitor,
+                                    HDC dc,
+                                    LPRECT pos,
+                                    LPARAM d) {
+  monitorByNameData* data = (monitorByNameData*)d;
+  memset(data->info, 0, sizeof(MONITORINFOEXA));
+  data->info->cbSize = sizeof(MONITORINFOEXA);
+  if ((*_GetMonitorInfo)(monitor, data->info)) {
+    if (stricmp(data->monitorName, data->info->szDevice) == 0)
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+#endif
+
+MonitorInfo::MonitorInfo(const char* devName) {
+#ifdef RFB_HAVE_MONITORINFO
+  if (!_EnumDisplayMonitors.isValid()) {
+    vlog.debug("EnumDisplayMonitors not found");
+  } else {
+    monitorByNameData data;
+    data.info = this;
+    data.monitorName = devName;
+
+    (*_EnumDisplayMonitors)(0, 0, &monitorByNameEnumProc, (LPARAM)&data);
+    if (stricmp(data.monitorName, szDevice) == 0)
+      return;
+  }
+#endif
+  // If multi-monitor is not built, or not supported by the OS,
+  //   or if the named monitor is not found, revert to the primary monitor.
+  vlog.debug("reverting to primary monitor");
+  cbSize = sizeof(MonitorInfo);
+  szDevice[0] = 0;
+
+  HWND desktop = GetDesktopWindow();
+  GetWindowRect(desktop, &rcMonitor);
+  SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0);
+  dwFlags = 0;
+}
+
+void MonitorInfo::moveTo(HWND handle) {
+  vlog.debug("moveTo monitor=%s", szDevice);
+
+#ifdef RFB_HAVE_MONITORINFO
+  MonitorInfo mi(handle);
+  if (strcmp(szDevice, mi.szDevice) != 0) {
+    centerWindow(handle, rcWork);
+    clipTo(handle);
+  }
+#endif
+}
+
+void MonitorInfo::clipTo(RECT* r) {
+  vlog.debug("clipTo monitor=%s", szDevice);
+
+  if (r->top < rcWork.top) {
+    r->bottom += rcWork.top - r->top; r->top = rcWork.top;
+  }
+  if (r->left < rcWork.left) {
+    r->right += rcWork.left - r->left; r->left = rcWork.left;
+  }
+  if (r->bottom > rcWork.bottom) {
+    r->top += rcWork.bottom - r->bottom; r->bottom = rcWork.bottom;
+  }
+  if (r->right > rcWork.right) {
+    r->left += rcWork.right - r->right; r->right = rcWork.right;
+  }
+  r->left = max(r->left, rcWork.left);
+  r->right = min(r->right, rcWork.right);
+  r->top = max(r->top, rcWork.top);
+  r->bottom = min(r->bottom, rcWork.bottom);
+}
+
+void MonitorInfo::clipTo(HWND handle) {
+  RECT r;
+  GetWindowRect(handle, &r);
+  clipTo(&r);
+  SetWindowPos(handle, 0, r.left, r.top, r.right-r.left, r.bottom-r.top,
+               SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+}
+
+
diff --git a/win/rfb_win32/MonitorInfo.h b/win/rfb_win32/MonitorInfo.h
new file mode 100644
index 0000000..acf2775
--- /dev/null
+++ b/win/rfb_win32/MonitorInfo.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// Helper class used to obtain information about a particular monitor.
+// This class wraps the Windows MONITORINFOEX ASCII structure, providing
+// methods that can safely be called on both multi-monitor aware systems
+// and older "legacy" systems.
+
+
+#ifndef __RFB_WIN32_MONITORINFO_H__
+#define __RFB_WIN32_MONITORINFO_H__
+
+#include <windows.h>
+#ifdef MONITOR_DEFAULTTONULL
+#define RFB_HAVE_MONITORINFO
+#endif
+
+namespace rfb {
+  namespace win32 {
+
+    // Structure containing info on the monitor nearest the window.
+    // Copes with multi-monitor OSes and older ones.
+#ifdef RFB_HAVE_MONITORINFO
+    struct MonitorInfo : MONITORINFOEXA {
+#else
+    struct MonitorInfo {
+      DWORD cbSize;
+      RECT rcMonitor;
+      RECT rcWork;
+      DWORD dwFlags;
+      char szDevice[1]; // Always null...
+#endif
+
+      // Constructor: Obtains monitor info for the monitor that has the
+      //   greatest overlap with the supplied window or rectangle.
+      MonitorInfo(HWND hwnd);
+      MonitorInfo(const RECT& r);
+
+      // Constructor: Obtains monitor info for the name monitor.  Monitor
+      //   names should be those obtained from the MonitorInfo
+      //   szDevice field, and usually look like "\\.\DISPLAY<n>"
+      MonitorInfo(const char* devName);
+
+      // Move the specified window to reside on the monitor.
+      void moveTo(HWND handle);
+
+      // Clip the specified rectangle or window to the monitor's working area.
+      //   The rectangle/window is moved so that as much as possible resides
+      //   on the working area of the monitor, and is then intersected with it.
+      void clipTo(HWND handle);
+      void clipTo(RECT* r);
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/MsgBox.h b/win/rfb_win32/MsgBox.h
new file mode 100644
index 0000000..5957139
--- /dev/null
+++ b/win/rfb_win32/MsgBox.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_WIN32_MSGBOX_H__
+#define __RFB_WIN32_MSGBOX_H__
+
+#include <windows.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+  namespace win32 {
+
+    // Define rfb::win32::AppName somewhere in the application.
+    // The MsgBox function will use the specified application name
+    // as the prefix for the message box title.
+    // Message box titles are based on the (standard Win32) flags
+    // passed to the MsgBox helper function.
+
+    extern TStr AppName;
+
+    // Wrapper around Win32 MessageBox()
+    static int MsgBox(HWND parent, const TCHAR* msg, UINT flags) {
+      const TCHAR* msgType = 0;
+      UINT tflags = flags & 0x70;
+      if (tflags == MB_ICONHAND)
+        msgType = _T("Error");
+      else if (tflags == MB_ICONQUESTION)
+        msgType = _T("Question");
+      else if (tflags == MB_ICONEXCLAMATION)
+        msgType = _T("Warning");
+      else if (tflags == MB_ICONASTERISK)
+        msgType = _T("Information");
+      flags |= MB_TOPMOST | MB_SETFOREGROUND;
+      int len = _tcslen(AppName.buf) + 1;
+      if (msgType) len += _tcslen(msgType) + 3;
+      TCharArray title = new TCHAR[len];
+      _tcscpy(title.buf, AppName.buf);
+      if (msgType) {
+        _tcscat(title.buf, _T(" : "));
+        _tcscat(title.buf, msgType);
+      }
+      return MessageBox(parent, msg, title.buf, flags);
+    }
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/MsgWindow.cxx b/win/rfb_win32/MsgWindow.cxx
new file mode 100644
index 0000000..95bd523
--- /dev/null
+++ b/win/rfb_win32/MsgWindow.cxx
@@ -0,0 +1,116 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- MsgWindow.cxx
+
+#include <rfb_win32/MsgWindow.h>
+#include <rfb_win32/WMShatter.h>
+#include <rfb/LogWriter.h>
+#include <rdr/Exception.h>
+#include <malloc.h>
+#include <tchar.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("MsgWindow");
+
+//
+// -=- MsgWindowClass
+//
+
+class MsgWindowClass {
+public:
+  MsgWindowClass();
+  ~MsgWindowClass();
+  ATOM classAtom;
+  HINSTANCE instance;
+};
+
+LRESULT CALLBACK MsgWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
+  LRESULT result;
+
+  if (msg == WM_CREATE)
+    SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams);
+  else if (msg == WM_DESTROY)
+    SetWindowLong(wnd, GWL_USERDATA, 0);
+  MsgWindow* _this = (MsgWindow*) GetWindowLong(wnd, GWL_USERDATA);
+  if (!_this) {
+    vlog.info("null _this in %x, message %x", wnd, msg);
+    return SafeDefWindowProc(wnd, msg, wParam, lParam);
+  }
+
+  try {
+    result = _this->processMessage(msg, wParam, lParam);
+  } catch (rdr::Exception& e) {
+    vlog.error("untrapped: %s", e.str());
+  }
+
+  return result;
+};
+
+MsgWindowClass::MsgWindowClass() : classAtom(0) {
+  WNDCLASS wndClass;
+  wndClass.style = 0;
+  wndClass.lpfnWndProc = MsgWindowProc;
+  wndClass.cbClsExtra = 0;
+  wndClass.cbWndExtra = 0;
+  wndClass.hInstance = instance = GetModuleHandle(0);
+  wndClass.hIcon = 0;
+  wndClass.hCursor = 0;
+  wndClass.hbrBackground = 0;
+  wndClass.lpszMenuName = 0;
+  wndClass.lpszClassName = _T("rfb::win32::MsgWindowClass");
+  classAtom = RegisterClass(&wndClass);
+  if (!classAtom) {
+    throw rdr::SystemException("unable to register MsgWindow window class", GetLastError());
+  }
+}
+
+MsgWindowClass::~MsgWindowClass() {
+  if (classAtom) {
+    UnregisterClass((const TCHAR*)classAtom, instance);
+  }
+}
+
+MsgWindowClass baseClass;
+
+//
+// -=- MsgWindow
+//
+
+MsgWindow::MsgWindow(const TCHAR* name_) : name(tstrDup(name_)), handle(0) {
+  vlog.debug("creating window \"%s\"", (const char*)CStr(name.buf));
+  handle = CreateWindow((const TCHAR*)baseClass.classAtom, name.buf, WS_OVERLAPPED,
+    0, 0, 10, 10, 0, 0, baseClass.instance, this);
+  if (!handle) {
+    throw rdr::SystemException("unable to create WMNotifier window instance", GetLastError());
+  }
+  vlog.debug("created window \"%s\" (%x)", (const char*)CStr(name.buf), handle);
+}
+
+MsgWindow::~MsgWindow() {
+  if (handle)
+    DestroyWindow(handle);
+  vlog.debug("destroyed window \"%s\" (%x)", (const char*)CStr(name.buf), handle); 
+}
+
+LRESULT
+MsgWindow::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  return SafeDefWindowProc(getHandle(), msg, wParam, lParam);
+}
diff --git a/win/rfb_win32/MsgWindow.h b/win/rfb_win32/MsgWindow.h
new file mode 100644
index 0000000..92b6cf2
--- /dev/null
+++ b/win/rfb_win32/MsgWindow.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- MsgWindow.h
+
+// Base-class for any hidden message-handling windows used in the rfb::win32
+// implementation.
+
+#ifndef __RFB_WIN32_MSG_WINDOW_H__
+#define __RFB_WIN32_MSG_WINDOW_H__
+
+#include <windows.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class MsgWindow {
+    public:
+      MsgWindow(const TCHAR* _name);
+      virtual ~MsgWindow();
+
+      const TCHAR* getName() {return name.buf;}
+      HWND getHandle() const {return handle;}
+
+      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+    protected:
+      TCharArray name;
+      HWND handle;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_MSG_WINDOW_H__
diff --git a/win/rfb_win32/OSVersion.cxx b/win/rfb_win32/OSVersion.cxx
new file mode 100644
index 0000000..3d74c95
--- /dev/null
+++ b/win/rfb_win32/OSVersion.cxx
@@ -0,0 +1,47 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- OSVersion.cxx
+
+#include <rfb_win32/OSVersion.h>
+#include <rdr/Exception.h>
+#include <tchar.h>
+
+using namespace rfb;
+using namespace win32;
+
+
+OSVersionInfo::OSVersionInfo() {
+  // Get OS Version Info
+  ZeroMemory(static_cast<OSVERSIONINFO*>(this), sizeof(this));
+  dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+  if (!GetVersionEx(this))
+    throw rdr::SystemException("unable to get system version info", GetLastError());
+
+  // Set the special extra flags
+  isPlatformNT = dwPlatformId == VER_PLATFORM_WIN32_NT;
+  isPlatformWindows = dwPlatformId == VER_PLATFORM_WIN32_WINDOWS;
+
+  cannotSwitchDesktop = isPlatformNT && (dwMajorVersion==4) &&
+    ((_tcscmp(szCSDVersion, _T("")) == 0) ||
+     (_tcscmp(szCSDVersion, _T("Service Pack 1")) == 0) ||
+     (_tcscmp(szCSDVersion, _T("Service Pack 2")) == 0));
+
+}
+
+OSVersionInfo rfb::win32::osVersion;
diff --git a/win/rfb_win32/OSVersion.h b/win/rfb_win32/OSVersion.h
new file mode 100644
index 0000000..18ec003
--- /dev/null
+++ b/win/rfb_win32/OSVersion.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- OSVersion.h
+
+// Operating system version info.
+// GetVersionInfo is called once at process initialisation, and any
+// extra flags (such as isWinNT) are calculated and saved at that
+// point.  It is assumed that the OS Version seldom changes during a
+// program's execution...
+
+#ifndef __RFB_WIN32_OS_VERSION_H__
+#define __RFB_WIN32_OS_VERSION_H__
+
+#include <windows.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    extern struct OSVersionInfo : OSVERSIONINFO {
+      OSVersionInfo();
+
+      // Is the OS one of the NT family (NT 3.51, NT4.0, 2K, XP, etc.)?
+      bool isPlatformNT;
+      // Is one of the Windows family?
+      bool isPlatformWindows;
+
+      // Is this OS one of those that blue-screens when grabbing another desktop (NT4 pre SP3)?
+      bool cannotSwitchDesktop;
+
+    } osVersion;
+
+  };
+
+};
+
+#endif // __RFB_WIN32_OS_VERSION_H__
diff --git a/win/rfb_win32/ProgressControl.cxx b/win/rfb_win32/ProgressControl.cxx
new file mode 100644
index 0000000..85bd15f
--- /dev/null
+++ b/win/rfb_win32/ProgressControl.cxx
@@ -0,0 +1,97 @@
+/* Copyright (C) 2005 TightVNC Team.  All Rights Reserved.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- ProgressControl.cxx
+
+#include <rfb_win32/ProgressControl.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+#define MAX_RANGE 0xFFFF
+
+ProgressControl::ProgressControl(HWND hwndProgress)
+{
+  m_hwndProgress = hwndProgress;
+  
+  m_dw64MaxValue = 0;
+  m_dw64CurrentValue = 0;
+}
+
+ProgressControl::~ProgressControl()
+{
+}
+
+bool
+ProgressControl::init(DWORD64 maxValue, DWORD64 position)
+{
+  if (m_dw64CurrentValue > m_dw64MaxValue) return false;
+  
+  m_dw64CurrentValue = position;
+  m_dw64MaxValue = maxValue;
+  
+  if (!SendMessage(m_hwndProgress, PBM_SETRANGE, (WPARAM) 0, MAKELPARAM(0, MAX_RANGE))) 
+    return false;
+  
+  return true;
+}
+
+bool
+ProgressControl::clear()
+{
+  m_dw64CurrentValue = 0;
+  return show();
+}
+
+bool
+ProgressControl::increase(DWORD64 value)
+{
+  if ((m_dw64MaxValue - m_dw64CurrentValue) > value) {
+    m_dw64CurrentValue += value;
+  } else {
+    m_dw64CurrentValue = m_dw64MaxValue;
+  }
+  return show();
+}
+
+bool
+ProgressControl::show()
+{
+  DWORD curPos;
+  if (m_dw64MaxValue != 0) {
+    curPos = (DWORD) ((m_dw64CurrentValue * MAX_RANGE) / m_dw64MaxValue);
+  } else {
+    curPos = 0;
+  }
+  
+  if (!SendMessage(m_hwndProgress, PBM_SETPOS, (WPARAM) curPos, (LPARAM) 0))
+    return false;
+  
+  return true;
+}
+
+int 
+ProgressControl::getCurrentPercent()
+{
+  if (m_dw64MaxValue == 0) return 0;
+
+  return ((int) ((m_dw64CurrentValue * 100) / m_dw64MaxValue));
+}
\ No newline at end of file
diff --git a/win/rfb_win32/ProgressControl.h b/win/rfb_win32/ProgressControl.h
new file mode 100644
index 0000000..ceeb153
--- /dev/null
+++ b/win/rfb_win32/ProgressControl.h
@@ -0,0 +1,56 @@
+/* Copyright (C) 2005 TightVNC Team.  All Rights Reserved.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- ProgressControl.h
+
+#ifndef __RFB_WIN32_PROGRESSCONTROL_H__
+#define __RFB_WIN32_PROGRESSCONTROL_H__
+
+#include <windows.h>
+#include <commctrl.h>
+
+namespace rfb {
+  namespace win32 {
+    class ProgressControl
+    {
+    public:
+      ProgressControl(HWND hwndProgress);
+      ~ProgressControl();
+      
+      bool init(DWORD64 maxValue, DWORD64 position);
+      
+      bool increase(DWORD64 value);
+      bool clear();
+
+      int getCurrentPercent();
+      
+    private:
+      HWND m_hwndProgress;
+      
+      DWORD64 m_dw64MaxValue;
+      DWORD64 m_dw64CurrentValue;
+      
+      bool show();
+    };
+  }
+}
+
+#endif // __RFB_WIN32_PROGRESSCONTROL_H__
diff --git a/win/rfb_win32/RegConfig.cxx b/win/rfb_win32/RegConfig.cxx
new file mode 100644
index 0000000..dd1c3b0
--- /dev/null
+++ b/win/rfb_win32/RegConfig.cxx
@@ -0,0 +1,114 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- RegConfig.cxx
+
+#include <malloc.h>
+
+#include <rfb_win32/RegConfig.h>
+#include <rfb/LogWriter.h>
+#include <rfb/util.h>
+//#include <rdr/HexOutStream.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+
+static LogWriter vlog("RegConfig");
+
+
+RegConfig::RegConfig(EventManager* em) : eventMgr(em), event(CreateEvent(0, TRUE, FALSE, 0)), callback(0) {
+  if (em->addEvent(event, this))
+    eventMgr = em;
+}
+
+RegConfig::~RegConfig() {
+  if (eventMgr)
+    eventMgr->removeEvent(event);
+}
+
+bool RegConfig::setKey(const HKEY rootkey, const TCHAR* keyname) {
+  try {
+    key.createKey(rootkey, keyname);
+    processEvent(event);
+    return true;
+  } catch (rdr::Exception& e) {
+    vlog.debug(e.str());
+    return false;
+  }
+}
+
+void RegConfig::loadRegistryConfig(RegKey& key) {
+  DWORD i = 0;
+  try {
+    while (1) {
+      TCharArray name = tstrDup(key.getValueName(i++));
+      if (!name.buf) break;
+      TCharArray value = key.getRepresentation(name.buf);
+      if (!value.buf || !Configuration::setParam(CStr(name.buf), CStr(value.buf)))
+        vlog.info("unable to process %s", CStr(name.buf));
+    }
+  } catch (rdr::SystemException& e) {
+    if (e.err != 6)
+      vlog.error(e.str());
+  }
+}
+
+void RegConfig::processEvent(HANDLE event_) {
+  vlog.info("registry changed");
+
+  // Reinstate the registry change notifications
+  ResetEvent(event);
+  key.awaitChange(true, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET, event);
+
+  // Load settings
+  loadRegistryConfig(key);
+
+  // Notify the callback, if supplied
+  if (callback)
+    callback->regConfigChanged();
+}
+
+
+RegConfigThread::RegConfigThread() : Thread("RegConfigThread"), config(&eventMgr) {
+}
+
+RegConfigThread::~RegConfigThread() {
+  join();
+}
+
+bool RegConfigThread::start(const HKEY rootKey, const TCHAR* keyname) {
+  if (config.setKey(rootKey, keyname)) {
+    Thread::start();
+    return true;
+  }
+  return false;
+}
+
+void RegConfigThread::run() {
+  DWORD result = 0;
+  MSG msg;
+  while ((result = eventMgr.getMessage(&msg, 0, 0, 0)) > 0) {}
+  if (result < 0)
+    throw rdr::SystemException("RegConfigThread failed", GetLastError());
+}
+
+Thread* RegConfigThread::join() {
+  PostThreadMessage(getThreadId(), WM_QUIT, 0, 0);
+  return Thread::join();
+}
diff --git a/win/rfb_win32/RegConfig.h b/win/rfb_win32/RegConfig.h
new file mode 100644
index 0000000..e9c01b1
--- /dev/null
+++ b/win/rfb_win32/RegConfig.h
@@ -0,0 +1,84 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- RegConfig.h
+
+// Class which monitors the registry and reads in the registry settings
+// whenever they change, or are added or removed.
+
+#ifndef __RFB_WIN32_REG_CONFIG_H__
+#define __RFB_WIN32_REG_CONFIG_H__
+
+#include <rfb/Threading.h>
+#include <rfb/Configuration.h>
+#include <rfb_win32/Registry.h>
+#include <rfb_win32/EventManager.h>
+#include <rfb_win32/Handle.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class RegConfig : EventHandler {
+    public:
+      RegConfig(EventManager* em);
+      ~RegConfig();
+
+      // Specify the registry key to read Configuration items from
+      bool setKey(const HKEY rootkey, const TCHAR* keyname);
+
+      // Support for a callback, run in the RegConfig host thread whenever
+      // the registry configuration changes
+      class Callback {
+      public:
+        virtual ~Callback() {}
+        virtual void regConfigChanged() = 0;
+      };
+      void setCallback(Callback* cb) { callback = cb; }
+
+      // Read entries from the specified key into the Configuration
+      static void loadRegistryConfig(RegKey& key);
+    protected:
+      // EventHandler interface and trigger event
+      virtual void processEvent(HANDLE event);
+
+      EventManager* eventMgr;
+      Handle event;
+      Callback* callback;
+      RegKey key;
+    };
+
+    class RegConfigThread : Thread {
+    public:
+      RegConfigThread();
+      ~RegConfigThread();
+
+      // Start the thread, reading from the specified key
+      bool start(const HKEY rootkey, const TCHAR* keyname);
+    protected:
+      void run();
+      Thread* join();
+      EventManager eventMgr;
+      RegConfig config;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_REG_CONFIG_H__
diff --git a/win/rfb_win32/Registry.cxx b/win/rfb_win32/Registry.cxx
new file mode 100644
index 0000000..4ece4ba
--- /dev/null
+++ b/win/rfb_win32/Registry.cxx
@@ -0,0 +1,316 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Registry.cxx
+
+#include <rfb_win32/Registry.h>
+#include <rfb_win32/Security.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rdr/MemOutStream.h>
+#include <rdr/HexOutstream.h>
+#include <rdr/HexInStream.h>
+#include <stdlib.h>
+#include <rfb/LogWriter.h>
+
+// These flags are required to control access control inheritance,
+// but are not defined by VC6's headers.  These definitions comes
+// from the Microsoft Platform SDK.
+#ifndef PROTECTED_DACL_SECURITY_INFORMATION
+#define PROTECTED_DACL_SECURITY_INFORMATION     (0x80000000L)
+#endif
+#ifndef UNPROTECTED_DACL_SECURITY_INFORMATION
+#define UNPROTECTED_DACL_SECURITY_INFORMATION     (0x20000000L)
+#endif
+
+
+using namespace rfb;
+using namespace rfb::win32;
+
+
+static LogWriter vlog("Registry");
+
+
+RegKey::RegKey() : key(0), freeKey(false), valueNameBufLen(0) {}
+
+RegKey::RegKey(const HKEY k) : key(0), freeKey(false), valueNameBufLen(0) {
+  LONG result = RegOpenKeyEx(k, 0, 0, KEY_ALL_ACCESS, &key);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegOpenKeyEx(HKEY)", result);
+  vlog.debug("duplicated %x to %x", k, key);
+  freeKey = true;
+}
+
+RegKey::RegKey(const RegKey& k) : key(0), freeKey(false), valueNameBufLen(0) {
+  LONG result = RegOpenKeyEx(k.key, 0, 0, KEY_ALL_ACCESS, &key);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegOpenKeyEx(RegKey&)", result);
+  vlog.debug("duplicated %x to %x", k.key, key);
+  freeKey = true;
+}
+
+RegKey::~RegKey() {
+  close();
+}
+
+
+void RegKey::setHKEY(HKEY k, bool fK) {
+  vlog.debug("setHKEY(%x,%d)", k, (int)fK);
+  close();
+  freeKey = fK;
+  key = k;
+}
+
+
+bool RegKey::createKey(const RegKey& root, const TCHAR* name) {
+  close();
+  LONG result = RegCreateKey(root.key, name, &key);
+  if (result != ERROR_SUCCESS) {
+    vlog.error("RegCreateKey(%x, %s): %x", root.key, name, result);
+    throw rdr::SystemException("RegCreateKeyEx", result);
+  }
+  vlog.debug("createKey(%x,%s) = %x", root.key, (const char*)CStr(name), key);
+  freeKey = true;
+  return true;
+}
+
+void RegKey::openKey(const RegKey& root, const TCHAR* name, bool readOnly) {
+  close();
+  LONG result = RegOpenKeyEx(root.key, name, 0, readOnly ? KEY_READ : KEY_ALL_ACCESS, &key);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegOpenKeyEx (open)", result);
+  vlog.debug("openKey(%x,%s,%s) = %x", root.key, (const char*)CStr(name),
+	         readOnly ? "ro" : "rw", key);
+  freeKey = true;
+}
+
+void RegKey::setDACL(const PACL acl, bool inherit) {
+  DWORD result;
+  typedef DWORD (WINAPI *_SetSecurityInfo_proto) (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID, PSID, PACL, PACL);
+  DynamicFn<_SetSecurityInfo_proto> _SetSecurityInfo(_T("advapi32.dll"), "SetSecurityInfo");
+  if (!_SetSecurityInfo.isValid())
+    throw rdr::SystemException("RegKey::setDACL failed", ERROR_CALL_NOT_IMPLEMENTED);
+  if ((result = (*_SetSecurityInfo)(key, SE_REGISTRY_KEY,
+    DACL_SECURITY_INFORMATION |
+    (inherit ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION),
+    0, 0, acl, 0)) != ERROR_SUCCESS)
+    throw rdr::SystemException("RegKey::setDACL failed", result);
+}
+
+void RegKey::close() {
+  if (freeKey) {
+    vlog.debug("RegCloseKey(%x)", key);
+    RegCloseKey(key);
+    key = 0;
+  }
+}
+
+void RegKey::deleteKey(const TCHAR* name) const {
+  LONG result = RegDeleteKey(key, name);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegDeleteKey", result);
+}
+
+void RegKey::deleteValue(const TCHAR* name) const {
+  LONG result = RegDeleteValue(key, name);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegDeleteValue", result);
+}
+
+void RegKey::awaitChange(bool watchSubTree, DWORD filter, HANDLE event) const {
+  LONG result = RegNotifyChangeKeyValue(key, watchSubTree, filter, event, event != 0);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegNotifyChangeKeyValue", result);
+}
+
+
+RegKey::operator HKEY() const {return key;}
+
+
+void RegKey::setExpandString(const TCHAR* valname, const TCHAR* value) const {
+  LONG result = RegSetValueEx(key, valname, 0, REG_EXPAND_SZ, (const BYTE*)value, (_tcslen(value)+1)*sizeof(TCHAR));
+  if (result != ERROR_SUCCESS) throw rdr::SystemException("setExpandString", result);
+}
+
+void RegKey::setString(const TCHAR* valname, const TCHAR* value) const {
+  LONG result = RegSetValueEx(key, valname, 0, REG_SZ, (const BYTE*)value, (_tcslen(value)+1)*sizeof(TCHAR));
+  if (result != ERROR_SUCCESS) throw rdr::SystemException("setString", result);
+}
+
+void RegKey::setBinary(const TCHAR* valname, const void* value, int length) const {
+  LONG result = RegSetValueEx(key, valname, 0, REG_BINARY, (const BYTE*)value, length);
+  if (result != ERROR_SUCCESS) throw rdr::SystemException("setBinary", result);
+}
+
+void RegKey::setInt(const TCHAR* valname, int value) const {
+  LONG result = RegSetValueEx(key, valname, 0, REG_DWORD, (const BYTE*)&value, sizeof(value));
+  if (result != ERROR_SUCCESS) throw rdr::SystemException("setInt", result);
+}
+
+void RegKey::setBool(const TCHAR* valname, bool value) const {
+  setInt(valname, value ? 1 : 0);
+}
+
+TCHAR* RegKey::getString(const TCHAR* valname) const {return getRepresentation(valname);}
+TCHAR* RegKey::getString(const TCHAR* valname, const TCHAR* def) const {
+  try {
+    return getString(valname);
+  } catch(rdr::Exception) {
+    return tstrDup(def);
+  }
+}
+
+void RegKey::getBinary(const TCHAR* valname, void** data, int* length) const {
+  TCharArray hex = getRepresentation(valname);
+  if (!rdr::HexInStream::hexStrToBin(CStr(hex.buf), (char**)data, length))
+    throw rdr::Exception("getBinary failed");
+}
+void RegKey::getBinary(const TCHAR* valname, void** data, int* length, void* def, int deflen) const {
+  try {
+    getBinary(valname, data, length);
+  } catch(rdr::Exception) {
+    if (deflen) {
+      *data = new char[deflen];
+      memcpy(*data, def, deflen);
+    } else
+      *data = 0;
+    *length = deflen;
+  }
+}
+
+int RegKey::getInt(const TCHAR* valname) const {
+  TCharArray tmp = getRepresentation(valname);
+  return _ttoi(tmp.buf);
+}
+int RegKey::getInt(const TCHAR* valname, int def) const {
+  try {
+    return getInt(valname);
+  } catch(rdr::Exception) {
+    return def;
+  }
+}
+
+bool RegKey::getBool(const TCHAR* valname) const {
+  return getInt(valname) > 0;
+}
+bool RegKey::getBool(const TCHAR* valname, bool def) const {
+  return getInt(valname, def ? 1 : 0) > 0;
+}
+
+static inline TCHAR* terminateData(char* data, int length)
+{
+  // We must terminate the string, just to be sure.  Stupid Win32...
+  int len = length/sizeof(TCHAR);
+  TCharArray str(len+1);
+  memcpy(str.buf, data, length);
+  str.buf[len] = 0;
+  return str.takeBuf();
+}
+
+TCHAR* RegKey::getRepresentation(const TCHAR* valname) const {
+  DWORD type, length;
+  LONG result = RegQueryValueEx(key, valname, 0, &type, 0, &length);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("get registry value length", result);
+  CharArray data(length);
+  result = RegQueryValueEx(key, valname, 0, &type, (BYTE*)data.buf, &length);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("get registry value", result);
+
+  switch (type) {
+  case REG_BINARY:
+    {
+      TCharArray hex = rdr::HexOutStream::binToHexStr(data.buf, length);
+      return hex.takeBuf();
+    }
+  case REG_SZ:
+    if (length) {
+      return terminateData(data.buf, length);
+    } else {
+      return tstrDup(_T(""));
+    }
+  case REG_DWORD:
+    {
+      TCharArray tmp(16);
+      _stprintf(tmp.buf, _T("%u"), *((DWORD*)data.buf));
+      return tmp.takeBuf();
+    }
+  case REG_EXPAND_SZ:
+    {
+    if (length) {
+      TCharArray str(terminateData(data.buf, length));
+      DWORD required = ExpandEnvironmentStrings(str.buf, 0, 0);
+      if (required==0)
+        throw rdr::SystemException("ExpandEnvironmentStrings", GetLastError());
+      TCharArray result(required);
+      length = ExpandEnvironmentStrings(str.buf, result.buf, required);
+      if (required<length)
+        rdr::Exception("unable to expand environment strings");
+      return result.takeBuf();
+    } else {
+      return tstrDup(_T(""));
+    }
+    }
+  default:
+    throw rdr::Exception("unsupported registry type");
+  }
+}
+
+bool RegKey::isValue(const TCHAR* valname) const {
+  try {
+    TCharArray tmp = getRepresentation(valname);
+    return true;
+  } catch(rdr::Exception) {
+    return false;
+  }
+}
+
+const TCHAR* RegKey::getValueName(int i) {
+  DWORD maxValueNameLen;
+  LONG result = RegQueryInfoKey(key, 0, 0, 0, 0, 0, 0, 0, &maxValueNameLen, 0, 0, 0);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegQueryInfoKey", result);
+  if (valueNameBufLen < maxValueNameLen + 1) {
+    valueNameBufLen = maxValueNameLen + 1;
+    delete [] valueName.buf;
+    valueName.buf = new TCHAR[valueNameBufLen];
+  }
+  DWORD length = valueNameBufLen;
+  result = RegEnumValue(key, i, valueName.buf, &length, NULL, 0, 0, 0);
+  if (result == ERROR_NO_MORE_ITEMS) return 0;
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegEnumValue", result);
+  return valueName.buf;
+}
+
+const TCHAR* RegKey::getKeyName(int i) {
+  DWORD maxValueNameLen;
+  LONG result = RegQueryInfoKey(key, 0, 0, 0, 0, &maxValueNameLen, 0, 0, 0, 0, 0, 0);
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegQueryInfoKey", result);
+  if (valueNameBufLen < maxValueNameLen + 1) {
+    valueNameBufLen = maxValueNameLen + 1;
+    delete [] valueName.buf;
+    valueName.buf = new TCHAR[valueNameBufLen];
+  }
+  DWORD length = valueNameBufLen;
+  result = RegEnumKeyEx(key, i, valueName.buf, &length, NULL, 0, 0, 0);
+  if (result == ERROR_NO_MORE_ITEMS) return 0;
+  if (result != ERROR_SUCCESS)
+    throw rdr::SystemException("RegEnumKey", result);
+  return valueName.buf;
+}
diff --git a/win/rfb_win32/Registry.h b/win/rfb_win32/Registry.h
new file mode 100644
index 0000000..68d535c
--- /dev/null
+++ b/win/rfb_win32/Registry.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+// -=- Registry.h
+
+// C++ wrappers around the Win32 Registry APIs
+
+#ifndef __RFB_WIN32_REGISTRY_H__
+#define __RFB_WIN32_REGISTRY_H__
+
+#include <windows.h>
+#include <rfb_win32/Security.h>
+#include <rfb/util.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class RegKey {
+    public:
+      // No key open
+      RegKey();
+
+      // Duplicate the specified existing key
+      RegKey(const HKEY k);
+      RegKey(const RegKey& k);
+
+      // Calls close() internally
+      ~RegKey();
+
+      void setHKEY(HKEY key, bool freeKey);
+    private:
+      RegKey& operator=(const RegKey& k);
+      HKEY& operator=(const HKEY& k);
+    public:
+
+      // Returns true if key was created, false if already existed
+      bool createKey(const RegKey& root, const TCHAR* name);
+
+      // Opens key if it exists, or raises an exception if not
+      void openKey(const RegKey& root, const TCHAR* name, bool readOnly=false);
+
+      // Set the (discretionary) access control list for the key
+      void setDACL(const PACL acl, bool inheritFromParent=true);
+
+      // Closes current key, if required
+      void close();
+
+      // Delete a subkey/value
+      void deleteKey(const TCHAR* name) const;
+      void deleteValue(const TCHAR* name) const;
+
+
+      // Block waiting for a registry change, OR return immediately and notify the
+      // event when there is a change, if specified
+      void awaitChange(bool watchSubTree, DWORD filter, HANDLE event=0) const;
+
+      void setExpandString(const TCHAR* valname, const TCHAR* s) const;
+      void setString(const TCHAR* valname, const TCHAR* s) const;
+      void setBinary(const TCHAR* valname, const void* data, int length) const;
+      void setInt(const TCHAR* valname, int i) const;
+      void setBool(const TCHAR* valname, bool b) const;
+
+      TCHAR* getString(const TCHAR* valname) const;
+      TCHAR* getString(const TCHAR* valname, const TCHAR* def) const;
+
+      void getBinary(const TCHAR* valname, void** data, int* length) const;
+      void getBinary(const TCHAR* valname, void** data, int* length, void* def, int deflength) const;
+
+      int getInt(const TCHAR* valname) const;
+      int getInt(const TCHAR* valname, int def) const;
+
+      bool getBool(const TCHAR* valname) const;
+      bool getBool(const TCHAR* valname, bool def) const;
+
+      TCHAR* getRepresentation(const TCHAR* valname) const;
+
+      bool isValue(const TCHAR* valname) const;
+
+      // Get the name of value/key number "i"
+      // If there are fewer than "i" values then return 0
+      // NAME IS OWNED BY RegKey OBJECT!
+      const TCHAR* getValueName(int i);
+      const TCHAR* getKeyName(int i);
+
+      operator HKEY() const;
+    protected:
+      HKEY key;
+      bool freeKey;
+      TCharArray valueName;
+      DWORD valueNameBufLen;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_REG_CONFIG_H__
diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx
new file mode 100644
index 0000000..0af5064
--- /dev/null
+++ b/win/rfb_win32/SDisplay.cxx
@@ -0,0 +1,524 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplay.cxx
+//
+// The SDisplay class encapsulates a particular system display.
+
+#include <rfb_win32/SDisplay.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/TsSessions.h>
+#include <rfb_win32/CleanDesktop.h>
+#include <rfb_win32/CurrentUser.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/MonitorInfo.h>
+#include <rfb_win32/SDisplayCorePolling.h>
+#include <rfb_win32/SDisplayCoreWMHooks.h>
+#include <rfb_win32/SDisplayCoreDriver.h>
+#include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
+
+
+using namespace rdr;
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SDisplay");
+
+// - SDisplay-specific configuration options
+
+IntParameter rfb::win32::SDisplay::updateMethod("UpdateMethod",
+  "How to discover desktop updates; 0 - Polling, 1 - Application hooking, 2 - Driver hooking.", 1);
+BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs",
+  "Disable local keyboard and pointer input while the server is in use", false);
+StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction",
+  "Action to perform when all clients have disconnected.  (None, Lock, Logoff)", "None");
+StringParameter displayDevice("DisplayDevice",
+  "Display device name of the monitor to be remoted, or empty to export the whole desktop.", "");
+BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
+  "Remove the desktop wallpaper when the server is in use.", false);
+BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern",
+  "Remove the desktop background pattern when the server is in use.", false);
+BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
+  "Disable desktop user interface effects when the server is in use.", false);
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// SDisplay
+//
+
+typedef BOOL (WINAPI *_LockWorkStation_proto)();
+DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
+
+// -=- Constructor/Destructor
+
+SDisplay::SDisplay()
+  : server(0), pb(0), device(0),
+    core(0), ptr(0), kbd(0), clipboard(0),
+    inputs(0), monitor(0), cleanDesktop(0), cursor(0),
+    statusLocation(0)
+{
+  updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
+}
+
+SDisplay::~SDisplay()
+{
+  // XXX when the VNCServer has been deleted with clients active, stop()
+  // doesn't get called - this ought to be fixed in VNCServerST.  In any event,
+  // we should never call any methods on VNCServer once we're being deleted.
+  // This is because it is supposed to be guaranteed that the SDesktop exists
+  // throughout the lifetime of the VNCServer.  So if we're being deleted, then
+  // the VNCServer ought not to exist and therefore we shouldn't invoke any
+  // methods on it.  Setting server to zero here ensures that stop() doesn't
+  // call setPixelBuffer(0) on the server.
+  server = 0;
+  if (core) stop();
+}
+
+
+// -=- SDesktop interface
+
+void SDisplay::start(VNCServer* vs)
+{
+  vlog.debug("starting");
+
+  // Try to make session zero the console session
+  if (!inConsoleSession())
+    setConsoleSession();
+
+  // Start the SDisplay core
+  server = vs;
+  startCore();
+
+  vlog.debug("started");
+
+  if (statusLocation) *statusLocation = true;
+}
+
+void SDisplay::stop()
+{
+  vlog.debug("stopping");
+
+  // If we successfully start()ed then perform the DisconnectAction
+  if (core) {
+    CurrentUserToken cut;
+    CharArray action = disconnectAction.getData();
+    if (stricmp(action.buf, "Logoff") == 0) {
+      if (!cut.h)
+        vlog.info("ignoring DisconnectAction=Logoff - no current user");
+      else
+        ExitWindowsEx(EWX_LOGOFF, 0);
+    } else if (stricmp(action.buf, "Lock") == 0) {
+      if (!cut.h) {
+        vlog.info("ignoring DisconnectAction=Lock - no current user");
+      } else {
+        if (_LockWorkStation.isValid())
+          (*_LockWorkStation)();
+        else
+          ExitWindowsEx(EWX_LOGOFF, 0);
+      }
+    }
+  }
+
+  // Stop the SDisplayCore
+  if (server)
+    server->setPixelBuffer(0);
+  stopCore();
+  server = 0;
+
+  vlog.debug("stopped");
+
+  if (statusLocation) *statusLocation = false;
+}
+
+
+void SDisplay::startCore() {
+
+  // Currently, we just check whether we're in the console session, and
+  //   fail if not
+  if (!inConsoleSession())
+    throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
+  
+  // Switch to the current input desktop
+  if (rfb::win32::desktopChangeRequired()) {
+    if (!rfb::win32::changeDesktop())
+      throw rdr::Exception("unable to switch into input desktop");
+  }
+
+  // Initialise the change tracker and clipper
+  updates.clear();
+  clipper.setUpdateTracker(server);
+
+  // Create the framebuffer object
+  recreatePixelBuffer(true);
+
+  // Create the SDisplayCore
+  updateMethod_ = updateMethod;
+  int tryMethod = updateMethod_;
+  while (!core) {
+    try {
+      if (tryMethod == 2)
+        core = new SDisplayCoreDriver(this, &updates);
+      else if (tryMethod == 1)
+        core = new SDisplayCoreWMHooks(this, &updates);
+      else
+        core = new SDisplayCorePolling(this, &updates);
+      core->setScreenRect(screenRect);
+    } catch (rdr::Exception& e) {
+      delete core; core = 0;
+      if (tryMethod == 0)
+        throw rdr::Exception("unable to access desktop");
+      tryMethod--;
+      vlog.error(e.str());
+    }
+  }
+  vlog.info("Started %s", core->methodName());
+
+  // Start display monitor, clipboard handler and input handlers
+  monitor = new WMMonitor;
+  monitor->setNotifier(this);
+  clipboard = new Clipboard;
+  clipboard->setNotifier(this);
+  ptr = new SPointer;
+  kbd = new SKeyboard;
+  inputs = new WMBlockInput;
+  cursor = new WMCursor;
+
+  // Apply desktop optimisations
+  cleanDesktop = new CleanDesktop;
+  if (removePattern)
+    cleanDesktop->disablePattern();
+  if (removeWallpaper)
+    cleanDesktop->disableWallpaper();
+  if (disableEffects)
+    cleanDesktop->disableEffects();
+  isWallpaperRemoved = removeWallpaper;
+  isPatternRemoved = removePattern;
+  areEffectsDisabled = disableEffects;
+}
+
+void SDisplay::stopCore() {
+  if (core)
+    vlog.info("Stopping %s", core->methodName());
+  delete core; core = 0;
+  delete pb; pb = 0;
+  delete device; device = 0;
+  delete monitor; monitor = 0;
+  delete clipboard; clipboard = 0;
+  delete inputs; inputs = 0;
+  delete ptr; ptr = 0;
+  delete kbd; kbd = 0;
+  delete cleanDesktop; cleanDesktop = 0;
+  delete cursor; cursor = 0;
+  ResetEvent(updateEvent);
+}
+
+
+bool SDisplay::areHooksAvailable() {
+  return WMHooks::areAvailable();
+}
+
+bool SDisplay::isDriverAvailable() {
+  return SDisplayCoreDriver::isAvailable();
+}
+
+
+bool SDisplay::isRestartRequired() {
+  // - We must restart the SDesktop if:
+  // 1. We are no longer in the input desktop.
+  // 2. The any setting has changed.
+
+  // - Check that our session is the Console
+  if (!inConsoleSession())
+    return true;
+
+  // - Check that we are in the input desktop
+  if (rfb::win32::desktopChangeRequired())
+    return true;
+
+  // - Check that the update method setting hasn't changed
+  //   NB: updateMethod reflects the *selected* update method, not
+  //   necessarily the one in use, since we fall back to simpler
+  //   methods if more advanced ones fail!
+  if (updateMethod_ != updateMethod)
+    return true;
+
+  // - Check that the desktop optimisation settings haven't changed
+  //   This isn't very efficient, but it shouldn't change very often!
+  if ((isWallpaperRemoved != removeWallpaper) ||
+      (isPatternRemoved != removePattern) ||
+      (areEffectsDisabled != disableEffects))
+    return true;
+
+  return false;
+}
+
+
+void SDisplay::restartCore() {
+  vlog.info("restarting");
+
+  // Stop the existing Core  related resources
+  stopCore();
+  try {
+    // Start a new Core if possible
+    startCore();
+    vlog.info("restarted");
+  } catch (rdr::Exception& e) {
+    // If startCore() fails then we MUST disconnect all clients,
+    // to cause the server to stop() the desktop.
+    // Otherwise, the SDesktop is in an inconsistent state
+    // and the server will crash.
+    server->closeClients(e.str());
+  }
+}
+
+
+void SDisplay::pointerEvent(const Point& pos, int buttonmask) {
+  if (pb->getRect().contains(pos)) {
+    Point screenPos = pos.translate(screenRect.tl);
+    // - Check that the SDesktop doesn't need restarting
+    if (isRestartRequired())
+      restartCore();
+    if (ptr)
+      ptr->pointerEvent(screenPos, buttonmask);
+  }
+}
+
+void SDisplay::keyEvent(rdr::U32 key, bool down) {
+  // - Check that the SDesktop doesn't need restarting
+  if (isRestartRequired())
+    restartCore();
+  if (kbd)
+    kbd->keyEvent(key, down);
+}
+
+void SDisplay::clientCutText(const char* text, int len) {
+  CharArray clip_sz(len+1);
+  memcpy(clip_sz.buf, text, len);
+  clip_sz.buf[len] = 0;
+  clipboard->setClipText(clip_sz.buf);
+}
+
+
+void SDisplay::framebufferUpdateRequest()
+{
+  SetEvent(updateEvent);
+}
+
+Point SDisplay::getFbSize() {
+  bool startAndStop = !core;
+
+  // If not started, do minimal initialisation to get desktop size.
+  if (startAndStop)
+    recreatePixelBuffer();
+  Point result = Point(pb->width(), pb->height());
+
+  // Destroy the initialised structures.
+  if (startAndStop)
+    stopCore();
+  return result;
+}
+
+
+void
+SDisplay::notifyClipboardChanged(const char* text, int len) {
+  vlog.debug("clipboard text changed");
+  if (server)
+    server->serverCutText(text, len);
+}
+
+
+void
+SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
+  switch (evt) {
+  case WMMonitor::Notifier::DisplaySizeChanged:
+    vlog.debug("desktop size changed");
+    recreatePixelBuffer();
+    break;
+  case WMMonitor::Notifier::DisplayPixelFormatChanged:
+    vlog.debug("desktop format changed");
+    recreatePixelBuffer();
+    break;
+  case WMMonitor::Notifier::DisplayColourMapChanged:
+    vlog.debug("desktop colormap changed");
+    pb->updateColourMap();
+    if (server)
+      server->setColourMapEntries();
+    break;
+  default:
+    vlog.error("unknown display event received");
+  }
+}
+
+void
+SDisplay::processEvent(HANDLE event) {
+  if (event == updateEvent) {
+    vlog.write(120, "processEvent");
+    ResetEvent(updateEvent);
+
+    // - If the SDisplay isn't even started then quit now
+    if (!core) {
+      vlog.error("not start()ed");
+      return;
+    }
+
+    // - Ensure that the disableLocalInputs flag is respected
+    inputs->blockInputs(disableLocalInputs);
+
+    // - Only process updates if the server is ready
+    if (server && server->clientsReadyForUpdate()) {
+      bool try_update = false;
+
+      // - Check that the SDesktop doesn't need restarting
+      if (isRestartRequired()) {
+        restartCore();
+        return;
+      }
+
+      // - Flush any updates from the core
+      try {
+        core->flushUpdates();
+      } catch (rdr::Exception& e) {
+        vlog.error(e.str());
+        restartCore();
+        return;
+      }
+
+      // Ensure the cursor is up to date
+      WMCursor::Info info = cursor->getCursorInfo();
+      if (old_cursor != info) {
+        // Update the cursor shape if the visibility has changed
+        bool set_cursor = info.visible != old_cursor.visible;
+        // OR if the cursor is visible and the shape has changed.
+        set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
+
+        // Update the cursor shape
+        if (set_cursor)
+          pb->setCursor(info.visible ? info.cursor : 0, server);
+
+        // Update the cursor position
+        // NB: First translate from Screen coordinates to Desktop
+        Point desktopPos = info.position.translate(screenRect.tl.negate());
+        server->setCursorPos(desktopPos);
+        try_update = true;
+
+        old_cursor = info;
+      }
+
+      // Flush any changes to the server
+      try_update = flushChangeTracker() || try_update;
+      if (try_update) {
+        server->tryUpdate();
+      }
+    }
+    return;
+  }
+  throw rdr::Exception("No such event");
+}
+
+
+// -=- Protected methods
+
+void
+SDisplay::recreatePixelBuffer(bool force) {
+  // Open the specified display device
+  //   If no device is specified, open entire screen using GetDC().
+  //   Opening the whole display with CreateDC doesn't work on multi-monitor
+  //   systems for some reason.
+  DeviceContext* new_device = 0;
+  TCharArray deviceName(displayDevice.getData());
+  if (deviceName.buf[0]) {
+    vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
+    new_device = new DeviceDC(deviceName.buf);
+  }
+  if (!new_device) {
+    vlog.info("Attaching to virtual desktop");
+    new_device = new WindowDC(0);
+  }
+
+  // Get the coordinates of the specified dispay device
+  Rect newScreenRect;
+  if (deviceName.buf[0]) {
+    MonitorInfo info(CStr(deviceName.buf));
+    newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
+                         info.rcMonitor.right, info.rcMonitor.bottom);
+  } else {
+    newScreenRect = new_device->getClipBox();
+  }
+
+  // If nothing has changed & a recreate has not been forced, delete
+  // the new device context and return
+  if (pb && !force &&
+    newScreenRect.equals(screenRect) &&
+    new_device->getPF().equal(pb->getPF())) {
+    delete new_device;
+    return;
+  }
+
+  // Flush any existing changes to the server
+  flushChangeTracker();
+
+  // Delete the old pixelbuffer and device context
+  vlog.debug("deleting old pixel buffer & device");
+  if (pb)
+    delete pb;
+  if (device)
+    delete device;
+
+  // Create a DeviceFrameBuffer attached to the new device
+  vlog.debug("creating pixel buffer");
+  DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
+
+  // Replace the old PixelBuffer
+  screenRect = newScreenRect;
+  pb = new_buffer;
+  device = new_device;
+
+  // Initialise the pixels
+  pb->grabRegion(pb->getRect());
+
+  // Prevent future grabRect operations from throwing exceptions
+  pb->setIgnoreGrabErrors(true);
+
+  // Update the clipping update tracker
+  clipper.setClipRect(pb->getRect());
+
+  // Inform the core of the changes
+  if (core)
+    core->setScreenRect(screenRect);
+
+  // Inform the server of the changes
+  if (server)
+    server->setPixelBuffer(pb);
+}
+
+bool SDisplay::flushChangeTracker() {
+  if (updates.is_empty())
+    return false;
+
+  vlog.write(120, "flushChangeTracker");
+
+  // Translate the update coordinates from Screen coords to Desktop
+  updates.translate(screenRect.tl.negate());
+
+  // Clip the updates & flush them to the server
+  updates.copyTo(&clipper);
+  updates.clear();
+  return true;
+}
diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h
new file mode 100644
index 0000000..6dbb50a
--- /dev/null
+++ b/win/rfb_win32/SDisplay.h
@@ -0,0 +1,163 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplay.h
+//
+// The SDisplay class encapsulates a system display.
+
+#ifndef __RFB_SDISPLAY_H__
+#define __RFB_SDISPLAY_H__
+
+#include <rfb/SDesktop.h>
+#include <rfb/UpdateTracker.h>
+#include <rfb/Configuration.h>
+#include <rfb_win32/Handle.h>
+#include <rfb_win32/EventManager.h>
+#include <rfb_win32/SInput.h>
+#include <rfb_win32/Clipboard.h>
+#include <rfb_win32/CleanDesktop.h>
+#include <rfb_win32/WMCursor.h>
+#include <rfb_win32/WMNotifier.h>
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <rfb_win32/DeviceContext.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    //
+    // -=- SDisplay
+    //
+
+    class SDisplayCore {
+    public:
+      virtual ~SDisplayCore() {};
+      virtual void setScreenRect(const Rect& screenRect_) = 0;
+      virtual void flushUpdates() = 0;
+      virtual const char* methodName() const = 0;
+    };
+
+    class SDisplay : public SDesktop,
+      WMMonitor::Notifier,
+      Clipboard::Notifier,
+      public EventHandler
+    {
+    public:
+      SDisplay();
+      virtual ~SDisplay();
+
+      // -=- SDesktop interface
+
+      virtual void start(VNCServer* vs);
+      virtual void stop();
+      virtual void pointerEvent(const Point& pos, int buttonmask);
+      virtual void keyEvent(rdr::U32 key, bool down);
+      virtual void clientCutText(const char* str, int len);
+      virtual void framebufferUpdateRequest();
+      virtual Point getFbSize();
+
+      // -=- Clipboard
+      
+      virtual void notifyClipboardChanged(const char* text, int len);
+
+      // -=- Display events
+      
+      virtual void notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt);
+
+      // -=- EventHandler interface
+
+      HANDLE getUpdateEvent() {return updateEvent;}
+      virtual void processEvent(HANDLE event);
+
+      // -=- Notification of whether or not SDisplay is started
+
+      void setStatusLocation(bool* status) {statusLocation = status;}
+
+      friend class SDisplayCore;
+
+      static IntParameter updateMethod;
+      static BoolParameter disableLocalInputs;
+      static StringParameter disconnectAction;
+      static BoolParameter removeWallpaper;
+      static BoolParameter removePattern;
+      static BoolParameter disableEffects;
+
+      // -=- Use by VNC Config to determine whether hooks, driver, etc are available
+      static bool areHooksAvailable();
+      static bool isDriverAvailable();
+
+
+    protected:
+      bool isRestartRequired();
+      void startCore();
+      void stopCore();
+      void restartCore();
+      void recreatePixelBuffer(bool force=false);
+      bool flushChangeTracker();  // true if flushed, false if empty
+
+      VNCServer* server;
+
+      // -=- Display pixel buffer
+      DeviceFrameBuffer* pb;
+      DeviceContext* device;
+
+      // -=- The coordinates of Window's entire virtual Screen
+      Rect screenRect;
+
+      // -=- All changes are collected in UN-CLIPPED Display coords and merged
+      //     When they are to be flushed to the VNCServer, they are changed
+      //     to server coords and clipped appropriately.
+      SimpleUpdateTracker updates;
+      ClippingUpdateTracker clipper;
+
+      // -=- Internal SDisplay implementation
+      SDisplayCore* core;
+      int updateMethod_;
+
+      // Inputs
+      SPointer* ptr;
+      SKeyboard* kbd;
+      Clipboard* clipboard;
+      WMBlockInput* inputs;
+
+      // Desktop state
+      WMMonitor* monitor;
+
+      // Desktop optimisation
+      CleanDesktop* cleanDesktop;
+      bool isWallpaperRemoved;
+      bool isPatternRemoved;
+      bool areEffectsDisabled;
+
+      // Cursor
+      WMCursor* cursor;
+      WMCursor::Info old_cursor;
+      Region old_cursor_region;
+      Point cursor_renderpos;
+
+      // -=- Event signalled to trigger an update to be flushed
+      Handle updateEvent;
+
+      // -=- Where to write the active/inactive indicator to
+      bool* statusLocation;
+    };
+
+  }
+}
+
+#endif // __RFB_SDISPLAY_H__
diff --git a/win/rfb_win32/SDisplayCoreDriver.h b/win/rfb_win32/SDisplayCoreDriver.h
new file mode 100644
index 0000000..5fea75c
--- /dev/null
+++ b/win/rfb_win32/SDisplayCoreDriver.h
@@ -0,0 +1,52 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplayCoreDriver.h
+//
+// Placeholder for SDisplayCore mirror-driver implementation.
+
+#ifndef __RFB_SDISPLAY_CORE_DRIVER_H__
+#define __RFB_SDISPLAY_CORE_DRIVER_H__
+
+#include <rfb_win32/SDisplay.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class SDisplayCoreDriver: public SDisplayCore {
+    public:
+      SDisplayCoreDriver(SDisplay* display, UpdateTracker* ut) {
+        throw rdr::Exception("Not supported");
+      }
+
+      // - Called by SDisplay to inform Core of the screen size
+      virtual void setScreenRect(const Rect& screenRect_) {}
+
+      // - Called by SDisplay to flush updates to the specified tracker
+      virtual void flushUpdates() {}
+
+      virtual const char* methodName() const { return "VNC Mirror Driver"; }
+
+      // - Determine whether the display driver is installed & usable
+      static bool isAvailable() { return false; }
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/SDisplayCorePolling.cxx b/win/rfb_win32/SDisplayCorePolling.cxx
new file mode 100644
index 0000000..fc57ecd
--- /dev/null
+++ b/win/rfb_win32/SDisplayCorePolling.cxx
@@ -0,0 +1,81 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplayCorePolling.cxx
+
+#include <rfb_win32/SDisplayCorePolling.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SDisplayCorePolling");
+
+const int POLLING_SEGMENTS = 16;
+
+const int SDisplayCorePolling::pollTimerId = 1;
+
+SDisplayCorePolling::SDisplayCorePolling(SDisplay* d, UpdateTracker* ut, int pollInterval_)
+  : MsgWindow(_T("rfb::win32::SDisplayCorePolling")), updateTracker(ut),
+  pollTimer(getHandle(), pollTimerId), pollNextStrip(false), display(d) {
+  pollInterval = max(10, (pollInterval_ / POLLING_SEGMENTS));
+  copyrect.setUpdateTracker(ut);
+}
+
+SDisplayCorePolling::~SDisplayCorePolling() {
+}
+
+LRESULT SDisplayCorePolling::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  if (msg == WM_TIMER && wParam == pollTimerId) {
+    pollNextStrip = true;
+    SetEvent(display->getUpdateEvent());
+    return 0;
+  }
+  return MsgWindow::processMessage(msg, wParam, lParam);
+}
+
+void SDisplayCorePolling::setScreenRect(const Rect& screenRect_) {
+  vlog.info("setScreenRect");
+  screenRect = screenRect_;
+  pollIncrementY = (screenRect.height()+POLLING_SEGMENTS-1)/POLLING_SEGMENTS;
+  pollNextY = screenRect.tl.y;
+  pollTimer.start(pollInterval);
+}
+
+void SDisplayCorePolling::flushUpdates() {
+  vlog.write(120, "flushUpdates");
+
+  // Check for window movement
+  while (copyrect.processEvent()) {}
+
+  if (pollNextStrip) {
+    // Poll the next strip of the screen (in Screen coordinates)
+    pollNextStrip = false;
+    Rect pollrect = screenRect;
+    if (pollNextY >= pollrect.br.y) {
+      // Yes.  Reset the counter and return
+      pollNextY = pollrect.tl.y;
+    } else {
+      // No.  Poll the next section
+      pollrect.tl.y = pollNextY;
+      pollNextY += pollIncrementY;
+      pollrect.br.y = min(pollNextY, pollrect.br.y);
+      updateTracker->add_changed(pollrect);
+    }
+  }
+}
diff --git a/win/rfb_win32/SDisplayCorePolling.h b/win/rfb_win32/SDisplayCorePolling.h
new file mode 100644
index 0000000..9e1b5ad
--- /dev/null
+++ b/win/rfb_win32/SDisplayCorePolling.h
@@ -0,0 +1,75 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplayCorePolling.h
+//
+// SDisplayCore implementation that simply polls the screen, in sections,
+// in order to detect changes.  This Core will signal the SDisplay's
+// updateEvent regularly, causing it to call the Core back to propagate
+// changes to the VNC Server.
+
+
+#ifndef __RFB_SDISPLAY_CORE_POLLING_H__
+#define __RFB_SDISPLAY_CORE_POLLING_H__
+
+#include <rfb_win32/SDisplay.h>
+#include <rfb_win32/IntervalTimer.h>
+#include <rfb_win32/WMWindowCopyRect.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class SDisplayCorePolling : public SDisplayCore, protected MsgWindow {
+    public:
+      SDisplayCorePolling(SDisplay* display, UpdateTracker* ut, int pollIntervalMs=50);
+      ~SDisplayCorePolling();
+
+      // - Called by SDisplay to inform Core of the screen size
+      virtual void setScreenRect(const Rect& screenRect_);
+
+      // - Called by SDisplay to flush updates to the specified tracker
+      virtual void flushUpdates();
+
+      virtual const char* methodName() const { return "Polling"; }
+
+    protected:
+      // - MsgWindow overrides
+      //   processMessage is used to service the cursor & polling timers
+      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+      // - Hooking subcomponents used to track the desktop state
+      WMCopyRect copyrect;
+
+      // - Background full screen polling fields
+      IntervalTimer pollTimer;
+      static const int pollTimerId;
+      Rect screenRect;
+      int pollInterval;
+      int pollNextY;
+      int pollIncrementY;
+      bool pollNextStrip;
+
+      // - Handle back to the owning SDisplay, and to the UpdateTracker to flush to
+      SDisplay* display;
+      UpdateTracker* updateTracker;
+    };
+
+  };
+};
+
+#endif
\ No newline at end of file
diff --git a/win/rfb_win32/SDisplayCoreWMHooks.cxx b/win/rfb_win32/SDisplayCoreWMHooks.cxx
new file mode 100644
index 0000000..10b88e0
--- /dev/null
+++ b/win/rfb_win32/SDisplayCoreWMHooks.cxx
@@ -0,0 +1,74 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplayCoreWMHooks.cxx
+
+#include <rfb_win32/SDisplayCoreWMHooks.h>
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SDisplayCoreWMHooks");
+
+const int SDisplayCoreWMHooks::cursorTimerId = 2;
+const int SDisplayCoreWMHooks::consolePollTimerId = 3;
+
+
+SDisplayCoreWMHooks::SDisplayCoreWMHooks(SDisplay* d, UpdateTracker* ut)
+  : SDisplayCorePolling(d, ut, 5000),
+  cursorTimer(getHandle(), cursorTimerId),
+  consolePollTimer(getHandle(), consolePollTimerId),
+  pollConsoles(false) {
+  if (!hooks.setEvent(display->getUpdateEvent()))
+    throw rdr::Exception("hook subsystem failed to initialise");
+  poller.setUpdateTracker(updateTracker);
+  cursorTimer.start(20);
+  consolePollTimer.start(200);
+}
+
+SDisplayCoreWMHooks::~SDisplayCoreWMHooks() {
+}
+
+LRESULT SDisplayCoreWMHooks::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  if (msg == WM_TIMER) {
+    if (wParam == cursorTimerId) {
+      SetEvent(display->getUpdateEvent());
+      return 0;
+    } else if (wParam == consolePollTimerId) {
+      pollConsoles = true;
+      SetEvent(display->getUpdateEvent());
+      return 0;
+    }
+  }
+  return SDisplayCorePolling::processMessage(msg, wParam, lParam);
+}
+
+void SDisplayCoreWMHooks::flushUpdates() {
+  // Poll any visible console windows
+  if (pollConsoles) {
+    pollConsoles = false;
+    poller.processEvent();
+  }
+
+  // Check for updates from the hooks
+  hooks.getUpdates(updateTracker);
+
+  // Check for updates from the polling Core
+  SDisplayCorePolling::flushUpdates();
+}
diff --git a/win/rfb_win32/SDisplayCoreWMHooks.h b/win/rfb_win32/SDisplayCoreWMHooks.h
new file mode 100644
index 0000000..24fa5cd
--- /dev/null
+++ b/win/rfb_win32/SDisplayCoreWMHooks.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SDisplayCoreWMHooks.h
+//
+// SDisplayCore implementation that uses WMHooks to capture changes to
+// the display.
+// Whenever changes are detected, the SDisplay's updateEvent is signalled,
+// so that it can perform housekeeping tasks (like ensuring the currently
+// active desktop is the correct one), before flushing changes from the
+// Core to the VNC Server.  The SDisplay will clip the changes before they
+// reach the VNC Server.
+
+
+#ifndef __RFB_SDISPLAY_CORE_WMHOOKS_H__
+#define __RFB_SDISPLAY_CORE_WMHOOKS_H__
+
+#include <rfb_win32/SDisplayCorePolling.h>
+#include <rfb_win32/WMHooks.h>
+#include <rfb_win32/WMPoller.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class SDisplayCoreWMHooks : public SDisplayCorePolling {
+    public:
+      SDisplayCoreWMHooks(SDisplay* display, UpdateTracker* ut);
+      ~SDisplayCoreWMHooks();
+
+      // - Called by SDisplay to flush updates to the specified tracker
+      virtual void flushUpdates();
+
+      virtual const char* methodName() const { return "VNC Hooks"; }
+
+    protected:
+      // - MsgWindow overrides
+      //   processMessage is used to service the cursor & polling timers
+      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+      // - Hooking subcomponents used to track the desktop state
+      WMHooks hooks;
+      WMPoller poller;
+      IntervalTimer cursorTimer;
+      IntervalTimer consolePollTimer;
+      bool pollConsoles;
+      static const int consolePollTimerId;
+      static const int cursorTimerId;
+    };
+
+  };
+};
+
+#endif
diff --git a/win/rfb_win32/SFileTransferManagerWin32.cxx b/win/rfb_win32/SFileTransferManagerWin32.cxx
new file mode 100644
index 0000000..edc898b
--- /dev/null
+++ b/win/rfb_win32/SFileTransferManagerWin32.cxx
@@ -0,0 +1,71 @@
+/* Copyright (C) 2006 TightVNC Team.  All Rights Reserved.
+ *
+ * Developed by Dennis Syrovatsky.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- SFileTransferManagerWin32.cxx
+
+#include <rfb_win32/SFileTransferManagerWin32.h>
+
+using namespace rfb;
+using namespace win32;
+
+SFileTransferManagerWin32::SFileTransferManagerWin32()
+{
+
+}
+
+SFileTransferManagerWin32::~SFileTransferManagerWin32()
+{
+
+}
+
+SFileTransfer *
+SFileTransferManagerWin32::createObject(network::Socket *sock)
+{
+  rfb::SFileTransfer *pFT = 0;
+  rfb::win32::SFileTransferWin32 *pFTWin32 = 0;
+
+  pFTWin32 = new SFileTransferWin32(sock);
+  if (pFTWin32 == NULL) return NULL;
+
+  pFT = (SFileTransfer *) pFTWin32;
+
+  m_lstFTObjects.push_front(pFT);
+
+  return pFT;
+}
+
+void 
+SFileTransferManagerWin32::processDownloadMsg(MSG msg)
+{
+  SFileTransfer *pFT = (SFileTransfer *)msg.lParam;
+
+  if (pFT != NULL) {
+    std::list<SFileTransfer*>::iterator i;
+    for (i=m_lstFTObjects.begin(); i!=m_lstFTObjects.end(); i++) {
+      if ((*i) == pFT) {
+        (*i)->sendFileDownloadPortion();
+        return;
+      }
+    }
+  }
+}
diff --git a/win/rfb_win32/SFileTransferManagerWin32.h b/win/rfb_win32/SFileTransferManagerWin32.h
new file mode 100644
index 0000000..ed1f997
--- /dev/null
+++ b/win/rfb_win32/SFileTransferManagerWin32.h
@@ -0,0 +1,48 @@
+/* Copyright (C) 2006 TightVNC Team.  All Rights Reserved.
+ *
+ * Developed by Dennis Syrovatsky.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- SFileTransferManagerWin32.h
+
+#ifndef __RFB_WIN32_SFILETRANSFERMANAGERWIN32_H__
+#define __RFB_WIN32_SFILETRANSFERMANAGERWIN32_H__
+
+#include <rfb/SFileTransfer.h>
+#include <rfb/SFileTransferManager.h>
+#include <rfb_win32/SFileTransferWin32.h>
+
+namespace rfb {
+  namespace win32 {
+    class SFileTransferManagerWin32 : public rfb::SFileTransferManager
+    {
+    public:
+      SFileTransferManagerWin32();
+      virtual ~SFileTransferManagerWin32();
+
+      void processDownloadMsg(MSG msg);
+
+      virtual SFileTransfer *createObject(network::Socket *sock);
+    };
+  };
+}
+
+#endif // __RFB_WIN32_SFILETRANSFERMANAGERWIN32_H__
diff --git a/win/rfb_win32/SFileTransferWin32.cxx b/win/rfb_win32/SFileTransferWin32.cxx
new file mode 100644
index 0000000..5aea412
--- /dev/null
+++ b/win/rfb_win32/SFileTransferWin32.cxx
@@ -0,0 +1,125 @@
+/* Copyright (C) 2006 TightVNC Team.  All Rights Reserved.
+ *
+ * Developed by Dennis Syrovatsky.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- SFileTransferWin32.cxx
+
+#include <rfb/msgTypes.h>
+#include <rfb_win32/FolderManager.h>
+#include <rfb_win32/SFileTransferWin32.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+SFileTransferWin32::SFileTransferWin32(network::Socket *sock) : SFileTransfer(sock)
+{
+}
+
+SFileTransferWin32::~SFileTransferWin32()
+{
+}
+
+bool 
+SFileTransferWin32::initDownloadCallback()
+{
+  PostThreadMessage(GetCurrentThreadId(), VNCM_FT_DOWNLOAD, (WPARAM) 0, (LPARAM) this);
+  return true;
+}
+
+bool 
+SFileTransferWin32::processDownloadCallback()
+{
+  return sendFileDownloadPortion();
+}
+
+bool 
+SFileTransferWin32::convertPathFromNet(char *pszPath)
+{
+  int len = strlen(pszPath);
+  if (pszPath[0] == '/') {
+    if (len == 1) { 
+      pszPath[0] = '\0'; 
+      return true; 
+    }
+  } else {
+    return false;
+  }
+
+  for(int i = 0; i < (len - 1); i++) {
+    if(pszPath[i+1] == '/') pszPath[i+1] = '\\';
+    pszPath[i] = pszPath[i+1];
+  }
+
+  pszPath[len-1] = '\0';
+  return true;
+}
+
+bool 
+SFileTransferWin32::makeFileList(char *pszPath, FileInfo *pFI, bool bDirOnly)
+{
+  FolderManager fm;
+  if (fm.getDirInfo(pszPath, pFI, bDirOnly)) 
+    return true; 
+  else 
+    return false;
+}
+
+bool 
+SFileTransferWin32::deleteIt(char *pszPath)
+{
+  FolderManager fm;
+
+  return fm.deleteIt(pszPath);
+}
+
+bool 
+SFileTransferWin32::renameIt(char *pszOldPath, char *pszNewPath)
+{
+  FolderManager fm;
+
+  return fm.renameIt(pszOldPath, pszNewPath);
+}
+
+bool 
+SFileTransferWin32::createDir(char *pszPath)
+{
+  FolderManager fm;
+
+  return fm.createDir(pszPath);
+}
+
+bool 
+SFileTransferWin32::getDirSize(char *pszName, unsigned short *pHighSize16, 
+                               unsigned int *pLowSize32)
+{
+  FolderManager fm;
+  DWORD64 dw64DirSize = 0;
+
+  if (!fm.getDirSize(pszName, &dw64DirSize)) return false;
+
+  if (dw64DirSize & 0xFFFF000000000000) return false;
+
+  *pHighSize16 = ((dw64DirSize & 0x0000FFFF00000000) >> 32);
+  *pLowSize32 = (dw64DirSize & 0x00000000FFFFFFFF);
+
+  return true;
+}
diff --git a/win/rfb_win32/SFileTransferWin32.h b/win/rfb_win32/SFileTransferWin32.h
new file mode 100644
index 0000000..5f682a4
--- /dev/null
+++ b/win/rfb_win32/SFileTransferWin32.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2006 TightVNC Team.  All Rights Reserved.
+ *
+ * Developed by Dennis Syrovatsky.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- SFileTransferWin32.h
+
+#ifndef __RFB_SFILETRANSFERWIN32_H__
+#define __RFB_SFILETRANSFERWIN32_H__
+
+#include <windows.h>
+
+#include <rfb/SFileTransfer.h>
+
+const UINT VNCM_FT_DOWNLOAD = WM_USER + 2;
+
+namespace rfb {
+  namespace win32 {
+    class SFileTransferWin32 : public rfb::SFileTransfer
+    {
+    public:
+      SFileTransferWin32(network::Socket *sock);
+      virtual ~SFileTransferWin32();
+
+      bool processDownloadCallback();
+      virtual bool initDownloadCallback();
+
+      virtual bool convertPathFromNet(char *pszPath);
+      virtual bool makeFileList(char *pszPath, FileInfo *pFI, bool bDirOnly);
+  
+      virtual bool deleteIt(char *pszPath);
+      virtual bool renameIt(char *pszOldPath, char *pszNewPath);
+      virtual bool createDir(char *pszPath);
+
+      virtual bool getDirSize(char *pszName, unsigned short *pHighSize16, unsigned int *pLowSize32);
+
+    };
+  };
+}
+
+#endif // __RFB_SFILETRANSFERWIN32_H__
diff --git a/win/rfb_win32/SInput.cxx b/win/rfb_win32/SInput.cxx
new file mode 100644
index 0000000..db59287
--- /dev/null
+++ b/win/rfb_win32/SInput.cxx
@@ -0,0 +1,466 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SInput.cxx
+//
+// A number of routines that accept VNC input event data and perform
+// the appropriate actions under Win32
+
+#define XK_MISCELLANY
+#define XK_LATIN1
+#define XK_CURRENCY
+#include <rfb/keysymdef.h>
+
+#include <tchar.h>
+#include <rfb_win32/SInput.h>
+#include <rfb_win32/MonitorInfo.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/keymap.h>
+#include <rdr/Exception.h>
+#include <rfb/LogWriter.h>
+
+#if(defined(INPUT_MOUSE) && defined(RFB_HAVE_MONITORINFO))
+#define RFB_HAVE_SENDINPUT
+#else
+#pragma message("  NOTE: Not building SendInput support.")
+#endif
+
+using namespace rfb;
+
+static LogWriter vlog("SInput");
+
+#ifdef RFB_HAVE_SENDINPUT
+typedef UINT (WINAPI *_SendInput_proto)(UINT, LPINPUT, int);
+static win32::DynamicFn<_SendInput_proto> _SendInput(_T("user32.dll"), "SendInput");
+#endif
+
+//
+// -=- Pointer implementation for Win32
+//
+
+static DWORD buttonDownMapping[8] = {
+  MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_RIGHTDOWN,
+  MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0
+};
+
+static DWORD buttonUpMapping[8] = {
+  MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTUP,
+  MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0
+};
+
+static DWORD buttonDataMapping[8] = {
+  0, 0, 0, 120, -120, 0, 0, 0
+};
+
+win32::SPointer::SPointer()
+  : last_buttonmask(0)
+{
+}
+
+void
+win32::SPointer::pointerEvent(const Point& pos, int buttonmask)
+{
+  // - We are specifying absolute coordinates
+  DWORD flags = MOUSEEVENTF_ABSOLUTE;
+
+  // - Has the pointer moved since the last event?
+  if (!last_position.equals(pos))
+    flags |= MOUSEEVENTF_MOVE;
+
+  // - If the system swaps left and right mouse buttons then we must
+  //   swap them here to negate the effect, so that we do the actual
+  //   action we mean to do
+  if (::GetSystemMetrics(SM_SWAPBUTTON)) {
+    bool leftDown = buttonmask & 1;
+    bool rightDown = buttonmask & 4;
+    buttonmask = (buttonmask & ~(1 | 4));
+    if (leftDown) buttonmask |= 4;
+    if (rightDown) buttonmask |= 1;
+  }
+
+  DWORD data = 0;
+  for (int i = 0; i < 8; i++) {
+    if ((buttonmask & (1<<i)) != (last_buttonmask & (1<<i))) {
+      if (buttonmask & (1<<i)) {
+        flags |= buttonDownMapping[i];
+        if (buttonDataMapping[i]) {
+          if (data) vlog.info("warning - two buttons set mouse_event data field");
+          data = buttonDataMapping[i];
+        }
+      } else {
+        flags |= buttonUpMapping[i];
+      }
+    }
+  }
+
+  last_position = pos;
+  last_buttonmask = buttonmask;
+
+  Rect primaryDisplay(0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN));
+  if (primaryDisplay.contains(pos)) {
+    // mouse_event wants coordinates specified as a proportion of the
+    // primary display's size, scaled to the range 0 to 65535
+    Point scaled;
+    scaled.x = (pos.x * 65535) / (primaryDisplay.width()-1);
+    scaled.y = (pos.y * 65535) / (primaryDisplay.height()-1);
+    ::mouse_event(flags, scaled.x, scaled.y, data, 0);
+  } else {
+    // The event lies outside the primary monitor.  Under Win2K, we can just use
+    // SendInput, which allows us to provide coordinates scaled to the virtual desktop.
+    // SendInput is available on all multi-monitor-aware platforms.
+#ifdef RFB_HAVE_SENDINPUT
+    if (osVersion.isPlatformNT) {
+      if (!_SendInput.isValid())
+        throw rdr::Exception("SendInput not available");
+      INPUT evt;
+      evt.type = INPUT_MOUSE;
+      Point vPos(pos.x-GetSystemMetrics(SM_XVIRTUALSCREEN),
+                 pos.y-GetSystemMetrics(SM_YVIRTUALSCREEN));
+      evt.mi.dx = (vPos.x * 65535) / (GetSystemMetrics(SM_CXVIRTUALSCREEN)-1);
+      evt.mi.dy = (vPos.y * 65535) / (GetSystemMetrics(SM_CYVIRTUALSCREEN)-1);
+      evt.mi.dwFlags = flags | MOUSEEVENTF_VIRTUALDESK;
+      evt.mi.dwExtraInfo = 0;
+      evt.mi.mouseData = data;
+      evt.mi.time = 0;
+      if ((*_SendInput)(1, &evt, sizeof(evt)) != 1)
+        throw rdr::SystemException("SendInput", GetLastError());
+    } else {
+      // Under Win9x, this is not addressable by either mouse_event or SendInput
+      // *** STUPID KLUDGY HACK ***
+      POINT cursorPos; GetCursorPos(&cursorPos);
+      ULONG oldSpeed, newSpeed = 10;
+      ULONG mouseInfo[3];
+      if (flags & MOUSEEVENTF_MOVE) {
+        flags &= ~MOUSEEVENTF_ABSOLUTE;
+        SystemParametersInfo(SPI_GETMOUSE, 0, &mouseInfo, 0);
+        SystemParametersInfo(SPI_GETMOUSESPEED, 0, &oldSpeed, 0);
+        vlog.debug("SPI_GETMOUSE %d, %d, %d, speed %d", mouseInfo[0], mouseInfo[1], mouseInfo[2], oldSpeed);
+        ULONG idealMouseInfo[] = {10, 0, 0};
+        SystemParametersInfo(SPI_SETMOUSESPEED, 0, &newSpeed, 0);
+        SystemParametersInfo(SPI_SETMOUSE, 0, &idealMouseInfo, 0);
+      }
+      ::mouse_event(flags, pos.x-cursorPos.x, pos.y-cursorPos.y, data, 0);
+      if (flags & MOUSEEVENTF_MOVE) {
+        SystemParametersInfo(SPI_SETMOUSE, 0, &mouseInfo, 0);
+        SystemParametersInfo(SPI_SETMOUSESPEED, 0, &oldSpeed, 0);
+      }
+    }
+#endif
+  }
+}
+
+//
+// -=- Keyboard implementation
+//
+
+BoolParameter rfb::win32::SKeyboard::deadKeyAware("DeadKeyAware",
+  "Whether to assume the viewer has already interpreted dead key sequences "
+  "into latin-1 characters", true);
+
+static bool oneShift;
+
+// The keysymToAscii table transforms a couple of awkward keysyms into their
+// ASCII equivalents.
+struct  keysymToAscii_t {
+  rdr::U32 keysym;
+  rdr::U8 ascii;
+};
+
+keysymToAscii_t keysymToAscii[] = {
+  { XK_KP_Space, ' ' },
+  { XK_KP_Equal, '=' },
+};
+
+rdr::U8 latin1DeadChars[] = {
+  XK_grave, XK_acute, XK_asciicircum, XK_diaeresis, XK_degree, XK_cedilla,
+  XK_asciitilde
+};
+
+struct latin1ToDeadChars_t {
+  rdr::U8 latin1Char;
+  rdr::U8 deadChar;
+  rdr::U8 baseChar;
+};
+
+latin1ToDeadChars_t latin1ToDeadChars[] = {
+
+  { XK_Agrave, XK_grave, XK_A },
+  { XK_Egrave, XK_grave, XK_E },
+  { XK_Igrave, XK_grave, XK_I },
+  { XK_Ograve, XK_grave, XK_O },
+  { XK_Ugrave, XK_grave, XK_U },
+  { XK_agrave, XK_grave, XK_a },
+  { XK_egrave, XK_grave, XK_e },
+  { XK_igrave, XK_grave, XK_i },
+  { XK_ograve, XK_grave, XK_o},
+  { XK_ugrave, XK_grave, XK_u },
+
+  { XK_Aacute, XK_acute, XK_A },
+  { XK_Eacute, XK_acute, XK_E },
+  { XK_Iacute, XK_acute, XK_I },
+  { XK_Oacute, XK_acute, XK_O },
+  { XK_Uacute, XK_acute, XK_U },
+  { XK_Yacute, XK_acute, XK_Y },
+  { XK_aacute, XK_acute, XK_a },
+  { XK_eacute, XK_acute, XK_e },
+  { XK_iacute, XK_acute, XK_i },
+  { XK_oacute, XK_acute, XK_o},
+  { XK_uacute, XK_acute, XK_u },
+  { XK_yacute, XK_acute, XK_y },
+
+  { XK_Acircumflex, XK_asciicircum, XK_A },
+  { XK_Ecircumflex, XK_asciicircum, XK_E },
+  { XK_Icircumflex, XK_asciicircum, XK_I },
+  { XK_Ocircumflex, XK_asciicircum, XK_O },
+  { XK_Ucircumflex, XK_asciicircum, XK_U },
+  { XK_acircumflex, XK_asciicircum, XK_a },
+  { XK_ecircumflex, XK_asciicircum, XK_e },
+  { XK_icircumflex, XK_asciicircum, XK_i },
+  { XK_ocircumflex, XK_asciicircum, XK_o},
+  { XK_ucircumflex, XK_asciicircum, XK_u },
+
+  { XK_Adiaeresis, XK_diaeresis, XK_A },
+  { XK_Ediaeresis, XK_diaeresis, XK_E },
+  { XK_Idiaeresis, XK_diaeresis, XK_I },
+  { XK_Odiaeresis, XK_diaeresis, XK_O },
+  { XK_Udiaeresis, XK_diaeresis, XK_U },
+  { XK_adiaeresis, XK_diaeresis, XK_a },
+  { XK_ediaeresis, XK_diaeresis, XK_e },
+  { XK_idiaeresis, XK_diaeresis, XK_i },
+  { XK_odiaeresis, XK_diaeresis, XK_o},
+  { XK_udiaeresis, XK_diaeresis, XK_u },
+  { XK_ydiaeresis, XK_diaeresis, XK_y },
+
+  { XK_Aring, XK_degree, XK_A },
+  { XK_aring, XK_degree, XK_a },
+
+  { XK_Ccedilla, XK_cedilla, XK_C },
+  { XK_ccedilla, XK_cedilla, XK_c },
+
+  { XK_Atilde, XK_asciitilde, XK_A },
+  { XK_Ntilde, XK_asciitilde, XK_N },
+  { XK_Otilde, XK_asciitilde, XK_O },
+  { XK_atilde, XK_asciitilde, XK_a },
+  { XK_ntilde, XK_asciitilde, XK_n },
+  { XK_otilde, XK_asciitilde, XK_o },
+};
+
+// doKeyboardEvent wraps the system keybd_event function and attempts to find
+// the appropriate scancode corresponding to the supplied virtual keycode.
+
+inline void doKeyboardEvent(BYTE vkCode, DWORD flags) {
+  vlog.debug("vkCode 0x%x flags 0x%x", vkCode, flags);
+  keybd_event(vkCode, MapVirtualKey(vkCode, 0), flags, 0);
+}
+
+// KeyStateModifier is a class which helps simplify generating a "fake" press
+// or release of shift, ctrl, alt, etc.  An instance of the class is created
+// for every key which may need to be pressed or released.  Then either press()
+// or release() may be called to make sure that the corresponding key is in the
+// right state.  The destructor of the class automatically reverts to the
+// previous state.
+
+class KeyStateModifier {
+public:
+  KeyStateModifier(int vkCode_, int flags_=0)
+    : vkCode(vkCode_), flags(flags_), pressed(false), released(false)
+  {}
+  void press() {
+    if (!(GetAsyncKeyState(vkCode) & 0x8000)) {
+      doKeyboardEvent(vkCode, flags);
+      pressed = true;
+    }
+  }
+  void release() {
+    if (GetAsyncKeyState(vkCode) & 0x8000) {
+      doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP);
+      released = true;
+    }
+  }
+  ~KeyStateModifier() {
+    if (pressed) {
+      doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP);
+    } else if (released) {
+      doKeyboardEvent(vkCode, flags);
+    }
+  }
+  int vkCode;
+  int flags;
+  bool pressed;
+  bool released;
+};
+
+
+// doKeyEventWithModifiers() generates a key event having first "pressed" or
+// "released" the shift, ctrl or alt modifiers if necessary.
+
+void doKeyEventWithModifiers(BYTE vkCode, BYTE modifierState, bool down)
+{
+  KeyStateModifier ctrl(VK_CONTROL);
+  KeyStateModifier alt(VK_MENU);
+  KeyStateModifier shift(VK_SHIFT);
+
+  if (down) {
+    if (modifierState & 2) ctrl.press();
+    if (modifierState & 4) alt.press();
+    if (modifierState & 1) {
+      shift.press(); 
+    } else {
+      shift.release();
+    }
+  }
+  doKeyboardEvent(vkCode, down ? 0 : KEYEVENTF_KEYUP);
+}
+
+
+win32::SKeyboard::SKeyboard()
+{
+  oneShift = rfb::win32::osVersion.isPlatformWindows;
+  for (int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) {
+    vkMap[keymap[i].keysym] = keymap[i].vk;
+    extendedMap[keymap[i].keysym] = keymap[i].extended;
+  }
+
+  // Find dead characters for the current keyboard layout
+  // XXX how could we handle the keyboard layout changing?
+  BYTE keystate[256];
+  memset(keystate, 0, 256);
+  for (int j = 0; j < sizeof(latin1DeadChars); j++) {
+    SHORT s = VkKeyScan(latin1DeadChars[j]);
+    if (s != -1) {
+      BYTE vkCode = LOBYTE(s);
+      BYTE modifierState = HIBYTE(s);
+      keystate[VK_SHIFT] = (modifierState & 1) ? 0x80 : 0;
+      keystate[VK_CONTROL] = (modifierState & 2) ? 0x80 : 0;
+      keystate[VK_MENU] = (modifierState & 4) ? 0x80 : 0;
+      rdr::U8 chars[2];
+      int nchars = ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0);
+      if (nchars < 0) {
+        vlog.debug("Found dead key 0x%x '%c'",
+                   latin1DeadChars[j], latin1DeadChars[j]);
+        deadChars.push_back(latin1DeadChars[j]);
+        ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0);
+      }
+    }
+  }
+}
+
+
+void win32::SKeyboard::keyEvent(rdr::U32 keysym, bool down)
+{
+  for (int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) {
+    if (keysymToAscii[i].keysym == keysym) {
+      keysym = keysymToAscii[i].ascii;
+      break;
+    }
+  }
+
+  if ((keysym >= 32 && keysym <= 126) ||
+      (keysym >= 160 && keysym <= 255))
+  {
+    // ordinary Latin-1 character
+
+    if (deadKeyAware) {
+      // Detect dead chars and generate the dead char followed by space so
+      // that we'll end up with the original char.
+      for (int i = 0; i < deadChars.size(); i++) {
+        if (keysym == deadChars[i]) {
+          SHORT dc = VkKeyScan(keysym);
+          if (dc != -1) {
+            if (down) {
+              vlog.info("latin-1 dead key: 0x%x vkCode 0x%x mod 0x%x "
+                        "followed by space", keysym, LOBYTE(dc), HIBYTE(dc));
+              doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true);
+              doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false);
+              doKeyEventWithModifiers(VK_SPACE, 0, true);
+              doKeyEventWithModifiers(VK_SPACE, 0, false);
+            }
+            return;
+          }
+        }
+      }
+    }
+
+    SHORT s = VkKeyScan(keysym);
+    if (s == -1) {
+      if (down) {
+        // not a single keypress - try synthesizing dead chars.
+        for (int j = 0;
+             j < sizeof(latin1ToDeadChars) / sizeof(latin1ToDeadChars_t);
+             j++) {
+          if (keysym == latin1ToDeadChars[j].latin1Char) {
+            for (int i = 0; i < deadChars.size(); i++) {
+              if (deadChars[i] == latin1ToDeadChars[j].deadChar) {
+                SHORT dc = VkKeyScan(latin1ToDeadChars[j].deadChar);
+                SHORT bc = VkKeyScan(latin1ToDeadChars[j].baseChar);
+                if (dc != -1 && bc != -1) {
+                  vlog.info("latin-1 key: 0x%x dead key vkCode 0x%x mod 0x%x "
+                            "followed by vkCode 0x%x mod 0x%x",
+                            keysym, LOBYTE(dc), HIBYTE(dc),
+                            LOBYTE(bc), HIBYTE(bc));
+                  doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true);
+                  doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false);
+                  doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), true);
+                  doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), false);
+                  return;
+                }
+                break;
+              }
+            }
+            break;
+          }
+        }
+        vlog.info("ignoring unrecognised Latin-1 keysym 0x%x",keysym);
+      }
+      return;
+    }
+
+    BYTE vkCode = LOBYTE(s);
+    BYTE modifierState = HIBYTE(s);
+    vlog.debug("latin-1 key: 0x%x vkCode 0x%x mod 0x%x down %d",
+               keysym, vkCode, modifierState, down);
+    doKeyEventWithModifiers(vkCode, modifierState, down);
+
+  } else {
+
+    // see if it's a recognised keyboard key, otherwise ignore it
+
+    if (vkMap.find(keysym) == vkMap.end()) {
+      vlog.info("ignoring unknown keysym 0x%x",keysym);
+      return;
+    }
+    BYTE vkCode = vkMap[keysym];
+    DWORD flags = 0;
+    if (extendedMap[keysym]) flags |= KEYEVENTF_EXTENDEDKEY;
+    if (!down) flags |= KEYEVENTF_KEYUP;
+
+    vlog.debug("keyboard key: keysym 0x%x vkCode 0x%x ext %d down %d",
+               keysym, vkCode, extendedMap[keysym], down);
+    if (down && (vkCode == VK_DELETE) &&
+        ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) &&
+        ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0))
+    {
+      rfb::win32::emulateCtrlAltDel();
+      return;
+    }
+
+    doKeyboardEvent(vkCode, flags);
+  }
+}
diff --git a/win/rfb_win32/SInput.h b/win/rfb_win32/SInput.h
new file mode 100644
index 0000000..2a0b3e6
--- /dev/null
+++ b/win/rfb_win32/SInput.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Input.h
+//
+// A number of routines that accept VNC-style input event data and perform
+// the appropriate actions under Win32
+
+#ifndef __RFB_WIN32_INPUT_H__
+#define __RFB_WIN32_INPUT_H__
+
+#include <rfb/Rect.h>
+#include <rfb/Configuration.h>
+#include <rdr/types.h>
+#include <map>
+#include <vector>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // -=- Pointer event handling
+
+    class SPointer {
+    public:
+      SPointer();
+      // - Create a pointer event at a the given coordinates, with the
+      //   specified button state.  The event must be specified using
+      //   Screen coordinates.
+      void pointerEvent(const Point& pos, int buttonmask);
+    protected:
+      Point last_position;
+      rdr::U8 last_buttonmask;
+    };
+
+    // -=- Keyboard event handling
+
+    class SKeyboard {
+    public:
+      SKeyboard();
+      void keyEvent(rdr::U32 key, bool down);
+      static BoolParameter deadKeyAware;
+    private:
+      std::map<rdr::U32,rdr::U8> vkMap;
+      std::map<rdr::U32,bool> extendedMap;
+      std::vector<rdr::U8> deadChars;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_INPUT_H__
diff --git a/win/rfb_win32/ScaledDIBSectionBuffer.cxx b/win/rfb_win32/ScaledDIBSectionBuffer.cxx
new file mode 100644
index 0000000..e6c15b8
--- /dev/null
+++ b/win/rfb_win32/ScaledDIBSectionBuffer.cxx
@@ -0,0 +1,124 @@
+/* Copyright (C) 2006 TightVNC Team.  All Rights Reserved.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- ScaledDIBSectionBuffer.cxx
+
+#include <math.h>
+
+#include <rfb_win32/ScaledDIBSectionBuffer.h>
+
+using namespace rfb;
+using namespace win32;
+
+ScaledDIBSectionBuffer::ScaledDIBSectionBuffer(HWND window) 
+  : src_buffer(0), scaling(false), DIBSectionBuffer(window) {
+  scaled_data = data;
+}
+
+ScaledDIBSectionBuffer::~ScaledDIBSectionBuffer() {
+  if (src_buffer) delete src_buffer;
+}
+
+void ScaledDIBSectionBuffer::setScale(int scale_) {
+  if (scale_ == getScale()) return;
+
+  if (src_buffer) {
+    delete src_buffer;
+    src_buffer = 0;
+  }
+  if (scale_ != 100) {
+    scaling = true;
+    src_buffer = new ManagedPixelBuffer(format, src_width, src_height);
+    src_data = &(src_buffer->data);
+  } else {
+    scaling = false;
+  }
+  ScaledPixelBuffer::setScale(scale_);
+}
+
+void ScaledDIBSectionBuffer::setPF(const PixelFormat &pf) {
+  if (scaling) src_buffer->setPF(pf);
+  DIBSectionBuffer::setPF(pf);
+  scaled_data = data;
+}
+
+void ScaledDIBSectionBuffer::setSize(int src_width_, int src_height_) {
+  src_width = src_width_;
+  src_height = src_height_;
+  if (scaling) {
+    src_buffer->setSize(src_width, src_height);
+  }
+  calculateScaledBufferSize();
+  recreateScaledBuffer();
+  scaled_data = data;
+}
+
+void ScaledDIBSectionBuffer::recreateScaledBuffer() {
+  width_  = scaled_width;
+  height_ = scaled_height;
+  DIBSectionBuffer::recreateBuffer();
+  scaled_data = data;
+}
+
+void ScaledDIBSectionBuffer::fillRect(const Rect &dest, Pixel pix) {
+  if (scaling) {
+    src_buffer->fillRect(dest, pix);
+    scaleRect(dest);
+  } else {
+    DIBSectionBuffer::fillRect(dest, pix);
+  }
+}
+
+void ScaledDIBSectionBuffer::imageRect(const Rect &dest, const void* pixels, int stride) {
+  if (scaling) {
+    src_buffer->imageRect(dest, pixels, stride);
+    scaleRect(dest);
+  } else {
+    DIBSectionBuffer::imageRect(dest, pixels, stride);
+  }
+}
+
+void ScaledDIBSectionBuffer::copyRect(const Rect &dest, const Point &move_by_delta) {
+  if (scaling) {
+    src_buffer->copyRect(dest, move_by_delta);
+    scaleRect(dest);
+  } else {
+    DIBSectionBuffer::copyRect(dest, move_by_delta);
+  }
+}
+      
+void ScaledDIBSectionBuffer::maskRect(const Rect& r, const void* pixels, const void* mask_) {
+  if (scaling) {
+    src_buffer->maskRect(r, pixels, mask_);
+    scaleRect(r);
+  } else {
+    DIBSectionBuffer::maskRect(r, pixels, mask_);
+  }
+}
+
+void ScaledDIBSectionBuffer::maskRect(const Rect& r, Pixel pixel, const void* mask_) {
+  if (scaling) {
+    src_buffer->maskRect(r, pixel, mask_);
+    scaleRect(r);
+  } else {
+    DIBSectionBuffer::maskRect(r, pixel, mask_);
+  }
+}
diff --git a/win/rfb_win32/ScaledDIBSectionBuffer.h b/win/rfb_win32/ScaledDIBSectionBuffer.h
new file mode 100644
index 0000000..3cc267b
--- /dev/null
+++ b/win/rfb_win32/ScaledDIBSectionBuffer.h
@@ -0,0 +1,78 @@
+/* Copyright (C) 2006 TightVNC Team.  All Rights Reserved.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ *
+ * TightVNC distribution homepage on the Web: http://www.tightvnc.com/
+ *
+ */
+
+// -=- ScaledDIBSectionBuffer.h
+
+#ifndef __RFB_WIN32_SCALED_DIB_SECTION_BUFFER_H__
+#define __RFB_WIN32_SCALED_DIB_SECTION_BUFFER_H__
+
+#include <rfb/ScaledPixelBuffer.h>
+
+#include <rfb_win32/DIBSectionBuffer.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    //
+    // -=- ScaledDIBSectionBuffer
+    //
+
+    class ScaledDIBSectionBuffer : public ScaledPixelBuffer, public DIBSectionBuffer {
+    public:
+      ScaledDIBSectionBuffer(HWND window);
+      virtual ~ScaledDIBSectionBuffer();
+
+      int width()  const { return scaled_width; }
+      int height() const { return scaled_height; }
+      bool isScaling() const { return scaling; }
+
+      virtual void setPF(const PixelFormat &pf);
+      virtual void setSize(int w, int h);
+      virtual void setScale(int scale);
+
+      Rect getRect() const { return ScaledPixelBuffer::getRect(); }
+      Rect getRect(const Point& pos) const { return ScaledPixelBuffer::getRect(pos); }
+
+      // -=- Overrides basic rendering operations of 
+      //     FullFramePixelBuffer class
+
+      virtual void fillRect(const Rect &dest, Pixel pix);
+      virtual void imageRect(const Rect &dest, const void* pixels, int stride=0);
+      virtual void copyRect(const Rect &dest, const Point &move_by_delta);
+      virtual void maskRect(const Rect& r, const void* pixels, const void* mask_);
+      virtual void maskRect(const Rect& r, Pixel pixel, const void* mask_);
+
+    protected:
+      virtual void recreateScaledBuffer();
+      virtual void recreateBuffer() {
+        recreateScaledBuffer();
+      };
+
+      ManagedPixelBuffer *src_buffer;
+      bool scaling;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_SCALED_DIB_SECTION_BUFFER_H__
diff --git a/win/rfb_win32/Security.cxx b/win/rfb_win32/Security.cxx
new file mode 100644
index 0000000..985f00c
--- /dev/null
+++ b/win/rfb_win32/Security.cxx
@@ -0,0 +1,192 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Security.cxx
+
+#include <rfb_win32/Security.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb/LogWriter.h>
+
+#include <lmcons.h>
+#include <Accctrl.h>
+#include <list>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SecurityWin32");
+
+
+Trustee::Trustee(const TCHAR* name,
+                 TRUSTEE_FORM form,
+                 TRUSTEE_TYPE type) {
+  pMultipleTrustee = 0;
+  MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+  TrusteeForm = form;
+  TrusteeType = type;
+  ptstrName = (TCHAR*)name;
+}
+
+
+ExplicitAccess::ExplicitAccess(const TCHAR* name,
+                               TRUSTEE_FORM type,
+                               DWORD perms,
+                               ACCESS_MODE mode,
+                               DWORD inherit) {
+  Trustee = rfb::win32::Trustee(name, type);
+  grfAccessPermissions = perms;
+  grfAccessMode = mode;
+  grfInheritance = inherit;
+}
+
+
+AccessEntries::AccessEntries() : entries(0), entry_count(0) {}
+
+AccessEntries::~AccessEntries() {
+  delete [] entries;
+}
+
+void AccessEntries::allocMinEntries(int count) {
+  if (count > entry_count) {
+    EXPLICIT_ACCESS* new_entries = new EXPLICIT_ACCESS[entry_count+1];
+    if (entries) {
+      memcpy(new_entries, entries, sizeof(EXPLICIT_ACCESS) * entry_count);
+      delete entries;
+    }
+    entries = new_entries;
+  }
+}
+
+void AccessEntries::addEntry(const TCHAR* trusteeName,
+                             DWORD permissions,
+                             ACCESS_MODE mode) {
+  allocMinEntries(entry_count+1);
+  ZeroMemory(&entries[entry_count], sizeof(EXPLICIT_ACCESS));
+  entries[entry_count] = ExplicitAccess(trusteeName, TRUSTEE_IS_NAME, permissions, mode);
+  entry_count++;
+}
+
+void AccessEntries::addEntry(const PSID sid,
+                             DWORD permissions,
+                             ACCESS_MODE mode) {
+  allocMinEntries(entry_count+1);
+  ZeroMemory(&entries[entry_count], sizeof(EXPLICIT_ACCESS));
+  entries[entry_count] = ExplicitAccess((TCHAR*)sid, TRUSTEE_IS_SID, permissions, mode);
+  entry_count++;
+}
+
+
+PSID Sid::copySID(const PSID sid) {
+  if (!IsValidSid(sid))
+    throw rdr::Exception("invalid SID in copyPSID");
+  PSID buf = (PSID)new rdr::U8[GetLengthSid(sid)];
+  if (!CopySid(GetLengthSid(sid), buf, sid))
+    throw rdr::SystemException("CopySid failed", GetLastError());
+  return buf;
+}
+
+void Sid::setSID(const PSID sid) {
+  delete [] buf;
+  buf = (rdr::U8*)copySID(sid);
+}
+
+void Sid::getUserNameAndDomain(TCHAR** name, TCHAR** domain) {
+  DWORD nameLen = 0;
+  DWORD domainLen = 0;
+  SID_NAME_USE use;
+  LookupAccountSid(0, (PSID)buf, 0, &nameLen, 0, &domainLen, &use);
+  if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+    throw rdr::SystemException("Unable to determine SID name lengths", GetLastError());
+  vlog.info("nameLen=%d, domainLen=%d, use=%d", nameLen, domainLen, use);
+  *name = new TCHAR[nameLen];
+  *domain = new TCHAR[domainLen];
+  if (!LookupAccountSid(0, (PSID)buf, *name, &nameLen, *domain, &domainLen, &use))
+    throw rdr::SystemException("Unable to lookup account SID", GetLastError());
+}
+
+
+Sid::Administrators::Administrators() {
+  PSID sid = 0;
+  SID_IDENTIFIER_AUTHORITY ntAuth = SECURITY_NT_AUTHORITY;
+  if (!AllocateAndInitializeSid(&ntAuth, 2,
+                                SECURITY_BUILTIN_DOMAIN_RID,
+                                DOMAIN_ALIAS_RID_ADMINS,
+                                0, 0, 0, 0, 0, 0, &sid)) 
+    throw rdr::SystemException("Sid::Administrators", GetLastError());
+  setSID(sid);
+  FreeSid(sid);
+}
+
+Sid::SYSTEM::SYSTEM() {
+  PSID sid = 0;
+  SID_IDENTIFIER_AUTHORITY ntAuth = SECURITY_NT_AUTHORITY;
+  if (!AllocateAndInitializeSid(&ntAuth, 1,
+                                SECURITY_LOCAL_SYSTEM_RID,
+                                0, 0, 0, 0, 0, 0, 0, &sid))
+          throw rdr::SystemException("Sid::SYSTEM", GetLastError());
+  setSID(sid);
+  FreeSid(sid);
+}
+
+Sid::FromToken::FromToken(HANDLE h) {
+  DWORD required = 0;
+  GetTokenInformation(h, TokenUser, 0, 0, &required);
+  rdr::U8Array tmp(required);
+  if (!GetTokenInformation(h, TokenUser, tmp.buf, required, &required))
+    throw rdr::SystemException("GetTokenInformation", GetLastError());
+  TOKEN_USER* tokenUser = (TOKEN_USER*)tmp.buf;
+  setSID(tokenUser->User.Sid);
+}
+
+
+PACL rfb::win32::CreateACL(const AccessEntries& ae, PACL existing_acl) {
+  typedef DWORD (WINAPI *_SetEntriesInAcl_proto) (ULONG, PEXPLICIT_ACCESS, PACL, PACL*);
+#ifdef UNICODE
+  const char* fnName = "SetEntriesInAclW";
+#else
+  const char* fnName = "SetEntriesInAclA";
+#endif
+  DynamicFn<_SetEntriesInAcl_proto> _SetEntriesInAcl(_T("advapi32.dll"), fnName);
+  if (!_SetEntriesInAcl.isValid())
+    throw rdr::SystemException("CreateACL failed; no SetEntriesInAcl", ERROR_CALL_NOT_IMPLEMENTED);
+  PACL new_dacl;
+  DWORD result;
+  if ((result = (*_SetEntriesInAcl)(ae.entry_count, ae.entries, existing_acl, &new_dacl)) != ERROR_SUCCESS)
+    throw rdr::SystemException("SetEntriesInAcl", result);
+  return new_dacl;
+}
+
+
+PSECURITY_DESCRIPTOR rfb::win32::CreateSdWithDacl(const PACL dacl) {
+  SECURITY_DESCRIPTOR absSD;
+  if (!InitializeSecurityDescriptor(&absSD, SECURITY_DESCRIPTOR_REVISION))
+    throw rdr::SystemException("InitializeSecurityDescriptor", GetLastError());
+  Sid::SYSTEM owner;
+  if (!SetSecurityDescriptorOwner(&absSD, owner, FALSE))
+    throw rdr::SystemException("SetSecurityDescriptorOwner", GetLastError());
+  Sid::Administrators group;
+  if (!SetSecurityDescriptorGroup(&absSD, group, FALSE))
+    throw rdr::SystemException("SetSecurityDescriptorGroupp", GetLastError());
+  if (!SetSecurityDescriptorDacl(&absSD, TRUE, dacl, FALSE))
+    throw rdr::SystemException("SetSecurityDescriptorDacl", GetLastError());
+  DWORD sdSize = GetSecurityDescriptorLength(&absSD);
+  SecurityDescriptorPtr sd(sdSize);
+  if (!MakeSelfRelativeSD(&absSD, sd, &sdSize))
+    throw rdr::SystemException("MakeSelfRelativeSD", GetLastError());
+  return sd.takeSD();
+}
diff --git a/win/rfb_win32/Security.h b/win/rfb_win32/Security.h
new file mode 100644
index 0000000..1e2e906
--- /dev/null
+++ b/win/rfb_win32/Security.h
@@ -0,0 +1,123 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// Security.h
+
+// Wrapper classes for a few Windows NT security structures/functions
+// that are used by VNC
+
+#ifndef __RFB_WIN32_SECURITY_H__
+#define __RFB_WIN32_SECURITY_H__
+
+#include <rdr/types.h>
+#include <rfb_win32/LocalMem.h>
+#include <rfb_win32/TCharArray.h>
+#include <aclapi.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    struct Trustee : public TRUSTEE {
+      Trustee(const TCHAR* name,
+              TRUSTEE_FORM form=TRUSTEE_IS_NAME,
+              TRUSTEE_TYPE type=TRUSTEE_IS_UNKNOWN);
+    };
+
+    struct ExplicitAccess : public EXPLICIT_ACCESS {
+      ExplicitAccess(const TCHAR* name,
+                     TRUSTEE_FORM type,
+                     DWORD perms,
+                     ACCESS_MODE mode,
+                     DWORD inherit=0);
+    };
+
+    // Helper class for building access control lists
+    struct AccessEntries {
+      AccessEntries();
+      ~AccessEntries();
+      void allocMinEntries(int count);
+      void addEntry(const TCHAR* trusteeName,
+                    DWORD permissions,
+                    ACCESS_MODE mode);
+      void addEntry(const PSID sid,
+                    DWORD permissions,
+                    ACCESS_MODE mode);
+
+      EXPLICIT_ACCESS* entries;
+      int entry_count;
+    };
+
+    // Helper class for handling SIDs
+    struct Sid : rdr::U8Array {
+      Sid() {}
+      operator PSID() const {return (PSID)buf;}
+      PSID takePSID() {PSID r = (PSID)buf; buf = 0; return r;}
+
+      static PSID copySID(const PSID sid);
+
+      void setSID(const PSID sid);
+
+      void getUserNameAndDomain(TCHAR** name, TCHAR** domain);
+
+      struct Administrators;
+      struct SYSTEM;
+      struct FromToken;
+
+    private:
+      Sid(const Sid&);
+      Sid& operator=(const Sid&);
+    };
+      
+    struct Sid::Administrators : public Sid {
+      Administrators();
+    };
+    struct Sid::SYSTEM : public Sid {
+      SYSTEM();
+    };
+    struct Sid::FromToken : public Sid {
+      FromToken(HANDLE h);
+    };
+
+    // Helper class for handling & freeing ACLs
+    struct AccessControlList : public LocalMem {
+      AccessControlList(int size) : LocalMem(size) {}
+      AccessControlList(PACL acl_=0) : LocalMem(acl_) {}
+      operator PACL() {return (PACL)ptr;}
+    };
+
+    // Create a new ACL based on supplied entries and, if supplied, existing ACL 
+    PACL CreateACL(const AccessEntries& ae, PACL existing_acl=0);
+
+    // Helper class for memory-management of self-relative SecurityDescriptors
+    struct SecurityDescriptorPtr : LocalMem {
+      SecurityDescriptorPtr(int size) : LocalMem(size) {}
+      SecurityDescriptorPtr(PSECURITY_DESCRIPTOR sd_=0) : LocalMem(sd_) {}
+      PSECURITY_DESCRIPTOR takeSD() {return takePtr();}
+    };
+
+    // Create a new self-relative Security Descriptor, owned by SYSTEM/Administrators,
+    //   with the supplied DACL and no SACL.  The returned value can be assigned
+    //   to a SecurityDescriptorPtr to be managed.
+    PSECURITY_DESCRIPTOR CreateSdWithDacl(const PACL dacl);
+
+  }
+
+}
+
+#endif
diff --git a/win/rfb_win32/Service.cxx b/win/rfb_win32/Service.cxx
new file mode 100644
index 0000000..2b11a22
--- /dev/null
+++ b/win/rfb_win32/Service.cxx
@@ -0,0 +1,645 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Service.cxx
+
+#include <rfb_win32/Service.h>
+#include <rfb_win32/MsgWindow.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/ModuleFileName.h>
+#include <rfb_win32/Registry.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb/Threading.h>
+#include <logmessages/messages.h>
+#include <rdr/Exception.h>
+#include <rfb/LogWriter.h>
+
+
+using namespace rdr;
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("Service");
+
+
+// - Internal service implementation functions
+
+Service* service = 0;
+
+VOID WINAPI serviceHandler(DWORD control) {
+  switch (control) {
+  case SERVICE_CONTROL_INTERROGATE:
+    vlog.info("cmd: report status");
+    service->setStatus();
+    return;
+  case SERVICE_CONTROL_PARAMCHANGE:
+    vlog.info("cmd: param change");
+    service->readParams();
+    return;
+  case SERVICE_CONTROL_SHUTDOWN:
+    vlog.info("cmd: OS shutdown");
+    service->osShuttingDown();
+    return;
+  case SERVICE_CONTROL_STOP:
+    vlog.info("cmd: stop");
+    service->setStatus(SERVICE_STOP_PENDING);
+    service->stop();
+    return;
+  };
+  vlog.debug("cmd: unknown %lu", control);
+}
+
+
+// -=- Message window derived class used under Win9x to implement stopService
+
+#define WM_SMSG_SERVICE_STOP WM_USER
+
+class ServiceMsgWindow : public MsgWindow {
+public:
+  ServiceMsgWindow(const TCHAR* name) : MsgWindow(name) {}
+  LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+    switch (msg) {
+    case WM_SMSG_SERVICE_STOP:
+      service->stop();
+      return TRUE;
+    }
+    return MsgWindow::processMessage(msg, wParam, lParam);
+  }
+
+  static const TCHAR* baseName;
+};
+
+const TCHAR* ServiceMsgWindow::baseName = _T("ServiceWindow:");
+
+
+// -=- Service main procedure, used under WinNT/2K/XP by the SCM
+
+VOID WINAPI serviceProc(DWORD dwArgc, LPTSTR* lpszArgv) {
+  vlog.debug("entering %s serviceProc", service->getName());
+  vlog.info("registering handler...");
+  service->status_handle = RegisterServiceCtrlHandler(service->getName(), serviceHandler);
+  if (!service->status_handle) {
+    DWORD err = GetLastError();
+    vlog.error("failed to register handler: %lu", err);
+    ExitProcess(err);
+  }
+  vlog.debug("registered handler (%lx)", service->status_handle);
+  service->setStatus(SERVICE_START_PENDING);
+  vlog.debug("entering %s serviceMain", service->getName());
+  service->status.dwWin32ExitCode = service->serviceMain(dwArgc, lpszArgv);
+  vlog.debug("leaving %s serviceMain", service->getName());
+  service->setStatus(SERVICE_STOPPED);
+}
+
+
+// -=- Service
+
+Service::Service(const TCHAR* name_) : name(name_) {
+  vlog.debug("Service");
+  status_handle = 0;
+  status.dwControlsAccepted = SERVICE_CONTROL_INTERROGATE | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
+  status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
+  status.dwWin32ExitCode = NO_ERROR;
+  status.dwServiceSpecificExitCode = 0;
+  status.dwCheckPoint = 0;
+  status.dwWaitHint = 30000;
+  status.dwCurrentState = SERVICE_STOPPED;
+}
+
+void
+Service::start() {
+  if (osVersion.isPlatformNT) {
+    SERVICE_TABLE_ENTRY entry[2];
+    entry[0].lpServiceName = (TCHAR*)name;
+    entry[0].lpServiceProc = serviceProc;
+    entry[1].lpServiceName = NULL;
+    entry[1].lpServiceProc = NULL;
+    vlog.debug("entering dispatcher");
+    if (!SetProcessShutdownParameters(0x100, 0))
+      vlog.error("unable to set shutdown parameters: %d", GetLastError());
+    service = this;
+    if (!StartServiceCtrlDispatcher(entry))
+      throw SystemException("unable to start service", GetLastError());
+  } else {
+
+    // - Create the service window, so the service can be stopped
+    TCharArray wndName(_tcslen(getName()) + _tcslen(ServiceMsgWindow::baseName) + 1);
+    _tcscpy(wndName.buf, ServiceMsgWindow::baseName);
+    _tcscat(wndName.buf, getName());
+    ServiceMsgWindow service_window(wndName.buf);
+
+    // - Locate the RegisterServiceProcess function
+	  typedef DWORD (WINAPI * _RegisterServiceProcess_proto)(DWORD, DWORD);
+    DynamicFn<_RegisterServiceProcess_proto> _RegisterServiceProcess(_T("kernel32.dll"), "RegisterServiceProcess");
+    if (!_RegisterServiceProcess.isValid())
+      throw Exception("unable to find RegisterServiceProcess");
+
+    // - Run the service
+    (*_RegisterServiceProcess)(NULL, 1);
+    service = this;
+    serviceMain(0, 0);
+	  (*_RegisterServiceProcess)(NULL, 0);
+  }
+}
+
+void
+Service::setStatus() {
+  setStatus(status.dwCurrentState);
+}
+
+void
+Service::setStatus(DWORD state) {
+  if (!osVersion.isPlatformNT)
+    return;
+  if (status_handle == 0) {
+    vlog.debug("warning - cannot setStatus");
+    return;
+  }
+  status.dwCurrentState = state;
+  status.dwCheckPoint++;
+  if (!SetServiceStatus(status_handle, &status)) {
+    status.dwCurrentState = SERVICE_STOPPED;
+    status.dwWin32ExitCode = GetLastError();
+    vlog.error("unable to set service status:%u", status.dwWin32ExitCode);
+  }
+  vlog.debug("set status to %u(%u)", state, status.dwCheckPoint);
+}
+
+Service::~Service() {
+  vlog.debug("~Service");
+  service = 0;
+}
+
+
+// Find out whether this process is running as the WinVNC service
+bool thisIsService() {
+  return service && (service->status.dwCurrentState != SERVICE_STOPPED);
+}
+
+
+// -=- Desktop handling code
+
+// Switch the current thread to the specified desktop
+static bool
+switchToDesktop(HDESK desktop) {
+  HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
+  if (!SetThreadDesktop(desktop)) {
+    vlog.debug("switchToDesktop failed:%u", GetLastError());
+    return false;
+  }
+  if (!CloseDesktop(old_desktop))
+    vlog.debug("unable to close old desktop:%u", GetLastError());
+  return true;
+}
+
+// Determine whether the thread's current desktop is the input one
+static bool
+inputDesktopSelected() {
+  HDESK current = GetThreadDesktop(GetCurrentThreadId());
+	HDESK input = OpenInputDesktop(0, FALSE,
+  	DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
+		DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
+		DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
+		DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
+  if (!input) {
+    vlog.debug("unable to OpenInputDesktop(1):%u", GetLastError());
+    return false;
+  }
+
+  DWORD size;
+  char currentname[256];
+  char inputname[256];
+
+  if (!GetUserObjectInformation(current, UOI_NAME, currentname, 256, &size)) {
+    vlog.debug("unable to GetUserObjectInformation(1):%u", GetLastError());
+    CloseDesktop(input);
+    return false;
+  }
+  if (!GetUserObjectInformation(input, UOI_NAME, inputname, 256, &size)) {
+    vlog.debug("unable to GetUserObjectInformation(2):%u", GetLastError());
+    CloseDesktop(input);
+    return false;
+  }
+  if (!CloseDesktop(input))
+    vlog.debug("unable to close input desktop:%u", GetLastError());
+
+  // *** vlog.debug("current=%s, input=%s", currentname, inputname);
+  bool result = strcmp(currentname, inputname) == 0;
+  return result;
+}
+
+// Switch the current thread into the input desktop
+static bool
+selectInputDesktop() {
+  // - Open the input desktop
+  HDESK desktop = OpenInputDesktop(0, FALSE,
+		DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
+		DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
+		DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
+		DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
+  if (!desktop) {
+    vlog.debug("unable to OpenInputDesktop(2):%u", GetLastError());
+    return false;
+  }
+
+  // - Switch into it
+  if (!switchToDesktop(desktop)) {
+    CloseDesktop(desktop);
+    return false;
+  }
+
+  // ***
+  DWORD size = 256;
+  char currentname[256];
+  if (GetUserObjectInformation(desktop, UOI_NAME, currentname, 256, &size)) {
+    vlog.debug("switched to %s", currentname);
+  }
+  // ***
+
+  vlog.debug("switched to input desktop");
+
+  return true;
+}
+
+
+// -=- Access points to desktop-switching routines
+
+bool
+rfb::win32::desktopChangeRequired() {
+  if (!osVersion.isPlatformNT)
+    return false;
+
+  return !inputDesktopSelected();
+}
+
+bool
+rfb::win32::changeDesktop() {
+  if (!osVersion.isPlatformNT)
+    return true;
+  if (osVersion.cannotSwitchDesktop)
+    return false;
+
+  return selectInputDesktop();
+}
+
+
+// -=- Ctrl-Alt-Del emulation
+
+class CADThread : public Thread {
+public:
+  CADThread() : Thread("CtrlAltDel Emulator"), result(false) {}
+  virtual void run() {
+	  HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
+
+    if (switchToDesktop(OpenDesktop(_T("Winlogon"), 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
+		  DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
+		  DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
+      DESKTOP_SWITCHDESKTOP | GENERIC_WRITE))) {
+	    PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));
+      switchToDesktop(old_desktop);
+      result = true;
+    }
+  }
+  bool result;
+};
+
+bool
+rfb::win32::emulateCtrlAltDel() {
+  if (!osVersion.isPlatformNT)
+    return false;
+
+  CADThread* cad_thread = new CADThread();
+  vlog.debug("emulate Ctrl-Alt-Del");
+  if (cad_thread) {
+    cad_thread->start();
+    cad_thread->join();
+    bool result = cad_thread->result;
+    delete cad_thread;
+    return result;
+  }
+  return false;
+}
+
+
+// -=- Application Event Log target Logger class
+
+class Logger_EventLog : public Logger {
+public:
+  Logger_EventLog(const TCHAR* srcname) : Logger("EventLog") {
+    eventlog = RegisterEventSource(NULL, srcname);
+    if (!eventlog)
+      printf("Unable to open event log:%ld\n", GetLastError());
+  }
+  ~Logger_EventLog() {
+    if (eventlog)
+      DeregisterEventSource(eventlog);
+  }
+
+  virtual void write(int level, const char *logname, const char *message) {
+    if (!eventlog) return;
+    TStr log(logname), msg(message);
+    const TCHAR* strings[] = {log, msg};
+    WORD type = EVENTLOG_INFORMATION_TYPE;
+    if (level == 0) type = EVENTLOG_ERROR_TYPE;
+    if (!ReportEvent(eventlog, type, 0, VNC4LogMessage, NULL, 2, 0, strings, NULL)) {
+      // *** It's not at all clear what is the correct behaviour if this fails...
+      printf("ReportEvent failed:%ld\n", GetLastError());
+    }
+  }
+
+protected:
+  HANDLE eventlog;
+};
+
+static Logger_EventLog* logger = 0;
+
+bool rfb::win32::initEventLogLogger(const TCHAR* srcname) {
+  if (logger)
+    return false;
+  if (osVersion.isPlatformNT) {
+    logger = new Logger_EventLog(srcname);
+    logger->registerLogger();
+    return true;
+  } else {
+    return false;
+  }
+}
+
+
+// -=- Registering and unregistering the service
+
+bool rfb::win32::registerService(const TCHAR* name, const TCHAR* desc,
+                                 int argc, const char* argv[]) {
+
+  // - Initialise the default service parameters
+  const TCHAR* defaultcmdline;
+  if (osVersion.isPlatformNT)
+    defaultcmdline = _T("-service");
+  else
+    defaultcmdline = _T("-noconsole -service");
+
+  // - Get the full pathname of our executable
+  ModuleFileName buffer;
+
+  // - Calculate the command-line length
+  int cmdline_len = _tcslen(buffer.buf) + 4;
+  int i;
+  for (i=0; i<argc; i++) {
+    cmdline_len += strlen(argv[i]) + 3;
+  }
+
+  // - Add the supplied extra parameters to the command line
+  TCharArray cmdline(cmdline_len+_tcslen(defaultcmdline));
+  _stprintf(cmdline.buf, _T("\"%s\" %s"), buffer.buf, defaultcmdline);
+  for (i=0; i<argc; i++) {
+    _tcscat(cmdline.buf, _T(" \""));
+    _tcscat(cmdline.buf, TStr(argv[i]));
+    _tcscat(cmdline.buf, _T("\""));
+  }
+    
+  // - Register the service
+
+  if (osVersion.isPlatformNT) {
+
+    // - Open the SCM
+    ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
+    if (!scm)
+      throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
+
+
+    ServiceHandle service = CreateService(scm,
+      name, desc, SC_MANAGER_ALL_ACCESS,
+      SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
+      SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
+      cmdline.buf, NULL, NULL, NULL, NULL, NULL);
+    if (!service)
+      throw rdr::SystemException("unable to create service", GetLastError());
+
+    // - Register the event log source
+    RegKey hk, hk2;
+
+    hk2.createKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"));
+    hk.createKey(hk2, name);
+
+    for (i=_tcslen(buffer.buf); i>0; i--) {
+      if (buffer.buf[i] == _T('\\')) {
+        buffer.buf[i+1] = 0;
+        break;
+      }
+    }
+
+    const TCHAR* dllFilename = _T("logmessages.dll");
+    TCharArray dllPath(_tcslen(buffer.buf) + _tcslen(dllFilename) + 1);
+    _tcscpy(dllPath.buf, buffer.buf);
+    _tcscat(dllPath.buf, dllFilename);
+ 
+    hk.setExpandString(_T("EventMessageFile"), dllPath.buf);
+    hk.setInt(_T("TypesSupported"), EVENTLOG_ERROR_TYPE | EVENTLOG_INFORMATION_TYPE);
+
+  } else {
+
+    RegKey services;
+    services.createKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"));
+    services.setString(name, cmdline.buf);
+
+  }
+
+  Sleep(500);
+
+  return true;
+}
+
+bool rfb::win32::unregisterService(const TCHAR* name) {
+  if (osVersion.isPlatformNT) {
+
+    // - Open the SCM
+    ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
+    if (!scm)
+      throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
+
+    // - Create the service
+    ServiceHandle service = OpenService(scm, name, SC_MANAGER_ALL_ACCESS);
+    if (!service)
+      throw rdr::SystemException("unable to locate the service", GetLastError());
+    if (!DeleteService(service))
+      throw rdr::SystemException("unable to remove the service", GetLastError());
+
+    // - Register the event log source
+    RegKey hk;
+    hk.openKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application"));
+    hk.deleteKey(name);
+
+  } else {
+
+		RegKey services;
+    services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"));
+    services.deleteValue(name);
+
+  }
+
+  Sleep(500);
+
+  return true;
+}
+
+
+// -=- Starting and stopping the service
+
+HWND findServiceWindow(const TCHAR* name) {
+  TCharArray wndName(_tcslen(ServiceMsgWindow::baseName)+_tcslen(name)+1);
+  _tcscpy(wndName.buf, ServiceMsgWindow::baseName);
+  _tcscat(wndName.buf, name);
+  vlog.debug("searching for %s window", CStr(wndName.buf));
+  return FindWindow(0, wndName.buf);
+}
+
+bool rfb::win32::startService(const TCHAR* name) {
+
+  if (osVersion.isPlatformNT) {
+    // - Open the SCM
+    ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+    if (!scm)
+      throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
+
+    // - Locate the service
+    ServiceHandle service = OpenService(scm, name, SERVICE_START);
+    if (!service)
+      throw rdr::SystemException("unable to open the service", GetLastError());
+
+    // - Start the service
+    if (!StartService(service, 0, NULL))
+      throw rdr::SystemException("unable to start the service", GetLastError());
+  } else {
+    // - Check there is no service window
+    if (findServiceWindow(name))
+      throw rdr::Exception("the service is already running");
+
+    // - Find the RunServices registry key
+		RegKey services;
+		services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices"));
+
+    // - Read the command-line from it
+    TCharArray cmdLine = services.getString(name);
+
+    // - Start the service
+    PROCESS_INFORMATION proc_info;
+    STARTUPINFO startup_info;
+    ZeroMemory(&startup_info, sizeof(startup_info));
+    startup_info.cb = sizeof(startup_info);
+    if (!CreateProcess(0, cmdLine.buf, 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, 0, &startup_info, &proc_info)) {
+      throw SystemException("unable to start service", GetLastError());
+    }
+  }
+
+  Sleep(500);
+
+  return true;
+}
+
+bool rfb::win32::stopService(const TCHAR* name) {
+  if (osVersion.isPlatformNT) {
+    // - Open the SCM
+    ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+    if (!scm)
+      throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
+
+    // - Locate the service
+    ServiceHandle service = OpenService(scm, name, SERVICE_STOP);
+    if (!service)
+      throw rdr::SystemException("unable to open the service", GetLastError());
+
+    // - Start the service
+    SERVICE_STATUS status;
+    if (!ControlService(service, SERVICE_CONTROL_STOP, &status))
+      throw rdr::SystemException("unable to stop the service", GetLastError());
+
+  } else {
+    // - Find the service window
+    HWND service_window = findServiceWindow(name);
+    if (!service_window)
+      throw Exception("unable to locate running service");
+
+    // Tell it to quit
+    vlog.debug("sending service stop request");
+    if (!SendMessage(service_window, WM_SMSG_SERVICE_STOP, 0, 0))
+      throw Exception("unable to stop service");
+
+    // Check it's quitting...
+    DWORD process_id = 0;
+    HANDLE process = 0;
+    if (!GetWindowThreadProcessId(service_window, &process_id))
+      throw SystemException("unable to verify service has quit", GetLastError());
+    process = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, process_id);
+    if (!process)
+      throw SystemException("unable to obtain service handle", GetLastError());
+    int retries = 5;
+    vlog.debug("checking status");
+    while (retries-- && (WaitForSingleObject(process, 1000) != WAIT_OBJECT_0)) {}
+    if (!retries) {
+      vlog.debug("failed to quit - terminating");
+      // May not have quit because of silly Win9x registry watching bug..
+      if (!TerminateProcess(process, 1))
+        throw SystemException("unable to terminate process!", GetLastError());
+      throw Exception("service failed to quit - called TerminateProcess");
+    }
+  }
+
+  Sleep(500);
+
+  return true;
+}
+
+DWORD rfb::win32::getServiceState(const TCHAR* name) {
+  if (osVersion.isPlatformNT) {
+    // - Open the SCM
+    ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
+    if (!scm)
+      throw rdr::SystemException("unable to open Service Control Manager", GetLastError());
+
+    // - Locate the service
+    ServiceHandle service = OpenService(scm, name, SERVICE_INTERROGATE);
+    if (!service)
+      throw rdr::SystemException("unable to open the service", GetLastError());
+
+    // - Get the service status
+    SERVICE_STATUS status;
+    if (!ControlService(service, SERVICE_CONTROL_INTERROGATE, (SERVICE_STATUS*)&status))
+      throw rdr::SystemException("unable to query the service", GetLastError());
+
+    return status.dwCurrentState;
+  } else {
+    HWND service_window = findServiceWindow(name);
+    return service_window ? SERVICE_RUNNING : SERVICE_STOPPED;
+  }
+}
+
+char* rfb::win32::serviceStateName(DWORD state) {
+  switch (state) {
+  case SERVICE_RUNNING: return strDup("Running");
+  case SERVICE_STOPPED: return strDup("Stopped");
+  case SERVICE_STOP_PENDING: return strDup("Stopping");
+  };
+  CharArray tmp(32);
+  sprintf(tmp.buf, "Unknown (%lu)", state);
+  return tmp.takeBuf();
+}
+
+
+bool rfb::win32::isServiceProcess() {
+  return service != 0;
+}
diff --git a/win/rfb_win32/Service.h b/win/rfb_win32/Service.h
new file mode 100644
index 0000000..00abe10
--- /dev/null
+++ b/win/rfb_win32/Service.h
@@ -0,0 +1,128 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Service.h
+//
+// Win32 service-mode code.
+// Derive your service from this code and let it handle the annoying Win32
+// service API.
+// The underlying implementation takes care of the differences between
+// Windows NT and Windows 95 based systems
+
+#ifndef __RFB_WIN32_SERVICE_H__
+#define __RFB_WIN32_SERVICE_H__
+
+#include <windows.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    //
+    // -=- Service
+    //
+
+    // Application base-class for services.
+
+    class Service {
+    public:
+
+      Service(const TCHAR* name_);
+      virtual ~Service();
+
+      const TCHAR* getName() {return name;}
+      SERVICE_STATUS& getStatus() {return status;}
+
+      void setStatus(DWORD status);
+      void setStatus();
+
+      // - Start the service, having initialised it
+      void start();
+
+      // - Service main procedure - override to implement a service
+      virtual DWORD serviceMain(int argc, TCHAR* argv[]) = 0;
+
+      // - Service control notifications
+
+      // To get notified when the OS is shutting down
+      virtual void osShuttingDown() {};
+
+      // To get notified when the service parameters change
+      virtual void readParams() {};
+
+      // To cause the serviceMain() routine to return
+      virtual void stop() {};
+
+    public:
+      SERVICE_STATUS_HANDLE status_handle;
+      SERVICE_STATUS status;
+    protected:
+      const TCHAR* name;
+    };
+
+    class ServiceHandle {
+    public:
+      ServiceHandle(SC_HANDLE h) : handle(h) {}
+      ~ServiceHandle() {CloseServiceHandle(handle);}
+      operator SC_HANDLE() const {return handle;}
+    protected:
+      SC_HANDLE handle;
+    };
+
+    // -=- Routines used by desktop back-end code to manage desktops/window stations
+
+    //     Returns false under Win9x
+    bool desktopChangeRequired();
+
+    //     Returns true under Win9x
+    bool changeDesktop();
+
+    // -=- Routines used by the SInput Keyboard class to emulate Ctrl-Alt-Del
+    //     Returns false under Win9x
+    bool emulateCtrlAltDel();
+
+    // -=- Routines to initialise the Event Log target Logger
+    //     Returns false under Win9x
+    bool initEventLogLogger(const TCHAR* srcname);
+
+    // -=- Routines to register/unregister the service
+    //     These routines also take care of registering the required
+    //     event source information, etc.
+    // *** should really accept TCHAR argv
+
+    bool registerService(const TCHAR* name, const TCHAR* desc, int argc, const char* argv[]);
+    bool unregisterService(const TCHAR* name);
+
+    bool startService(const TCHAR* name);
+    bool stopService(const TCHAR* name);
+
+    // -=- Get the state of the named service (one of the NT service state values)
+    DWORD getServiceState(const TCHAR* name);
+
+    // -=- Convert a supplied service state value to a printable string e.g. Running, Stopped...
+    //     The caller must delete the returned string buffer
+    char* serviceStateName(DWORD state);
+
+    // -=- Routine to determine whether the host process is running a service
+    bool isServiceProcess();
+
+  };
+
+};
+
+#endif // __RFB_WIN32_SERVICE_NT_H__
diff --git a/win/rfb_win32/SocketManager.cxx b/win/rfb_win32/SocketManager.cxx
new file mode 100644
index 0000000..1d52bc8
--- /dev/null
+++ b/win/rfb_win32/SocketManager.cxx
@@ -0,0 +1,213 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SocketManager.cxx
+
+#include <winsock2.h>
+#include <list>
+#include <rfb/LogWriter.h>
+#include <rfb_win32/SocketManager.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SocketManager");
+
+
+// -=- SocketManager
+
+SocketManager::SocketManager() {
+}
+
+SocketManager::~SocketManager() {
+}
+
+
+static requestAddressChangeEvents(network::SocketListener* sock_) {
+  DWORD dummy = 0;
+  if (WSAIoctl(sock_->getFd(), SIO_ADDRESS_LIST_CHANGE, 0, 0, 0, 0, &dummy, 0, 0) == SOCKET_ERROR) {
+    DWORD err = WSAGetLastError();
+    if (err != WSAEWOULDBLOCK)
+      vlog.error("Unable to track address changes", err);
+  }
+}
+
+
+void SocketManager::addListener(network::SocketListener* sock_,
+                                network::SocketServer* srvr,
+                                AddressChangeNotifier* acn) {
+  WSAEVENT event = WSACreateEvent();
+  long flags = FD_ACCEPT | FD_CLOSE;
+  if (acn)
+    flags |= FD_ADDRESS_LIST_CHANGE;
+  try {
+    if (event && (WSAEventSelect(sock_->getFd(), event, flags) == SOCKET_ERROR))
+      throw rdr::SystemException("Unable to select on listener", WSAGetLastError());
+
+    // requestAddressChangeEvents MUST happen after WSAEventSelect, so that the socket is non-blocking
+    if (acn)
+      requestAddressChangeEvents(sock_);
+
+    // addEvent is the last thing we do, so that the event is NOT registered if previous steps fail
+    if (!event || !addEvent(event, this))
+      throw rdr::Exception("Unable to add listener");
+  } catch (rdr::Exception& e) {
+    if (event)
+      WSACloseEvent(event);
+    delete sock_;
+    vlog.error(e.str());
+    throw;
+  }
+
+  ListenInfo li;
+  li.sock = sock_;
+  li.server = srvr;
+  li.notifier = acn;
+  listeners[event] = li;
+}
+
+void SocketManager::remListener(network::SocketListener* sock) {
+  std::map<HANDLE,ListenInfo>::iterator i;
+  for (i=listeners.begin(); i!=listeners.end(); i++) {
+    if (i->second.sock == sock) {
+      removeEvent(i->first);
+      WSACloseEvent(i->first);
+      delete sock;
+      listeners.erase(i);
+      return;
+    }
+  }
+  throw rdr::Exception("Listener not registered");
+}
+
+
+void SocketManager::addSocket(network::Socket* sock_, network::SocketServer* srvr, bool outgoing) {
+  WSAEVENT event = WSACreateEvent();
+  if (!event || !addEvent(event, this) ||
+      (WSAEventSelect(sock_->getFd(), event, FD_READ | FD_CLOSE) == SOCKET_ERROR)) {
+    if (event)
+      WSACloseEvent(event);
+    delete sock_;
+    vlog.error("Unable to add connection");
+    return;
+  }
+  ConnInfo ci;
+  ci.sock = sock_;
+  ci.server = srvr;
+  connections[event] = ci;
+  srvr->addSocket(sock_, outgoing);
+}
+
+void SocketManager::remSocket(network::Socket* sock_) {
+  std::map<HANDLE,ConnInfo>::iterator i;
+  for (i=connections.begin(); i!=connections.end(); i++) {
+    if (i->second.sock == sock_) {
+      i->second.server->removeSocket(sock_);
+      removeEvent(i->first);
+      WSACloseEvent(i->first);
+      delete sock_;
+      connections.erase(i);
+      return;
+    }
+  }
+  throw rdr::Exception("Socket not registered");
+}
+
+
+int SocketManager::checkTimeouts() {
+  network::SocketServer* server = 0;
+  int timeout = EventManager::checkTimeouts();
+
+  std::map<HANDLE,ListenInfo>::iterator i;
+  for (i=listeners.begin(); i!=listeners.end(); i++)
+    soonestTimeout(&timeout, i->second.server->checkTimeouts());
+
+  std::list<network::Socket*> shutdownSocks;
+  std::map<HANDLE,ConnInfo>::iterator j, j_next;
+  for (j=connections.begin(); j!=connections.end(); j=j_next) {
+    j_next = j; j_next++;
+    if (j->second.sock->isShutdown())
+      shutdownSocks.push_back(j->second.sock);
+  }
+
+  std::list<network::Socket*>::iterator k;
+  for (k=shutdownSocks.begin(); k!=shutdownSocks.end(); k++)
+    remSocket(*k);
+
+  return timeout;
+}
+
+
+void SocketManager::processEvent(HANDLE event) {
+  if (listeners.count(event)) {
+    ListenInfo li = listeners[event];
+
+    // Accept an incoming connection
+    vlog.debug("accepting incoming connection");
+
+    // What kind of event is this?
+    WSANETWORKEVENTS network_events;
+    WSAEnumNetworkEvents(li.sock->getFd(), event, &network_events);
+    if (network_events.lNetworkEvents & FD_ACCEPT) {
+      network::Socket* new_sock = li.sock->accept();
+      if (new_sock && li.server->getDisable()) {
+        delete new_sock;
+        new_sock = 0;
+      }
+      if (new_sock)
+        addSocket(new_sock, li.server, false);
+    } else if (network_events.lNetworkEvents & FD_CLOSE) {
+      vlog.info("deleting listening socket");
+      remListener(li.sock);
+    } else if (network_events.lNetworkEvents & FD_ADDRESS_LIST_CHANGE) {
+      li.notifier->processAddressChange(li.sock);
+      DWORD dummy = 0;
+      requestAddressChangeEvents(li.sock);
+    } else {
+      vlog.error("unknown listener event: %lx", network_events.lNetworkEvents);
+    }
+  } else if (connections.count(event)) {
+    ConnInfo ci = connections[event];
+
+    try {
+      // Process data from an active connection
+
+      // Cancel event notification for this socket
+      if (WSAEventSelect(ci.sock->getFd(), event, 0) == SOCKET_ERROR)
+        throw rdr::SystemException("unable to disable WSAEventSelect:%u", WSAGetLastError());
+
+      // Reset the event object
+      WSAResetEvent(event);
+
+      // Call the socket server to process the event
+      ci.server->processSocketEvent(ci.sock);
+      if (ci.sock->isShutdown()) {
+        remSocket(ci.sock);
+        return;
+      }
+
+      // Re-instate the required socket event
+      // If the read event is still valid, the event object gets set here
+      if (WSAEventSelect(ci.sock->getFd(), event, FD_READ | FD_CLOSE) == SOCKET_ERROR)
+        throw rdr::SystemException("unable to re-enable WSAEventSelect:%u", WSAGetLastError());
+    } catch (rdr::Exception& e) {
+      vlog.error(e.str());
+      remSocket(ci.sock);
+    }
+  }
+}
diff --git a/win/rfb_win32/SocketManager.h b/win/rfb_win32/SocketManager.h
new file mode 100644
index 0000000..ef35974
--- /dev/null
+++ b/win/rfb_win32/SocketManager.h
@@ -0,0 +1,90 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- SocketManager.h
+
+// Socket manager class for Win32.
+// Passed a network::SocketListener and a network::SocketServer when
+// constructed.  Uses WSAAsyncSelect to get notifications of network 
+// connection attempts.  When an incoming connection is received,
+// the manager will call network::SocketServer::addClient().  If
+// addClient returns true then the manager registers interest in
+// network events on that socket, and calls
+// network::SocketServer::processSocketEvent().
+
+#ifndef __RFB_WIN32_SOCKET_MGR_H__
+#define __RFB_WIN32_SOCKET_MGR_H__
+
+#include <map>
+#include <network/Socket.h>
+#include <rfb_win32/EventManager.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class SocketManager : public EventManager, EventHandler {
+    public:
+      SocketManager();
+      virtual ~SocketManager();
+
+      // AddressChangeNotifier callback interface
+      // If an object implementing this is passed to addListener then it will be
+      // called whenever the SocketListener's address list changes
+      class AddressChangeNotifier {
+      public:
+        virtual ~AddressChangeNotifier() {}
+        virtual void processAddressChange(network::SocketListener* sl) = 0;
+      };
+
+      // Add a listening socket.  Incoming connections will be added to the supplied
+      // SocketServer.
+      void addListener(network::SocketListener* sock_,
+                       network::SocketServer* srvr,
+                       AddressChangeNotifier* acn = 0);
+
+      // Remove and delete a listening socket.
+      void remListener(network::SocketListener* sock);
+
+      // Add an already-connected socket.  Socket events will cause the supplied
+      // SocketServer to be called.  The socket must ALREADY BE REGISTERED with
+      // the SocketServer.
+      void addSocket(network::Socket* sock_, network::SocketServer* srvr, bool outgoing=true);
+
+    protected:
+      virtual int checkTimeouts();
+      virtual void processEvent(HANDLE event);
+      virtual void remSocket(network::Socket* sock);
+
+      struct ConnInfo {
+        network::Socket* sock;
+        network::SocketServer* server;
+      };
+      struct ListenInfo {
+        network::SocketListener* sock;
+        network::SocketServer* server;
+        AddressChangeNotifier* notifier;
+      };
+      std::map<HANDLE, ListenInfo> listeners;
+      std::map<HANDLE, ConnInfo> connections;
+   };
+
+  }
+
+}
+
+#endif
diff --git a/win/rfb_win32/TCharArray.cxx b/win/rfb_win32/TCharArray.cxx
new file mode 100644
index 0000000..fd4c078
--- /dev/null
+++ b/win/rfb_win32/TCharArray.cxx
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  WCHAR* wstrDup(const WCHAR* s) {
+    if (!s) return 0;
+    WCHAR* t = new WCHAR[wcslen(s)+1];
+    memcpy(t, s, sizeof(WCHAR)*(wcslen(s)+1));
+    return t;
+  }
+  void wstrFree(WCHAR* s) {delete [] s;}
+
+  char* strDup(const WCHAR* s) {
+    if (!s) return 0;
+    int len = wcslen(s);
+    char* t = new char[len+1];
+    t[WideCharToMultiByte(CP_ACP, 0, s, len, t, len, 0, 0)] = 0;
+    return t;
+  }
+
+  WCHAR* wstrDup(const char* s) {
+    if (!s) return 0;
+    int len = strlen(s);
+    WCHAR* t = new WCHAR[len+1];
+    t[MultiByteToWideChar(CP_ACP, 0, s, len, t, len)] = 0;
+    return t;
+  }
+
+
+  bool wstrSplit(const WCHAR* src, const WCHAR limiter, WCHAR** out1, WCHAR** out2, bool fromEnd) {
+    WCharArray out1old, out2old;
+    if (out1) out1old.buf = *out1;
+    if (out2) out2old.buf = *out2;
+    int len = wcslen(src);
+    int i=0, increment=1, limit=len;
+    if (fromEnd) {
+      i=len-1; increment = -1; limit = -1;
+    }
+    while (i!=limit) {
+      if (src[i] == limiter) {
+        if (out1) {
+          *out1 = new WCHAR[i+1];
+          if (i) memcpy(*out1, src, sizeof(WCHAR)*i);
+          (*out1)[i] = 0;
+        }
+        if (out2) {
+          *out2 = new WCHAR[len-i];
+          if (len-i-1) memcpy(*out2, &src[i+1], sizeof(WCHAR)*(len-i-1));
+          (*out2)[len-i-1] = 0;
+        }
+        return true;
+      }
+      i+=increment;
+    }
+    if (out1) *out1 = wstrDup(src);
+    if (out2) *out2 = 0;
+    return false;
+  }
+
+  bool wstrContains(const WCHAR* src, WCHAR c) {
+    int l=wcslen(src);
+    for (int i=0; i<l; i++)
+      if (src[i] == c) return true;
+    return false;
+  }
+
+};
diff --git a/win/rfb_win32/TCharArray.h b/win/rfb_win32/TCharArray.h
new file mode 100644
index 0000000..dde63b7
--- /dev/null
+++ b/win/rfb_win32/TCharArray.h
@@ -0,0 +1,135 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- TCharArray.h
+
+// This library contains the wide-character equivalent of CharArray, named
+// WCharArray.  In addition to providing wide-character equivalents of
+// the char* string manipulation functions (strDup, strFree, etc), special
+// versions of those functions are provided which attempt to convert from
+// one format to the other.
+//    e.g. char* t = "hello world"; WCHAR* w = wstrDup(t);
+//    Results in w containing the wide-character text "hello world".
+// For convenience, the WStr and CStr classes are also provided.  These
+// accept an existing (const) WCHAR* or char* null-terminated string and
+// create a read-only copy of that in the desired format.  The new copy
+// will actually be the original copy if the format has not changed, otherwise
+// it will be a new buffer owned by the WStr/CStr.
+
+// In addition to providing wide character functions, this header defines
+// TCHAR* handling classes & functions.  TCHAR is defined at compile time to
+// either char or WCHAR.  Programs can treat this as a third data type and
+// call TStr() whenever a TCHAR* is required but a char* or WCHAR* is supplied,
+// and TStr will do the right thing.
+
+#ifndef __RFB_WIN32_TCHARARRAY_H__
+#define __RFB_WIN32_TCHARARRAY_H__
+
+#include <windows.h>
+#include <tchar.h>
+#include <rfb/util.h>
+#include <rfb/Password.h>
+
+namespace rfb {
+
+  // -=- String duplication and cleanup functions.
+  //     These routines also handle conversion between WCHAR* and char*
+
+  char* strDup(const WCHAR* s);
+  WCHAR* wstrDup(const WCHAR* s);
+  WCHAR* wstrDup(const char* s);
+  void wstrFree(WCHAR* s);
+
+  bool wstrSplit(const WCHAR* src, const WCHAR limiter, WCHAR** out1, WCHAR** out2, bool fromEnd=false);
+  bool wstrContains(const WCHAR* src, WCHAR c);
+
+  // -=- Temporary format conversion classes
+  //     CStr accepts WCHAR* or char* and behaves like a char*
+  //     WStr accepts WCHAR* or char* and behaves like a WCHAR*
+
+  struct WStr {
+    WStr(const char* s) : buf(wstrDup(s)), free_(true) {}
+    WStr(const WCHAR* s) : buf(s), free_(false) {}
+    ~WStr() {if (free_) wstrFree((WCHAR*)buf);}
+    operator const WCHAR*() {return buf;}
+    const WCHAR* buf;
+    bool free_;
+  };
+
+  struct CStr {
+    CStr(const char* s) : buf(s), free_(false) {}
+    CStr(const WCHAR* s) : buf(strDup(s)), free_(true) {}
+    ~CStr() {if (free_) strFree((char*)buf);}
+    operator const char*() {return buf;}
+    const char* buf;
+    bool free_;
+  };
+
+  // -=- Class to handle cleanup of arrays of native Win32 characters
+  class WCharArray {
+  public:
+    WCharArray() : buf(0) {}
+    WCharArray(char* str) : buf(wstrDup(str)) {strFree(str);} // note: assumes ownership
+    WCharArray(WCHAR* str) : buf(str) {}                      // note: assumes ownership
+    WCharArray(int len) {
+      buf = new WCHAR[len];
+    }
+    ~WCharArray() {
+      delete [] buf;
+    }
+    // Get the buffer pointer & clear it (i.e. caller takes ownership)
+    WCHAR* takeBuf() {WCHAR* tmp = buf; buf = 0; return tmp;}
+    void replaceBuf(WCHAR* str) {delete [] buf; buf = str;}
+    WCHAR* buf;
+  };
+
+  // -=- Wide-character-based password-buffer handler.  Zeroes the password
+  //     buffer when deleted or replaced.
+  class WPlainPasswd : public WCharArray {
+  public:
+    WPlainPasswd() {}
+    WPlainPasswd(WCHAR* str) : WCharArray(str) {}
+    ~WPlainPasswd() {replaceBuf(0);}
+    void replaceBuf(WCHAR* str) {
+      if (buf)
+        memset(buf, 0, sizeof(WCHAR)*wcslen(buf));
+      WCharArray::replaceBuf(str);
+    }
+  };
+    
+#ifdef _UNICODE
+#define tstrDup wstrDup
+#define tstrFree wstrFree
+#define tstrSplit wstrSplit
+#define tstrContains wstrContains
+  typedef WCharArray TCharArray;
+  typedef WStr TStr;
+  typedef WPlainPasswd TPlainPasswd;
+#else
+#define tstrDup strDup
+#define tstrFree strFree
+#define tstrSplit strSplit
+#define tstrContains strContains
+  typedef CharArray TCharArray;
+  typedef CStr TStr;
+  typedef PlainPasswd TPlainPasswd;
+#endif
+
+};
+
+#endif
diff --git a/win/rfb_win32/Threading.cxx b/win/rfb_win32/Threading.cxx
new file mode 100644
index 0000000..c41ac38
--- /dev/null
+++ b/win/rfb_win32/Threading.cxx
@@ -0,0 +1,151 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Threading.cxx
+// Win32 Threading interface implementation
+
+#include <malloc.h>
+
+#include <rdr/Exception.h>
+#include <rfb/LogWriter.h>
+#include <rfb/util.h>
+#include <rfb_win32/Threading.h>
+
+using namespace rfb;
+
+static LogWriter vlog("Threading");
+
+static DWORD threadStorage = TlsAlloc();
+
+
+inline void logAction(Thread* t, const char* action) {
+  vlog.debug("%-16.16s %s(%lx)", action, t->getName(), t);
+}
+
+inline void logError(Thread* t, const char* err) {
+  vlog.error("%-16.16s %s(%lx):%s", "failed", t->getName(), t, err);
+}
+
+
+DWORD WINAPI
+Thread::threadProc(LPVOID lpParameter) {
+  Thread* thread = (Thread*) lpParameter;
+  TlsSetValue(threadStorage, thread);
+  logAction(thread, "started");
+  try {
+    thread->run();
+    logAction(thread, "stopped");
+  } catch (rdr::Exception& e) {
+    logError(thread, e.str());
+  }
+  bool deleteThread = false;
+  {
+    Lock l(thread->mutex);
+    thread->state = ThreadStopped;
+    thread->sig->signal();
+    deleteThread = thread->deleteAfterRun;
+  }
+  if (deleteThread)
+    delete thread;
+  return 0;
+}
+
+Thread::Thread(const char* name_) : name(strDup(name_ ? name_ : "Unnamed")), sig(0), deleteAfterRun(false) {
+  sig = new Condition(mutex);
+  cond_event.h = CreateEvent(NULL, TRUE, FALSE, NULL);
+  thread.h = CreateThread(NULL, 0, threadProc, this, CREATE_SUSPENDED, &thread_id);
+  state = ThreadCreated;
+  logAction(this, "created");
+}
+
+Thread::Thread(HANDLE thread_, DWORD thread_id_) : name(strDup("Native")), sig(0), deleteAfterRun(false),
+  thread(thread_), thread_id(thread_id_) {
+  cond_event.h = CreateEvent(NULL, TRUE, FALSE, NULL);
+  state = ThreadNative;
+  logAction(this, "created");
+}
+
+Thread::~Thread() {
+  logAction(this, "destroying");
+  if (!deleteAfterRun && state != ThreadNative)
+    this->join();
+  if (sig)
+    delete sig;
+  logAction(this, "destroyed");
+}
+
+void
+Thread::run() {
+}
+
+void
+Thread::start() {
+  Lock l(mutex);
+  if (state == ThreadCreated) {
+    state = ThreadStarted;
+    sig->signal();
+    ResumeThread(thread);
+  }
+}
+
+Thread*
+Thread::join() {
+  if (deleteAfterRun)
+    throw rdr::Exception("attempt to join() with deleteAfterRun thread");
+  Lock l(mutex);
+  if (state == ThreadJoined) {
+    logAction(this, "already joined");
+  } else {
+    logAction(this, "joining");
+    while (state == ThreadStarted) {
+      sig->wait();
+      logAction(this, "checking");
+    }
+    state = ThreadJoined;
+    logAction(this, "joined");
+  }
+  return this;
+}
+
+const char*
+Thread::getName() const {
+  return name.buf;
+}
+
+ThreadState
+Thread::getState() const {
+  return state;
+}
+
+unsigned long
+Thread::getThreadId() const {
+  return thread_id;
+}
+
+
+Thread*
+Thread::self() {
+  Thread* thread = (Thread*) TlsGetValue(threadStorage);
+  if (!thread) {
+    // *** memory leak - could use GetExitCodeThread to lazily detect when
+    //     to clean up native thread objects
+    thread = new Thread(GetCurrentThread(), GetCurrentThreadId());
+    TlsSetValue(threadStorage, thread);
+  }
+  return thread;
+}
diff --git a/win/rfb_win32/Threading.h b/win/rfb_win32/Threading.h
new file mode 100644
index 0000000..850f04d
--- /dev/null
+++ b/win/rfb_win32/Threading.h
@@ -0,0 +1,154 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Threading_win32.h
+// Win32 Threading interface implementation
+
+#ifndef __RFB_THREADING_IMPL_WIN32
+#define __RFB_THREADING_IMPL_WIN32
+
+#define __RFB_THREADING_IMPL WIN32
+
+#include <rfb_win32/Handle.h>
+#include <rfb/util.h>
+#include <rdr/Exception.h>
+//#include <stdio.h>
+
+
+namespace rfb {
+
+  class Mutex {
+  public:
+    Mutex() {
+      InitializeCriticalSection(&crit);
+    }
+    ~Mutex() {
+      DeleteCriticalSection(&crit);
+    }
+    friend class Lock;
+    friend class Condition;
+  protected:
+    void enter() {EnterCriticalSection(&crit);}
+    void exit() {LeaveCriticalSection(&crit);}
+    CRITICAL_SECTION crit;
+  };
+
+  class Lock {
+  public:
+    Lock(Mutex& m) : mutex(m) {m.enter();}
+    ~Lock() {mutex.exit();}
+  protected:
+    Mutex& mutex;
+  };
+
+  enum ThreadState {ThreadCreated, ThreadStarted, ThreadStopped, ThreadJoined, ThreadNative};
+
+  class Thread {
+  public:
+    Thread(const char* name_=0);
+    virtual ~Thread();
+
+    virtual void run();
+
+    virtual void start();
+    virtual Thread* join();
+
+    const char* getName() const;
+    ThreadState getState() const;
+
+    // Determines whether the thread should delete itself when run() returns
+    // If you set this, you must NEVER call join()!
+    void setDeleteAfterRun() {deleteAfterRun = true;};
+
+    unsigned long getThreadId() const;
+
+    static Thread* self();
+
+    friend class Condition;
+
+  protected:
+    Thread(HANDLE thread_, DWORD thread_id_);
+    static DWORD WINAPI threadProc(LPVOID lpParameter);
+
+    win32::Handle thread;
+    DWORD thread_id;
+    CharArray name;
+    ThreadState state;
+    Condition* sig;
+    Mutex mutex;
+
+    win32::Handle cond_event;
+	  Thread* cond_next;
+
+    bool deleteAfterRun;
+  };
+
+  class Condition {
+  public:
+    Condition(Mutex& m) : mutex(m), waiting(0) {
+    }
+    ~Condition() {
+    }
+
+    // Wake up the specified number of threads that are waiting
+    // on this Condition, or all of them if -1 is specified.
+    void signal(int howMany=1) {
+      Lock l(cond_lock);
+      while (waiting && howMany!=0) {
+        SetEvent(waiting->cond_event);
+        waiting = waiting->cond_next;
+        if (howMany>0) --howMany;
+      }
+    }
+
+    // NB: Must hold "mutex" to call wait()
+    // Wait until either the Condition is signalled or the timeout
+    // expires.
+    void wait(DWORD timeout=INFINITE) {
+      Thread* self = Thread::self();
+      ResetEvent(self->cond_event);
+      { Lock l(cond_lock);
+        self->cond_next = waiting;
+        waiting = self;
+      }
+      mutex.exit();
+      DWORD result = WaitForSingleObject(self->cond_event, timeout);
+      mutex.enter();
+      if (result == WAIT_TIMEOUT) {
+        Lock l(cond_lock);
+        // Remove this thread from the Condition
+        for (Thread** removeFrom = &waiting; *removeFrom; removeFrom = &(*removeFrom)->cond_next) {
+          if (*removeFrom == self) {
+            *removeFrom = self->cond_next;
+            break;
+          }
+        }
+      } else if (result == WAIT_FAILED) {
+        throw rdr::SystemException("failed to wait on Condition", GetLastError());
+      }
+    }
+    
+  protected:
+    Mutex& mutex;
+    Mutex cond_lock;
+	  Thread* waiting;
+  };
+
+};
+
+#endif // __RFB_THREADING_IMPL
diff --git a/win/rfb_win32/ToolBar.cxx b/win/rfb_win32/ToolBar.cxx
new file mode 100644
index 0000000..6392ebd
--- /dev/null
+++ b/win/rfb_win32/ToolBar.cxx
@@ -0,0 +1,216 @@
+/* Copyright (C) 2004 TightVNC Team.  All Rights Reserved.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- ToolBar control class.
+
+#include "ToolBar.h"
+
+using namespace rfb::win32;
+
+ToolBar::ToolBar() : hwndToolBar(0), tbID(-1) {
+  INITCOMMONCONTROLSEX icex;
+
+  // Ensure that the common control DLL is loaded
+  icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
+  icex.dwICC  = ICC_BAR_CLASSES;
+  InitCommonControlsEx(&icex);
+}
+
+ToolBar::~ToolBar() {
+  DestroyWindow(getHandle());
+}
+
+bool ToolBar::create(int _tbID, HWND _parentHwnd, DWORD dwStyle) {
+  parentHwnd = _parentHwnd;
+  dwStyle |= WS_CHILD;
+
+  // Create the ToolBar window
+  hwndToolBar = CreateWindowEx(0, TOOLBARCLASSNAME, 0, dwStyle, 
+    0, 0, 25, 25, parentHwnd, (HMENU)_tbID, GetModuleHandle(0), 0);
+
+  if (hwndToolBar) {
+    tbID = _tbID;
+
+    // It's required for backward compatibility
+    SendMessage(hwndToolBar, TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
+  }
+  return (hwndToolBar ? true : false);
+};
+
+int ToolBar::addBitmap(int nButtons, UINT bitmapID) {
+  assert(nButtons > 0);
+  TBADDBITMAP resBitmap;
+  resBitmap.hInst = GetModuleHandle(0);
+  resBitmap.nID = bitmapID;
+  return SendMessage(getHandle(), TB_ADDBITMAP, nButtons, (LPARAM)&resBitmap);
+}
+
+int ToolBar::addSystemBitmap(UINT stdBitmapID) {
+  TBADDBITMAP resBitmap;
+  resBitmap.hInst = HINST_COMMCTRL;
+  resBitmap.nID = stdBitmapID;
+  return SendMessage(getHandle(), TB_ADDBITMAP, 0, (LPARAM)&resBitmap);
+}
+
+bool ToolBar::setBitmapSize(int width, int height) {
+  int result = SendMessage(getHandle(), TB_SETBITMAPSIZE, 
+    0, MAKELONG(width, height));
+  return (result ? true : false);
+}
+
+bool ToolBar::addButton(int iBitmap, int idCommand, BYTE state, BYTE style, UINT dwData, int iString) {
+  TBBUTTON tbb;
+  tbb.iBitmap = iBitmap;
+  tbb.idCommand = idCommand;
+  tbb.fsState = state;
+  tbb.fsStyle = style;
+  tbb.dwData = dwData;
+  tbb.iString = iString;
+
+  int result = SendMessage(getHandle(), TB_ADDBUTTONS, 1, (LPARAM)&tbb);
+  if (result) {
+    SendMessage(getHandle(), TB_AUTOSIZE, 0, 0);
+  }
+  return (result ? true : false);
+}
+
+bool ToolBar::addNButton(int nButtons, LPTBBUTTON tbb) {
+  assert(nButtons > 0);
+  assert(tbb > 0);
+  int result = SendMessage(getHandle(), TB_ADDBUTTONS, nButtons, (LPARAM)tbb);
+  if (result) {
+    SendMessage(getHandle(), TB_AUTOSIZE, 0, 0);
+  }
+  return (result ? true : false);
+}
+
+bool ToolBar::deleteButton(int indexButton) {
+  assert(indexButton >= 0);
+  int result = SendMessage(getHandle(), TB_DELETEBUTTON, indexButton, 0);
+  
+  if (result) {
+    SendMessage(getHandle(), TB_AUTOSIZE, 0, 0);
+  }
+  return (result ? true : false);
+}
+
+bool ToolBar::insertButton(int indexButton, LPTBBUTTON tbb) {
+  assert(indexButton >= 0);
+  assert(tbb > 0);
+  int result = SendMessage(getHandle(), TB_INSERTBUTTON, 
+    indexButton, (LPARAM)tbb);
+
+  if (result) {
+    SendMessage(getHandle(), TB_AUTOSIZE, 0, 0);
+  }
+  return (result ? true : false);
+}
+
+int ToolBar::getButtonInfo(int idButton, TBBUTTONINFO *btnInfo) {
+  assert(idButton >= 0);
+  assert(btnInfo > 0);
+  return SendMessage(getHandle(), TB_GETBUTTONINFO, idButton, (LPARAM)btnInfo);
+}
+
+int ToolBar::getButtonsHeight() {
+  return HIWORD(SendMessage(getHandle(), TB_GETBUTTONSIZE, 0, 0));
+}
+
+int ToolBar::getButtonsWidth() {
+  return LOWORD(SendMessage(getHandle(), TB_GETBUTTONSIZE, 0, 0));
+}
+
+bool ToolBar::setButtonInfo(int idButton, TBBUTTONINFO* btnInfo) {
+  assert(idButton >= 0);
+  assert(btnInfo > 0);
+  int result = SendMessage(getHandle(), TB_SETBUTTONINFO, 
+    idButton, (LPARAM)(LPTBBUTTONINFO)btnInfo);
+  return (result ? true : false);
+}
+
+bool ToolBar::checkButton(int idButton, bool check) {
+  assert(idButton >= 0);
+  int result = SendMessage(getHandle(), TB_CHECKBUTTON, 
+    idButton, MAKELONG(check, 0));
+  return (result ? true : false);
+}
+
+bool ToolBar::enableButton(int idButton, bool enable) {
+  assert(idButton >= 0);
+  int result = SendMessage(getHandle(), TB_ENABLEBUTTON, 
+    idButton, MAKELONG(enable, 0));
+  return (result ? true : false);
+}
+
+bool ToolBar::pressButton(int idButton, bool press) {
+  assert(idButton >= 0);
+  int result = SendMessage(getHandle(), TB_PRESSBUTTON, 
+    idButton, MAKELONG(press, 0));
+  return (result ? true : false);
+}
+
+bool ToolBar::getButtonRect(int nIndex, LPRECT buttonRect) {
+  int result = SendMessage(getHandle(), TB_GETITEMRECT, 
+    nIndex, (LPARAM)buttonRect);
+  return (result ? true : false);
+}
+
+bool ToolBar::setButtonSize(int width, int height) {
+  assert(width > 0);
+  assert(height > 0);
+  int result = SendMessage(getHandle(), TB_SETBUTTONSIZE, 
+    0, MAKELONG(width, height));
+  if (result) {
+    SendMessage(getHandle(), TB_AUTOSIZE, 0, 0);
+    return true;
+  }
+  return false; 
+}
+
+void ToolBar::autoSize() {
+  DWORD style = SendMessage(getHandle(), TB_GETSTYLE,  0, 0);
+  if (style & CCS_NORESIZE) {
+    RECT r, btnRect;
+    GetClientRect(parentHwnd, &r);
+    getButtonRect(0, &btnRect);
+    int height = getButtonsHeight() + btnRect.top * 2 + 2;
+    SetWindowPos(getHandle(), HWND_TOP, 0, 0, r.right - r.left, height, 
+      SWP_NOMOVE);
+  } else {
+    SendMessage(getHandle(), TB_AUTOSIZE, 0, 0);
+  }
+}
+
+int ToolBar::getHeight() {
+  RECT r;
+  GetWindowRect(getHandle(), &r);
+  return r.bottom - r.top;
+}
+
+void ToolBar::show() {
+  ShowWindow(getHandle(), SW_SHOW);
+}
+
+void ToolBar::hide() {
+  ShowWindow(getHandle(), SW_HIDE);
+}
+
+bool ToolBar::isVisible() {
+  DWORD style = GetWindowLong(getHandle(), GWL_STYLE);
+  return (bool)(style & WS_VISIBLE);
+}
diff --git a/win/rfb_win32/ToolBar.h b/win/rfb_win32/ToolBar.h
new file mode 100644
index 0000000..2242c2a
--- /dev/null
+++ b/win/rfb_win32/ToolBar.h
@@ -0,0 +1,132 @@
+/* Copyright (C) 2004 TightVNC Team.  All Rights Reserved.
+ *    
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- ToolBar control class.
+
+#include <windows.h>
+#include <commctrl.h>
+#include <assert.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class ToolBar {
+    public:
+      ToolBar();
+      virtual ~ToolBar();
+
+      // create() creates a windows toolbar. dwStyle is a combination of 
+      // the toolbar control and button styles. It returns TRUE if successful,
+      // or FALSE otherwise.
+      bool create(int tbID, HWND parentHwnd, 
+                  DWORD dwStyle = WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT);
+
+      // -=- Button images operations
+
+      // addBitmap() adds one or more images from resources to
+      // the list of button images available for a toolbar.
+      // Returns the index of the first new image if successful,
+      // or -1 otherwise.
+      int addBitmap(int nButtons, UINT bitmapID);
+
+      // addSystemBitmap() adds the system-defined button bitmaps to the list
+      // of the toolbar button specifying by stdBitmapID. Returns the index of 
+      // the first new image if successful, or -1 otherwise.
+      int addSystemBitmap(UINT stdBitmapID);
+
+      // setBitmapSize() sets the size of the bitmapped images to be added
+      // to a toolbar. It returns TRUE if successful, or FALSE otherwise.
+      // You must call it before addBitmap().
+      bool setBitmapSize(int width, int height);
+
+      // -=- Button operations
+
+      // addButton() adds one button.
+      bool addButton(int iBitmap, int idCommand, BYTE state=TBSTATE_ENABLED, 
+                     BYTE style=TBSTYLE_BUTTON,  UINT dwData=0, int iString=0);
+
+      // addNButton() adds nButtons buttons to a toolbar.
+      bool addNButton(int nButtons, LPTBBUTTON tbb);
+
+      // deleteButton() removes a button from the toolbar.
+      bool deleteButton(int nIndex);
+
+      // insertButton() inserts a button in a toolbar control by index.
+      bool insertButton(int nIndex, LPTBBUTTON tbb);
+
+      // getButtonInfo() retrieves extended information about a toolbar's 
+      // button. It returns index of the button if successful, or -1 otherwise.
+      int getButtonInfo(int idButton, TBBUTTONINFO *btnInfo);
+
+      // getButtonsHeight() retrieves the height of the toolbar buttons.
+      int getButtonsHeight();
+
+      // getButtonsWidth() retrieves the width of the toolbar buttons.
+      int getButtonsWidth();
+
+      // setButtonInfo() sets the information for an existing button 
+      // in a toolbar.
+      bool setButtonInfo(int idButton, TBBUTTONINFO* ptbbi);
+
+      // checkButton() checks or unchecks a given button in a toolbar control.
+      bool checkButton(int idButton, bool check);
+
+      // enableButton() enables or disables the specified button 
+      // in the toolbar.
+      bool enableButton(int idButton, bool enable);
+
+      // pressButton() presses or releases the specified button in the toolbar.
+      bool pressButton(int idButton, bool press);
+
+      // getButtonRect() gets the bounding rectangle of a button in a toolbar.
+      bool getButtonRect(int nIndex, LPRECT buttonRect);
+  
+      // setButtonSize() sets the size of the buttons to be added to a toolbar.
+      // Button size must be largen the button bitmap.
+      bool setButtonSize(int width, int height);
+
+      // -=- ToolBar operations
+
+      // autoSize() resizes the toolbar window.
+      void autoSize();
+
+      // getHandle() returns handle to a toolbar window.
+      HWND getHandle() { return hwndToolBar; }
+
+      // getHeight() returns the toolbar window height.
+      int getHeight();
+
+      // show() displays the toolbar window.
+      void show();
+
+      // hide() hides the toolbar window.
+      void hide();
+
+      // isVisible() check the toolbar window on visible.
+      bool isVisible();
+
+    protected:
+      HWND hwndToolBar;
+      HWND parentHwnd;
+      int tbID;
+    };
+
+  }; // win32
+
+}; // rfb
diff --git a/win/rfb_win32/TrayIcon.h b/win/rfb_win32/TrayIcon.h
new file mode 100644
index 0000000..dc5102a
--- /dev/null
+++ b/win/rfb_win32/TrayIcon.h
@@ -0,0 +1,89 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- CView.h
+
+// An instance of the CView class is created for each VNC Viewer connection.
+
+#ifndef __RFB_WIN32_TRAY_ICON_H__
+#define __RFB_WIN32_TRAY_ICON_H__
+
+#include <windows.h>
+#include <shellapi.h>
+#include <rfb_win32/MsgWindow.h>
+#include <rdr/Exception.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class TrayIcon : public MsgWindow {
+    public:
+      TrayIcon() : MsgWindow(_T("VNCTray")) {
+#ifdef NOTIFYICONDATA_V1_SIZE
+        nid.cbSize = NOTIFYICONDATA_V1_SIZE;
+#else
+        nid.cbSize = sizeof(NOTIFYICONDATA);
+#endif
+
+        nid.hWnd = getHandle();
+        nid.uID = 0;
+        nid.hIcon = 0;
+        nid.uFlags = NIF_ICON | NIF_MESSAGE;
+        nid.uCallbackMessage = WM_USER;
+      }
+      virtual ~TrayIcon() {
+        remove();
+      }
+      bool setIcon(UINT icon) {
+        if (icon == 0) {
+          return remove();
+        } else {
+          nid.hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(icon),
+            IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
+          return refresh();
+        }
+      }
+      bool setToolTip(const TCHAR* text) {
+        if (text == 0) {
+          nid.uFlags &= ~NIF_TIP;
+        } else {
+          const int tipLen = sizeof(nid.szTip)/sizeof(TCHAR);
+          _tcsncpy(nid.szTip, text, tipLen);
+          nid.szTip[tipLen-1] = 0;
+          nid.uFlags |= NIF_TIP;
+        }
+        return refresh();
+      }
+      bool remove() {
+        return Shell_NotifyIcon(NIM_DELETE, &nid) != 0;
+      }
+      bool refresh() {
+        return Shell_NotifyIcon(NIM_MODIFY, &nid) || Shell_NotifyIcon(NIM_ADD, &nid);
+      }
+    protected:
+      NOTIFYICONDATA nid;
+    };
+
+  };
+
+};
+
+#endif
+
+
diff --git a/win/rfb_win32/TsSessions.cxx b/win/rfb_win32/TsSessions.cxx
new file mode 100644
index 0000000..efe7564
--- /dev/null
+++ b/win/rfb_win32/TsSessions.cxx
@@ -0,0 +1,93 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#include <rfb_win32/TsSessions.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb/LogWriter.h>
+#include <rdr/Exception.h>
+#include <tchar.h>
+
+#ifdef ERROR_CTX_WINSTATION_BUSY
+#define RFB_HAVE_WINSTATION_CONNECT
+#else
+#pragma message("  NOTE: Not building WinStationConnect support.")
+#endif
+
+static rfb::LogWriter vlog("TsSessions");
+
+namespace rfb {
+namespace win32 {
+
+  // Windows XP (and later) functions used to handle session Ids
+  typedef BOOLEAN (WINAPI *_WinStationConnect_proto) (HANDLE,ULONG,ULONG,PCWSTR,ULONG);
+  DynamicFn<_WinStationConnect_proto> _WinStationConnect(_T("winsta.dll"), "WinStationConnectW");
+  typedef DWORD (WINAPI *_WTSGetActiveConsoleSessionId_proto) ();
+  DynamicFn<_WTSGetActiveConsoleSessionId_proto> _WTSGetActiveConsoleSessionId(_T("kernel32.dll"), "WTSGetActiveConsoleSessionId");
+  typedef BOOL (WINAPI *_ProcessIdToSessionId_proto) (DWORD, DWORD*);
+  DynamicFn<_ProcessIdToSessionId_proto> _ProcessIdToSessionId(_T("kernel32.dll"), "ProcessIdToSessionId");
+  typedef BOOL (WINAPI *_LockWorkStation_proto)();
+  DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
+
+
+  ProcessSessionId::ProcessSessionId(DWORD processId) {
+    id = 0;
+    if (!_ProcessIdToSessionId.isValid())
+      return;
+    if (processId == -1)
+      processId = GetCurrentProcessId();
+    if (!(*_ProcessIdToSessionId)(GetCurrentProcessId(), &id))
+      throw rdr::SystemException("ProcessIdToSessionId", GetLastError());
+  }
+
+  ProcessSessionId mySessionId;
+
+  ConsoleSessionId::ConsoleSessionId() {
+    if (_WTSGetActiveConsoleSessionId.isValid())
+      id = (*_WTSGetActiveConsoleSessionId)();
+    else
+      id = 0;
+  }
+
+  bool inConsoleSession() {
+    ConsoleSessionId console;
+    return console.id == mySessionId.id;
+  }
+
+  void setConsoleSession(DWORD sessionId) {
+#ifdef RFB_HAVE_WINSTATION_CONNECT
+    if (!_WinStationConnect.isValid())
+      throw rdr::Exception("WinSta APIs missing");
+    if (sessionId == -1)
+      sessionId = mySessionId.id;
+
+    // Try to reconnect our session to the console
+    ConsoleSessionId console;
+    vlog.info("Console session is %d", console.id);
+    if (!(*_WinStationConnect)(0, sessionId, console.id, L"", 0))
+      throw rdr::SystemException("Unable to connect session to Console", GetLastError());
+
+    // Lock the newly connected session, for security
+    if (_LockWorkStation.isValid())
+      (*_LockWorkStation)();
+#else
+    throw rdr::Exception("setConsoleSession not implemented");
+#endif
+  }
+
+};
+};
diff --git a/win/rfb_win32/TsSessions.h b/win/rfb_win32/TsSessions.h
new file mode 100644
index 0000000..b15ada7
--- /dev/null
+++ b/win/rfb_win32/TsSessions.h
@@ -0,0 +1,61 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// Windows version-independent Terminal Services Session discovery
+// and manipulation API.  This code will eventually be replaced
+// by the full TS-compatibility scheme.
+
+#ifndef __RFB_WIN32_TSSESSIONS_H__
+#define __RFB_WIN32_TSSESSIONS_H__
+
+#include <windows.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    struct SessionId {
+      DWORD id;
+    };
+
+    // Session Id for a given process
+    struct ProcessSessionId : SessionId {
+      ProcessSessionId(DWORD processId = -1);
+    };
+
+    // Session Id for current process
+    extern ProcessSessionId mySessionId;
+
+    // Current console Session Id
+    struct ConsoleSessionId : SessionId {
+      ConsoleSessionId();
+    };
+
+    // Check whether the process is in the Console session at present
+    bool inConsoleSession();
+
+    // Make the specified session the Console session.
+    //   If sessionId is -1 then the process' session is
+    //   made the Console session.
+    void setConsoleSession(DWORD sessionId = -1);
+
+  };
+
+};
+
+#endif
diff --git a/win/rfb_win32/WMCursor.cxx b/win/rfb_win32/WMCursor.cxx
new file mode 100644
index 0000000..4d696cb
--- /dev/null
+++ b/win/rfb_win32/WMCursor.cxx
@@ -0,0 +1,104 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMCursor.cxx
+
+// *** DOESN'T SEEM TO WORK WITH GetCursorInfo POS CODE BUILT-IN UNDER NT4SP6
+// *** INSTEAD, WE LOOK FOR Win2000/Win98 OR ABOVE
+
+#include <rfb_win32/WMCursor.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
+
+using namespace rdr;
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMCursor");
+
+
+#ifdef CURSOR_SHOWING
+#define RFB_HAVE_GETCURSORINFO
+#else
+#pragma message("  NOTE: Not building GetCursorInfo support.")
+#endif
+
+#ifdef RFB_HAVE_GETCURSORINFO
+typedef BOOL (WINAPI *_GetCursorInfo_proto)(PCURSORINFO pci);
+DynamicFn<_GetCursorInfo_proto> _GetCursorInfo(_T("user32.dll"), "GetCursorInfo");
+#endif
+
+WMCursor::WMCursor() : hooks(0), use_getCursorInfo(false), cursor(0) {
+#ifdef RFB_HAVE_GETCURSORINFO
+  // Check the OS version
+  bool is_win98 = (osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) &&
+    (osVersion.dwMajorVersion > 4) || ((osVersion.dwMajorVersion == 4) && (osVersion.dwMinorVersion > 0));
+  bool is_win2K = (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osVersion.dwMajorVersion >= 5);
+
+  // Use GetCursorInfo if OS version is sufficient
+  use_getCursorInfo = (is_win98 || is_win2K) && _GetCursorInfo.isValid();
+#endif
+  cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
+  if (!use_getCursorInfo) {
+    hooks = new WMCursorHooks();
+    if (hooks && hooks->start()) {
+      vlog.info("falling back to cursor hooking: %p", hooks);
+    } else {
+      delete hooks;
+      hooks = 0;
+      vlog.error("unable to monitor cursor shape");
+    }
+  } else {
+    vlog.info("using GetCursorInfo");
+  }
+}
+
+WMCursor::~WMCursor() {
+  vlog.debug("deleting WMCursorHooks (%p)", hooks);
+  if (hooks)
+    delete hooks;
+}
+  
+WMCursor::Info
+WMCursor::getCursorInfo() {
+  Info result;
+#ifdef RFB_HAVE_GETCURSORINFO
+  if (use_getCursorInfo) {
+    CURSORINFO info;
+    info.cbSize = sizeof(CURSORINFO);
+    if ((*_GetCursorInfo)(&info)) {
+      result.cursor = info.hCursor;
+      result.position = Point(info.ptScreenPos.x, info.ptScreenPos.y);
+      result.visible = info.flags & CURSOR_SHOWING;
+      return result;
+    }
+  }
+#endif
+  // Fall back to the old way of doing things
+  POINT pos;
+  if (hooks)
+    cursor = hooks->getCursor();
+  result.cursor = cursor;
+  result.visible = cursor != 0;
+  GetCursorPos(&pos);
+  result.position.x = pos.x;
+  result.position.y = pos.y;
+  return result;
+}
diff --git a/win/rfb_win32/WMCursor.h b/win/rfb_win32/WMCursor.h
new file mode 100644
index 0000000..41f9ee8
--- /dev/null
+++ b/win/rfb_win32/WMCursor.h
@@ -0,0 +1,61 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMCursor.h
+
+// WMCursor provides a single API through which the cursor state can be obtained
+// The underlying implementation will use either GetCursorInfo, or use the
+// wm_hooks library if GetCursorInfo is not available.
+
+#ifndef __RFB_WIN32_WM_CURSOR_H__
+#define __RFB_WIN32_WM_CURSOR_H__
+
+#include <windows.h>
+#include <rfb_win32/WMHooks.h>
+
+namespace rfb {
+  namespace win32 {
+
+    class WMCursor {
+    public:
+      WMCursor();
+      ~WMCursor();
+
+      struct Info {
+        HCURSOR cursor;
+        Point position;
+        bool visible;
+        Info() : cursor(0), visible(false) {}
+        bool operator!=(const Info& info) {
+          return ((cursor != info.cursor) ||
+            (!position.equals(info.position)) ||
+            (visible != info.visible));
+        }
+      };
+
+      Info getCursorInfo();
+    protected:
+      WMCursorHooks* hooks;
+      bool use_getCursorInfo;
+      HCURSOR cursor;
+    };
+
+  };
+};
+
+#endif // __RFB_WIN32_WM_CURSOR_H__
diff --git a/win/rfb_win32/WMHooks.cxx b/win/rfb_win32/WMHooks.cxx
new file mode 100644
index 0000000..2d69053
--- /dev/null
+++ b/win/rfb_win32/WMHooks.cxx
@@ -0,0 +1,394 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMHooks.cxx
+
+#include <rfb_win32/WMHooks.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/MsgWindow.h>
+#include <rfb_win32/IntervalTimer.h>
+#include <rfb/Threading.h>
+#include <rfb/LogWriter.h>
+
+#include <list>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMHooks");
+
+
+typedef UINT (*WM_Hooks_WMVAL_proto)();
+typedef BOOL (*WM_Hooks_Install_proto)(DWORD owner, DWORD thread);
+typedef BOOL (*WM_Hooks_Remove_proto)(DWORD owner);
+typedef BOOL (*WM_Hooks_EnableCursorShape_proto)(BOOL enable);
+#ifdef _DEBUG
+typedef void (*WM_Hooks_SetDiagnosticRange_proto)(UINT min, UINT max);
+DynamicFn<WM_Hooks_SetDiagnosticRange_proto> WM_Hooks_SetDiagnosticRange(_T("wm_hooks.dll"), "WM_Hooks_SetDiagnosticRange");
+#endif
+
+
+class WMHooksThread : public Thread {
+public:
+  WMHooksThread() : Thread("WMHookThread"), active(true),
+    WM_Hooks_Install(_T("wm_hooks.dll"), "WM_Hooks_Install"),
+    WM_Hooks_Remove(_T("wm_hooks.dll"), "WM_Hooks_Remove"),
+    WM_Hooks_EnableCursorShape(_T("wm_hooks.dll"), "WM_Hooks_EnableCursorShape") {
+  }
+  virtual void run();
+  virtual Thread* join();
+  DynamicFn<WM_Hooks_Install_proto> WM_Hooks_Install;;
+  DynamicFn<WM_Hooks_Remove_proto> WM_Hooks_Remove;
+  DynamicFn<WM_Hooks_EnableCursorShape_proto> WM_Hooks_EnableCursorShape;
+protected:
+  bool active;
+};
+
+WMHooksThread* hook_mgr = 0;
+std::list<WMHooks*> hooks;
+std::list<WMCursorHooks*> cursor_hooks;
+Mutex hook_mgr_lock;
+HCURSOR hook_cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
+
+
+static bool StartHookThread() {
+  if (hook_mgr)
+    return true;
+  vlog.debug("creating thread");
+  hook_mgr = new WMHooksThread();
+  if (!hook_mgr->WM_Hooks_Install.isValid() ||
+      !hook_mgr->WM_Hooks_Remove.isValid()) {
+    vlog.debug("hooks not available");
+    return false;
+  }
+  vlog.debug("installing hooks");
+  if (!(*hook_mgr->WM_Hooks_Install)(hook_mgr->getThreadId(), 0)) {
+    vlog.error("failed to initialise hooks");
+    delete hook_mgr->join();
+    hook_mgr = 0;
+    return false;
+  }
+  vlog.debug("starting thread");
+  hook_mgr->start();
+  return true;
+}
+
+static void StopHookThread() {
+  if (!hook_mgr)
+    return;
+  if (!hooks.empty() || !cursor_hooks.empty())
+    return;
+  vlog.debug("closing thread");
+  delete hook_mgr->join();
+  hook_mgr = 0;
+}
+
+
+static bool AddHook(WMHooks* hook) {
+  vlog.debug("adding hook");
+  Lock l(hook_mgr_lock);
+  if (!StartHookThread())
+    return false;
+  hooks.push_back(hook);
+  return true;
+}
+
+static bool AddCursorHook(WMCursorHooks* hook) {
+  vlog.debug("adding cursor hook");
+  Lock l(hook_mgr_lock);
+  if (!StartHookThread())
+    return false;
+  if (!hook_mgr->WM_Hooks_EnableCursorShape.isValid())
+    return false;
+  if (cursor_hooks.empty() && !(*hook_mgr->WM_Hooks_EnableCursorShape)(TRUE))
+    return false;
+  cursor_hooks.push_back(hook);
+  return true;
+}
+
+static bool RemHook(WMHooks* hook) {
+  {
+    vlog.debug("removing hook");
+    Lock l(hook_mgr_lock);
+    hooks.remove(hook);
+  }
+  StopHookThread();
+  return true;
+}
+
+static bool RemCursorHook(WMCursorHooks* hook) {
+  {
+    vlog.debug("removing cursor hook");
+    Lock l(hook_mgr_lock);
+    cursor_hooks.remove(hook);
+    if (hook_mgr->WM_Hooks_EnableCursorShape.isValid() &&
+        cursor_hooks.empty())
+      (*hook_mgr->WM_Hooks_EnableCursorShape)(FALSE);
+  }
+  StopHookThread();
+  return true;
+}
+
+static void NotifyHooksRegion(const Region& r) {
+  Lock l(hook_mgr_lock);
+  std::list<WMHooks*>::iterator i;
+  for (i=hooks.begin(); i!=hooks.end(); i++)
+    (*i)->NotifyHooksRegion(r);
+}
+
+static void NotifyHooksCursor(HCURSOR c) {
+  Lock l(hook_mgr_lock);
+  hook_cursor = c;
+}
+
+
+static UINT GetMsgVal(DynamicFn<WM_Hooks_WMVAL_proto>& fn) {
+  if (fn.isValid())
+    return (*fn)();
+  return WM_NULL;
+}
+
+void
+WMHooksThread::run() {
+  // Obtain message ids for all supported hook messages
+  DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowChanged");
+  DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowBorderChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowBorderChanged");
+  DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowClientAreaChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowClientAreaChanged");
+  DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_RectangleChanged(_T("wm_hooks.dll"), "WM_Hooks_RectangleChanged");
+  DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_CursorChanged(_T("wm_hooks.dll"), "WM_Hooks_CursorChanged");
+  UINT windowMsg = GetMsgVal(WM_Hooks_WindowChanged);
+  UINT clientAreaMsg = GetMsgVal(WM_Hooks_WindowClientAreaChanged);
+  UINT borderMsg = GetMsgVal(WM_Hooks_WindowBorderChanged);
+  UINT rectangleMsg = GetMsgVal(WM_Hooks_RectangleChanged);
+  UINT cursorMsg = GetMsgVal(WM_Hooks_CursorChanged);
+#ifdef _DEBUG
+  DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_Diagnostic(_T("wm_hooks.dll"), "WM_Hooks_Diagnostic");
+  UINT diagnosticMsg = GetMsgVal(WM_Hooks_Diagnostic);
+#endif
+  MSG msg;
+  RECT wrect;
+  HWND hwnd;
+  int count = 0;
+
+  // Update delay handling
+  //   We delay updates by 40-80ms, so that the triggering application has time to
+  //   actually complete them before we notify the hook callbacks & they go off
+  //   capturing screen state.
+  const int updateDelayMs = 40;
+  MsgWindow updateDelayWnd(_T("WMHooks::updateDelay"));
+  IntervalTimer updateDelayTimer(updateDelayWnd.getHandle(), 1);
+  Region updates[2];
+  int activeRgn = 0;
+
+  vlog.debug("starting hook thread");
+
+  while (active && GetMessage(&msg, NULL, 0, 0)) {
+    count++;
+
+    if (msg.message == WM_TIMER) {
+      // Actually notify callbacks of graphical updates
+      NotifyHooksRegion(updates[1-activeRgn]);
+      if (updates[activeRgn].is_empty())
+        updateDelayTimer.stop();
+      activeRgn = 1-activeRgn;
+      updates[activeRgn].clear();
+
+    } else if (msg.message == windowMsg) {
+      // An entire window has (potentially) changed
+      hwnd = (HWND) msg.lParam;
+      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
+        GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) {
+          updates[activeRgn].assign_union(Rect(wrect.left, wrect.top,
+                                               wrect.right, wrect.bottom));
+          updateDelayTimer.start(updateDelayMs);
+      }
+
+    } else if (msg.message == clientAreaMsg) {
+      // The client area of a window has (potentially) changed
+      hwnd = (HWND) msg.lParam;
+      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
+          GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
+      {
+        POINT pt = {0,0};
+        if (ClientToScreen(hwnd, &pt)) {
+          updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,
+                                               wrect.right+pt.x, wrect.bottom+pt.y));
+          updateDelayTimer.start(updateDelayMs);
+        }
+      }
+
+    } else if (msg.message == borderMsg) {
+      hwnd = (HWND) msg.lParam;
+      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
+          GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
+      {
+        Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
+        RECT crect;
+        POINT pt = {0,0};
+        if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) &&
+            !IsRectEmpty(&crect))
+        {
+          changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
+                                       crect.right+pt.x, crect.bottom+pt.y));
+        }
+        if (!changed.is_empty()) {
+          updates[activeRgn].assign_union(changed);
+          updateDelayTimer.start(updateDelayMs);
+        }
+      }
+    } else if (msg.message == rectangleMsg) {
+      Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
+                    LOWORD(msg.lParam), HIWORD(msg.lParam));
+      if (!r.is_empty()) {
+        updates[activeRgn].assign_union(r);
+        updateDelayTimer.start(updateDelayMs);
+      }
+
+    } else if (msg.message == cursorMsg) {
+      NotifyHooksCursor((HCURSOR)msg.lParam);
+#ifdef _DEBUG
+    } else if (msg.message == diagnosticMsg) {
+      vlog.info("DIAG msg=%x(%d) wnd=%lx", msg.wParam, msg.wParam, msg.lParam);
+#endif
+    }
+  }
+
+  vlog.debug("stopping hook thread - processed %d events", count);
+  (*WM_Hooks_Remove)(getThreadId());
+}
+
+Thread*
+WMHooksThread::join() {
+  vlog.debug("stopping WMHooks thread");
+  active = false;
+  PostThreadMessage(thread_id, WM_QUIT, 0, 0);
+  vlog.debug("joining WMHooks thread");
+  return Thread::join();
+}
+
+// -=- WMHooks class
+
+rfb::win32::WMHooks::WMHooks() : updateEvent(0) {
+}
+
+rfb::win32::WMHooks::~WMHooks() {
+  RemHook(this);
+}
+
+bool rfb::win32::WMHooks::setEvent(HANDLE ue) {
+  if (updateEvent)
+    RemHook(this);
+  updateEvent = ue;
+  return AddHook(this);
+}
+
+bool rfb::win32::WMHooks::getUpdates(UpdateTracker* ut) {
+  if (!updatesReady) return false;
+  Lock l(hook_mgr_lock);
+  updates.copyTo(ut);
+  updates.clear();
+  updatesReady = false;
+  return true;
+}
+
+bool rfb::win32::WMHooks::areAvailable() {
+  WMHooksThread wmht;
+  return wmht.WM_Hooks_Install.isValid();
+}
+
+#ifdef _DEBUG
+void
+rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
+  if (WM_Hooks_SetDiagnosticRange.isValid())
+    (*WM_Hooks_SetDiagnosticRange)(min, max);
+}
+#endif
+
+void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {
+  // hook_mgr_lock is already held at this point
+  updates.add_changed(r);
+  updatesReady = true;
+  SetEvent(updateEvent);
+}
+
+
+// -=- WMBlockInput class
+
+rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
+}
+
+rfb::win32::WMBlockInput::~WMBlockInput() {
+  blockInputs(false);
+}
+
+typedef BOOL (*WM_Hooks_EnableRealInputs_proto)(BOOL pointer, BOOL keyboard);
+DynamicFn<WM_Hooks_EnableRealInputs_proto>* WM_Hooks_EnableRealInputs = 0;
+static bool blockRealInputs(bool block_) {
+  // NB: Requires blockMutex to be held!
+  if (block_) {
+    if (WM_Hooks_EnableRealInputs)
+      return true;
+    // Enable blocking
+    WM_Hooks_EnableRealInputs = new DynamicFn<WM_Hooks_EnableRealInputs_proto>(_T("wm_hooks.dll"), "WM_Hooks_EnableRealInputs");
+    if (WM_Hooks_EnableRealInputs->isValid() && (**WM_Hooks_EnableRealInputs)(false, false))
+      return true;
+  }
+  if (WM_Hooks_EnableRealInputs) {
+    // Clean up the DynamicFn, either if init failed, or block_ is false
+    if (WM_Hooks_EnableRealInputs->isValid())
+      (**WM_Hooks_EnableRealInputs)(true, true);
+    delete WM_Hooks_EnableRealInputs;
+    WM_Hooks_EnableRealInputs = 0;
+  }
+  return block_ == (WM_Hooks_EnableRealInputs != 0);
+}
+
+Mutex blockMutex;
+int blockCount = 0;
+
+bool rfb::win32::WMBlockInput::blockInputs(bool on) {
+  if (active == on) return true;
+  Lock l(blockMutex);
+  int newCount = on ? blockCount+1 : blockCount-1;
+  if (!blockRealInputs(newCount > 0))
+    return false;
+  blockCount = newCount;
+  active = on;
+  return true;
+}
+
+
+// -=- WMCursorHooks class
+
+rfb::win32::WMCursorHooks::WMCursorHooks() {
+}
+
+rfb::win32::WMCursorHooks::~WMCursorHooks() {
+  RemCursorHook(this);
+}
+
+bool
+rfb::win32::WMCursorHooks::start() {
+  return AddCursorHook(this);
+}
+
+HCURSOR
+rfb::win32::WMCursorHooks::getCursor() const {
+  return hook_cursor;
+}
diff --git a/win/rfb_win32/WMHooks.h b/win/rfb_win32/WMHooks.h
new file mode 100644
index 0000000..4713b41
--- /dev/null
+++ b/win/rfb_win32/WMHooks.h
@@ -0,0 +1,92 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMHooks.h
+
+#ifndef __RFB_WIN32_WM_HOOKS_H__
+#define __RFB_WIN32_WM_HOOKS_H__
+
+#include <windows.h>
+#include <rfb/UpdateTracker.h>
+#include <rdr/Exception.h>
+#include <rfb_win32/Win32Util.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // -=- WMHooks
+    //     Uses the wm_hooks DLL to intercept window messages, to get _hints_ as
+    //     to what may have changed on-screen.  Updates are notified via a Win32
+    //     event, and retrieved using the getUpdates method, which is thread-safe.
+    class WMHooks {
+    public:
+      WMHooks();
+      ~WMHooks();
+
+      // Specify the event object to notify.  Starts the hook subsystem if it is
+      // not already active, and returns false if the hooks fail to start.
+      bool setEvent(HANDLE updateEvent);
+
+      // Copies any new updates to the UpdateTracker.  Returns true if new updates
+      // were added, false otherwise.
+      bool getUpdates(UpdateTracker* ut);
+
+      // Determine whether the hooks DLL is installed on the system
+      static bool areAvailable();
+
+#ifdef _DEBUG
+      // Get notifications of any messages in the given range, to any hooked window
+      void setDiagnosticRange(UINT min, UINT max);
+#endif
+
+      // * INTERNAL NOTIFICATION FUNCTION * 
+      void NotifyHooksRegion(const Region& r);
+    protected:
+      HANDLE updateEvent;
+      bool updatesReady;
+      SimpleUpdateTracker updates;
+    };
+
+    // -=- Support for filtering out local input events while remote connections are
+    //     active.  Implemented using SetWindowsHookEx for portability.
+    class WMBlockInput {
+    public:
+      WMBlockInput();
+      ~WMBlockInput();
+      bool blockInputs(bool block);
+    protected:
+      bool active;
+    };
+
+    // - Legacy cursor handling support
+    class WMCursorHooks {
+    public:
+      WMCursorHooks();
+      ~WMCursorHooks();
+
+      bool start();
+
+      HCURSOR getCursor() const;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WM_HOOKS_H__
diff --git a/win/rfb_win32/WMNotifier.cxx b/win/rfb_win32/WMNotifier.cxx
new file mode 100644
index 0000000..20a5445
--- /dev/null
+++ b/win/rfb_win32/WMNotifier.cxx
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMNotifier.cxx
+
+#include <rfb_win32/WMNotifier.h>
+#include <rfb_win32/WMShatter.h>
+#include <rfb_win32/MsgWindow.h>
+
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMMonitor");
+
+
+WMMonitor::WMMonitor() : MsgWindow(_T("WMMonitor")), notifier(0) {
+}
+
+WMMonitor::~WMMonitor() {
+}
+
+
+LRESULT
+WMMonitor::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  switch (msg) {
+  case WM_DISPLAYCHANGE:
+    if (notifier) {
+      notifier->notifyDisplayEvent(Notifier::DisplaySizeChanged);
+      notifier->notifyDisplayEvent(Notifier::DisplayPixelFormatChanged);
+    }
+    break;
+	case WM_SYSCOLORCHANGE:
+  case WM_PALETTECHANGED:
+    if (notifier) {
+      notifier->notifyDisplayEvent(Notifier::DisplayColourMapChanged);
+    }
+    break;
+  };
+  return MsgWindow::processMessage(msg, wParam, lParam);
+}
diff --git a/win/rfb_win32/WMNotifier.h b/win/rfb_win32/WMNotifier.h
new file mode 100644
index 0000000..a760964
--- /dev/null
+++ b/win/rfb_win32/WMNotifier.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMNotifier.h
+//
+// The WMNotifier is used to get callbacks indicating changes in the state
+// of the system, for instance in the size/format/palette of the display.
+// The WMNotifier contains a Win32 window, which receives notifications of
+// system events and stores them.  Whenever processEvent is called, any
+// incoming events are processed and the appropriate notifier called.
+
+#ifndef __RFB_WIN32_NOTIFIER_H__
+#define __RFB_WIN32_NOTIFIER_H__
+
+#include <rfb/SDesktop.h>
+#include <rfb/Threading.h>
+#include <rfb_win32/MsgWindow.h>
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <rfb_win32/SInput.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // -=- Window Message Monitor implementation
+
+    class WMMonitor : MsgWindow {
+    public:
+
+      class Notifier {
+      public:
+        typedef enum {DisplaySizeChanged, DisplayColourMapChanged,
+          DisplayPixelFormatChanged} DisplayEventType;
+        virtual void notifyDisplayEvent(DisplayEventType evt) = 0;
+      };
+
+      WMMonitor();
+      virtual ~WMMonitor();
+
+      void setNotifier(Notifier* wmn) {notifier=wmn;}
+
+    protected:
+      // - Internal MsgWindow callback
+      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+      Notifier* notifier;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WMNOTIFIER_H__
diff --git a/win/rfb_win32/WMPoller.cxx b/win/rfb_win32/WMPoller.cxx
new file mode 100644
index 0000000..f850534
--- /dev/null
+++ b/win/rfb_win32/WMPoller.cxx
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMPoller.cxx
+
+#include <rfb_win32/WMPoller.h>
+#include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
+#include <rfb/Configuration.h>
+
+#include <tchar.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMPoller");
+
+BoolParameter rfb::win32::WMPoller::poll_console_windows("PollConsoleWindows",
+  "Server should poll console windows for updates", true);
+
+// -=- WMPoller class
+
+bool
+rfb::win32::WMPoller::processEvent() {
+  PollInfo info;
+  if (poll_console_windows && ut) {
+    ::EnumWindows(WMPoller::enumWindowProc, (LPARAM) &info);
+    ut->add_changed(info.poll_include);
+  }
+  return false;
+}
+
+bool
+rfb::win32::WMPoller::setUpdateTracker(UpdateTracker* ut_) {
+  ut = ut_;
+  return true;
+}
+
+bool
+rfb::win32::WMPoller::checkPollWindow(HWND w) {
+  TCHAR buffer[128];
+  if (!GetClassName(w, buffer, 128))
+    throw rdr::SystemException("unable to get window class:%u", GetLastError());
+  if ((_tcscmp(buffer, _T("tty")) != 0) &&
+    (_tcscmp(buffer, _T("ConsoleWindowClass")) != 0)) {
+    return false;
+  }
+  return true;
+}
+
+void
+rfb::win32::WMPoller::pollWindow(HWND w, PollInfo* i) {
+  RECT r;
+  if (IsWindowVisible(w) && GetWindowRect(w, &r)) {
+    if (IsRectEmpty(&r)) return;
+    Region wrgn(Rect(r.left, r.top, r.right, r.bottom));
+    if (checkPollWindow(w)) {
+      wrgn.assign_subtract(i->poll_exclude);
+      i->poll_include.assign_union(wrgn);
+    } else {
+      i->poll_exclude.assign_union(wrgn);
+    }
+  }
+}
+
+BOOL CALLBACK
+rfb::win32::WMPoller::enumWindowProc(HWND w, LPARAM lp) {
+  pollWindow(w, (PollInfo*)lp);
+  return TRUE;
+}
diff --git a/win/rfb_win32/WMPoller.h b/win/rfb_win32/WMPoller.h
new file mode 100644
index 0000000..851b69f
--- /dev/null
+++ b/win/rfb_win32/WMPoller.h
@@ -0,0 +1,62 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMPoller.h
+//
+// Polls the foreground window.  If the pollOnlyConsoles flag is set,
+// then checks the window class of the foreground window first and
+// only polls it if it's a console.
+// If the pollAllWindows flag is set then iterates through visible
+// windows, and polls the visible bits.  If pollOnlyConsoles is also
+// set then only visible parts of console windows will be polled.
+
+#ifndef __RFB_WIN32_WM_POLLER_H__
+#define __RFB_WIN32_WM_POLLER_H__
+
+#include <windows.h>
+#include <rfb/UpdateTracker.h>
+#include <rfb/Configuration.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class WMPoller {
+    public:
+      WMPoller() : ut(0) {}
+
+      bool processEvent();
+      bool setUpdateTracker(UpdateTracker* ut);
+
+      static BoolParameter poll_console_windows;
+    protected:
+      struct PollInfo {
+        Region poll_include;
+        Region poll_exclude;
+      };
+      static bool checkPollWindow(HWND w);
+      static void pollWindow(HWND w, PollInfo* info);
+      static BOOL CALLBACK enumWindowProc(HWND w, LPARAM lp);
+      UpdateTracker* ut;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WM_POLLER_H__
diff --git a/win/rfb_win32/WMShatter.cxx b/win/rfb_win32/WMShatter.cxx
new file mode 100644
index 0000000..e68abfb
--- /dev/null
+++ b/win/rfb_win32/WMShatter.cxx
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMShatter.cxx
+
+#include <rfb_win32/WMShatter.h>
+
+#include <rfb/LogWriter.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMShatter");
+
+bool
+rfb::win32::IsSafeWM(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
+  bool result = true;
+  switch (msg) {
+    // - UNSAFE MESSAGES
+  case WM_TIMER:
+    result = lParam == 0;
+    break;
+  };
+  if (!result) {
+    vlog.info("IsSafeWM: 0x%x received 0x%x(%u, %lu) - not safe", window, msg, wParam, lParam);
+  }
+  return result;
+}
+
+LRESULT
+rfb::win32::SafeDefWindowProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) {
+  if (IsSafeWM(window, msg, wParam, lParam))
+    return DefWindowProc(window, msg, wParam, lParam);
+  return 0;
+}
+
+LRESULT
+rfb::win32::SafeDispatchMessage(const MSG* msg) {
+  if (IsSafeWM(msg->hwnd, msg->message, msg->wParam, msg->lParam))
+    return DispatchMessage(msg);
+  return 0;
+}
diff --git a/win/rfb_win32/WMShatter.h b/win/rfb_win32/WMShatter.h
new file mode 100644
index 0000000..3ea63b1
--- /dev/null
+++ b/win/rfb_win32/WMShatter.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMShatter.h
+//
+// WMShatter provides the IsSafeWM routine, which returns true iff the
+// supplied window message is safe to pass to DispatchMessage, or to
+// process in the window procedure.
+//
+// This is only required, of course, to avoid so-called "shatter" attacks
+// to be made against the VNC server, which take advantage of the noddy
+// design of the Win32 window messaging system.
+//
+// The API here is designed to hopefully be future proof, so that if they
+// ever come up with a proper way to determine whether a message is safe
+// or not then it can just be reimplemented here...
+
+#ifndef __RFB_WIN32_SHATTER_H__
+#define __RFB_WIN32_SHATTER_H__
+
+#include <windows.h>
+
+namespace rfb {
+  namespace win32 {
+
+    bool IsSafeWM(HWND window, UINT msg, WPARAM wParam, LPARAM lParam);
+
+    LRESULT SafeDefWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+    LRESULT SafeDispatchMessage(const MSG* msg);
+
+  };
+};
+
+#endif // __RFB_WIN32_SHATTER_H__
diff --git a/win/rfb_win32/WMWindowCopyRect.cxx b/win/rfb_win32/WMWindowCopyRect.cxx
new file mode 100644
index 0000000..63c1da2
--- /dev/null
+++ b/win/rfb_win32/WMWindowCopyRect.cxx
@@ -0,0 +1,67 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMCopyRect.cxx
+
+#include <rfb_win32/WMWindowCopyRect.h>
+#include <rfb/LogWriter.h>
+#include <windows.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMCopyRect");
+
+// -=- WMHooks class
+
+rfb::win32::WMCopyRect::WMCopyRect() : ut(0), fg_window(0) {
+}
+
+bool
+rfb::win32::WMCopyRect::processEvent() {
+  // See if the foreground window has moved
+  HWND window = GetForegroundWindow();
+  if (window) {
+    RECT wrect;
+    if (IsWindow(window) && IsWindowVisible(window) && GetWindowRect(window, &wrect)) {
+      Rect winrect(wrect.left, wrect.top, wrect.right, wrect.bottom);
+      if (fg_window == window) {
+        if (!fg_window_rect.tl.equals(winrect.tl)  && ut) {
+          // Window has moved - send a copyrect event to the client
+          Point delta = Point(winrect.tl.x-fg_window_rect.tl.x, winrect.tl.y-fg_window_rect.tl.y);
+          Region copy_dest = winrect;
+          ut->add_copied(copy_dest, delta);
+          ut->add_changed(Region(fg_window_rect).subtract(copy_dest));
+        }
+      }
+      fg_window = window;
+      fg_window_rect = winrect;
+    } else {
+      fg_window = 0;
+    }
+  } else {
+    fg_window = 0;
+  }
+  return false;
+}
+
+bool
+rfb::win32::WMCopyRect::setUpdateTracker(UpdateTracker* ut_) {
+  ut = ut_;
+  return true;
+}
diff --git a/win/rfb_win32/WMWindowCopyRect.h b/win/rfb_win32/WMWindowCopyRect.h
new file mode 100644
index 0000000..5a0e876
--- /dev/null
+++ b/win/rfb_win32/WMWindowCopyRect.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- WMWindowCopyRect.h
+//
+// Helper class which produces copyRect actions by monitoring the location
+// of the current foreground window.
+// Whenever processEvent is called, the foreground window's position is
+// recalculated and a copy event flushed to the supplied UpdateTracker
+// if appropriate.
+
+#ifndef __RFB_WIN32_WM_WINDOW_COPYRECT_H__
+#define __RFB_WIN32_WM_WINDOW_COPYRECT_H__
+
+#include <rfb/UpdateTracker.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class WMCopyRect {
+    public:
+      WMCopyRect();
+
+      bool processEvent();
+      bool setUpdateTracker(UpdateTracker* ut);
+
+    protected:
+      UpdateTracker* ut;
+      void* fg_window;
+      Rect fg_window_rect;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WM_WINDOW_COPYRECT_H__
diff --git a/win/rfb_win32/Win32Util.cxx b/win/rfb_win32/Win32Util.cxx
new file mode 100644
index 0000000..ef8039a
--- /dev/null
+++ b/win/rfb_win32/Win32Util.cxx
@@ -0,0 +1,114 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// Win32Util.cxx
+
+#include <rfb_win32/ModuleFileName.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/MonitorInfo.h>
+#include <rfb_win32/Handle.h>
+#include <rdr/HexOutStream.h>
+#include <rdr/Exception.h>
+
+namespace rfb {
+namespace win32 {
+
+
+FileVersionInfo::FileVersionInfo(const TCHAR* filename) {
+  // Get executable name
+  ModuleFileName exeName;
+  if (!filename)
+    filename = exeName.buf;
+
+  // Attempt to open the file, to cause Access Denied, etc, errors
+  // to be correctly reported, since the GetFileVersionInfoXXX calls lie...
+  {
+    Handle file(CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0));
+	  if (file.h == INVALID_HANDLE_VALUE)
+      throw rdr::SystemException("Failed to open file", GetLastError());
+  }
+
+  // Get version info size
+  DWORD handle;
+  int size = GetFileVersionInfoSize((TCHAR*)filename, &handle);
+  if (!size)
+    throw rdr::SystemException("GetVersionInfoSize failed", GetLastError());
+
+  // Get version info
+  buf = new TCHAR[size];
+  if (!GetFileVersionInfo((TCHAR*)filename, handle, size, buf))
+    throw rdr::SystemException("GetVersionInfo failed", GetLastError());
+}
+
+const TCHAR* FileVersionInfo::getVerString(const TCHAR* name, DWORD langId) {
+  char langIdBuf[sizeof(langId)];
+  for (int i=sizeof(langIdBuf)-1; i>=0; i--) {
+    langIdBuf[i] = (char) (langId & 0xff);
+    langId = langId >> 8;
+  }
+
+  TCharArray langIdStr = rdr::HexOutStream::binToHexStr(langIdBuf, sizeof(langId));
+  TCharArray infoName(_tcslen(_T("StringFileInfo")) + 4 + _tcslen(name) + _tcslen(langIdStr.buf));
+  _stprintf(infoName.buf, _T("\\StringFileInfo\\%s\\%s"), langIdStr.buf, name);
+
+  // Locate the required version string within the version info
+  TCHAR* buffer = 0;
+  UINT length = 0;
+  if (!VerQueryValue(buf, infoName.buf, (void**)&buffer, &length)) {
+    printf("unable to find %s version string", CStr(infoName.buf));
+    throw rdr::Exception("VerQueryValue failed");
+  }
+  return buffer;
+}
+
+
+bool splitPath(const TCHAR* path, TCHAR** dir, TCHAR** file) {
+  return tstrSplit(path, '\\', dir, file, true);
+}
+
+
+void centerWindow(HWND handle, HWND parent) {
+  RECT r;
+  MonitorInfo mi(parent ? parent : handle);
+  if (!parent || !IsWindowVisible(parent) || !GetWindowRect(parent, &r))
+    r=mi.rcWork;
+  centerWindow(handle, r);
+  mi.clipTo(handle);
+}
+
+void centerWindow(HWND handle, const RECT& r) {
+  RECT wr;
+  if (!GetWindowRect(handle, &wr)) return;
+  int w = wr.right-wr.left;
+  int h = wr.bottom-wr.top;
+  int x = (r.left + r.right - w)/2;
+  int y = (r.top + r.bottom - h)/2;
+  UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSIZE;
+  SetWindowPos(handle, 0, x, y, 0, 0, flags);
+}
+
+void resizeWindow(HWND handle, int width, int height) {
+  RECT r;
+  GetWindowRect(handle, &r);
+  SetWindowPos(handle, 0, 0, 0, width, height, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
+  centerWindow(handle, r);
+}
+
+
+};
+};
diff --git a/win/rfb_win32/Win32Util.h b/win/rfb_win32/Win32Util.h
new file mode 100644
index 0000000..8cc1a2b
--- /dev/null
+++ b/win/rfb_win32/Win32Util.h
@@ -0,0 +1,55 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// -=- Win32Util.h
+
+// Miscellaneous but useful Win32 API utility functions & classes.
+// In particular, a set of classes which wrap GDI objects,
+// and some to handle palettes.
+
+#ifndef __RFB_WIN32_GDIUTIL_H__
+#define __RFB_WIN32_GDIUTIL_H__
+
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    struct FileVersionInfo : public TCharArray {
+      FileVersionInfo(const TCHAR* filename=0);
+      const TCHAR* getVerString(const TCHAR* name, DWORD langId = 0x080904b0);
+    };
+
+    bool splitPath(const TCHAR* path, TCHAR** dir, TCHAR** file);
+
+    // Center the window to a rectangle, or to a parent window.
+    // Optionally, resize the window to lay within the rect or parent window
+    // If the parent window is NULL then the working area if the window's
+    // current monitor is used instead.
+    void centerWindow(HWND handle, const RECT& r);
+    void centerWindow(HWND handle, HWND parent);
+
+    // resizeWindow resizes a window about its center
+    void resizeWindow(HWND handle, int width, int height);
+
+  };
+
+};
+
+#endif
diff --git a/win/rfb_win32/keymap.h b/win/rfb_win32/keymap.h
new file mode 100644
index 0000000..a340d09
--- /dev/null
+++ b/win/rfb_win32/keymap.h
@@ -0,0 +1,149 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+// keymap.h - this file is shared between SInput.cxx and CKeyboard.cxx
+//
+// Mapping of X keysyms to and from Windows VK codes.  Ordering here must be
+// such that when we look up a Windows VK code we get the preferred X keysym.
+// Going the other way there is no problem because an X keysym always maps to
+// exactly one Windows VK code.  This map only contain keys which are not the
+// normal keys for printable ASCII characters.  For example it does not contain
+// VK_SPACE (note that things like VK_ADD are for the plus key on the keypad,
+// not on the main keyboard).
+
+struct keymap_t {
+  rdr::U32 keysym;
+  rdr::U8 vk;
+  bool extended;
+};
+
+static keymap_t keymap[] = {
+
+  { XK_BackSpace,        VK_BACK, 0 },
+  { XK_Tab,              VK_TAB, 0 },
+  { XK_Clear,            VK_CLEAR, 0 },
+  { XK_Return,           VK_RETURN, 0 },
+  { XK_Pause,            VK_PAUSE, 0 },
+  { XK_Escape,           VK_ESCAPE, 0 },
+  { XK_Delete,           VK_DELETE, 1 },
+
+  // Cursor control & motion
+
+  { XK_Home,             VK_HOME, 1 },
+  { XK_Left,             VK_LEFT, 1 },
+  { XK_Up,               VK_UP, 1 },
+  { XK_Right,            VK_RIGHT, 1 },
+  { XK_Down,             VK_DOWN, 1 },
+  { XK_Page_Up,          VK_PRIOR, 1 },
+  { XK_Page_Down,        VK_NEXT, 1 },
+  { XK_End,              VK_END, 1 },
+
+  // Misc functions
+
+  { XK_Select,           VK_SELECT, 0 },
+  { XK_Print,            VK_SNAPSHOT, 0 },
+  { XK_Execute,          VK_EXECUTE, 0 },
+  { XK_Insert,           VK_INSERT, 1 },
+  { XK_Help,             VK_HELP, 0 },
+  { XK_Break,            VK_CANCEL, 1 },
+
+  // Auxilliary Functions - must come before XK_KP_F1, etc
+
+  { XK_F1,               VK_F1, 0 },
+  { XK_F2,               VK_F2, 0 },
+  { XK_F3,               VK_F3, 0 },
+  { XK_F4,               VK_F4, 0 },
+  { XK_F5,               VK_F5, 0 },
+  { XK_F6,               VK_F6, 0 },
+  { XK_F7,               VK_F7, 0 },
+  { XK_F8,               VK_F8, 0 },
+  { XK_F9,               VK_F9, 0 },
+  { XK_F10,              VK_F10, 0 },
+  { XK_F11,              VK_F11, 0 },
+  { XK_F12,              VK_F12, 0 },
+  { XK_F13,              VK_F13, 0 },
+  { XK_F14,              VK_F14, 0 },
+  { XK_F15,              VK_F15, 0 },
+  { XK_F16,              VK_F16, 0 },
+  { XK_F17,              VK_F17, 0 },
+  { XK_F18,              VK_F18, 0 },
+  { XK_F19,              VK_F19, 0 },
+  { XK_F20,              VK_F20, 0 },
+  { XK_F21,              VK_F21, 0 },
+  { XK_F22,              VK_F22, 0 },
+  { XK_F23,              VK_F23, 0 },
+  { XK_F24,              VK_F24, 0 },
+
+  // Keypad Functions, keypad numbers
+
+  { XK_KP_Tab,           VK_TAB, 0 },
+  { XK_KP_Enter,         VK_RETURN, 1 },
+  { XK_KP_F1,            VK_F1, 0 },
+  { XK_KP_F2,            VK_F2, 0 },
+  { XK_KP_F3,            VK_F3, 0 },
+  { XK_KP_F4,            VK_F4, 0 },
+  { XK_KP_Home,          VK_HOME, 0 },
+  { XK_KP_Left,          VK_LEFT, 0 },
+  { XK_KP_Up,            VK_UP, 0 },
+  { XK_KP_Right,         VK_RIGHT, 0 },
+  { XK_KP_Down,          VK_DOWN, 0 },
+  { XK_KP_End,           VK_END, 0 },
+  { XK_KP_Page_Up,       VK_PRIOR, 0 },
+  { XK_KP_Page_Down,     VK_NEXT, 0 },
+  { XK_KP_Begin,         VK_CLEAR, 0 },
+  { XK_KP_Insert,        VK_INSERT, 0 },
+  { XK_KP_Delete,        VK_DELETE, 0 },
+  { XK_KP_Multiply,      VK_MULTIPLY, 0 },
+  { XK_KP_Add,           VK_ADD, 0 },
+  { XK_KP_Separator,     VK_SEPARATOR, 0 },
+  { XK_KP_Subtract,      VK_SUBTRACT, 0 },
+  { XK_KP_Decimal,       VK_DECIMAL, 0 },
+  { XK_KP_Divide,        VK_DIVIDE, 1 },
+
+  { XK_KP_0,             VK_NUMPAD0, 0 },
+  { XK_KP_1,             VK_NUMPAD1, 0 },
+  { XK_KP_2,             VK_NUMPAD2, 0 },
+  { XK_KP_3,             VK_NUMPAD3, 0 },
+  { XK_KP_4,             VK_NUMPAD4, 0 },
+  { XK_KP_5,             VK_NUMPAD5, 0 },
+  { XK_KP_6,             VK_NUMPAD6, 0 },
+  { XK_KP_7,             VK_NUMPAD7, 0 },
+  { XK_KP_8,             VK_NUMPAD8, 0 },
+  { XK_KP_9,             VK_NUMPAD9, 0 },
+
+  // Modifiers
+    
+  { XK_Shift_L,          VK_SHIFT, 0 },
+  { XK_Shift_R,          VK_SHIFT, 0 },
+  { XK_Control_L,        VK_CONTROL, 0 },
+  { XK_Control_R,        VK_CONTROL, 1 },
+  { XK_Alt_L,            VK_MENU, 0 },
+  { XK_Alt_R,            VK_MENU, 1 },
+
+  // Left & Right Windows keys & Windows Menu Key
+
+  { XK_Super_L,          VK_LWIN, 0 },
+  { XK_Super_R,          VK_RWIN, 0 },
+  { XK_Menu,             VK_APPS, 0 },
+
+  // Japanese stuff - almost certainly wrong...
+
+  { XK_Kanji,            VK_KANJI, 0 },
+  { XK_Kana_Shift,       VK_KANA, 0 },
+
+};
diff --git a/win/rfb_win32/rfb_win32.dsp b/win/rfb_win32/rfb_win32.dsp
new file mode 100644
index 0000000..acfb5aa
--- /dev/null
+++ b/win/rfb_win32/rfb_win32.dsp
@@ -0,0 +1,513 @@
+# Microsoft Developer Studio Project File - Name="rfb_win32" - Package Owner=<4>

+# Microsoft Developer Studio Generated Build File, Format Version 6.00

+# ** DO NOT EDIT **

+

+# TARGTYPE "Win32 (x86) Static Library" 0x0104

+

+CFG=rfb_win32 - Win32 Debug Unicode

+!MESSAGE This is not a valid makefile. To build this project using NMAKE,

+!MESSAGE use the Export Makefile command and run

+!MESSAGE 

+!MESSAGE NMAKE /f "rfb_win32.mak".

+!MESSAGE 

+!MESSAGE You can specify a configuration when running NMAKE

+!MESSAGE by defining the macro CFG on the command line. For example:

+!MESSAGE 

+!MESSAGE NMAKE /f "rfb_win32.mak" CFG="rfb_win32 - Win32 Debug Unicode"

+!MESSAGE 

+!MESSAGE Possible choices for configuration are:

+!MESSAGE 

+!MESSAGE "rfb_win32 - Win32 Release" (based on "Win32 (x86) Static Library")

+!MESSAGE "rfb_win32 - Win32 Debug" (based on "Win32 (x86) Static Library")

+!MESSAGE "rfb_win32 - Win32 Debug Unicode" (based on "Win32 (x86) Static Library")

+!MESSAGE 

+

+# Begin Project

+# PROP AllowPerConfigDependencies 0

+# PROP Scc_ProjName ""

+# PROP Scc_LocalPath ""

+CPP=cl.exe

+RSC=rc.exe

+

+!IF  "$(CFG)" == "rfb_win32 - Win32 Release"

+

+# PROP BASE Use_MFC 0

+# PROP BASE Use_Debug_Libraries 0

+# PROP BASE Output_Dir "Release"

+# PROP BASE Intermediate_Dir "Release"

+# PROP BASE Target_Dir ""

+# PROP Use_MFC 0

+# PROP Use_Debug_Libraries 0

+# PROP Output_Dir "..\Release"

+# PROP Intermediate_Dir "..\Release\rfb_win32"

+# PROP Target_Dir ""

+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c

+# ADD CPP /nologo /MT /W3 /GX /O2 /I ".." /I "../../common" /FI"rdr/msvcwarning.h" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "WIN32" /YX /FD /c

+# ADD BASE RSC /l 0x809 /d "NDEBUG"

+# ADD RSC /l 0x809 /d "NDEBUG"

+BSC32=bscmake.exe

+# ADD BASE BSC32 /nologo

+# ADD BSC32 /nologo

+LIB32=link.exe -lib

+# ADD BASE LIB32 /nologo

+# ADD LIB32 /nologo

+

+!ELSEIF  "$(CFG)" == "rfb_win32 - Win32 Debug"

+

+# PROP BASE Use_MFC 0

+# PROP BASE Use_Debug_Libraries 1

+# PROP BASE Output_Dir "Debug"

+# PROP BASE Intermediate_Dir "Debug"

+# PROP BASE Target_Dir ""

+# PROP Use_MFC 0

+# PROP Use_Debug_Libraries 1

+# PROP Output_Dir "..\Debug"

+# PROP Intermediate_Dir "..\Debug\rfb_win32"

+# PROP Target_Dir ""

+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c

+# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /I ".." /I "../../common" /FI"rdr/msvcwarning.h" /D "_DEBUG" /D "_MBCS" /D "_LIB" /D "WIN32" /YX /FD /GZ /c

+# ADD BASE RSC /l 0x809 /d "_DEBUG"

+# ADD RSC /l 0x809 /d "_DEBUG"

+BSC32=bscmake.exe

+# ADD BASE BSC32 /nologo

+# ADD BSC32 /nologo

+LIB32=link.exe -lib

+# ADD BASE LIB32 /nologo

+# ADD LIB32 /nologo

+

+!ELSEIF  "$(CFG)" == "rfb_win32 - Win32 Debug Unicode"

+

+# PROP BASE Use_MFC 0

+# PROP BASE Use_Debug_Libraries 1

+# PROP BASE Output_Dir "rfb_win32___Win32_Debug_Unicode"

+# PROP BASE Intermediate_Dir "rfb_win32___Win32_Debug_Unicode"

+# PROP BASE Target_Dir ""

+# PROP Use_MFC 0

+# PROP Use_Debug_Libraries 1

+# PROP Output_Dir "..\Debug_Unicode"

+# PROP Intermediate_Dir "..\Debug_Unicode\rfb_win32"

+# PROP Target_Dir ""

+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /I ".." /FI"msvcwarning.h" /D "_DEBUG" /D "_LIB" /D "WIN32" /D "_MBCS" /YX /FD /GZ /c

+# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /I ".." /I "../../common" /FI"rdr/msvcwarning.h" /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /D "_LIB" /D "WIN32" /YX /FD /GZ /c

+# ADD BASE RSC /l 0x809 /d "_DEBUG"

+# ADD RSC /l 0x809 /d "_DEBUG"

+BSC32=bscmake.exe

+# ADD BASE BSC32 /nologo

+# ADD BSC32 /nologo

+LIB32=link.exe -lib

+# ADD BASE LIB32 /nologo

+# ADD LIB32 /nologo

+

+!ENDIF 

+

+# Begin Target

+

+# Name "rfb_win32 - Win32 Release"

+# Name "rfb_win32 - Win32 Debug"

+# Name "rfb_win32 - Win32 Debug Unicode"

+# Begin Group "Source Files"

+

+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"

+# Begin Source File

+

+SOURCE=.\AboutDialog.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\CKeyboard.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\CleanDesktop.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Clipboard.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\CPointer.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\CurrentUser.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\DeviceContext.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\DeviceFrameBuffer.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Dialog.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\DIBSectionBuffer.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\DynamicFn.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\EventManager.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\FolderManager.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\LaunchProcess.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\ListViewControl.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\LowLevelKeyEvents.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\MonitorInfo.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\MsgWindow.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\OSVersion.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\ProgressControl.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\RegConfig.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Registry.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\ScaledDIBSectionBuffer.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplay.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplayCorePolling.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplayCoreWMHooks.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Security.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Service.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SFileTransferManagerWin32.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SFileTransferWin32.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SInput.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\SocketManager.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\TCharArray.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Threading.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\ToolBar.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\TsSessions.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\Win32Util.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMCursor.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMHooks.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMNotifier.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMPoller.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMShatter.cxx

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMWindowCopyRect.cxx

+# End Source File

+# End Group

+# Begin Group "Header Files"

+

+# PROP Default_Filter "h;hpp;hxx;hm;inl"

+# Begin Source File

+

+SOURCE=.\AboutDialog.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\BitmapInfo.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\CKeyboard.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\CleanDesktop.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Clipboard.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\CompatibleBitmap.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\ComputerName.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\CPointer.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\CurrentUser.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\DeviceContext.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\DeviceFrameBuffer.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Dialog.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\DIBSectionBuffer.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\DynamicFn.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\EventManager.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\FolderManager.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Handle.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\IconInfo.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\IntervalTimer.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\keymap.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\LaunchProcess.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\ListViewControl.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\LocalMem.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\LogicalPalette.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\LowLevelKeyEvents.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\ModuleFileName.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\MonitorInfo.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\MsgBox.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\MsgWindow.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\OSVersion.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\ProgressControl.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\RegConfig.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Registry.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\ScaledDIBSectionBuffer.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplay.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplayCoreDriver.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplayCorePolling.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SDisplayCoreWMHooks.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Security.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Service.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SFileTransferManagerWin32.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SFileTransferWin32.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SInput.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\SocketManager.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\TCharArray.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Threading.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\ToolBar.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\TrayIcon.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\TsSessions.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\Win32Util.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMCursor.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMHooks.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMNotifier.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMPoller.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMShatter.h

+# End Source File

+# Begin Source File

+

+SOURCE=.\WMWindowCopyRect.h

+# End Source File

+# End Group

+# End Target

+# End Project