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