123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612 |
- /* 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 colormap 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);
- }
|