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