The "rfb_win32" library merged with VNC 4.1.1 code.

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/branches/merge-with-vnc-4.1.1@523 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/rfb_win32/SDisplay.cxx b/rfb_win32/SDisplay.cxx
index 4916c48..0af5064 100644
--- a/rfb_win32/SDisplay.cxx
+++ b/rfb_win32/SDisplay.cxx
@@ -1,5 +1,5 @@
-/* Copyright (C) 2002-2004 RealVNC Ltd.  All Rights Reserved.
- *    
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -20,21 +20,19 @@
 //
 // 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/TsSessions.h>
 #include <rfb_win32/CleanDesktop.h>
-
-#include <rfb/util.h>
-#include <rfb/LogWriter.h>
+#include <rfb_win32/CurrentUser.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/MonitorInfo.h>
+#include <rfb_win32/SDisplayCorePolling.h>
+#include <rfb_win32/SDisplayCoreWMHooks.h>
+#include <rfb_win32/SDisplayCoreDriver.h>
 #include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
 
-#include <rfb/Configuration.h>
 
 using namespace rdr;
 using namespace rfb;
@@ -44,209 +42,37 @@
 
 // - SDisplay-specific configuration options
 
-BoolParameter rfb::win32::SDisplay::use_hooks("UseHooks",
-  "Set hooks in the operating system to capture display updates more efficiently", true);
+IntParameter rfb::win32::SDisplay::updateMethod("UpdateMethod",
+  "How to discover desktop updates; 0 - Polling, 1 - Application hooking, 2 - Driver hooking.", 1);
 BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs",
   "Disable local keyboard and pointer input while the server is in use", false);
 StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction",
   "Action to perform when all clients have disconnected.  (None, Lock, Logoff)", "None");
-
+StringParameter displayDevice("DisplayDevice",
+  "Display device name of the monitor to be remoted, or empty to export the whole desktop.", "");
 BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
-  "Remove the desktop wallpaper when the server in in use.", false);
+  "Remove the desktop wallpaper when the server is in use.", false);
 BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern",
-  "Remove the desktop background pattern when the server in in use.", false);
+  "Remove the desktop background pattern when the server is in use.", false);
 BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
   "Disable desktop user interface effects when the server is in use.", false);
 
 
-// - 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
 //
 
+typedef BOOL (WINAPI *_LockWorkStation_proto)();
+DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
+
 // -=- 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)
+SDisplay::SDisplay()
+  : server(0), pb(0), device(0),
+    core(0), ptr(0), kbd(0), clipboard(0),
+    inputs(0), monitor(0), cleanDesktop(0), cursor(0),
+    statusLocation(0)
 {
   updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
 }
@@ -271,57 +97,14 @@
 void SDisplay::start(VNCServer* vs)
 {
   vlog.debug("starting");
+
+  // Try to make session zero the console session
+  if (!inConsoleSession())
+    setConsoleSession();
+
+  // Start the SDisplay core
   server = vs;
-
-  // 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);
+  startCore();
 
   vlog.debug("started");
 
@@ -331,108 +114,229 @@
 void SDisplay::stop()
 {
   vlog.debug("stopping");
+
+  // If we successfully start()ed then perform the DisconnectAction
   if (core) {
-    // If SDisplay was actually active then perform the disconnect action
+    CurrentUserToken cut;
     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)();
+      if (!cut.h)
+        vlog.info("ignoring DisconnectAction=Logoff - no current user");
       else
         ExitWindowsEx(EWX_LOGOFF, 0);
+    } else if (stricmp(action.buf, "Lock") == 0) {
+      if (!cut.h) {
+        vlog.info("ignoring DisconnectAction=Lock - no current user");
+      } else {
+        if (_LockWorkStation.isValid())
+          (*_LockWorkStation)();
+        else
+          ExitWindowsEx(EWX_LOGOFF, 0);
+      }
     }
   }
-  delete core;
-  core = 0;
-  delete pb;
-  pb = 0;
-  if (device) {
-    if (releaseDevice)
-      ReleaseDC(0, device);
-    else
-      DeleteDC(device);
-  }
-  device = 0;
+
+  // Stop the SDisplayCore
   if (server)
     server->setPixelBuffer(0);
-
+  stopCore();
   server = 0;
+
   vlog.debug("stopped");
 
   if (statusLocation) *statusLocation = false;
 }
 
-void SDisplay::restart() {
-  vlog.debug("restarting");
-  // Close down the hooks
-  delete core;
-  core = 0;
+
+void SDisplay::startCore() {
+
+  // Currently, we just check whether we're in the console session, and
+  //   fail if not
+  if (!inConsoleSession())
+    throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
+  
+  // Switch to the current input desktop
+  if (rfb::win32::desktopChangeRequired()) {
+    if (!rfb::win32::changeDesktop())
+      throw rdr::Exception("unable to switch into input desktop");
+  }
+
+  // Initialise the change tracker and clipper
+  updates.clear();
+  clipper.setUpdateTracker(server);
+
+  // Create the framebuffer object
+  recreatePixelBuffer(true);
+
+  // Create the SDisplayCore
+  updateMethod_ = updateMethod;
+  int tryMethod = updateMethod_;
+  while (!core) {
+    try {
+      if (tryMethod == 2)
+        core = new SDisplayCoreDriver(this, &updates);
+      else if (tryMethod == 1)
+        core = new SDisplayCoreWMHooks(this, &updates);
+      else
+        core = new SDisplayCorePolling(this, &updates);
+      core->setScreenRect(screenRect);
+    } catch (rdr::Exception& e) {
+      delete core; core = 0;
+      if (tryMethod == 0)
+        throw rdr::Exception("unable to access desktop");
+      tryMethod--;
+      vlog.error(e.str());
+    }
+  }
+  vlog.info("Started %s", core->methodName());
+
+  // Start display monitor, clipboard handler and input handlers
+  monitor = new WMMonitor;
+  monitor->setNotifier(this);
+  clipboard = new Clipboard;
+  clipboard->setNotifier(this);
+  ptr = new SPointer;
+  kbd = new SKeyboard;
+  inputs = new WMBlockInput;
+  cursor = new WMCursor;
+
+  // Apply desktop optimisations
+  cleanDesktop = new CleanDesktop;
+  if (removePattern)
+    cleanDesktop->disablePattern();
+  if (removeWallpaper)
+    cleanDesktop->disableWallpaper();
+  if (disableEffects)
+    cleanDesktop->disableEffects();
+  isWallpaperRemoved = removeWallpaper;
+  isPatternRemoved = removePattern;
+  areEffectsDisabled = disableEffects;
+}
+
+void SDisplay::stopCore() {
+  if (core)
+    vlog.info("Stopping %s", core->methodName());
+  delete core; core = 0;
+  delete pb; pb = 0;
+  delete device; device = 0;
+  delete monitor; monitor = 0;
+  delete clipboard; clipboard = 0;
+  delete inputs; inputs = 0;
+  delete ptr; ptr = 0;
+  delete kbd; kbd = 0;
+  delete cleanDesktop; cleanDesktop = 0;
+  delete cursor; cursor = 0;
+  ResetEvent(updateEvent);
+}
+
+
+bool SDisplay::areHooksAvailable() {
+  return WMHooks::areAvailable();
+}
+
+bool SDisplay::isDriverAvailable() {
+  return SDisplayCoreDriver::isAvailable();
+}
+
+
+bool SDisplay::isRestartRequired() {
+  // - We must restart the SDesktop if:
+  // 1. We are no longer in the input desktop.
+  // 2. The any setting has changed.
+
+  // - Check that our session is the Console
+  if (!inConsoleSession())
+    return true;
+
+  // - Check that we are in the input desktop
+  if (rfb::win32::desktopChangeRequired())
+    return true;
+
+  // - Check that the update method setting hasn't changed
+  //   NB: updateMethod reflects the *selected* update method, not
+  //   necessarily the one in use, since we fall back to simpler
+  //   methods if more advanced ones fail!
+  if (updateMethod_ != updateMethod)
+    return true;
+
+  // - Check that the desktop optimisation settings haven't changed
+  //   This isn't very efficient, but it shouldn't change very often!
+  if ((isWallpaperRemoved != removeWallpaper) ||
+      (isPatternRemoved != removePattern) ||
+      (areEffectsDisabled != disableEffects))
+    return true;
+
+  return false;
+}
+
+
+void SDisplay::restartCore() {
+  vlog.info("restarting");
+
+  // Stop the existing Core  related resources
+  stopCore();
   try {
-    // Re-start the hooks if possible
-    start(server);
-    vlog.debug("restarted");
+    // Start a new Core if possible
+    startCore();
+    vlog.info("restarted");
   } catch (rdr::Exception& e) {
-    // If start() fails then we MUST disconnect all clients,
-    // to cause the server to stop using the desktop.
+    // If startCore() fails then we MUST disconnect all clients,
+    // to cause the server to stop() the desktop.
     // Otherwise, the SDesktop is in an inconsistent state
-    // and the server will crash
+    // and the server will crash.
     server->closeClients(e.str());
   }
 }
 
 
-void SDisplay::pointerEvent(const Point& pos, rdr::U8 buttonmask) {
+void SDisplay::pointerEvent(const Point& pos, int buttonmask) {
   if (pb->getRect().contains(pos)) {
     Point screenPos = pos.translate(screenRect.tl);
-    core->ptr.pointerEvent(screenPos, buttonmask);
+    // - Check that the SDesktop doesn't need restarting
+    if (isRestartRequired())
+      restartCore();
+    if (ptr)
+      ptr->pointerEvent(screenPos, buttonmask);
   }
 }
 
 void SDisplay::keyEvent(rdr::U32 key, bool down) {
-  core->kbd.keyEvent(key, down);
+  // - Check that the SDesktop doesn't need restarting
+  if (isRestartRequired())
+    restartCore();
+  if (kbd)
+    kbd->keyEvent(key, down);
 }
 
 void SDisplay::clientCutText(const char* text, int len) {
   CharArray clip_sz(len+1);
   memcpy(clip_sz.buf, text, len);
   clip_sz.buf[len] = 0;
-  core->clipboard.setClipText(clip_sz.buf);
+  clipboard->setClipText(clip_sz.buf);
 }
 
 
 void SDisplay::framebufferUpdateRequest()
 {
-  triggerUpdate();
+  SetEvent(updateEvent);
 }
 
 Point SDisplay::getFbSize() {
   bool startAndStop = !core;
+
   // If not started, do minimal initialisation to get desktop size.
-  if (startAndStop) recreatePixelBuffer();
+  if (startAndStop)
+    recreatePixelBuffer();
   Point result = Point(pb->width(), pb->height());
+
   // Destroy the initialised structures.
-  if (startAndStop) stop();
+  if (startAndStop)
+    stopCore();
   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)
@@ -462,36 +366,42 @@
   }
 }
 
-bool
+void
 SDisplay::processEvent(HANDLE event) {
   if (event == updateEvent) {
-    vlog.info("processEvent");
+    vlog.write(120, "processEvent");
     ResetEvent(updateEvent);
 
     // - If the SDisplay isn't even started then quit now
     if (!core) {
       vlog.error("not start()ed");
-      return true;
+      return;
     }
 
     // - Ensure that the disableLocalInputs flag is respected
-    core->wm_input.blockInputs(SDisplay::disableLocalInputs);
+    inputs->blockInputs(disableLocalInputs);
 
     // - Only process updates if the server is ready
     if (server && server->clientsReadyForUpdate()) {
       bool try_update = false;
 
       // - Check that the SDesktop doesn't need restarting
-      if (core->isRestartRequired()) {
-        restart();
-        return true;
+      if (isRestartRequired()) {
+        restartCore();
+        return;
       }
-    
-      // *** window dragging can be improved - more frequent, more cunning about updates
-      while (core->wm_copyrect.processEvent()) {}
-        
+
+      // - Flush any updates from the core
+      try {
+        core->flushUpdates();
+      } catch (rdr::Exception& e) {
+        vlog.error(e.str());
+        restartCore();
+        return;
+      }
+
       // Ensure the cursor is up to date
-      WMCursor::Info info = core->cursor.getCursorInfo();
+      WMCursor::Info info = cursor->getCursorInfo();
       if (old_cursor != info) {
         // Update the cursor shape if the visibility has changed
         bool set_cursor = info.visible != old_cursor.visible;
@@ -505,7 +415,7 @@
         // 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);
+        server->setCursorPos(desktopPos);
         try_update = true;
 
         old_cursor = info;
@@ -513,100 +423,102 @@
 
       // Flush any changes to the server
       try_update = flushChangeTracker() || try_update;
-      if (try_update)
+      if (try_update) {
         server->tryUpdate();
+      }
     }
-  } else {
-    CloseHandle(event);
-    return false;
+    return;
   }
-  return true;
+  throw rdr::Exception("No such event");
 }
 
 
 // -=- Protected methods
 
 void
-SDisplay::recreatePixelBuffer() {
-  vlog.debug("attaching to device %s", deviceName);
-
+SDisplay::recreatePixelBuffer(bool force) {
   // 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 no device is specified, open entire screen using GetDC().
+  //   Opening the whole display with CreateDC doesn't work on multi-monitor
+  //   systems for some reason.
+  DeviceContext* new_device = 0;
+  TCharArray deviceName(displayDevice.getData());
+  if (deviceName.buf[0]) {
+    vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
+    new_device = new DeviceDC(deviceName.buf);
   }
-  if (!new_device)
-    throw SystemException("cannot open the display", GetLastError());
+  if (!new_device) {
+    vlog.info("Attaching to virtual desktop");
+    new_device = new WindowDC(0);
+  }
 
-  // Get the coordinates of the entire virtual display
+  // Get the coordinates of the specified dispay device
   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);
-
+  if (deviceName.buf[0]) {
+    MonitorInfo info(CStr(deviceName.buf));
+    newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
+                         info.rcMonitor.right, info.rcMonitor.bottom);
   } else {
-    delete new_buffer;
-    DeleteDC(new_device);
+    newScreenRect = new_device->getClipBox();
   }
+
+  // If nothing has changed & a recreate has not been forced, delete
+  // the new device context and return
+  if (pb && !force &&
+    newScreenRect.equals(screenRect) &&
+    new_device->getPF().equal(pb->getPF())) {
+    delete new_device;
+    return;
+  }
+
+  // Flush any existing changes to the server
+  flushChangeTracker();
+
+  // Delete the old pixelbuffer and device context
+  vlog.debug("deleting old pixel buffer & device");
+  if (pb)
+    delete pb;
+  if (device)
+    delete device;
+
+  // Create a DeviceFrameBuffer attached to the new device
+  vlog.debug("creating pixel buffer");
+  DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
+
+  // Replace the old PixelBuffer
+  screenRect = newScreenRect;
+  pb = new_buffer;
+  device = new_device;
+
+  // Initialise the pixels
+  pb->grabRegion(pb->getRect());
+
+  // Prevent future grabRect operations from throwing exceptions
+  pb->setIgnoreGrabErrors(true);
+
+  // Update the clipping update tracker
+  clipper.setClipRect(pb->getRect());
+
+  // Inform the core of the changes
+  if (core)
+    core->setScreenRect(screenRect);
+
+  // Inform the server of the changes
+  if (server)
+    server->setPixelBuffer(pb);
 }
 
 bool SDisplay::flushChangeTracker() {
-  if (change_tracker.is_empty())
+  if (updates.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);
+  vlog.write(120, "flushChangeTracker");
+
+  // Translate the update coordinates from Screen coords to Desktop
+  updates.translate(screenRect.tl.negate());
+
+  // Clip the updates & flush them to the server
+  updates.copyTo(&clipper);
+  updates.clear();
+  return true;
 }