Migrating to new directory structure adopted from the RealVNC's source tree. More changes will follow.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@591 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx
new file mode 100644
index 0000000..0af5064
--- /dev/null
+++ b/win/rfb_win32/SDisplay.cxx
@@ -0,0 +1,524 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- SDisplay.cxx
+//
+// The SDisplay class encapsulates a particular system display.
+
+#include <rfb_win32/SDisplay.h>
+#include <rfb_win32/Service.h>
+#include <rfb_win32/TsSessions.h>
+#include <rfb_win32/CleanDesktop.h>
+#include <rfb_win32/CurrentUser.h>
+#include <rfb_win32/DynamicFn.h>
+#include <rfb_win32/MonitorInfo.h>
+#include <rfb_win32/SDisplayCorePolling.h>
+#include <rfb_win32/SDisplayCoreWMHooks.h>
+#include <rfb_win32/SDisplayCoreDriver.h>
+#include <rfb/Exception.h>
+#include <rfb/LogWriter.h>
+
+
+using namespace rdr;
+using namespace rfb;
+using namespace rfb::win32;
+
+static LogWriter vlog("SDisplay");
+
+// - SDisplay-specific configuration options
+
+IntParameter rfb::win32::SDisplay::updateMethod("UpdateMethod",
+ "How to discover desktop updates; 0 - Polling, 1 - Application hooking, 2 - Driver hooking.", 1);
+BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs",
+ "Disable local keyboard and pointer input while the server is in use", false);
+StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction",
+ "Action to perform when all clients have disconnected. (None, Lock, Logoff)", "None");
+StringParameter displayDevice("DisplayDevice",
+ "Display device name of the monitor to be remoted, or empty to export the whole desktop.", "");
+BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
+ "Remove the desktop wallpaper when the server is in use.", false);
+BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern",
+ "Remove the desktop background pattern when the server is in use.", false);
+BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
+ "Disable desktop user interface effects when the server is in use.", false);
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// SDisplay
+//
+
+typedef BOOL (WINAPI *_LockWorkStation_proto)();
+DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
+
+// -=- Constructor/Destructor
+
+SDisplay::SDisplay()
+ : server(0), pb(0), device(0),
+ core(0), ptr(0), kbd(0), clipboard(0),
+ inputs(0), monitor(0), cleanDesktop(0), cursor(0),
+ statusLocation(0)
+{
+ updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
+}
+
+SDisplay::~SDisplay()
+{
+ // XXX when the VNCServer has been deleted with clients active, stop()
+ // doesn't get called - this ought to be fixed in VNCServerST. In any event,
+ // we should never call any methods on VNCServer once we're being deleted.
+ // This is because it is supposed to be guaranteed that the SDesktop exists
+ // throughout the lifetime of the VNCServer. So if we're being deleted, then
+ // the VNCServer ought not to exist and therefore we shouldn't invoke any
+ // methods on it. Setting server to zero here ensures that stop() doesn't
+ // call setPixelBuffer(0) on the server.
+ server = 0;
+ if (core) stop();
+}
+
+
+// -=- SDesktop interface
+
+void SDisplay::start(VNCServer* vs)
+{
+ vlog.debug("starting");
+
+ // Try to make session zero the console session
+ if (!inConsoleSession())
+ setConsoleSession();
+
+ // Start the SDisplay core
+ server = vs;
+ startCore();
+
+ vlog.debug("started");
+
+ if (statusLocation) *statusLocation = true;
+}
+
+void SDisplay::stop()
+{
+ vlog.debug("stopping");
+
+ // If we successfully start()ed then perform the DisconnectAction
+ if (core) {
+ CurrentUserToken cut;
+ CharArray action = disconnectAction.getData();
+ if (stricmp(action.buf, "Logoff") == 0) {
+ if (!cut.h)
+ vlog.info("ignoring DisconnectAction=Logoff - no current user");
+ else
+ ExitWindowsEx(EWX_LOGOFF, 0);
+ } else if (stricmp(action.buf, "Lock") == 0) {
+ if (!cut.h) {
+ vlog.info("ignoring DisconnectAction=Lock - no current user");
+ } else {
+ if (_LockWorkStation.isValid())
+ (*_LockWorkStation)();
+ else
+ ExitWindowsEx(EWX_LOGOFF, 0);
+ }
+ }
+ }
+
+ // Stop the SDisplayCore
+ if (server)
+ server->setPixelBuffer(0);
+ stopCore();
+ server = 0;
+
+ vlog.debug("stopped");
+
+ if (statusLocation) *statusLocation = false;
+}
+
+
+void SDisplay::startCore() {
+
+ // Currently, we just check whether we're in the console session, and
+ // fail if not
+ if (!inConsoleSession())
+ throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
+
+ // Switch to the current input desktop
+ if (rfb::win32::desktopChangeRequired()) {
+ if (!rfb::win32::changeDesktop())
+ throw rdr::Exception("unable to switch into input desktop");
+ }
+
+ // Initialise the change tracker and clipper
+ updates.clear();
+ clipper.setUpdateTracker(server);
+
+ // Create the framebuffer object
+ recreatePixelBuffer(true);
+
+ // Create the SDisplayCore
+ updateMethod_ = updateMethod;
+ int tryMethod = updateMethod_;
+ while (!core) {
+ try {
+ if (tryMethod == 2)
+ core = new SDisplayCoreDriver(this, &updates);
+ else if (tryMethod == 1)
+ core = new SDisplayCoreWMHooks(this, &updates);
+ else
+ core = new SDisplayCorePolling(this, &updates);
+ core->setScreenRect(screenRect);
+ } catch (rdr::Exception& e) {
+ delete core; core = 0;
+ if (tryMethod == 0)
+ throw rdr::Exception("unable to access desktop");
+ tryMethod--;
+ vlog.error(e.str());
+ }
+ }
+ vlog.info("Started %s", core->methodName());
+
+ // Start display monitor, clipboard handler and input handlers
+ monitor = new WMMonitor;
+ monitor->setNotifier(this);
+ clipboard = new Clipboard;
+ clipboard->setNotifier(this);
+ ptr = new SPointer;
+ kbd = new SKeyboard;
+ inputs = new WMBlockInput;
+ cursor = new WMCursor;
+
+ // Apply desktop optimisations
+ cleanDesktop = new CleanDesktop;
+ if (removePattern)
+ cleanDesktop->disablePattern();
+ if (removeWallpaper)
+ cleanDesktop->disableWallpaper();
+ if (disableEffects)
+ cleanDesktop->disableEffects();
+ isWallpaperRemoved = removeWallpaper;
+ isPatternRemoved = removePattern;
+ areEffectsDisabled = disableEffects;
+}
+
+void SDisplay::stopCore() {
+ if (core)
+ vlog.info("Stopping %s", core->methodName());
+ delete core; core = 0;
+ delete pb; pb = 0;
+ delete device; device = 0;
+ delete monitor; monitor = 0;
+ delete clipboard; clipboard = 0;
+ delete inputs; inputs = 0;
+ delete ptr; ptr = 0;
+ delete kbd; kbd = 0;
+ delete cleanDesktop; cleanDesktop = 0;
+ delete cursor; cursor = 0;
+ ResetEvent(updateEvent);
+}
+
+
+bool SDisplay::areHooksAvailable() {
+ return WMHooks::areAvailable();
+}
+
+bool SDisplay::isDriverAvailable() {
+ return SDisplayCoreDriver::isAvailable();
+}
+
+
+bool SDisplay::isRestartRequired() {
+ // - We must restart the SDesktop if:
+ // 1. We are no longer in the input desktop.
+ // 2. The any setting has changed.
+
+ // - Check that our session is the Console
+ if (!inConsoleSession())
+ return true;
+
+ // - Check that we are in the input desktop
+ if (rfb::win32::desktopChangeRequired())
+ return true;
+
+ // - Check that the update method setting hasn't changed
+ // NB: updateMethod reflects the *selected* update method, not
+ // necessarily the one in use, since we fall back to simpler
+ // methods if more advanced ones fail!
+ if (updateMethod_ != updateMethod)
+ return true;
+
+ // - Check that the desktop optimisation settings haven't changed
+ // This isn't very efficient, but it shouldn't change very often!
+ if ((isWallpaperRemoved != removeWallpaper) ||
+ (isPatternRemoved != removePattern) ||
+ (areEffectsDisabled != disableEffects))
+ return true;
+
+ return false;
+}
+
+
+void SDisplay::restartCore() {
+ vlog.info("restarting");
+
+ // Stop the existing Core related resources
+ stopCore();
+ try {
+ // Start a new Core if possible
+ startCore();
+ vlog.info("restarted");
+ } catch (rdr::Exception& e) {
+ // If startCore() fails then we MUST disconnect all clients,
+ // to cause the server to stop() the desktop.
+ // Otherwise, the SDesktop is in an inconsistent state
+ // and the server will crash.
+ server->closeClients(e.str());
+ }
+}
+
+
+void SDisplay::pointerEvent(const Point& pos, int buttonmask) {
+ if (pb->getRect().contains(pos)) {
+ Point screenPos = pos.translate(screenRect.tl);
+ // - Check that the SDesktop doesn't need restarting
+ if (isRestartRequired())
+ restartCore();
+ if (ptr)
+ ptr->pointerEvent(screenPos, buttonmask);
+ }
+}
+
+void SDisplay::keyEvent(rdr::U32 key, bool down) {
+ // - Check that the SDesktop doesn't need restarting
+ if (isRestartRequired())
+ restartCore();
+ if (kbd)
+ kbd->keyEvent(key, down);
+}
+
+void SDisplay::clientCutText(const char* text, int len) {
+ CharArray clip_sz(len+1);
+ memcpy(clip_sz.buf, text, len);
+ clip_sz.buf[len] = 0;
+ clipboard->setClipText(clip_sz.buf);
+}
+
+
+void SDisplay::framebufferUpdateRequest()
+{
+ SetEvent(updateEvent);
+}
+
+Point SDisplay::getFbSize() {
+ bool startAndStop = !core;
+
+ // If not started, do minimal initialisation to get desktop size.
+ if (startAndStop)
+ recreatePixelBuffer();
+ Point result = Point(pb->width(), pb->height());
+
+ // Destroy the initialised structures.
+ if (startAndStop)
+ stopCore();
+ return result;
+}
+
+
+void
+SDisplay::notifyClipboardChanged(const char* text, int len) {
+ vlog.debug("clipboard text changed");
+ if (server)
+ server->serverCutText(text, len);
+}
+
+
+void
+SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
+ switch (evt) {
+ case WMMonitor::Notifier::DisplaySizeChanged:
+ vlog.debug("desktop size changed");
+ recreatePixelBuffer();
+ break;
+ case WMMonitor::Notifier::DisplayPixelFormatChanged:
+ vlog.debug("desktop format changed");
+ recreatePixelBuffer();
+ break;
+ case WMMonitor::Notifier::DisplayColourMapChanged:
+ vlog.debug("desktop colormap changed");
+ pb->updateColourMap();
+ if (server)
+ server->setColourMapEntries();
+ break;
+ default:
+ vlog.error("unknown display event received");
+ }
+}
+
+void
+SDisplay::processEvent(HANDLE event) {
+ if (event == updateEvent) {
+ vlog.write(120, "processEvent");
+ ResetEvent(updateEvent);
+
+ // - If the SDisplay isn't even started then quit now
+ if (!core) {
+ vlog.error("not start()ed");
+ return;
+ }
+
+ // - Ensure that the disableLocalInputs flag is respected
+ inputs->blockInputs(disableLocalInputs);
+
+ // - Only process updates if the server is ready
+ if (server && server->clientsReadyForUpdate()) {
+ bool try_update = false;
+
+ // - Check that the SDesktop doesn't need restarting
+ if (isRestartRequired()) {
+ restartCore();
+ return;
+ }
+
+ // - Flush any updates from the core
+ try {
+ core->flushUpdates();
+ } catch (rdr::Exception& e) {
+ vlog.error(e.str());
+ restartCore();
+ return;
+ }
+
+ // Ensure the cursor is up to date
+ WMCursor::Info info = cursor->getCursorInfo();
+ if (old_cursor != info) {
+ // Update the cursor shape if the visibility has changed
+ bool set_cursor = info.visible != old_cursor.visible;
+ // OR if the cursor is visible and the shape has changed.
+ set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
+
+ // Update the cursor shape
+ if (set_cursor)
+ pb->setCursor(info.visible ? info.cursor : 0, server);
+
+ // Update the cursor position
+ // NB: First translate from Screen coordinates to Desktop
+ Point desktopPos = info.position.translate(screenRect.tl.negate());
+ server->setCursorPos(desktopPos);
+ try_update = true;
+
+ old_cursor = info;
+ }
+
+ // Flush any changes to the server
+ try_update = flushChangeTracker() || try_update;
+ if (try_update) {
+ server->tryUpdate();
+ }
+ }
+ return;
+ }
+ throw rdr::Exception("No such event");
+}
+
+
+// -=- Protected methods
+
+void
+SDisplay::recreatePixelBuffer(bool force) {
+ // Open the specified display device
+ // If no device is specified, open entire screen using GetDC().
+ // Opening the whole display with CreateDC doesn't work on multi-monitor
+ // systems for some reason.
+ DeviceContext* new_device = 0;
+ TCharArray deviceName(displayDevice.getData());
+ if (deviceName.buf[0]) {
+ vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
+ new_device = new DeviceDC(deviceName.buf);
+ }
+ if (!new_device) {
+ vlog.info("Attaching to virtual desktop");
+ new_device = new WindowDC(0);
+ }
+
+ // Get the coordinates of the specified dispay device
+ Rect newScreenRect;
+ if (deviceName.buf[0]) {
+ MonitorInfo info(CStr(deviceName.buf));
+ newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
+ info.rcMonitor.right, info.rcMonitor.bottom);
+ } else {
+ newScreenRect = new_device->getClipBox();
+ }
+
+ // If nothing has changed & a recreate has not been forced, delete
+ // the new device context and return
+ if (pb && !force &&
+ newScreenRect.equals(screenRect) &&
+ new_device->getPF().equal(pb->getPF())) {
+ delete new_device;
+ return;
+ }
+
+ // Flush any existing changes to the server
+ flushChangeTracker();
+
+ // Delete the old pixelbuffer and device context
+ vlog.debug("deleting old pixel buffer & device");
+ if (pb)
+ delete pb;
+ if (device)
+ delete device;
+
+ // Create a DeviceFrameBuffer attached to the new device
+ vlog.debug("creating pixel buffer");
+ DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
+
+ // Replace the old PixelBuffer
+ screenRect = newScreenRect;
+ pb = new_buffer;
+ device = new_device;
+
+ // Initialise the pixels
+ pb->grabRegion(pb->getRect());
+
+ // Prevent future grabRect operations from throwing exceptions
+ pb->setIgnoreGrabErrors(true);
+
+ // Update the clipping update tracker
+ clipper.setClipRect(pb->getRect());
+
+ // Inform the core of the changes
+ if (core)
+ core->setScreenRect(screenRect);
+
+ // Inform the server of the changes
+ if (server)
+ server->setPixelBuffer(pb);
+}
+
+bool SDisplay::flushChangeTracker() {
+ if (updates.is_empty())
+ return false;
+
+ vlog.write(120, "flushChangeTracker");
+
+ // Translate the update coordinates from Screen coords to Desktop
+ updates.translate(screenRect.tl.negate());
+
+ // Clip the updates & flush them to the server
+ updates.copyTo(&clipper);
+ updates.clear();
+ return true;
+}