Initial revision


git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/rfb_win32/AboutDialog.cxx b/rfb_win32/AboutDialog.cxx
new file mode 100644
index 0000000..efb15c0
--- /dev/null
+++ b/rfb_win32/AboutDialog.cxx
@@ -0,0 +1,49 @@
+/* Copyright (C) 2002-2003 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("FileVersion")));
+  SetWindowText(GetDlgItem(handle, Copyright), verInfo.getVerString(_T("LegalCopyright")));
+  SetWindowText(GetDlgItem(handle, Description), verInfo.getVerString(_T("FileDescription")));
+}
diff --git a/rfb_win32/AboutDialog.h b/rfb_win32/AboutDialog.h
new file mode 100644
index 0000000..cb1713d
--- /dev/null
+++ b/rfb_win32/AboutDialog.h
@@ -0,0 +1,55 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/CKeyboard.cxx b/rfb_win32/CKeyboard.cxx
new file mode 100644
index 0000000..ad852a0
--- /dev/null
+++ b/rfb_win32/CKeyboard.cxx
@@ -0,0 +1,259 @@
+/* Copyright (C) 2002-2003 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/CMsgWriter.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(CMsgWriter* 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->writeKeyEvent(keysym, false);
+    }
+  }
+  ~ModifierKeyReleaser() {
+    if (keysym) {
+      vlog.debug("fake press extendedVkey 0x%x, keysym 0x%x",
+                 extendedVkey, keysym);
+      writer->writeKeyEvent(keysym, true);
+    }
+  }
+  CMsgWriter* 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(CMsgWriter* 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(CMsgWriter* 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->writeKeyEvent((*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(CMsgWriter* writer, int extendedVkey)
+{
+  if (downKeysym.find(extendedVkey) != downKeysym.end()) {
+    vlog.debug("release extendedVkey 0x%x, keysym 0x%x",
+               extendedVkey, downKeysym[extendedVkey]);
+    writer->writeKeyEvent(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(CMsgWriter* 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->writeKeyEvent(downKeysym[extendedVkey], false);
+    }
+  }
+  vlog.debug("press   extendedVkey 0x%x, keysym 0x%x",
+             extendedVkey, keysym);
+  writer->writeKeyEvent(keysym, true);
+  downKeysym[extendedVkey] = keysym;
+}
diff --git a/rfb_win32/CKeyboard.h b/rfb_win32/CKeyboard.h
new file mode 100644
index 0000000..10346ff
--- /dev/null
+++ b/rfb_win32/CKeyboard.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2003 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 <rdr/types.h>
+#include <map>
+
+namespace rfb {
+
+  class CMsgWriter;
+
+  namespace win32 {
+
+    class CKeyboard {
+    public:
+      void keyEvent(CMsgWriter* writer, rdr::U8 vkey, rdr::U32 flags,
+                    bool down);
+      void releaseAllKeys(CMsgWriter* 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(CMsgWriter* writer, int extendedVkey);
+      void win32::CKeyboard::pressKey(CMsgWriter* writer, int extendedVkey,
+                                      rdr::U32 keysym);
+      std::map<int,rdr::U32> downKeysym;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_CKEYBOARD_H__
diff --git a/rfb_win32/CPointer.cxx b/rfb_win32/CPointer.cxx
new file mode 100644
index 0000000..1cab662
--- /dev/null
+++ b/rfb_win32/CPointer.cxx
@@ -0,0 +1,188 @@
+/* Copyright (C) 2002-2003 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.
+ */
+
+#define WIN32_LEAN_AND_MEAN
+#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(CMsgWriter* writer, int x, int y, int buttonMask) {
+  //
+  // - Duplicate Event Filtering
+  //
+
+  bool maskChanged = buttonMask != currButtonMask;
+  bool posChanged = !Point(x, y).equals(currPos);
+  if (!(posChanged || maskChanged))
+    return;
+
+  // Pass on the event to the event-interval handler
+  threePointerEvent(writer, x, y, buttonMask);
+
+  // Save the position and mask
+  currPos = Point(x, y);
+  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(CMsgWriter* writer, int x, int y, 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 = Point(x, y);
+        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.x, threePos.y, threeMask);
+        else
+          threeEmulating = ((buttonMask & 5) == 5);
+      }
+
+    } else {
+    
+      if (threeTimer.isActive()) {
+        // - We are timing for an emulation event
+
+        if (abs(threePos.x - x) <= 4 || abs(threePos.y - 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.x, threePos.y, 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, x, y, buttonMask);
+}
+
+void CPointer::intervalPointerEvent(CMsgWriter* writer, int x, int y, int buttonMask) {
+  //
+  // - Pointer Event Interval
+  //
+  vlog.write(101, "ptrEvent: %d,%d (%lx)", x, 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->writePointerEvent(intervalPos.x, intervalPos.y, intervalMask);
+    intervalQueued = false;
+  }
+
+  if (!sendNow) {
+    // If we're not sending now then just queue the event
+    intervalQueued = true;
+    intervalPos = Point(x, y);
+    intervalMask = buttonMask;
+  } else {
+    // Start the interval timer if required, and send the event
+    intervalQueued = false;
+    intervalMask = buttonMask;
+    if (pointerEventInterval)
+      intervalTimer.start(pointerEventInterval);
+    writer->writePointerEvent(x, y, buttonMask);
+  }
+}
+
+void CPointer::handleTimer(CMsgWriter* writer, int timerId) {
+  if (timerId == intervalTimer.getId()) {
+    // Pointer interval has expired - send any queued events
+    if (intervalQueued) {
+      writer->writePointerEvent(intervalPos.x, intervalPos.y, 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.x, threePos.y, threeMask);
+  }
+}
diff --git a/rfb_win32/CPointer.h b/rfb_win32/CPointer.h
new file mode 100644
index 0000000..f111de7
--- /dev/null
+++ b/rfb_win32/CPointer.h
@@ -0,0 +1,76 @@
+/* Copyright (C) 2002-2003 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/CMsgWriter.h>
+#include <rfb/Rect.h>
+#include <rfb_win32/IntervalTimer.h>
+
+namespace rfb {
+
+  class CMsgWriter;
+
+  namespace win32 {
+
+    class CPointer {
+    public:
+      CPointer();
+      ~CPointer();
+
+      void pointerEvent(CMsgWriter* writer, int x, int y, int buttonMask);
+      void handleTimer(CMsgWriter* 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(CMsgWriter* writer, int x, int y, int buttonMask);
+      IntervalTimer intervalTimer;
+      bool intervalQueued;
+      Point intervalPos;
+      int intervalMask;
+
+      void threePointerEvent(CMsgWriter* writer, int x, int y, int buttonMask);
+      IntervalTimer threeTimer;
+      Point threePos;
+      int threeMask;
+      bool threeEmulating;
+    };
+
+  }; // win32
+
+}; // rfb
+
+#endif // __RFB_WIN32_CPOINTER_H__
diff --git a/rfb_win32/CleanDesktop.cxx b/rfb_win32/CleanDesktop.cxx
new file mode 100644
index 0000000..9fb8347
--- /dev/null
+++ b/rfb_win32/CleanDesktop.cxx
@@ -0,0 +1,255 @@
+/* Copyright (C) 2002-2004 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
+
+#define WIN32_LEAN_AND_MEAN
+#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/LogWriter.h>
+#include <rdr/Exception.h>
+
+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();
+  }
+  bool enable(bool enable_) {
+    // - Get the current Active Desktop options
+    COMPONENTSOPT adOptions;
+    memset(&adOptions, 0, sizeof(adOptions));
+    adOptions.dwSize = sizeof(adOptions);
+
+    HRESULT result = handle->GetDesktopItemOptions(&adOptions, 0);
+    if (result != S_OK) {
+      vlog.error("failed to get Active Desktop options: %d", result);
+      return false;
+    }
+
+    // - If Active Desktop is active, disable it
+    if ((adOptions.fActiveDesktop==0) != (enable_==0)) {
+      if (enable_)
+        vlog.debug("enabling Active Desktop");
+      else
+        vlog.debug("disabling Active Desktop");
+
+      adOptions.fActiveDesktop = enable_;
+      result = handle->SetDesktopItemOptions(&adOptions, 0);
+      if (result != S_OK) {
+        vlog.error("failed to disable ActiveDesktop: %d", result);
+        return false;
+      }
+      handle->ApplyChanges(AD_APPLY_REFRESH);
+      return true;
+    }
+    return false;
+  }
+  IActiveDesktop* handle;
+};
+
+
+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), 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() {
+#if (WINVER >= 0x500)
+  try {
+    ImpersonateCurrentUser icu;
+
+    vlog.debug("disable desktop effects");
+
+    SysParamsInfo(SPI_SETFONTSMOOTHING, FALSE, 0, SPIF_SENDCHANGE);
+    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);
+    }
+    restoreEffects = true;
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+#else
+      vlog.info("disableffects not implemented");
+#endif
+}
+
+void CleanDesktop::enableEffects() {
+  try {
+    if (restoreEffects) {
+#if (WINVER >= 0x500)
+      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);
+      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("enableEffects not implemented");
+#endif
+    }
+
+  } catch (rdr::Exception& e) {
+    vlog.info(e.str());
+  }
+}
diff --git a/rfb_win32/CleanDesktop.h b/rfb_win32/CleanDesktop.h
new file mode 100644
index 0000000..73f4153
--- /dev/null
+++ b/rfb_win32/CleanDesktop.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/Clipboard.cxx b/rfb_win32/Clipboard.cxx
new file mode 100644
index 0000000..96d1e94
--- /dev/null
+++ b/rfb_win32/Clipboard.cxx
@@ -0,0 +1,199 @@
+/* Copyright (C) 2002-2004 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;
+}
+
+
+//
+// -=- ASCII filter (in-place)
+//
+
+void
+removeNonAsciiChars(char* text) {
+  int len = strlen(text);
+  int i=0, j=0;
+  for (; i<len; i++) {
+    if ((text[i] >= 1) && (text[i] <= 127))
+      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);
+                removeNonAsciiChars(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);
+    removeNonAsciiChars(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/rfb_win32/Clipboard.h b/rfb_win32/Clipboard.h
new file mode 100644
index 0000000..57297e1
--- /dev/null
+++ b/rfb_win32/Clipboard.h
@@ -0,0 +1,66 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/CurrentUser.cxx b/rfb_win32/CurrentUser.cxx
new file mode 100644
index 0000000..1a24485
--- /dev/null
+++ b/rfb_win32/CurrentUser.cxx
@@ -0,0 +1,93 @@
+/* Copyright (C) 2002-2004 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 <windows.h>
+#include <lmcons.h>
+#include <rfb_win32/CurrentUser.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/OSVersion.h>
+
+using namespace rfb;
+using namespace win32;
+
+
+CurrentUserToken::CurrentUserToken() : isValid_(false) {
+  // - If the OS doesn't support security, ignore it
+  if (!isServiceProcess()) {
+    // - Running in User-Mode - just get our current token
+    if (!OpenProcessToken(GetCurrentProcess(), GENERIC_ALL, &h)) {
+      DWORD err = GetLastError();
+      if (err != ERROR_CALL_NOT_IMPLEMENTED)
+       throw rdr::SystemException("OpenProcessToken failed", GetLastError());
+    }
+    isValid_ = true;
+  } else {
+    // - Under XP/2003 and above, we can just ask the operating system
+    typedef BOOL (WINAPI *WTSQueryUserToken_proto)(ULONG, PHANDLE);
+    DynamicFn<WTSQueryUserToken_proto> _WTSQueryUserToken(_T("wtsapi32.dll"), "WTSQueryUserToken");
+    if (_WTSQueryUserToken.isValid()) {
+      (*_WTSQueryUserToken)(-1, &h);
+      isValid_ = true;
+    } else {
+      // - Under NT/2K we have to resort to a nasty hack...
+      HWND tray = FindWindow(_T("Shell_TrayWnd"), 0);
+      if (!tray)
+        return;
+      DWORD processId = 0;
+      GetWindowThreadProcessId(tray, &processId);
+      if (!processId)
+        return;
+      Handle process = OpenProcess(MAXIMUM_ALLOWED, FALSE, processId);
+      if (!process.h)
+        return;
+      OpenProcessToken(process, MAXIMUM_ALLOWED, &h);
+      isValid_ = true;
+    }
+  }
+}
+
+
+ImpersonateCurrentUser::ImpersonateCurrentUser() {
+  RegCloseKey(HKEY_CURRENT_USER);
+  if (!isServiceProcess())
+    return;
+  if (!token.isValid())
+    throw rdr::Exception("CurrentUserToken is not valid");
+  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);
+  }
+}
+
+
+UserName::UserName() : TCharArray(UNLEN+1) {
+  DWORD len = UNLEN+1;
+  if (!GetUserName(buf, &len))
+    throw rdr::SystemException("GetUserName failed", GetLastError());
+}
diff --git a/rfb_win32/CurrentUser.h b/rfb_win32/CurrentUser.h
new file mode 100644
index 0000000..469946b
--- /dev/null
+++ b/rfb_win32/CurrentUser.h
@@ -0,0 +1,78 @@
+/* Copyright (C) 2002-2004 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/Service.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb_win32/Win32Util.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    // CurrentUserToken
+    //   h == 0
+    //     if platform is not NT *or* unable to get token
+    //     *or* if process is hosting service.
+    //   h != 0
+    //     if process is hosting service *and*
+    //     if platform is NT *and* able to get token.
+    //   isValid() == true
+    //     if platform is not NT *or* token is valid.
+
+    struct CurrentUserToken : public Handle {
+      CurrentUserToken();
+      bool isValid() const {return isValid_;};
+    private:
+      bool isValid_;
+    };
+
+    // 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.
+
+    struct UserName : public TCharArray {
+      UserName();
+    };
+
+  }
+
+}
+
+#endif
diff --git a/rfb_win32/DIBSectionBuffer.cxx b/rfb_win32/DIBSectionBuffer.cxx
new file mode 100644
index 0000000..2174376
--- /dev/null
+++ b/rfb_win32/DIBSectionBuffer.cxx
@@ -0,0 +1,221 @@
+/* Copyright (C) 2002-2004 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/LogWriter.h>
+
+#include <rfb_win32/DIBSectionBuffer.h>
+#include <rfb_win32/Win32Util.h>
+
+
+using namespace rfb;
+using namespace win32;
+
+static LogWriter vlog("DIBSection");
+
+
+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.debug("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;
+
+    vlog.debug("area=%d, bpp=%d", width_ * height_, format.bpp);
+
+    // 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;
+      }
+    } else {
+      // Set the DIBSection's palette
+      refreshPalette();
+    }
+  }
+}
+
+void DIBSectionBuffer::refreshPalette() {
+  if (format.bpp > 8) {
+    vlog.error("refresh palette called for truecolour DIB");
+    return;
+  }
+  vlog.debug("refreshing palette");
+  if (device)
+    copyPaletteToDIB(palette, device, bitmap);
+  else
+    copyPaletteToDIB(palette, WindowDC(window), bitmap);
+}
+
+
diff --git a/rfb_win32/DIBSectionBuffer.h b/rfb_win32/DIBSectionBuffer.h
new file mode 100644
index 0000000..51e2da3
--- /dev/null
+++ b/rfb_win32/DIBSectionBuffer.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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/rfb_win32/DeviceFrameBuffer.cxx b/rfb_win32/DeviceFrameBuffer.cxx
new file mode 100644
index 0000000..a4d1021
--- /dev/null
+++ b/rfb_win32/DeviceFrameBuffer.cxx
@@ -0,0 +1,298 @@
+/* Copyright (C) 2002-2004 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.
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <assert.h>
+
+#include <vector>
+
+#include <rfb/LogWriter.h>
+#include <rfb/Exception.h>
+#include <rdr/types.h>
+#include <rfb/VNCServer.h>
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/OSVersion.h>
+
+namespace rfb {
+
+namespace win32 {
+
+static LogWriter vlog("FrameBuffer");
+
+
+// -=- 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
+  RECT cr;
+  if (!GetClipBox(device, &cr))
+    throw rdr::SystemException("GetClipBox", GetLastError());
+  deviceCoords = Rect(cr.left, cr.top, cr.right, cr.bottom);
+  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 ? 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 colour 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, 0, 0, 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());
+
+    assert(maskInfo.bmPlanes == 1 && maskInfo.bmBitsPixel == 1);
+
+    // - 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.x, cursor.hotspot.y,
+                      cursorBm.data, cursor.mask.buf);
+  } catch (rdr::Exception& e) {
+    vlog.error(e.str());
+  }
+}
+
+
+void
+DeviceFrameBuffer::updateColourMap() {
+  if (!format.trueColour)
+    copyDevicePaletteToDIB(device, this);
+}
+
+}; // namespace win32
+
+}; // namespace rfb
diff --git a/rfb_win32/DeviceFrameBuffer.h b/rfb_win32/DeviceFrameBuffer.h
new file mode 100644
index 0000000..5e97b22
--- /dev/null
+++ b/rfb_win32/DeviceFrameBuffer.h
@@ -0,0 +1,121 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <rfb_win32/DIBSectionBuffer.h>
+#include <rfb/Cursor.h>
+#include <rfb/Region.h>
+#include <rfb/Exception.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;}
+
+    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;
+    };
+
+    // -=- createDisplayDeviceFrameBuffer
+    // createDisplayDeviceFrameBuffer must be passed the name of a display device,
+    // and will return a new FrameBuffer object attached to that display
+    // device.
+    // If the device name is not specified then the default display is
+    // returned.
+
+    DeviceFrameBuffer *createDisplayDeviceFrameBuffer(const char *device=0);
+
+    // -=- createDisplayFrameBuffers
+    // Creates a set of framebuffers, one for each available display
+    // device.
+
+    typedef std::vector<DeviceFrameBuffer *> DeviceFrameBuffers;
+    void createDisplayDeviceFrameBuffers(DeviceFrameBuffers *fbs);
+
+  };
+
+};
+
+#endif // __RFB_WIN32_DEVICE_FRAME_BUFFER_H__
diff --git a/rfb_win32/Dialog.cxx b/rfb_win32/Dialog.cxx
new file mode 100644
index 0000000..157cf5f
--- /dev/null
+++ b/rfb_win32/Dialog.cxx
@@ -0,0 +1,353 @@
+/* Copyright (C) 2002-2003 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
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <extra/LoadBMP.cxx>
+#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() {
+}
+
+
+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_NOCONTEXTHELP)*/;
+    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);
+
+#if (WINVER >= 0x0500)
+#ifdef _DIALOG_CAPTURE
+    // *** NOT TESTED
+    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());
+        char filename[256];
+        sprintf(filename, "%s\\capture%d.bmp", tmpdir, i);
+        saveBMP(filename, &fb);
+        i++;
+      }
+      ReleaseDC(handle, dc);
+    } else {
+#endif
+#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;
+      }
+#if (WINVER >= 0x0500)
+#ifdef _DIALOG_CAPTURE
+    }
+#endif
+#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/rfb_win32/Dialog.h b/rfb_win32/Dialog.h
new file mode 100644
index 0000000..d46133a
--- /dev/null
+++ b/rfb_win32/Dialog.h
@@ -0,0 +1,159 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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/rfb_win32/IntervalTimer.h b/rfb_win32/IntervalTimer.h
new file mode 100644
index 0000000..645d0ee
--- /dev/null
+++ b/rfb_win32/IntervalTimer.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/LaunchProcess.cxx b/rfb_win32/LaunchProcess.cxx
new file mode 100644
index 0000000..0a48d60
--- /dev/null
+++ b/rfb_win32/LaunchProcess.cxx
@@ -0,0 +1,92 @@
+/* Copyright (C) 2002-2004 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/Win32Util.h>
+
+#include <rfb/util.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) {
+  if (procInfo.hProcess && (WaitForSingleObject(procInfo.hProcess, 0) != WAIT_OBJECT_0))
+    return;
+  await();
+
+  // - 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 VNC server
+  // 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);
+#ifdef _DEBUG
+  DWORD flags = CREATE_NEW_CONSOLE;
+#else
+  DWORD flags = CREATE_NO_WINDOW;
+#endif
+  BOOL success;
+  if (userToken)
+    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::await() {
+  if (!procInfo.hProcess)
+    return;
+  WaitForSingleObject(procInfo.hProcess, INFINITE);
+  CloseHandle(procInfo.hProcess);
+  CloseHandle(procInfo.hThread);
+  memset(&procInfo, 0, sizeof(procInfo));
+}
+
diff --git a/rfb_win32/LaunchProcess.h b/rfb_win32/LaunchProcess.h
new file mode 100644
index 0000000..6fd34e9
--- /dev/null
+++ b/rfb_win32/LaunchProcess.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class LaunchProcess {
+    public:
+      LaunchProcess(const TCHAR* exeName_, const TCHAR* params);
+      ~LaunchProcess();
+
+      // If userToken is 0 then starts as current user, otherwise
+      // starts as the specified user.  userToken must be a primary token.
+      void start(HANDLE userToken);
+
+      // Wait for the process to quit, and close the handles to it.
+      void await();
+
+      PROCESS_INFORMATION procInfo;
+    protected:
+      TCharArray exeName;
+      TCharArray params;
+    };
+
+
+  };
+
+};
+
+#endif
diff --git a/rfb_win32/MsgWindow.cxx b/rfb_win32/MsgWindow.cxx
new file mode 100644
index 0000000..519d6ab
--- /dev/null
+++ b/rfb_win32/MsgWindow.cxx
@@ -0,0 +1,116 @@
+/* Copyright (C) 2002-2004 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);
+}
\ No newline at end of file
diff --git a/rfb_win32/MsgWindow.h b/rfb_win32/MsgWindow.h
new file mode 100644
index 0000000..94baca3
--- /dev/null
+++ b/rfb_win32/MsgWindow.h
@@ -0,0 +1,55 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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/rfb_win32/OSVersion.cxx b/rfb_win32/OSVersion.cxx
new file mode 100644
index 0000000..1976098
--- /dev/null
+++ b/rfb_win32/OSVersion.cxx
@@ -0,0 +1,47 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/OSVersion.h b/rfb_win32/OSVersion.h
new file mode 100644
index 0000000..1d52943
--- /dev/null
+++ b/rfb_win32/OSVersion.h
@@ -0,0 +1,54 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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/rfb_win32/RegConfig.cxx b/rfb_win32/RegConfig.cxx
new file mode 100644
index 0000000..fcb309b
--- /dev/null
+++ b/rfb_win32/RegConfig.cxx
@@ -0,0 +1,151 @@
+/* Copyright (C) 2002-2003 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");
+
+
+class rfb::win32::RegReaderThread : public Thread {
+public:
+  RegReaderThread(RegistryReader& reader, const HKEY key);
+  ~RegReaderThread();
+  virtual void run();
+  virtual Thread* join();
+protected:
+  RegistryReader& reader;
+  RegKey key;
+  HANDLE event;
+};
+
+RegReaderThread::RegReaderThread(RegistryReader& reader_, const HKEY key_) : Thread("RegConfig"), reader(reader_), key(key_) {
+}
+
+RegReaderThread::~RegReaderThread() {
+}
+
+void
+RegReaderThread::run() {
+  vlog.debug("RegReaderThread started");
+  while (key) {
+    // - Wait for changes
+    vlog.debug("waiting for changes");
+    key.awaitChange(true, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET);
+
+    // - Load settings
+    RegistryReader::loadRegistryConfig(key);
+
+    // - Notify specified thread of changes
+    if (reader.notifyThread)
+      PostThreadMessage(reader.notifyThread->getThreadId(),
+        reader.notifyMsg.message, 
+        reader.notifyMsg.wParam, 
+        reader.notifyMsg.lParam);
+    else if (reader.notifyWindow)
+      PostMessage(reader.notifyWindow,
+        reader.notifyMsg.message, 
+        reader.notifyMsg.wParam, 
+        reader.notifyMsg.lParam);
+  }
+}
+
+Thread*
+RegReaderThread::join() {
+  RegKey old_key = key;
+  key.close();
+  if ((HKEY)old_key) {
+    // *** Closing the key doesn't always seem to work
+    //     Writing to it always will, instead...
+    vlog.debug("closing key");
+    old_key.setString(_T("dummy"), _T(""));
+  }
+  return Thread::join();
+}
+
+
+RegistryReader::RegistryReader() : thread(0), notifyThread(0) {
+  memset(&notifyMsg, 0, sizeof(notifyMsg));
+}
+
+RegistryReader::~RegistryReader() {
+  if (thread) delete thread->join();
+}
+
+bool RegistryReader::setKey(const HKEY rootkey, const TCHAR* keyname) {
+  if (thread) delete thread->join();
+  thread = 0;
+  
+  RegKey key;
+  try {
+    key.createKey(rootkey, keyname);
+    loadRegistryConfig(key);
+  } catch (rdr::Exception& e) {
+    vlog.debug(e.str());
+    return false;
+  }
+  thread = new RegReaderThread(*this, key);
+  if (thread) thread->start();
+  return true;
+}
+
+void
+RegistryReader::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());
+  }
+}
+
+bool RegistryReader::setNotifyThread(Thread* thread, UINT winMsg, WPARAM wParam, LPARAM lParam) {
+  notifyMsg.message = winMsg;
+  notifyMsg.wParam = wParam;
+  notifyMsg.lParam = lParam;
+  notifyThread = thread;
+  notifyWindow = 0;
+  return true;
+}
+
+bool RegistryReader::setNotifyWindow(HWND window, UINT winMsg, WPARAM wParam, LPARAM lParam) {
+  notifyMsg.message = winMsg;
+  notifyMsg.wParam = wParam;
+  notifyMsg.lParam = lParam;
+  notifyWindow = window;
+  notifyThread = 0;
+  return true;
+}
+
diff --git a/rfb_win32/RegConfig.h b/rfb_win32/RegConfig.h
new file mode 100644
index 0000000..3fced85
--- /dev/null
+++ b/rfb_win32/RegConfig.h
@@ -0,0 +1,56 @@
+/* Copyright (C) 2002-2003 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>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class RegistryReader {
+    public:
+      RegistryReader();
+      ~RegistryReader();
+      bool setKey(const HKEY rootkey, const TCHAR* keyname);
+      bool setNotifyThread(Thread* thread, UINT winMsg, WPARAM wParam=0, LPARAM lParam=0);
+      bool setNotifyWindow(HWND window, UINT winMsg, WPARAM wParam=0, LPARAM lParam=0);
+      static void loadRegistryConfig(RegKey& key);
+    protected:
+      friend class RegReaderThread;
+      Thread* thread;
+      Thread* notifyThread;
+      HWND notifyWindow;
+      MSG notifyMsg;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_REG_CONFIG_H__
diff --git a/rfb_win32/Registry.cxx b/rfb_win32/Registry.cxx
new file mode 100644
index 0000000..de9238f
--- /dev/null
+++ b/rfb_win32/Registry.cxx
@@ -0,0 +1,272 @@
+/* Copyright (C) 2002-2004 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/LogWriter.h>
+#include <rfb_win32/Registry.h>
+#include <rdr/MemOutStream.h>
+#include <rdr/HexOutstream.h>
+#include <rdr/HexInStream.h>
+#include <rfb_win32/Security.h>
+
+#include <stdlib.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) {
+  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);
+  }
+  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);
+  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) {
+    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) const {
+  LONG result = RegNotifyChangeKeyValue(key, watchSubTree, filter, 0, FALSE);
+  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;
+}
+
+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) {
+      // We must terminate the string, just to be sure.  Stupid Win32...
+      int len = length/sizeof(TCHAR);
+      TCharArray str(len+1);
+      memcpy(str.buf, data.buf, length);
+      str.buf[len] = 0;
+      return str.takeBuf();
+    } else {
+      return tstrDup(_T(""));
+    }
+  case REG_DWORD:
+    {
+      TCharArray tmp(16);
+      _stprintf(tmp.buf, _T("%u"), *((DWORD*)data.buf));
+      return tmp.takeBuf();
+    }
+  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;
+}
diff --git a/rfb_win32/Registry.h b/rfb_win32/Registry.h
new file mode 100644
index 0000000..1998c49
--- /dev/null
+++ b/rfb_win32/Registry.h
@@ -0,0 +1,111 @@
+/* Copyright (C) 2002-2004 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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);
+    protected:
+      HKEY operator=(const RegKey& k);
+      HKEY operator=(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;
+
+
+      void awaitChange(bool watchSubTree, DWORD filter) 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 number "i"
+      // If there are fewer than "i" values then return 0
+      // NAME IS OWNED BY RegKey OBJECT!
+      const TCHAR* getValueName(int i);
+
+      operator HKEY() const;
+    protected:
+      HKEY key;
+      bool freeKey;
+      TCharArray valueName;
+      DWORD valueNameBufLen;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_REG_CONFIG_H__
diff --git a/rfb_win32/SDisplay.cxx b/rfb_win32/SDisplay.cxx
new file mode 100644
index 0000000..6fa3ff0
--- /dev/null
+++ b/rfb_win32/SDisplay.cxx
@@ -0,0 +1,612 @@
+/* Copyright (C) 2002-2004 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 <assert.h>
+
+#include <rfb_win32/SDisplay.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/WMShatter.h>
+#include <rfb_win32/osVersion.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/IntervalTimer.h>
+#include <rfb_win32/CleanDesktop.h>
+
+#include <rfb/util.h>
+#include <rfb/LogWriter.h>
+#include <rfb/Exception.h>
+
+#include <rfb/Configuration.h>
+
+using namespace rdr;
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SDisplay");
+
+// - SDisplay-specific configuration options
+
+BoolParameter rfb::win32::SDisplay::use_hooks("UseHooks",
+  "Set hooks in the operating system to capture display updates more efficiently", true);
+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");
+
+BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
+  "Remove the desktop wallpaper when the server in in use.", false);
+BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern",
+  "Remove the desktop background pattern when the server in in use.", false);
+BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
+  "Disable desktop user interface effects when the server is in use.", false);
+
+
+// - WM_TIMER ID values
+
+#define TIMER_CURSOR 1
+#define TIMER_UPDATE 2
+#define TIMER_UPDATE_AND_POLL 3
+
+
+// -=- Polling settings
+
+const int POLLING_SEGMENTS = 16;
+
+const int FG_POLLING_FPS = 20;
+const int FG_POLLING_FS_INTERVAL = 1000 / FG_POLLING_FPS;
+const int FG_POLLING_INTERVAL = FG_POLLING_FS_INTERVAL / POLLING_SEGMENTS;
+
+const int BG_POLLING_FS_INTERVAL = 5000;
+const int BG_POLLING_INTERVAL = BG_POLLING_FS_INTERVAL / POLLING_SEGMENTS;
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// SDisplayCore
+//
+
+// The SDisplay Core object is created by SDisplay's start() method
+// and deleted by its stop() method.
+// The Core must be created in the current input desktop in order
+// to operate - SDisplay is responsible for ensuring that.
+// The structures contained in the Core are manipulated directly
+// by the SDisplay, which is also responsible for detecting when
+// a desktop-switch is required.
+
+class rfb::win32::SDisplayCore : public MsgWindow {
+public:
+  SDisplayCore(SDisplay* display);
+  ~SDisplayCore();
+
+  void setPixelBuffer(DeviceFrameBuffer* pb_);
+
+  bool isRestartRequired();
+
+  virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+  // -=- Timers
+  IntervalTimer pollTimer;
+  IntervalTimer cursorTimer;
+
+  // -=- Input handling
+  rfb::win32::SPointer ptr;
+  rfb::win32::SKeyboard kbd;
+  rfb::win32::Clipboard clipboard;
+
+  // -=- Hook handling objects used outside thread run() method
+  WMCopyRect wm_copyrect;
+  WMPoller wm_poller;
+  WMCursor cursor;
+  WMMonitor wm_monitor;
+  WMHooks wm_hooks;
+  WMBlockInput wm_input;
+
+  // -=- Tidying the desktop
+  CleanDesktop cleanDesktop;
+  bool isWallpaperRemoved;
+  bool isPatternRemoved;
+  bool areEffectsDisabled;
+
+  // -=- Full screen polling
+  int poll_next_y;
+  int poll_y_increment;
+
+  // Are we using hooks?
+  bool use_hooks;
+  bool using_hooks;
+
+  // State of the display object
+  SDisplay* display;
+};
+
+SDisplayCore::SDisplayCore(SDisplay* display_)
+: MsgWindow(_T("SDisplayCore")), display(display_),
+  using_hooks(0), use_hooks(rfb::win32::SDisplay::use_hooks),
+  isWallpaperRemoved(rfb::win32::SDisplay::removeWallpaper),
+  isPatternRemoved(rfb::win32::SDisplay::removePattern),
+  areEffectsDisabled(rfb::win32::SDisplay::disableEffects),
+  pollTimer(getHandle(), TIMER_UPDATE_AND_POLL),
+  cursorTimer(getHandle(), TIMER_CURSOR) {
+  setPixelBuffer(display->pb);
+}
+
+SDisplayCore::~SDisplayCore() {
+}
+
+void SDisplayCore::setPixelBuffer(DeviceFrameBuffer* pb) {
+  poll_y_increment = (display->pb->height()+POLLING_SEGMENTS-1)/POLLING_SEGMENTS;
+  poll_next_y = display->screenRect.tl.y;
+  wm_hooks.setClipRect(display->screenRect);
+  wm_copyrect.setClipRect(display->screenRect);
+  wm_poller.setClipRect(display->screenRect);
+}
+
+
+bool SDisplayCore::isRestartRequired() {
+  // - We must restart the SDesktop if:
+  // 1. We are no longer in the input desktop.
+  // 2. The use_hooks setting has changed.
+
+  // - Check that we are in the input desktop
+  if (rfb::win32::desktopChangeRequired())
+    return true;
+
+  // - Check that the hooks setting hasn't changed
+  // NB: We can't just check using_hooks because that can be false
+  // because they failed, even though use_hooks is true!
+  if (use_hooks != rfb::win32::SDisplay::use_hooks)
+    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 != rfb::win32::SDisplay::removeWallpaper) ||
+      (isPatternRemoved != rfb::win32::SDisplay::removePattern) ||
+      (areEffectsDisabled != rfb::win32::SDisplay::disableEffects))
+    return true;
+
+  return false;
+}
+
+LRESULT SDisplayCore::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  switch (msg) {
+
+  case WM_TIMER:
+
+    if (display->server && display->server->clientsReadyForUpdate()) {
+
+      // - Check that the SDesktop doesn't need restarting
+      if (isRestartRequired()) {
+        display->restart();
+        return 0;
+      }
+    
+      // - Action depends on the timer message type
+      switch (wParam) {
+
+        // POLL THE SCREEN
+      case TIMER_UPDATE_AND_POLL:
+        // Handle window dragging, polling of consoles, etc.
+        while (wm_poller.processEvent()) {}
+
+        // Poll the next strip of the screen (in Screen coordinates)
+        {
+          Rect pollrect = display->screenRect;
+          if (poll_next_y >= pollrect.br.y) {
+            // Yes.  Reset the counter and return
+            poll_next_y = pollrect.tl.y;
+          } else {
+            // No.  Poll the next section
+            pollrect.tl.y = poll_next_y;
+            poll_next_y += poll_y_increment;
+            pollrect.br.y = min(poll_next_y, pollrect.br.y);
+            display->add_changed(pollrect);
+          }
+        }
+        break;
+
+      case TIMER_CURSOR:
+        display->triggerUpdate();
+        break;
+
+      };
+
+    }
+    return 0;
+
+  };
+
+  return MsgWindow::processMessage(msg, wParam, lParam);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// SDisplay
+//
+
+// -=- Constructor/Destructor
+
+SDisplay::SDisplay(const TCHAR* devName)
+  : server(0), change_tracker(true), pb(0),
+    deviceName(tstrDup(devName)), device(0), releaseDevice(false),
+    core(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");
+  server = vs;
+
+  // Switch to the current input desktop
+  // ***
+  if (rfb::win32::desktopChangeRequired()) {
+    if (!rfb::win32::changeDesktop())
+      throw rdr::Exception("unable to switch into input desktop");
+  }
+
+  // Clear the change tracker
+  change_tracker.clear();
+
+  // Create the framebuffer object
+  recreatePixelBuffer();
+
+  // Create the SDisplayCore
+  core = new SDisplayCore(this);
+  assert(core);
+
+  // Start display monitor and clipboard handler
+  core->wm_monitor.setNotifier(this);
+  core->clipboard.setNotifier(this);
+
+  // Apply desktop optimisations
+  if (removePattern)
+    core->cleanDesktop.disablePattern();
+  if (removeWallpaper)
+    core->cleanDesktop.disableWallpaper();
+  if (disableEffects)
+    core->cleanDesktop.disableEffects();
+
+  // Start hooks
+  core->wm_hooks.setClipRect(screenRect);
+  if (core->use_hooks) {
+    // core->wm_hooks.setDiagnosticRange(0, 0x400-1);
+    core->using_hooks = core->wm_hooks.setUpdateTracker(this);
+    if (!core->using_hooks)
+      vlog.debug("hook subsystem failed to initialise");
+  }
+
+  // Set up timers
+  core->pollTimer.start(core->using_hooks ? BG_POLLING_INTERVAL : FG_POLLING_INTERVAL);
+  core->cursorTimer.start(10);
+
+  // Register an interest in faked copyrect events
+  core->wm_copyrect.setUpdateTracker(&change_tracker);
+  core->wm_copyrect.setClipRect(screenRect);
+
+  // Polling of particular windows on the desktop
+  core->wm_poller.setUpdateTracker(&change_tracker);
+  core->wm_poller.setClipRect(screenRect);
+
+  vlog.debug("started");
+
+  if (statusLocation) *statusLocation = true;
+}
+
+void SDisplay::stop()
+{
+  vlog.debug("stopping");
+  if (core) {
+    // If SDisplay was actually active then perform the disconnect action
+    CharArray action = disconnectAction.getData();
+    if (stricmp(action.buf, "Logoff") == 0) {
+      ExitWindowsEx(EWX_LOGOFF, 0);
+    } else if (stricmp(action.buf, "Lock") == 0) {
+      typedef BOOL (WINAPI *_LockWorkStation_proto)();
+      DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
+      if (_LockWorkStation.isValid())
+        (*_LockWorkStation)();
+      else
+        ExitWindowsEx(EWX_LOGOFF, 0);
+    }
+  }
+  delete core;
+  core = 0;
+  delete pb;
+  pb = 0;
+  if (device) {
+    if (releaseDevice)
+      ReleaseDC(0, device);
+    else
+      DeleteDC(device);
+  }
+  device = 0;
+  if (server)
+    server->setPixelBuffer(0);
+
+  server = 0;
+  vlog.debug("stopped");
+
+  if (statusLocation) *statusLocation = false;
+}
+
+void SDisplay::restart() {
+  vlog.debug("restarting");
+  // Close down the hooks
+  delete core;
+  core = 0;
+  try {
+    // Re-start the hooks if possible
+    start(server);
+    vlog.debug("restarted");
+  } catch (rdr::Exception& e) {
+    // If start() fails then we MUST disconnect all clients,
+    // to cause the server to stop using 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, rdr::U8 buttonmask) {
+  if (pb->getRect().contains(pos)) {
+    Point screenPos = pos.translate(screenRect.tl);
+    core->ptr.pointerEvent(screenPos, buttonmask);
+  }
+}
+
+void SDisplay::keyEvent(rdr::U32 key, bool down) {
+  core->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;
+  core->clipboard.setClipText(clip_sz.buf);
+}
+
+
+void SDisplay::framebufferUpdateRequest()
+{
+  triggerUpdate();
+}
+
+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) stop();
+  return result;
+}
+
+
+void
+SDisplay::add_changed(const Region& rgn) {
+  change_tracker.add_changed(rgn);
+  triggerUpdate();
+}
+
+void
+SDisplay::add_copied(const Region& dest, const Point& delta) {
+  change_tracker.add_copied(dest, delta);
+  triggerUpdate();
+}
+
+
+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 colourmap changed");
+    pb->updateColourMap();
+    if (server)
+      server->setColourMapEntries();
+    break;
+  default:
+    vlog.error("unknown display event received");
+  }
+}
+
+bool
+SDisplay::processEvent(HANDLE event) {
+  if (event == updateEvent) {
+    vlog.info("processEvent");
+    ResetEvent(updateEvent);
+
+    // - If the SDisplay isn't even started then quit now
+    if (!core) {
+      vlog.error("not start()ed");
+      return true;
+    }
+
+    // - Ensure that the disableLocalInputs flag is respected
+    core->wm_input.blockInputs(SDisplay::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 (core->isRestartRequired()) {
+        restart();
+        return true;
+      }
+    
+      // *** window dragging can be improved - more frequent, more cunning about updates
+      while (core->wm_copyrect.processEvent()) {}
+        
+      // Ensure the cursor is up to date
+      WMCursor::Info info = core->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.x, desktopPos.y);
+        try_update = true;
+
+        old_cursor = info;
+      }
+
+      // Flush any changes to the server
+      try_update = flushChangeTracker() || try_update;
+      if (try_update)
+        server->tryUpdate();
+    }
+  } else {
+    CloseHandle(event);
+    return false;
+  }
+  return true;
+}
+
+
+// -=- Protected methods
+
+void
+SDisplay::recreatePixelBuffer() {
+  vlog.debug("attaching to device %s", deviceName);
+
+  // Open the specified display device
+  HDC new_device;
+  if (deviceName.buf) {
+    new_device = ::CreateDC(_T("DISPLAY"), deviceName.buf, NULL, NULL);
+    releaseDevice = false;
+  } else {
+    // If no device is specified, open entire screen.
+    // Doing this with CreateDC creates problems on multi-monitor systems.
+    new_device = ::GetDC(0);
+    releaseDevice = true;
+  }
+  if (!new_device)
+    throw SystemException("cannot open the display", GetLastError());
+
+  // Get the coordinates of the entire virtual display
+  Rect newScreenRect;
+  {
+    WindowDC rootDC(0);
+    RECT r;
+    if (!GetClipBox(rootDC, &r))
+      throw rdr::SystemException("GetClipBox", GetLastError());
+    newScreenRect = Rect(r.left, r.top, r.right, r.bottom);
+  }
+
+  // Create a DeviceFrameBuffer attached to it
+  DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(new_device);
+
+  // Has anything actually changed about the screen or the buffer?
+  if (!pb ||
+      (!newScreenRect.equals(screenRect)) ||
+      (!new_buffer->getPF().equal(pb->getPF())))
+  {
+    // Yes.  Update the buffer state.
+    screenRect = newScreenRect;
+    vlog.debug("creating pixel buffer for device");
+
+    // Flush any existing changes to the server
+    flushChangeTracker();
+
+    // Replace the old PixelBuffer
+    if (pb) delete pb;
+    if (device) DeleteDC(device);
+    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 SDisplayCore if required
+    if (core)
+      core->setPixelBuffer(pb);
+
+    // Inform the server of the changes
+    if (server)
+      server->setPixelBuffer(pb);
+
+  } else {
+    delete new_buffer;
+    DeleteDC(new_device);
+  }
+}
+
+bool SDisplay::flushChangeTracker() {
+  if (change_tracker.is_empty())
+    return false;
+  // Translate the update coordinates from Screen coords to Desktop
+  change_tracker.translate(screenRect.tl.negate());
+  // Flush the updates through
+  change_tracker.get_update(*server);
+  change_tracker.clear();
+  return true;
+}
+
+void SDisplay::triggerUpdate() {
+  if (core)
+    SetEvent(updateEvent);
+}
diff --git a/rfb_win32/SDisplay.h b/rfb_win32/SDisplay.h
new file mode 100644
index 0000000..c4c08bf
--- /dev/null
+++ b/rfb_win32/SDisplay.h
@@ -0,0 +1,149 @@
+/* Copyright (C) 2002-2004 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.
+
+// *** THIS INTERFACE NEEDS TIDYING TO SEPARATE COORDINATE SYSTEMS BETTER ***
+
+#ifndef __RFB_SDISPLAY_H__
+#define __RFB_SDISPLAY_H__
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <rfb/SDesktop.h>
+#include <rfb/UpdateTracker.h>
+#include <rfb/Configuration.h>
+#include <rfb/util.h>
+
+#include <winsock2.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/SocketManager.h>
+#include <rfb_win32/DeviceFrameBuffer.h>
+#include <rfb_win32/SInput.h>
+#include <rfb_win32/Clipboard.h>
+#include <rfb_win32/MsgWindow.h>
+#include <rfb_win32/WMCursor.h>
+#include <rfb_win32/WMHooks.h>
+#include <rfb_win32/WMNotifier.h>
+#include <rfb_win32/WMWindowCopyRect.h>
+#include <rfb_win32/WMPoller.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    //
+    // -=- SDisplay
+    //
+
+    class SDisplayCore;
+
+    class SDisplay : public SDesktop,
+      WMMonitor::Notifier,
+      Clipboard::Notifier,
+      UpdateTracker,
+      public SocketManager::EventHandler
+    {
+    public:
+      SDisplay(const TCHAR* device=0);
+      virtual ~SDisplay();
+
+      // -=- SDesktop interface
+
+      virtual void start(VNCServer* vs);
+      virtual void stop();
+      virtual void pointerEvent(const Point& pos, rdr::U8 buttonmask);
+      virtual void keyEvent(rdr::U32 key, bool down);
+      virtual void clientCutText(const char* str, int len);
+      virtual void framebufferUpdateRequest();
+      virtual Point getFbSize();
+
+      // -=- UpdateTracker
+
+      virtual void add_changed(const Region& rgn);
+      virtual void add_copied(const Region& dest, const Point& delta);
+
+      // -=- 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 bool processEvent(HANDLE event);
+
+      // -=- Notification of whether or not SDisplay is started
+
+      void setStatusLocation(bool* status) {statusLocation = status;}
+
+      friend class SDisplayCore;
+
+      static BoolParameter use_hooks;
+      static BoolParameter disableLocalInputs;
+      static StringParameter disconnectAction;
+      static BoolParameter removeWallpaper;
+      static BoolParameter removePattern;
+      static BoolParameter disableEffects;
+
+    protected:
+      void restart();
+      void recreatePixelBuffer();
+      bool flushChangeTracker();  // true if flushed, false if empty
+
+      void triggerUpdate();
+
+      VNCServer* server;
+
+      // -=- Display pixel buffer
+      DeviceFrameBuffer* pb;
+      TCharArray deviceName;
+      HDC device;
+      bool releaseDevice;
+
+      // -=- The coordinates of Window's entire virtual Screen
+      Rect screenRect;
+
+      // -=- All changes are collected in Display coords and merged
+      SimpleUpdateTracker change_tracker;
+
+      // -=- Internal SDisplay implementation
+      SDisplayCore* core;
+
+      // -=- 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/rfb_win32/SInput.cxx b/rfb_win32/SInput.cxx
new file mode 100644
index 0000000..457a861
--- /dev/null
+++ b/rfb_win32/SInput.cxx
@@ -0,0 +1,459 @@
+/* Copyright (C) 2002-2003 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>
+
+// * Force the windows headers to include all the SendInput stuff
+#define _WIN32_WINNT 0x401
+
+#include <rfb_win32/SInput.h>
+#include <rfb_win32/Service.h>
+#include <rfb/LogWriter.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb_win32/Win32Util.h>
+#include "keymap.h"
+
+using namespace rfb;
+
+static LogWriter vlog("SInput");
+
+
+typedef UINT (WINAPI *_SendInput_proto)(UINT, LPINPUT, int);
+static win32::DynamicFn<_SendInput_proto> _SendInput(_T("user32.dll"), "SendInput");
+
+//
+// -=- 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, rdr::U8 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 SM_CXVIRTUALSCREEN
+    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/rfb_win32/SInput.h b/rfb_win32/SInput.h
new file mode 100644
index 0000000..dcd779e
--- /dev/null
+++ b/rfb_win32/SInput.h
@@ -0,0 +1,70 @@
+/* Copyright (C) 2002-2003 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 {
+
+  class CMsgWriter;
+
+  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, rdr::U8 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/rfb_win32/Security.h b/rfb_win32/Security.h
new file mode 100644
index 0000000..d92e314
--- /dev/null
+++ b/rfb_win32/Security.h
@@ -0,0 +1,198 @@
+/* Copyright (C) 2002-2004 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 <rdr/Exception.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/TCharArray.h>
+
+#include <lmcons.h>
+#include <Accctrl.h>
+#include <aclapi.h>
+
+#include <list>
+
+namespace rfb {
+
+  namespace win32 {
+
+    struct Trustee : public TRUSTEE {
+      Trustee(const TCHAR* name,
+              TRUSTEE_FORM form=TRUSTEE_IS_NAME,
+              TRUSTEE_TYPE type=TRUSTEE_IS_UNKNOWN)
+      {
+        pMultipleTrustee = 0;
+        MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
+        TrusteeForm = form;
+        TrusteeType = type;
+        ptstrName = (TCHAR*)name;
+      }
+    };
+
+    struct ExplicitAccess : public EXPLICIT_ACCESS {
+      ExplicitAccess(const TCHAR* name,
+                     TRUSTEE_FORM type,
+                     DWORD perms,
+                     ACCESS_MODE mode,
+                     DWORD inherit=0)
+      {
+        Trustee = rfb::win32::Trustee(name, type);
+        grfAccessPermissions = perms;
+        grfAccessMode = mode;
+        grfInheritance = inherit;
+      }
+    };
+
+    // Helper class for building access control lists
+    struct AccessEntries {
+      AccessEntries() : entries(0), entry_count(0) {}
+      ~AccessEntries() {delete [] entries;}
+      void 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 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 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++;
+      }
+
+      EXPLICIT_ACCESS* entries;
+      int entry_count;
+    };
+
+    // Helper class for handling SIDs
+    struct Sid {
+      Sid() : sid(0) {}
+      Sid(PSID sid_) : sid(sid_) {}
+      ~Sid() {
+        if (sid) FreeSid(sid);
+      }
+      operator PSID() const {return sid;}
+      PSID operator=(const PSID sid_) {
+        if (sid) FreeSid(sid);
+        sid = sid_;
+      }
+
+      static PSID 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());
+        return sid;
+      }
+      static PSID 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());
+        return sid;
+      }
+
+    protected:
+      PSID sid;
+    };
+      
+    // 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 
+    static PACL CreateACL(const AccessEntries& ae, PACL existing_acl=0) {
+      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;
+    }
+
+    // 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.
+    static PSECURITY_DESCRIPTOR CreateSdWithDacl(const PACL dacl) {
+      SECURITY_DESCRIPTOR absSD;
+      if (!InitializeSecurityDescriptor(&absSD, SECURITY_DESCRIPTOR_REVISION))
+        throw rdr::SystemException("InitializeSecurityDescriptor", GetLastError());
+      Sid owner(Sid::SYSTEM());
+      if (!SetSecurityDescriptorOwner(&absSD, owner, FALSE))
+        throw rdr::SystemException("SetSecurityDescriptorOwner", GetLastError());
+      Sid group(Sid::Administrators());
+      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();
+    }
+
+  }
+
+}
+
+#endif
diff --git a/rfb_win32/Service.cxx b/rfb_win32/Service.cxx
new file mode 100644
index 0000000..b00c290
--- /dev/null
+++ b/rfb_win32/Service.cxx
@@ -0,0 +1,638 @@
+/* Copyright (C) 2002-2003 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/Registry.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb/Threading.h>
+#include <rfb/LogWriter.h>
+#include <rfb/util.h>
+#include <rdr/Exception.h>
+
+#include <logmessages/messages.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) {
+  vlog.debug("service control %u", control);
+  switch (control) {
+  case SERVICE_CONTROL_INTERROGATE:
+    service->setStatus();
+    break;
+  case SERVICE_CONTROL_PARAMCHANGE:
+    service->readParams();
+    break;
+  case SERVICE_CONTROL_SHUTDOWN:
+    service->osShuttingDown();
+    break;
+  case SERVICE_CONTROL_STOP:
+    service->setStatus(SERVICE_STOP_PENDING);
+    service->stop();
+    break;
+  }
+}
+
+
+// -=- 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());
+  service->status_handle = RegisterServiceCtrlHandler(service->getName(), serviceHandler);
+  if (!service->status_handle) {
+    vlog.error("unable to register service control handler");
+    return;
+  }
+  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.dwWin32ExitCode = GetLastError();
+    vlog.error("unable to set service status:%u", status.dwWin32ExitCode);
+    stop();
+  }
+  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;
+}
+
+void rfb::win32::printServiceStatus(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());
+
+    printf("Service is in the ");
+    switch (status.dwCurrentState) {
+    case SERVICE_RUNNING: printf("running"); break;
+    case SERVICE_STOPPED: printf("stopped"); break;
+    case SERVICE_STOP_PENDING: printf("stop pending"); break;
+    default: printf("unknown (%lu)", status.dwCurrentState); break;
+    };
+    printf(" state.\n");
+
+  } else {
+    HWND service_window = findServiceWindow(name);
+    printf("Service is in the ");
+    if (!service_window) printf("stopped");
+    else printf("running");
+    printf(" state.\n");
+  }
+}
+
+
+bool rfb::win32::isServiceProcess() {
+  return service != 0;
+}
\ No newline at end of file
diff --git a/rfb_win32/Service.h b/rfb_win32/Service.h
new file mode 100644
index 0000000..164381a
--- /dev/null
+++ b/rfb_win32/Service.h
@@ -0,0 +1,123 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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() = 0;
+
+      // To get notified when the service parameters change
+      virtual void readParams() = 0;
+
+      // To cause the serviceMain() routine to return
+      virtual void stop() = 0;
+
+    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);
+    void printServiceStatus(const TCHAR* name);
+
+    // -=- Routine to determine whether the host process is running a service
+    bool isServiceProcess();
+
+  };
+
+};
+
+#endif // __RFB_WIN32_SERVICE_NT_H__
diff --git a/rfb_win32/SocketManager.cxx b/rfb_win32/SocketManager.cxx
new file mode 100644
index 0000000..6ebd5c0
--- /dev/null
+++ b/rfb_win32/SocketManager.cxx
@@ -0,0 +1,246 @@
+/* Copyright (C) 2002-2004 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
+
+#define WIN32_LEAN_AND_MEAN
+#include <winsock2.h>
+#include <assert.h>
+
+#include <rfb/LogWriter.h>
+#include <rfb_win32/SocketManager.h>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SocketManager");
+
+
+// -=- SocketManager
+
+SocketManager::SocketManager() : sockets(0), events(0), nSockets(0), nAvail(0) {
+}
+
+SocketManager::~SocketManager() {
+  for (int i=0; i<nSockets; i++) {
+    if (!sockets[i].is_event)
+      WSACloseEvent(events[i]);
+  }
+  delete [] events;
+  delete [] sockets;
+}
+
+
+void SocketManager::addListener(network::SocketListener* sock_, network::SocketServer* srvr) {
+  WSAEVENT event = WSACreateEvent();
+  assert(event != WSA_INVALID_EVENT);
+  addListener(sock_, event, srvr);
+}
+
+void SocketManager::addSocket(network::Socket* sock_, network::SocketServer* srvr) {
+  WSAEVENT event = WSACreateEvent();
+  assert(event != WSA_INVALID_EVENT);
+  addSocket(sock_, event, srvr);
+}
+
+
+BOOL SocketManager::getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg) {
+  while (true) {
+    // First check for idle timeout
+
+    network::SocketServer* server = 0;
+    int timeout = 0;
+    for (int i=0; i<nSockets; i++) {
+      if (!sockets[i].is_event &&
+          sockets[i].server != server) {
+        server = sockets[i].server;
+        int t = server->checkTimeouts();
+        if (t > 0 && (timeout == 0 || t < timeout))
+          timeout = t;
+      }
+    }
+    if (timeout == 0)
+      timeout = INFINITE;
+
+    // - Network IO is less common than messages - process it first
+    DWORD result;
+    if (nSockets) {
+      result = WaitForMultipleObjects(nSockets, events, FALSE, 0);
+      if (result == WAIT_TIMEOUT) {
+        if (PeekMessage(msg, hwnd, minMsg, maxMsg, PM_REMOVE)) 
+          return msg->message != WM_QUIT;
+
+        result = MsgWaitForMultipleObjects(nSockets, events, FALSE, timeout,
+                                           QS_ALLINPUT);
+        if (result == WAIT_OBJECT_0 + nSockets) {
+          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 + nSockets))) {
+      int index = result - WAIT_OBJECT_0;
+
+      // - Process a socket event
+
+      if (sockets[index].is_event) {
+        // Process a general Win32 event
+        // NB: The handler must reset the event!
+
+        if (!sockets[index].handler->processEvent(events[index])) {
+          removeSocket(index);
+          continue;
+        }
+      } else if (sockets[index].is_conn) {
+        // Process data from an active connection
+
+        // Cancel event notification for this socket
+        if (WSAEventSelect(sockets[index].fd, events[index], 0) == SOCKET_ERROR)
+          vlog.info("unable to disable WSAEventSelect:%u", WSAGetLastError());
+
+        // Reset the event object
+        WSAResetEvent(events[index]);
+
+        // Call the socket server to process the event
+        if (!sockets[index].server->processSocketEvent(sockets[index].sock.conn)) {
+          removeSocket(index);
+          continue;
+        }
+
+        // Re-instate the required socket event
+        // If the read event is still valid, the event object gets set here
+        if (WSAEventSelect(sockets[index].fd, events[index], FD_READ | FD_CLOSE) == SOCKET_ERROR)
+          throw rdr::SystemException("unable to re-enable WSAEventSelect:%u", WSAGetLastError());
+
+      } else {
+        // Accept an incoming connection
+        vlog.debug("accepting incoming connection");
+
+        // What kind of event is this?
+        WSANETWORKEVENTS network_events;
+        WSAEnumNetworkEvents(sockets[index].fd, events[index], &network_events);
+        if (network_events.lNetworkEvents & FD_ACCEPT) {
+          network::Socket* new_sock = sockets[index].sock.listener->accept();
+          if (new_sock) {
+            sockets[index].server->addClient(new_sock);
+            addSocket(new_sock, sockets[index].server);
+          }
+        } else if (network_events.lNetworkEvents & FD_CLOSE) {
+          vlog.info("deleting listening socket");
+          network::SocketListener* s = sockets[index].sock.listener;
+          removeSocket(index);
+          delete s;
+        } else {
+          vlog.error("unknown network event for listener");
+        }
+
+      }
+    } else if (result == WAIT_FAILED) {
+      throw rdr::SystemException("unable to wait for events", GetLastError());
+    }
+  }
+}
+
+
+void SocketManager::resizeArrays(int numSockets) {
+  if (nAvail >= numSockets) return;
+  while (nAvail < numSockets)
+    nAvail = max(16, nAvail*2);
+
+  SocketInfo* newinfo = new SocketInfo[nAvail];
+  HANDLE* newevents = new HANDLE[nAvail];
+  for (int i=0; i<nSockets; i++) {
+    newinfo[i] = sockets[i];
+    newevents[i] = events[i];
+  }
+  delete [] sockets;
+  delete [] events;
+  sockets = newinfo;
+  events = newevents;
+}
+
+void SocketManager::addSocket(network::Socket* sock, HANDLE event, network::SocketServer* server) {
+  resizeArrays(nSockets+1);
+
+  sockets[nSockets].sock.conn = sock;
+  sockets[nSockets].fd = sock->getFd();
+  sockets[nSockets].server = server;
+  events[nSockets] = event;
+  sockets[nSockets].is_conn = true;
+  sockets[nSockets].is_event = false;
+
+  if (WSAEventSelect(sock->getFd(), event, FD_READ | FD_CLOSE) == SOCKET_ERROR)
+    throw rdr::SystemException("unable to select on socket", WSAGetLastError());
+  nSockets++;
+}
+
+void SocketManager::addListener(network::SocketListener* sock, HANDLE event, network::SocketServer* server) {
+  resizeArrays(nSockets+1);
+
+  sockets[nSockets].sock.listener = sock;
+  sockets[nSockets].fd = sock->getFd();
+  sockets[nSockets].server = server;
+  events[nSockets] = event;
+  sockets[nSockets].is_conn = false;
+  sockets[nSockets].is_event = false;
+
+  if (WSAEventSelect(sock->getFd(), event, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
+    throw rdr::SystemException("unable to select on listener", WSAGetLastError());
+  nSockets++;
+}
+
+void SocketManager::remListener(network::SocketListener* sock) {
+  for (int index=0; index<nSockets; index++) {
+    if (!sockets[index].is_conn &&
+        !sockets[index].is_event) {
+      vlog.debug("removing listening socket");
+      removeSocket(index);
+      delete sock;
+    }
+  }
+}
+
+void SocketManager::addEvent(HANDLE event, EventHandler* ecb) {
+  resizeArrays(nSockets+1);
+
+  sockets[nSockets].handler = ecb;
+  events[nSockets] = event;
+  sockets[nSockets].is_conn = false;
+  sockets[nSockets].is_event = true;
+
+  nSockets++;
+}
+
+void SocketManager::removeSocket(int index) {
+  if (index >= nSockets)
+    throw rdr::Exception("attempting to remove unregistered socket");
+
+  if (!sockets[index].is_event)
+    WSACloseEvent(events[index]);
+
+  for (int i=index; i<nSockets-1; i++) {
+    sockets[i] = sockets[i+1];
+    events[i] = events[i+1];
+  }
+
+  nSockets--;
+}
+
diff --git a/rfb_win32/SocketManager.h b/rfb_win32/SocketManager.h
new file mode 100644
index 0000000..791370f
--- /dev/null
+++ b/rfb_win32/SocketManager.h
@@ -0,0 +1,107 @@
+/* Copyright (C) 2002-2004 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 <list>
+
+#include <network/Socket.h>
+#include <rfb_win32/MsgWindow.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class SocketManager {
+    public:
+      SocketManager();
+      virtual ~SocketManager();
+
+      // Add a listening socket.  Incoming connections will be added to the supplied
+      // SocketServer.
+      void addListener(network::SocketListener* sock_, network::SocketServer* srvr);
+
+      // 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);
+
+      // Add a Win32 event & handler for it to the SocketManager
+      // This event will be blocked on along with the registered Sockets, and the
+      // handler called whenever it is discovered to be set.
+      // NB: SocketManager does NOT call ResetEvent on the event!
+      // NB: If processEvent returns false then the event is no longer registered,
+      //     and the event object is assumed to have been closed by processEvent()
+      struct EventHandler {
+        virtual ~EventHandler() {}
+        virtual bool processEvent(HANDLE event) = 0;
+      };
+      void addEvent(HANDLE event, EventHandler* ecb);
+
+      // getMessage
+      //
+      // Either return a message from the thread's message queue or process a socket
+      // event.
+      // Returns whenever a message needs processing.  Returns false if message is
+      // WM_QUIT, true for all other messages.
+      BOOL getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg);
+
+    protected:
+      void addListener(network::SocketListener* sock, HANDLE event, network::SocketServer* server);
+      void addSocket(network::Socket* sock, HANDLE event, network::SocketServer* server);
+      void resizeArrays(int numSockets);
+      void removeSocket(int index);
+      struct SocketInfo {
+        union {
+          network::Socket* conn;
+          network::SocketListener* listener;
+        } sock;
+        SOCKET fd;
+        bool is_conn;
+        bool is_event;
+        union {
+          network::SocketServer* server;
+          EventHandler* handler;
+        };
+      };
+      SocketInfo* sockets;
+      HANDLE* events;
+      int nSockets;
+      int nAvail;
+   };
+
+  }
+
+}
+
+#endif
diff --git a/rfb_win32/TCharArray.cxx b/rfb_win32/TCharArray.cxx
new file mode 100644
index 0000000..f8f03a6
--- /dev/null
+++ b/rfb_win32/TCharArray.cxx
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/TCharArray.h b/rfb_win32/TCharArray.h
new file mode 100644
index 0000000..399e00a
--- /dev/null
+++ b/rfb_win32/TCharArray.h
@@ -0,0 +1,119 @@
+/* Copyright (C) 2002-2004 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__
+
+#define WIN32_LEAN_AND_MEAN
+#include <tchar.h>
+
+#include <rfb/util.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;
+  };
+
+#ifdef _UNICODE
+#define tstrDup wstrDup
+#define tstrFree wstrFree
+#define tstrSplit wstrSplit
+#define tstrContains wstrContains
+  typedef WCharArray TCharArray;
+  typedef WStr TStr;
+#else
+#define tstrDup strDup
+#define tstrFree strFree
+#define tstrSplit strSplit
+#define tstrContains strContains
+  typedef CharArray TCharArray;
+  typedef CStr TStr;
+#endif
+
+};
+
+#endif
\ No newline at end of file
diff --git a/rfb_win32/TrayIcon.h b/rfb_win32/TrayIcon.h
new file mode 100644
index 0000000..85680f3
--- /dev/null
+++ b/rfb_win32/TrayIcon.h
@@ -0,0 +1,90 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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/rfb_win32/WMCursor.cxx b/rfb_win32/WMCursor.cxx
new file mode 100644
index 0000000..871d937
--- /dev/null
+++ b/rfb_win32/WMCursor.cxx
@@ -0,0 +1,98 @@
+/* Copyright (C) 2002-2003 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
+#define WINVER 0x0500
+
+#include <rfb_win32/WMCursor.h>
+#include <rfb_win32/OSVersion.h>
+#include <rfb_win32/Win32Util.h>
+#include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
+
+using namespace rdr;
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMCursor");
+
+
+typedef BOOL (WINAPI *_GetCursorInfo_proto)(PCURSORINFO pci);
+DynamicFn<_GetCursorInfo_proto> _GetCursorInfo(_T("user32.dll"), "GetCursorInfo");
+
+
+WMCursor::WMCursor() : hooks(0), library(0), use_getCursorInfo(false), cursor(0) {
+#if (WINVER >= 0x0500)
+  // 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();
+#else
+#pragma message ("not building in GetCursorInfo support")
+#endif
+  if (!use_getCursorInfo) {
+    hooks = new WMCursorHooks();
+    if (hooks && hooks->start()) {
+      vlog.info("falling back to cursor hooking");
+    } else {
+      delete hooks;
+      hooks = 0;
+      vlog.error("unable to monitor cursor shape");
+    }
+  } else {
+    vlog.info("using GetCursorInfo");
+  }
+  cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
+}
+
+WMCursor::~WMCursor() {
+  if (hooks) delete hooks;
+  if (library) FreeLibrary(library);
+}
+  
+WMCursor::Info
+WMCursor::getCursorInfo() {
+  Info result;
+#if (WINVER >= 0x0500)
+  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/rfb_win32/WMCursor.h b/rfb_win32/WMCursor.h
new file mode 100644
index 0000000..a96822a
--- /dev/null
+++ b/rfb_win32/WMCursor.h
@@ -0,0 +1,65 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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;
+      HMODULE library;
+      bool use_getCursorInfo;
+      HCURSOR cursor;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WM_CURSOR_H__
diff --git a/rfb_win32/WMHooks.cxx b/rfb_win32/WMHooks.cxx
new file mode 100644
index 0000000..26a2363
--- /dev/null
+++ b/rfb_win32/WMHooks.cxx
@@ -0,0 +1,324 @@
+/* Copyright (C) 2002-2003 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 <wm_hooks/wm_hooks.h>
+
+#include <rfb_win32/WMHooks.h>
+#include <rfb_win32/Service.h>
+#include <rfb/Threading.h>
+#include <rfb/LogWriter.h>
+
+#include <list>
+
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("WMHooks");
+
+class WMHooksThread : public Thread {
+public:
+  WMHooksThread() : Thread("WMHookThread"), active(true) {}
+  virtual void run();
+  virtual Thread* join();
+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);
+
+
+bool
+StartHookThread() {
+  if (hook_mgr) return true;
+  vlog.debug("opening hook thread");
+  hook_mgr = new WMHooksThread();
+  if (!WM_Hooks_Install(hook_mgr->getThreadId(), 0)) {
+    vlog.error("failed to initialise hooks");
+    delete hook_mgr->join();
+    hook_mgr = 0;
+    return false;
+  }
+  hook_mgr->start();
+  return true;
+}
+
+void
+StopHookThread() {
+  if (!hook_mgr) return;
+  if (!hooks.empty() || !cursor_hooks.empty()) return;
+  vlog.debug("closing hook thread");
+  delete hook_mgr->join();
+  hook_mgr = 0;
+}
+
+
+bool
+AddHook(WMHooks* hook) {
+  vlog.debug("adding hook");
+  Lock l(hook_mgr_lock);
+  if (!StartHookThread()) return false;
+  hooks.push_back(hook);
+  return true;
+}
+
+bool
+AddCursorHook(WMCursorHooks* hook) {
+  vlog.debug("adding cursor hook");
+  Lock l(hook_mgr_lock);
+  if (cursor_hooks.empty()) WM_Hooks_EnableCursorShape(TRUE);
+  if (!StartHookThread()) return false;
+  cursor_hooks.push_back(hook);
+  return true;
+}
+
+bool
+RemHook(WMHooks* hook) {
+  {
+    vlog.debug("removing hook");
+    Lock l(hook_mgr_lock);
+    hooks.remove(hook);
+  }
+  StopHookThread();
+  return true;
+}
+
+bool
+RemCursorHook(WMCursorHooks* hook) {
+  {
+    vlog.debug("removing cursor hook");
+    Lock l(hook_mgr_lock);
+    cursor_hooks.remove(hook);
+  }
+  StopHookThread();
+  if (cursor_hooks.empty()) WM_Hooks_EnableCursorShape(FALSE);
+  return true;
+}
+
+void
+NotifyHooksRegion(const Region& r) {
+  Lock l(hook_mgr_lock);
+  std::list<WMHooks*>::iterator i;
+  for (i=hooks.begin(); i!=hooks.end(); i++) {
+    (*i)->new_changes.add_changed(r);
+    if (!(*i)->notified) {
+      (*i)->notified = true;
+      PostMessage((*i)->getHandle(), WM_USER, 0, 0);
+    }
+  }
+}
+
+void
+NotifyHooksCursor(HCURSOR c) {
+  Lock l(hook_mgr_lock);
+  hook_cursor = c;
+}
+
+void
+WMHooksThread::run() {
+  UINT windowMsg = WM_Hooks_WindowChanged();
+  UINT clientAreaMsg = WM_Hooks_WindowClientAreaChanged();
+  UINT borderMsg = WM_Hooks_WindowBorderChanged();
+  UINT rectangleMsg = WM_Hooks_RectangleChanged();
+  UINT cursorMsg = WM_Hooks_CursorChanged();
+#ifdef _DEBUG
+  UINT diagnosticMsg = WM_Hooks_Diagnostic();
+#endif
+  MSG msg;
+  RECT wrect;
+  HWND hwnd;
+  int count = 0;
+
+  vlog.debug("starting hook thread");
+
+  while (active && GetMessage(&msg, NULL, 0, 0)) {
+    count++;
+    if (msg.message == windowMsg) {
+      hwnd = (HWND) msg.lParam;
+      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
+          GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
+      {
+        NotifyHooksRegion(Rect(wrect.left, wrect.top,
+                               wrect.right, wrect.bottom));
+
+      }
+    } else if (msg.message == clientAreaMsg) {
+      hwnd = (HWND) msg.lParam;
+      if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
+          GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
+      {
+        POINT pt = {0,0};
+        if (ClientToScreen(hwnd, &pt)) {
+          NotifyHooksRegion(Rect(wrect.left+pt.x, wrect.top+pt.y,
+                                 wrect.right+pt.x, wrect.bottom+pt.y));
+        }
+      }
+    } 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));
+        }
+        NotifyHooksRegion(changed);
+      }
+    } else if (msg.message == rectangleMsg) {
+      Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
+                    LOWORD(msg.lParam), HIWORD(msg.lParam));
+      if (!r.is_empty()) {
+        NotifyHooksRegion(r);
+      }
+    } 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()
+  : clipper(0), new_changes(true), fg_window(0),
+  notified(false), MsgWindow(_T("WMHooks")) {
+}
+
+rfb::win32::WMHooks::~WMHooks() {
+  RemHook(this);
+  if (clipper) delete clipper;
+}
+
+LRESULT
+rfb::win32::WMHooks::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
+  switch (msg) {
+  case WM_USER:
+    {
+      // *** Yield, to allow the triggering update event to be processed
+      //     BEFORE we try to grab the resulting changes.
+      // *** IMPROVES THINGS NOTICABLY ON WinXP
+      Sleep(0);
+      // ***
+
+      Lock l(hook_mgr_lock);
+      notified = false;
+      new_changes.get_update(*clipper);
+      new_changes.clear();
+    }
+    break;
+  }
+  return MsgWindow::processMessage(msg, wParam, lParam);
+}
+
+bool
+rfb::win32::WMHooks::setClipRect(const Rect& r) {
+  clip_region = r;
+  if (clipper) clipper->set_clip_region(clip_region);
+  return true;
+}
+
+bool
+rfb::win32::WMHooks::setUpdateTracker(UpdateTracker* ut) {
+  if (clipper) delete clipper;
+  clipper = new ClippedUpdateTracker(*ut);
+  clipper->set_clip_region(clip_region);
+  return AddHook(this);
+}
+
+#ifdef _DEBUG
+void
+rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
+  WM_Hooks_SetDiagnosticRange(min, max);
+}
+#endif
+
+
+// -=- WMBlockInput class
+
+Mutex blockMutex;
+int blockCount = 0;
+
+rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
+}
+
+rfb::win32::WMBlockInput::~WMBlockInput() {
+  blockInputs(false);
+}
+
+bool rfb::win32::WMBlockInput::blockInputs(bool on) {
+  if (on == active) return true;
+  vlog.debug("blockInput changed");
+  Lock l(blockMutex);
+  int newCount = blockCount;
+  if (on)
+    newCount++;
+  else
+    newCount--;
+  if (WM_Hooks_EnableRealInputs(newCount==0, newCount==0)) {
+    vlog.debug("set blocking to %d", newCount);
+    blockCount = newCount;
+    active = on;
+    return true;
+  }
+  return false;
+}
+
+
+// -=- 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/rfb_win32/WMHooks.h b/rfb_win32/WMHooks.h
new file mode 100644
index 0000000..791df76
--- /dev/null
+++ b/rfb_win32/WMHooks.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <rfb/UpdateTracker.h>
+#include <rdr/Exception.h>
+#include <rfb_win32/MsgWindow.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class WMHooks : public MsgWindow {
+    public:
+      WMHooks();
+      ~WMHooks();
+
+      bool setClipRect(const Rect& cr);
+      bool setUpdateTracker(UpdateTracker* ut);
+
+      virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
+
+#ifdef _DEBUG
+      // Get notifications of any messages in the given range, to any hooked window
+      void setDiagnosticRange(UINT min, UINT max);
+#endif
+
+    protected:
+      ClippedUpdateTracker* clipper;
+      Region clip_region;
+
+      void* fg_window;
+      Rect fg_window_rect;
+
+    public:
+      SimpleUpdateTracker new_changes;
+      bool notified;
+    };
+
+    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/rfb_win32/WMNotifier.cxx b/rfb_win32/WMNotifier.cxx
new file mode 100644
index 0000000..9773abf
--- /dev/null
+++ b/rfb_win32/WMNotifier.cxx
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002-2003 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")) {
+}
+
+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/rfb_win32/WMNotifier.h b/rfb_win32/WMNotifier.h
new file mode 100644
index 0000000..564d176
--- /dev/null
+++ b/rfb_win32/WMNotifier.h
@@ -0,0 +1,68 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/WMPoller.cxx b/rfb_win32/WMPoller.cxx
new file mode 100644
index 0000000..f568b21
--- /dev/null
+++ b/rfb_win32/WMPoller.cxx
@@ -0,0 +1,101 @@
+/* Copyright (C) 2002-2003 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
+
+rfb::win32::WMPoller::WMPoller() : clipper(0) {
+}
+
+rfb::win32::WMPoller::~WMPoller() {
+  if (clipper) delete clipper;
+}
+
+bool
+rfb::win32::WMPoller::processEvent() {
+  PollInfo info;
+  if (clipper && poll_console_windows) {
+    ::EnumWindows(WMPoller::enumWindowProc, (LPARAM) &info);
+    clipper->add_changed(info.poll_include);
+  }
+  return false;
+}
+
+bool
+rfb::win32::WMPoller::setClipRect(const Rect& r) {
+  clip_region = r;
+  if (clipper) clipper->set_clip_region(clip_region);
+  return true;
+}
+
+bool
+rfb::win32::WMPoller::setUpdateTracker(UpdateTracker* ut) {
+  if (clipper) delete clipper;
+  clipper = new ClippedUpdateTracker(*ut);
+  clipper->set_clip_region(clip_region);
+  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/rfb_win32/WMPoller.h b/rfb_win32/WMPoller.h
new file mode 100644
index 0000000..3f3f402
--- /dev/null
+++ b/rfb_win32/WMPoller.h
@@ -0,0 +1,67 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <rfb/UpdateTracker.h>
+#include <rfb/Configuration.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class WMPoller {
+    public:
+      WMPoller();
+      ~WMPoller();
+
+      bool processEvent();
+      bool setClipRect(const Rect& cr);
+      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);
+
+      ClippedUpdateTracker* clipper;
+      Region clip_region;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WM_POLLER_H__
diff --git a/rfb_win32/WMShatter.cxx b/rfb_win32/WMShatter.cxx
new file mode 100644
index 0000000..f6a7484
--- /dev/null
+++ b/rfb_win32/WMShatter.cxx
@@ -0,0 +1,57 @@
+/* Copyright (C) 2002-2003 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/rfb_win32/WMShatter.h b/rfb_win32/WMShatter.h
new file mode 100644
index 0000000..7b81678
--- /dev/null
+++ b/rfb_win32/WMShatter.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2003 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__
+
+#define WIN32_LEAN_AND_MEAN
+#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/rfb_win32/WMWindowCopyRect.cxx b/rfb_win32/WMWindowCopyRect.cxx
new file mode 100644
index 0000000..46d85ea
--- /dev/null
+++ b/rfb_win32/WMWindowCopyRect.cxx
@@ -0,0 +1,83 @@
+/* Copyright (C) 2002-2003 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() : clipper(0), fg_window(0) {
+}
+
+rfb::win32::WMCopyRect::~WMCopyRect() {
+  if (clipper) delete clipper;
+}
+
+bool
+rfb::win32::WMCopyRect::processEvent() {
+  if (clipper) {
+    // 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)) {
+            // 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;
+            clipper->add_copied(copy_dest, delta);
+            clipper->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::setClipRect(const Rect& r) {
+  clip_region = r;
+  if (clipper) clipper->set_clip_region(clip_region);
+  return true;
+}
+
+bool
+rfb::win32::WMCopyRect::setUpdateTracker(UpdateTracker* ut) {
+  if (clipper) delete clipper;
+  clipper = new ClippedUpdateTracker(*ut);
+  clipper->set_clip_region(clip_region);
+  return true;
+}
diff --git a/rfb_win32/WMWindowCopyRect.h b/rfb_win32/WMWindowCopyRect.h
new file mode 100644
index 0000000..0750d86
--- /dev/null
+++ b/rfb_win32/WMWindowCopyRect.h
@@ -0,0 +1,56 @@
+/* Copyright (C) 2002-2003 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();
+      ~WMCopyRect();
+
+      bool processEvent();
+      bool setClipRect(const Rect& cr);
+      bool setUpdateTracker(UpdateTracker* ut);
+
+    protected:
+      ClippedUpdateTracker* clipper;
+      Region clip_region;
+      void* fg_window;
+      Rect fg_window_rect;
+    };
+
+  };
+
+};
+
+#endif // __RFB_WIN32_WM_WINDOW_COPYRECT_H__
diff --git a/rfb_win32/Win32Util.cxx b/rfb_win32/Win32Util.cxx
new file mode 100644
index 0000000..e25f43a
--- /dev/null
+++ b/rfb_win32/Win32Util.cxx
@@ -0,0 +1,447 @@
+/* Copyright (C) 2002-2004 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/Win32Util.h>
+#include <rdr/Exception.h>
+#include <rdr/HexOutStream.h>
+
+
+namespace rfb {
+namespace win32 {
+
+LogicalPalette::LogicalPalette() : palette(0), numEntries(0) {
+  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::~LogicalPalette() {
+  if (palette)
+    if (!DeleteObject(palette))
+      throw rdr::SystemException("del palette failed", GetLastError());
+}
+
+void LogicalPalette::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;
+}
+
+
+static LogWriter dcLog("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());
+  }
+
+  // -=- Munge the bitmap info here
+  switch (bi.bmiHeader.biBitCount) {
+  case 1:
+  case 4:
+    bi.bmiHeader.biBitCount = 8;
+    break;
+  case 24:
+    bi.bmiHeader.biBitCount = 32;
+    break;
+  }
+  bi.bmiHeader.biPlanes = 1;
+
+  format.trueColour = bi.bmiHeader.biBitCount > 8;
+  format.bigEndian = 0;
+  format.bpp = format.depth = 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
+        dcLog.info("16-bit High Colour");
+        rMask = 0x7c00;
+        bMask = 0x001f;
+        gMask = 0x03e0;
+        format.depth = 15;
+        break;
+      case 24:
+      case 32:
+        // RGB 888 - True Colour
+        dcLog.info("24/32-bit High Colour");
+        rMask = 0xff0000;
+        gMask = 0x00ff00;
+        bMask = 0x0000ff;
+        format.depth = 24;
+        break;
+      default:
+        dcLog.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;
+      dcLog.info("BitFields format: %lu, (%lx, %lx, %lx)",
+        bi.bmiHeader.biBitCount, rMask, gMask, bMask);
+      if (format.bpp == 32)
+        format.depth = 24; // ...probably
+      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);
+  }
+
+  return format;
+}
+
+
+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);
+}
+
+
+CompatibleBitmap::CompatibleBitmap(HDC hdc, int width, int height) {
+  hbmp = CreateCompatibleBitmap(hdc, width, height);
+  if (!hbmp)
+    throw rdr::SystemException("CreateCompatibleBitmap() failed", 
+    GetLastError());
+}
+CompatibleBitmap::~CompatibleBitmap() {
+  if (hbmp) DeleteObject(hbmp);
+}
+
+
+PaletteSelector::PaletteSelector(HDC dc, HPALETTE pal) : device(dc), redrawRequired(false) {
+  oldPal = SelectPalette(dc, pal, FALSE);
+  redrawRequired = RealizePalette(dc) > 0;
+}
+PaletteSelector::~PaletteSelector() {
+  if (oldPal) SelectPalette(device, oldPal, TRUE);
+}
+
+
+IconInfo::IconInfo(HICON icon) {
+  if (!GetIconInfo(icon, this))
+    throw rdr::SystemException("GetIconInfo() failed", GetLastError());
+}
+IconInfo::~IconInfo() {
+  if (hbmColor)
+    DeleteObject(hbmColor);
+  if (hbmMask)
+    DeleteObject(hbmMask);
+}
+
+
+ModuleFileName::ModuleFileName(HMODULE module) : TCharArray(MAX_PATH) {
+  if (!module) module = GetModuleHandle(0);
+  if (!GetModuleFileName(module, buf, MAX_PATH))
+    buf[0] = 0;
+}
+
+
+FileVersionInfo::FileVersionInfo(const TCHAR* filename) {
+  // Get executable name
+  ModuleFileName exeName;
+  if (!filename) filename = exeName.buf;
+
+  // 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] = 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);
+}
+
+
+static LogWriter dfbLog("DynamicFn");
+
+DynamicFnBase::DynamicFnBase(const TCHAR* dllName, const char* fnName) : dllHandle(0), fnPtr(0) {
+  dllHandle = LoadLibrary(dllName);
+  if (!dllHandle) {
+    dfbLog.info("DLL %s not found (%d)", (const char*)CStr(dllName), GetLastError());
+    return;
+  }
+  fnPtr = GetProcAddress(dllHandle, fnName);
+  if (!fnPtr)
+    dfbLog.info("proc %s not found in %s (%d)", fnName, (const char*)CStr(dllName), GetLastError());
+}
+
+DynamicFnBase::~DynamicFnBase() {
+  if (dllHandle)
+    FreeLibrary(dllHandle);
+}
+
+
+static LogWriter miLog("MonitorInfo");
+
+MonitorInfo::MonitorInfo(HWND window) {
+#if (WINVER >= 0x0500)
+  typedef HMONITOR (WINAPI *_MonitorFromWindow_proto)(HWND,DWORD);
+  rfb::win32::DynamicFn<_MonitorFromWindow_proto> _MonitorFromWindow(_T("user32.dll"), "MonitorFromWindow");
+  typedef BOOL (WINAPI *_GetMonitorInfo_proto)(HMONITOR,LPMONITORINFO);
+  rfb::win32::DynamicFn<_GetMonitorInfo_proto> _GetMonitorInfo(_T("user32.dll"), "GetMonitorInfoA");
+
+  // Can we dynamically link to the monitor functions?
+  if (_MonitorFromWindow.isValid()) {
+    if (_GetMonitorInfo.isValid()) {
+      HMONITOR monitor = (*_MonitorFromWindow)(window, MONITOR_DEFAULTTONEAREST);
+      miLog.debug("monitor=%lx", monitor);
+      if (monitor) {
+        memset(this, 0, sizeof(MONITORINFOEXA));
+        cbSize = sizeof(MONITORINFOEXA);
+        if ((*_GetMonitorInfo)(monitor, this)) {
+          miLog.debug("monitor is %d,%d-%d,%d", rcMonitor.left, rcMonitor.top, rcMonitor.right, rcMonitor.bottom);
+          miLog.debug("work area is %d,%d-%d,%d", rcWork.left, rcWork.top, rcWork.right, rcWork.bottom);
+          miLog.debug("device is \"%s\"", szDevice);
+          return;
+        }
+        miLog.error("failed to get monitor info: %ld", GetLastError());
+      }
+    } else {
+      miLog.debug("GetMonitorInfo not found");
+    }
+  } else {
+      miLog.debug("MonitorFromWindow not found");
+  }
+#else
+#pragma message ("not building in GetMonitorInfo")
+  cbSize = sizeof(MonitorInfo);
+  szDevice[0] = 0;
+#endif
+
+  // Legacy fallbacks - just return the desktop settings
+  miLog.debug("using legacy fall-backs");
+  HWND desktop = GetDesktopWindow();
+  GetWindowRect(desktop, &rcMonitor);
+  SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0);
+  dwFlags = 0;
+}
+
+
+#if (WINVER >= 0x0500)
+
+struct moveToMonitorData {
+  HWND window;
+  const char* monitorName;
+};
+
+typedef BOOL (WINAPI *_GetMonitorInfo_proto)(HMONITOR,LPMONITORINFO);
+static rfb::win32::DynamicFn<_GetMonitorInfo_proto> _GetMonitorInfo(_T("user32.dll"), "GetMonitorInfoA");
+
+static BOOL CALLBACK moveToMonitorEnumProc(HMONITOR monitor,
+                                    HDC dc,
+                                    LPRECT pos,
+                                    LPARAM d) {
+  moveToMonitorData* data = (moveToMonitorData*)d;
+  MONITORINFOEXA info;
+  memset(&info, 0, sizeof(info));
+  info.cbSize = sizeof(info);
+
+  if ((*_GetMonitorInfo)(monitor, &info)) {
+    if (stricmp(data->monitorName, info.szDevice) == 0) {
+      SetWindowPos(data->window, 0,
+        info.rcMonitor.left, info.rcMonitor.top,
+        info.rcMonitor.right, info.rcMonitor.bottom,
+        SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
+      return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+#endif
+
+void moveToMonitor(HWND handle, const char* device) {
+  miLog.debug("moveToMonitor %s", device);
+
+#if (WINVER >= 0x500)
+  typedef BOOL (WINAPI *_EnumDisplayMonitors_proto)(HDC, LPCRECT, MONITORENUMPROC, LPARAM);
+  rfb::win32::DynamicFn<_EnumDisplayMonitors_proto> _EnumDisplayMonitors(_T("user32.dll"), "EnumDisplayMonitors");
+  if (!_EnumDisplayMonitors.isValid()) {
+    miLog.debug("EnumDisplayMonitors not found");
+    return;
+  }
+
+  moveToMonitorData data;
+  data.window = handle;
+  data.monitorName = device;
+
+  (*_EnumDisplayMonitors)(0, 0, &moveToMonitorEnumProc, (LPARAM)&data);
+#endif
+}
+
+
+void centerWindow(HWND handle, HWND parent, bool clipToParent) {
+  RECT r;
+  if (parent && IsWindowVisible(parent)) {
+    if (!GetWindowRect(parent, &r)) return;
+  } else {
+    MonitorInfo mi(handle);
+    r=mi.rcWork;
+  }
+  centerWindow(handle, r, clipToParent);
+}
+
+void centerWindow(HWND handle, const RECT& r, bool clipToRect) {
+  RECT wr;
+  if (!GetWindowRect(handle, &wr)) return;
+  int w = wr.right-wr.left;
+  int h = wr.bottom-wr.top;
+  if (clipToRect) {
+    w = min(r.right-r.left, w);
+    h = min(r.bottom-r.top, h);
+  }
+  int x = (r.left + r.right - w)/2;
+  int y = (r.top + r.bottom - h)/2;
+  UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | (clipToRect ? 0 : SWP_NOSIZE);
+  SetWindowPos(handle, 0, x, y, w, h, flags);
+}
+
+
+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);
+}
+
+
+};
+};
diff --git a/rfb_win32/Win32Util.h b/rfb_win32/Win32Util.h
new file mode 100644
index 0000000..5f0ab5a
--- /dev/null
+++ b/rfb_win32/Win32Util.h
@@ -0,0 +1,217 @@
+/* Copyright (C) 2002-2004 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/ColourMap.h>
+#include <rfb/PixelFormat.h>
+#include <rfb/Rect.h>
+#include <rfb_win32/TCharArray.h>
+
+namespace rfb {
+
+  namespace win32 {
+
+    class LogicalPalette {
+    public:
+      LogicalPalette();
+      ~LogicalPalette();
+      void setEntries(int start, int count, const Colour* cols);
+      HPALETTE getHandle() {return palette;}
+    protected:
+      HPALETTE palette;
+      int numEntries;
+    };
+
+    class DeviceContext {
+    public:
+      DeviceContext() : dc(0) {}
+      virtual ~DeviceContext() {}
+      operator HDC() const {return dc;}
+      PixelFormat getPF() const;
+      static PixelFormat getPF(HDC dc);
+    protected:
+      HDC dc;
+    };
+
+    class WindowDC : public DeviceContext {
+    public:
+      WindowDC(HWND wnd);
+      virtual ~WindowDC();
+    protected:
+      HWND hwnd;
+    };
+
+    class CompatibleDC : public DeviceContext {
+    public:
+      CompatibleDC(HDC existing);
+      virtual ~CompatibleDC();
+    };
+
+    class BitmapDC : public CompatibleDC {
+    public:
+      BitmapDC(HDC hdc, HBITMAP hbitmap);
+      ~BitmapDC();
+    protected:
+      HBITMAP oldBitmap;
+    };
+
+    class CompatibleBitmap {
+    public:
+      CompatibleBitmap(HDC hdc, int width, int height);
+      virtual ~CompatibleBitmap();
+      operator HBITMAP() const {return hbmp;}
+    protected:
+      HBITMAP hbmp;
+    };
+
+    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;
+    }
+
+    class PaletteSelector {
+    public:
+      PaletteSelector(HDC dc, HPALETTE pal);
+      ~PaletteSelector();
+      bool isRedrawRequired() {return redrawRequired;}
+    protected:
+      HPALETTE oldPal;
+      HDC device;
+      bool redrawRequired;
+    };
+
+    struct IconInfo : public ICONINFO {
+      IconInfo(HICON icon);
+      ~IconInfo();
+    };
+
+    struct ModuleFileName : public TCharArray {
+      ModuleFileName(HMODULE module=0);
+    };
+
+    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);
+
+    class DynamicFnBase {
+    public:
+      DynamicFnBase(const TCHAR* dllName, const char* fnName);
+      ~DynamicFnBase();
+      bool isValid() const {return fnPtr != 0;}
+    protected:
+      void* fnPtr;
+      HMODULE dllHandle;
+    };
+
+    template<class T> class DynamicFn : public DynamicFnBase {
+    public:
+      DynamicFn(const TCHAR* dllName, const char* fnName) : DynamicFnBase(dllName, fnName) {}
+      T operator *() const {return (T)fnPtr;};
+    };
+
+    // Structure containing info on the monitor nearest the window.
+    // Copes with multi-monitor OSes and older ones.
+#if (WINVER >= 0x0500)
+    struct MonitorInfo : MONITORINFOEXA {
+      MonitorInfo(HWND hwnd);
+    };
+#else
+    struct MonitorInfo {
+      MonitorInfo(HWND hwnd);
+      DWORD cbSize;
+      RECT rcMonitor;
+      RECT rcWork;
+      DWORD dwFlags;
+      char szDevice[1]; // Always null...
+    };
+#endif
+    void moveToMonitor(HWND handle, const char* device);
+
+    class Handle {
+    public:
+      Handle(HANDLE h_=0) : h(h_) {}
+      ~Handle() {
+        if (h) CloseHandle(h);
+      }
+      operator HANDLE() {return h;}
+      HANDLE h;
+    };
+
+    // 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, bool clipToRect=false);
+    void centerWindow(HWND handle, HWND parent, bool clipToRect=false);
+
+    // MsgBox helper function.  Define rfb::win32::AppName somewhere in your
+    // code and MsgBox will use its value in informational messages.
+    extern TStr AppName;
+    int MsgBox(HWND parent, const TCHAR* message, UINT flags);
+
+    // 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(""));
+      }
+    };
+
+    // 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/rfb_win32/keymap.h b/rfb_win32/keymap.h
new file mode 100644
index 0000000..69ce66f
--- /dev/null
+++ b/rfb_win32/keymap.h
@@ -0,0 +1,149 @@
+/* Copyright (C) 2002-2004 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/rfb_win32/msvcwarning.h b/rfb_win32/msvcwarning.h
new file mode 100644
index 0000000..e50d9c1
--- /dev/null
+++ b/rfb_win32/msvcwarning.h
@@ -0,0 +1,20 @@
+/* Copyright (C) 2002-2003 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.
+ */
+#pragma warning( disable : 4244 ) // loss of data e.g. int to char
+#pragma warning( disable : 4800 ) // forcing bool 'true' or 'false'
+#pragma warning( disable : 4786 ) // debug info truncated
diff --git a/rfb_win32/rfb_win32.dsp b/rfb_win32/rfb_win32.dsp
new file mode 100644
index 0000000..2118bcb
--- /dev/null
+++ b/rfb_win32/rfb_win32.dsp
@@ -0,0 +1,341 @@
+# 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"
+# 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 ".." /FI"msvcwarning.h" /D "NDEBUG" /D "_LIB" /D "WIN32" /D "_MBCS" /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"
+# 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 ".." /FI"msvcwarning.h" /D "_DEBUG" /D "_LIB" /D "WIN32" /D "_MBCS" /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"
+# 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 ".." /FI"msvcwarning.h" /D "_LIB" /D "_DEBUG" /D "WIN32" /D "_UNICODE" /D "UNICODE" /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=.\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=.\LaunchProcess.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=.\RegConfig.cxx
+# End Source File
+# Begin Source File
+
+SOURCE=.\Registry.cxx
+# End Source File
+# Begin Source File
+
+SOURCE=.\SDisplay.cxx
+# End Source File
+# Begin Source File
+
+SOURCE=.\Service.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=.\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=.\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=.\CPointer.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\CurrentUser.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=.\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=.\MsgWindow.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\OSVersion.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=.\SDisplay.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=.\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=.\TrayIcon.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