KasmVNC/win/rfb_win32/SDisplay.cxx
2020-09-21 13:17:11 +03:00

495 lines
14 KiB
C++

/* 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/MonitorInfo.h>
#include <rfb_win32/SDisplayCorePolling.h>
#include <rfb_win32/SDisplayCoreWMHooks.h>
#include <rfb/Exception.h>
#include <rfb/LogWriter.h>
#include <rfb/ledStates.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::disableEffects("DisableEffects",
"Disable desktop user interface effects when the server is in use.", false);
//////////////////////////////////////////////////////////////////////////////
//
// SDisplay
//
// -=- 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), ledState(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 {
LockWorkStation();
}
}
}
// 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 == 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("%s", 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 (removeWallpaper)
cleanDesktop->disableWallpaper();
if (disableEffects)
cleanDesktop->disableEffects();
isWallpaperRemoved = removeWallpaper;
areEffectsDisabled = disableEffects;
checkLedState();
if (server)
server->setLEDState(ledState);
}
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::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) ||
(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 keysym, rdr::U32 keycode, bool down) {
// - Check that the SDesktop doesn't need restarting
if (isRestartRequired())
restartCore();
if (kbd)
kbd->keyEvent(keysym, keycode, down);
}
bool SDisplay::checkLedState() {
unsigned state = 0;
if (GetKeyState(VK_SCROLL) & 0x0001)
state |= ledScrollLock;
if (GetKeyState(VK_NUMLOCK) & 0x0001)
state |= ledNumLock;
if (GetKeyState(VK_CAPITAL) & 0x0001)
state |= ledCapsLock;
if (ledState != state) {
ledState = state;
return true;
}
return false;
}
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::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;
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) {
// - 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("%s", 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);
old_cursor = info;
}
// Flush any changes to the server
flushChangeTracker();
// Forward current LED state to the server
if (checkLedState())
server->setLEDState(ledState);
}
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;
}