blob: 26a7bbcef6ea7dd10206f02f7431a772c6b2dd5f [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18
19// -=- SDisplay.cxx
20//
21// The SDisplay class encapsulates a particular system display.
22
23#include <rfb_win32/SDisplay.h>
24#include <rfb_win32/Service.h>
25#include <rfb_win32/TsSessions.h>
26#include <rfb_win32/CleanDesktop.h>
27#include <rfb_win32/CurrentUser.h>
28#include <rfb_win32/DynamicFn.h>
29#include <rfb_win32/MonitorInfo.h>
30#include <rfb_win32/SDisplayCorePolling.h>
31#include <rfb_win32/SDisplayCoreWMHooks.h>
32#include <rfb_win32/SDisplayCoreDriver.h>
33#include <rfb/Exception.h>
34#include <rfb/LogWriter.h>
35
36
37using namespace rdr;
38using namespace rfb;
39using namespace rfb::win32;
40
41static LogWriter vlog("SDisplay");
42
43// - SDisplay-specific configuration options
44
45IntParameter rfb::win32::SDisplay::updateMethod("UpdateMethod",
46 "How to discover desktop updates; 0 - Polling, 1 - Application hooking, 2 - Driver hooking.", 1);
47BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs",
48 "Disable local keyboard and pointer input while the server is in use", false);
49StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction",
50 "Action to perform when all clients have disconnected. (None, Lock, Logoff)", "None");
51StringParameter displayDevice("DisplayDevice",
52 "Display device name of the monitor to be remoted, or empty to export the whole desktop.", "");
53BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
54 "Remove the desktop wallpaper when the server is in use.", false);
55BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern",
56 "Remove the desktop background pattern when the server is in use.", false);
57BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
58 "Disable desktop user interface effects when the server is in use.", false);
59
60
61//////////////////////////////////////////////////////////////////////////////
62//
63// SDisplay
64//
65
66typedef BOOL (WINAPI *_LockWorkStation_proto)();
67DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
68
69// -=- Constructor/Destructor
70
71SDisplay::SDisplay()
72 : server(0), pb(0), device(0),
73 core(0), ptr(0), kbd(0), clipboard(0),
74 inputs(0), monitor(0), cleanDesktop(0), cursor(0),
75 statusLocation(0)
76{
77 updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
78}
79
80SDisplay::~SDisplay()
81{
82 // XXX when the VNCServer has been deleted with clients active, stop()
83 // doesn't get called - this ought to be fixed in VNCServerST. In any event,
84 // we should never call any methods on VNCServer once we're being deleted.
85 // This is because it is supposed to be guaranteed that the SDesktop exists
86 // throughout the lifetime of the VNCServer. So if we're being deleted, then
87 // the VNCServer ought not to exist and therefore we shouldn't invoke any
88 // methods on it. Setting server to zero here ensures that stop() doesn't
89 // call setPixelBuffer(0) on the server.
90 server = 0;
91 if (core) stop();
92}
93
94
95// -=- SDesktop interface
96
97void SDisplay::start(VNCServer* vs)
98{
99 vlog.debug("starting");
100
101 // Try to make session zero the console session
102 if (!inConsoleSession())
103 setConsoleSession();
104
105 // Start the SDisplay core
106 server = vs;
107 startCore();
108
109 vlog.debug("started");
110
111 if (statusLocation) *statusLocation = true;
112}
113
114void SDisplay::stop()
115{
116 vlog.debug("stopping");
117
118 // If we successfully start()ed then perform the DisconnectAction
119 if (core) {
120 CurrentUserToken cut;
Peter Åstrandb22dbef2008-12-09 14:57:53 +0000121 CharArray action(disconnectAction.getData());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000122 if (stricmp(action.buf, "Logoff") == 0) {
123 if (!cut.h)
124 vlog.info("ignoring DisconnectAction=Logoff - no current user");
125 else
126 ExitWindowsEx(EWX_LOGOFF, 0);
127 } else if (stricmp(action.buf, "Lock") == 0) {
128 if (!cut.h) {
129 vlog.info("ignoring DisconnectAction=Lock - no current user");
130 } else {
131 if (_LockWorkStation.isValid())
132 (*_LockWorkStation)();
133 else
134 ExitWindowsEx(EWX_LOGOFF, 0);
135 }
136 }
137 }
138
139 // Stop the SDisplayCore
140 if (server)
141 server->setPixelBuffer(0);
142 stopCore();
143 server = 0;
144
145 vlog.debug("stopped");
146
147 if (statusLocation) *statusLocation = false;
148}
149
150
151void SDisplay::startCore() {
152
153 // Currently, we just check whether we're in the console session, and
154 // fail if not
155 if (!inConsoleSession())
156 throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
157
158 // Switch to the current input desktop
159 if (rfb::win32::desktopChangeRequired()) {
160 if (!rfb::win32::changeDesktop())
161 throw rdr::Exception("unable to switch into input desktop");
162 }
163
164 // Initialise the change tracker and clipper
165 updates.clear();
166 clipper.setUpdateTracker(server);
167
168 // Create the framebuffer object
169 recreatePixelBuffer(true);
170
171 // Create the SDisplayCore
172 updateMethod_ = updateMethod;
173 int tryMethod = updateMethod_;
174 while (!core) {
175 try {
176 if (tryMethod == 2)
177 core = new SDisplayCoreDriver(this, &updates);
178 else if (tryMethod == 1)
179 core = new SDisplayCoreWMHooks(this, &updates);
180 else
181 core = new SDisplayCorePolling(this, &updates);
182 core->setScreenRect(screenRect);
183 } catch (rdr::Exception& e) {
184 delete core; core = 0;
185 if (tryMethod == 0)
186 throw rdr::Exception("unable to access desktop");
187 tryMethod--;
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000188 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000189 }
190 }
191 vlog.info("Started %s", core->methodName());
192
193 // Start display monitor, clipboard handler and input handlers
194 monitor = new WMMonitor;
195 monitor->setNotifier(this);
196 clipboard = new Clipboard;
197 clipboard->setNotifier(this);
198 ptr = new SPointer;
199 kbd = new SKeyboard;
200 inputs = new WMBlockInput;
201 cursor = new WMCursor;
202
203 // Apply desktop optimisations
204 cleanDesktop = new CleanDesktop;
205 if (removePattern)
206 cleanDesktop->disablePattern();
207 if (removeWallpaper)
208 cleanDesktop->disableWallpaper();
209 if (disableEffects)
210 cleanDesktop->disableEffects();
211 isWallpaperRemoved = removeWallpaper;
212 isPatternRemoved = removePattern;
213 areEffectsDisabled = disableEffects;
214}
215
216void SDisplay::stopCore() {
217 if (core)
218 vlog.info("Stopping %s", core->methodName());
219 delete core; core = 0;
220 delete pb; pb = 0;
221 delete device; device = 0;
222 delete monitor; monitor = 0;
223 delete clipboard; clipboard = 0;
224 delete inputs; inputs = 0;
225 delete ptr; ptr = 0;
226 delete kbd; kbd = 0;
227 delete cleanDesktop; cleanDesktop = 0;
228 delete cursor; cursor = 0;
229 ResetEvent(updateEvent);
230}
231
232
233bool SDisplay::areHooksAvailable() {
234 return WMHooks::areAvailable();
235}
236
237bool SDisplay::isDriverAvailable() {
238 return SDisplayCoreDriver::isAvailable();
239}
240
241
242bool SDisplay::isRestartRequired() {
243 // - We must restart the SDesktop if:
244 // 1. We are no longer in the input desktop.
245 // 2. The any setting has changed.
246
247 // - Check that our session is the Console
248 if (!inConsoleSession())
249 return true;
250
251 // - Check that we are in the input desktop
252 if (rfb::win32::desktopChangeRequired())
253 return true;
254
255 // - Check that the update method setting hasn't changed
256 // NB: updateMethod reflects the *selected* update method, not
257 // necessarily the one in use, since we fall back to simpler
258 // methods if more advanced ones fail!
259 if (updateMethod_ != updateMethod)
260 return true;
261
262 // - Check that the desktop optimisation settings haven't changed
263 // This isn't very efficient, but it shouldn't change very often!
264 if ((isWallpaperRemoved != removeWallpaper) ||
265 (isPatternRemoved != removePattern) ||
266 (areEffectsDisabled != disableEffects))
267 return true;
268
269 return false;
270}
271
272
273void SDisplay::restartCore() {
274 vlog.info("restarting");
275
276 // Stop the existing Core related resources
277 stopCore();
278 try {
279 // Start a new Core if possible
280 startCore();
281 vlog.info("restarted");
282 } catch (rdr::Exception& e) {
283 // If startCore() fails then we MUST disconnect all clients,
284 // to cause the server to stop() the desktop.
285 // Otherwise, the SDesktop is in an inconsistent state
286 // and the server will crash.
287 server->closeClients(e.str());
288 }
289}
290
291
292void SDisplay::pointerEvent(const Point& pos, int buttonmask) {
293 if (pb->getRect().contains(pos)) {
294 Point screenPos = pos.translate(screenRect.tl);
295 // - Check that the SDesktop doesn't need restarting
296 if (isRestartRequired())
297 restartCore();
298 if (ptr)
299 ptr->pointerEvent(screenPos, buttonmask);
300 }
301}
302
303void SDisplay::keyEvent(rdr::U32 key, bool down) {
304 // - Check that the SDesktop doesn't need restarting
305 if (isRestartRequired())
306 restartCore();
307 if (kbd)
308 kbd->keyEvent(key, down);
309}
310
311void SDisplay::clientCutText(const char* text, int len) {
312 CharArray clip_sz(len+1);
313 memcpy(clip_sz.buf, text, len);
314 clip_sz.buf[len] = 0;
315 clipboard->setClipText(clip_sz.buf);
316}
317
318
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000319Point SDisplay::getFbSize() {
320 bool startAndStop = !core;
321
322 // If not started, do minimal initialisation to get desktop size.
323 if (startAndStop)
324 recreatePixelBuffer();
325 Point result = Point(pb->width(), pb->height());
326
327 // Destroy the initialised structures.
328 if (startAndStop)
329 stopCore();
330 return result;
331}
332
333
334void
335SDisplay::notifyClipboardChanged(const char* text, int len) {
336 vlog.debug("clipboard text changed");
337 if (server)
338 server->serverCutText(text, len);
339}
340
341
342void
343SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
344 switch (evt) {
345 case WMMonitor::Notifier::DisplaySizeChanged:
346 vlog.debug("desktop size changed");
347 recreatePixelBuffer();
348 break;
349 case WMMonitor::Notifier::DisplayPixelFormatChanged:
350 vlog.debug("desktop format changed");
351 recreatePixelBuffer();
352 break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000353 default:
354 vlog.error("unknown display event received");
355 }
356}
357
358void
359SDisplay::processEvent(HANDLE event) {
360 if (event == updateEvent) {
361 vlog.write(120, "processEvent");
362 ResetEvent(updateEvent);
363
364 // - If the SDisplay isn't even started then quit now
365 if (!core) {
366 vlog.error("not start()ed");
367 return;
368 }
369
370 // - Ensure that the disableLocalInputs flag is respected
371 inputs->blockInputs(disableLocalInputs);
372
373 // - Only process updates if the server is ready
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000374 if (server) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000375 // - Check that the SDesktop doesn't need restarting
376 if (isRestartRequired()) {
377 restartCore();
378 return;
379 }
380
381 // - Flush any updates from the core
382 try {
383 core->flushUpdates();
384 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000385 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000386 restartCore();
387 return;
388 }
389
390 // Ensure the cursor is up to date
391 WMCursor::Info info = cursor->getCursorInfo();
392 if (old_cursor != info) {
393 // Update the cursor shape if the visibility has changed
394 bool set_cursor = info.visible != old_cursor.visible;
395 // OR if the cursor is visible and the shape has changed.
396 set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
397
398 // Update the cursor shape
399 if (set_cursor)
400 pb->setCursor(info.visible ? info.cursor : 0, server);
401
402 // Update the cursor position
403 // NB: First translate from Screen coordinates to Desktop
404 Point desktopPos = info.position.translate(screenRect.tl.negate());
405 server->setCursorPos(desktopPos);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000406
407 old_cursor = info;
408 }
409
410 // Flush any changes to the server
Pierre Ossmanbbf955e2011-11-08 12:44:10 +0000411 flushChangeTracker();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000412 }
413 return;
414 }
415 throw rdr::Exception("No such event");
416}
417
418
419// -=- Protected methods
420
421void
422SDisplay::recreatePixelBuffer(bool force) {
423 // Open the specified display device
424 // If no device is specified, open entire screen using GetDC().
425 // Opening the whole display with CreateDC doesn't work on multi-monitor
426 // systems for some reason.
427 DeviceContext* new_device = 0;
428 TCharArray deviceName(displayDevice.getData());
429 if (deviceName.buf[0]) {
430 vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
431 new_device = new DeviceDC(deviceName.buf);
432 }
433 if (!new_device) {
434 vlog.info("Attaching to virtual desktop");
435 new_device = new WindowDC(0);
436 }
437
438 // Get the coordinates of the specified dispay device
439 Rect newScreenRect;
440 if (deviceName.buf[0]) {
441 MonitorInfo info(CStr(deviceName.buf));
442 newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
443 info.rcMonitor.right, info.rcMonitor.bottom);
444 } else {
445 newScreenRect = new_device->getClipBox();
446 }
447
448 // If nothing has changed & a recreate has not been forced, delete
449 // the new device context and return
450 if (pb && !force &&
451 newScreenRect.equals(screenRect) &&
452 new_device->getPF().equal(pb->getPF())) {
453 delete new_device;
454 return;
455 }
456
457 // Flush any existing changes to the server
458 flushChangeTracker();
459
460 // Delete the old pixelbuffer and device context
461 vlog.debug("deleting old pixel buffer & device");
462 if (pb)
463 delete pb;
464 if (device)
465 delete device;
466
467 // Create a DeviceFrameBuffer attached to the new device
468 vlog.debug("creating pixel buffer");
469 DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
470
471 // Replace the old PixelBuffer
472 screenRect = newScreenRect;
473 pb = new_buffer;
474 device = new_device;
475
476 // Initialise the pixels
477 pb->grabRegion(pb->getRect());
478
479 // Prevent future grabRect operations from throwing exceptions
480 pb->setIgnoreGrabErrors(true);
481
482 // Update the clipping update tracker
483 clipper.setClipRect(pb->getRect());
484
485 // Inform the core of the changes
486 if (core)
487 core->setScreenRect(screenRect);
488
489 // Inform the server of the changes
490 if (server)
491 server->setPixelBuffer(pb);
492}
493
494bool SDisplay::flushChangeTracker() {
495 if (updates.is_empty())
496 return false;
497
498 vlog.write(120, "flushChangeTracker");
499
500 // Translate the update coordinates from Screen coords to Desktop
501 updates.translate(screenRect.tl.negate());
502
503 // Clip the updates & flush them to the server
504 updates.copyTo(&clipper);
505 updates.clear();
506 return true;
507}