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