blob: 06eccd9a21fb750a1fefc20ede0c8c1bb65e4406 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
Pierre Ossman66f1db52019-05-02 12:32:03 +02002 * Copyright 2011-2019 Pierre Ossman for Cendio AB
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00003 *
4 * This is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This software is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this software; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
17 * USA.
18 */
19
20// -=- SDisplay.cxx
21//
22// The SDisplay class encapsulates a particular system display.
23
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020024#include <assert.h>
25
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000026#include <rfb_win32/SDisplay.h>
27#include <rfb_win32/Service.h>
28#include <rfb_win32/TsSessions.h>
29#include <rfb_win32/CleanDesktop.h>
30#include <rfb_win32/CurrentUser.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000031#include <rfb_win32/MonitorInfo.h>
32#include <rfb_win32/SDisplayCorePolling.h>
33#include <rfb_win32/SDisplayCoreWMHooks.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000034#include <rfb/Exception.h>
35#include <rfb/LogWriter.h>
Rahul Kalec719e4a2017-07-13 00:36:02 +020036#include <rfb/ledStates.h>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000037
38
39using namespace rdr;
40using namespace rfb;
41using namespace rfb::win32;
42
43static LogWriter vlog("SDisplay");
44
45// - SDisplay-specific configuration options
46
47IntParameter rfb::win32::SDisplay::updateMethod("UpdateMethod",
Pierre Ossmand06906d2019-09-06 14:47:28 +020048 "How to discover desktop updates; 0 - Polling, 1 - Application hooking, 2 - Driver hooking.", 0);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000049BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs",
50 "Disable local keyboard and pointer input while the server is in use", false);
51StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction",
52 "Action to perform when all clients have disconnected. (None, Lock, Logoff)", "None");
53StringParameter displayDevice("DisplayDevice",
54 "Display device name of the monitor to be remoted, or empty to export the whole desktop.", "");
55BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
56 "Remove the desktop wallpaper when the server is in use.", false);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000057BoolParameter 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
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000066// -=- Constructor/Destructor
67
68SDisplay::SDisplay()
69 : server(0), pb(0), device(0),
70 core(0), ptr(0), kbd(0), clipboard(0),
71 inputs(0), monitor(0), cleanDesktop(0), cursor(0),
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +020072 statusLocation(0), queryConnectionHandler(0), ledState(0)
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000073{
74 updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
Pierre Ossman10688ef2018-09-29 11:24:19 +020075 terminateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000076}
77
78SDisplay::~SDisplay()
79{
80 // XXX when the VNCServer has been deleted with clients active, stop()
81 // doesn't get called - this ought to be fixed in VNCServerST. In any event,
82 // we should never call any methods on VNCServer once we're being deleted.
83 // This is because it is supposed to be guaranteed that the SDesktop exists
84 // throughout the lifetime of the VNCServer. So if we're being deleted, then
85 // the VNCServer ought not to exist and therefore we shouldn't invoke any
86 // methods on it. Setting server to zero here ensures that stop() doesn't
87 // call setPixelBuffer(0) on the server.
88 server = 0;
89 if (core) stop();
90}
91
92
93// -=- SDesktop interface
94
95void SDisplay::start(VNCServer* vs)
96{
97 vlog.debug("starting");
98
99 // Try to make session zero the console session
100 if (!inConsoleSession())
101 setConsoleSession();
102
103 // Start the SDisplay core
104 server = vs;
105 startCore();
106
107 vlog.debug("started");
108
109 if (statusLocation) *statusLocation = true;
110}
111
112void SDisplay::stop()
113{
114 vlog.debug("stopping");
115
116 // If we successfully start()ed then perform the DisconnectAction
117 if (core) {
118 CurrentUserToken cut;
Peter Åstrandb22dbef2008-12-09 14:57:53 +0000119 CharArray action(disconnectAction.getData());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000120 if (stricmp(action.buf, "Logoff") == 0) {
121 if (!cut.h)
122 vlog.info("ignoring DisconnectAction=Logoff - no current user");
123 else
124 ExitWindowsEx(EWX_LOGOFF, 0);
125 } else if (stricmp(action.buf, "Lock") == 0) {
126 if (!cut.h) {
127 vlog.info("ignoring DisconnectAction=Lock - no current user");
128 } else {
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100129 LockWorkStation();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000130 }
131 }
132 }
133
134 // Stop the SDisplayCore
135 if (server)
136 server->setPixelBuffer(0);
137 stopCore();
138 server = 0;
139
140 vlog.debug("stopped");
141
142 if (statusLocation) *statusLocation = false;
143}
144
Pierre Ossman10688ef2018-09-29 11:24:19 +0200145void SDisplay::terminate()
146{
147 SetEvent(terminateEvent);
148}
149
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000150
Pierre Ossmaneef6c9a2018-10-05 17:11:25 +0200151void SDisplay::queryConnection(network::Socket* sock,
152 const char* userName)
153{
154 assert(server != NULL);
155
156 if (queryConnectionHandler) {
157 queryConnectionHandler->queryConnection(sock, userName);
158 return;
159 }
160
161 server->approveConnection(sock, true);
162}
163
164
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000165void SDisplay::startCore() {
166
167 // Currently, we just check whether we're in the console session, and
168 // fail if not
169 if (!inConsoleSession())
170 throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
171
172 // Switch to the current input desktop
173 if (rfb::win32::desktopChangeRequired()) {
174 if (!rfb::win32::changeDesktop())
175 throw rdr::Exception("unable to switch into input desktop");
176 }
177
178 // Initialise the change tracker and clipper
179 updates.clear();
180 clipper.setUpdateTracker(server);
181
182 // Create the framebuffer object
183 recreatePixelBuffer(true);
184
185 // Create the SDisplayCore
186 updateMethod_ = updateMethod;
187 int tryMethod = updateMethod_;
188 while (!core) {
189 try {
Pierre Ossman4ab1e5d2016-01-12 12:29:32 +0100190 if (tryMethod == 1)
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000191 core = new SDisplayCoreWMHooks(this, &updates);
192 else
193 core = new SDisplayCorePolling(this, &updates);
194 core->setScreenRect(screenRect);
195 } catch (rdr::Exception& e) {
196 delete core; core = 0;
197 if (tryMethod == 0)
198 throw rdr::Exception("unable to access desktop");
199 tryMethod--;
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000200 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000201 }
202 }
203 vlog.info("Started %s", core->methodName());
204
205 // Start display monitor, clipboard handler and input handlers
206 monitor = new WMMonitor;
207 monitor->setNotifier(this);
208 clipboard = new Clipboard;
209 clipboard->setNotifier(this);
210 ptr = new SPointer;
211 kbd = new SKeyboard;
212 inputs = new WMBlockInput;
213 cursor = new WMCursor;
214
215 // Apply desktop optimisations
216 cleanDesktop = new CleanDesktop;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000217 if (removeWallpaper)
218 cleanDesktop->disableWallpaper();
219 if (disableEffects)
220 cleanDesktop->disableEffects();
221 isWallpaperRemoved = removeWallpaper;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000222 areEffectsDisabled = disableEffects;
Rahul Kalec719e4a2017-07-13 00:36:02 +0200223
224 checkLedState();
225 if (server)
226 server->setLEDState(ledState);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000227}
228
229void SDisplay::stopCore() {
230 if (core)
231 vlog.info("Stopping %s", core->methodName());
232 delete core; core = 0;
233 delete pb; pb = 0;
234 delete device; device = 0;
235 delete monitor; monitor = 0;
236 delete clipboard; clipboard = 0;
237 delete inputs; inputs = 0;
238 delete ptr; ptr = 0;
239 delete kbd; kbd = 0;
240 delete cleanDesktop; cleanDesktop = 0;
241 delete cursor; cursor = 0;
242 ResetEvent(updateEvent);
243}
244
245
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000246bool SDisplay::isRestartRequired() {
247 // - We must restart the SDesktop if:
248 // 1. We are no longer in the input desktop.
249 // 2. The any setting has changed.
250
251 // - Check that our session is the Console
252 if (!inConsoleSession())
253 return true;
254
255 // - Check that we are in the input desktop
256 if (rfb::win32::desktopChangeRequired())
257 return true;
258
259 // - Check that the update method setting hasn't changed
260 // NB: updateMethod reflects the *selected* update method, not
261 // necessarily the one in use, since we fall back to simpler
262 // methods if more advanced ones fail!
263 if (updateMethod_ != updateMethod)
264 return true;
265
266 // - Check that the desktop optimisation settings haven't changed
267 // This isn't very efficient, but it shouldn't change very often!
268 if ((isWallpaperRemoved != removeWallpaper) ||
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000269 (areEffectsDisabled != disableEffects))
270 return true;
271
272 return false;
273}
274
275
276void SDisplay::restartCore() {
277 vlog.info("restarting");
278
279 // Stop the existing Core related resources
280 stopCore();
281 try {
282 // Start a new Core if possible
283 startCore();
284 vlog.info("restarted");
285 } catch (rdr::Exception& e) {
286 // If startCore() fails then we MUST disconnect all clients,
287 // to cause the server to stop() the desktop.
288 // Otherwise, the SDesktop is in an inconsistent state
289 // and the server will crash.
290 server->closeClients(e.str());
291 }
292}
293
294
Pierre Ossman615d16b2019-05-03 10:53:06 +0200295void SDisplay::handleClipboardRequest() {
296 CharArray data(clipboard->getClipText());
297 server->sendClipboardData(data.buf);
298}
299
300void SDisplay::handleClipboardAnnounce(bool available) {
301 // FIXME: Wait for an application to actually request it
302 if (available)
303 server->requestClipboard();
304}
305
306void SDisplay::handleClipboardData(const char* data) {
307 clipboard->setClipText(data);
308}
309
310
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000311void SDisplay::pointerEvent(const Point& pos, int buttonmask) {
312 if (pb->getRect().contains(pos)) {
313 Point screenPos = pos.translate(screenRect.tl);
314 // - Check that the SDesktop doesn't need restarting
315 if (isRestartRequired())
316 restartCore();
317 if (ptr)
318 ptr->pointerEvent(screenPos, buttonmask);
319 }
320}
321
Pierre Ossman5ae28212017-05-16 14:30:38 +0200322void SDisplay::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000323 // - Check that the SDesktop doesn't need restarting
324 if (isRestartRequired())
325 restartCore();
326 if (kbd)
Pierre Ossman5ae28212017-05-16 14:30:38 +0200327 kbd->keyEvent(keysym, keycode, down);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000328}
329
Rahul Kalec719e4a2017-07-13 00:36:02 +0200330bool SDisplay::checkLedState() {
331 unsigned state = 0;
332
333 if (GetKeyState(VK_SCROLL) & 0x0001)
334 state |= ledScrollLock;
335 if (GetKeyState(VK_NUMLOCK) & 0x0001)
336 state |= ledNumLock;
337 if (GetKeyState(VK_CAPITAL) & 0x0001)
338 state |= ledCapsLock;
339
340 if (ledState != state) {
341 ledState = state;
342 return true;
343 }
344
345 return false;
346}
347
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000348
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000349void
Pierre Ossman615d16b2019-05-03 10:53:06 +0200350SDisplay::notifyClipboardChanged(bool available) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000351 vlog.debug("clipboard text changed");
352 if (server)
Pierre Ossman615d16b2019-05-03 10:53:06 +0200353 server->announceClipboard(available);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000354}
355
356
357void
358SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
359 switch (evt) {
360 case WMMonitor::Notifier::DisplaySizeChanged:
361 vlog.debug("desktop size changed");
362 recreatePixelBuffer();
363 break;
364 case WMMonitor::Notifier::DisplayPixelFormatChanged:
365 vlog.debug("desktop format changed");
366 recreatePixelBuffer();
367 break;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000368 default:
369 vlog.error("unknown display event received");
370 }
371}
372
373void
374SDisplay::processEvent(HANDLE event) {
375 if (event == updateEvent) {
376 vlog.write(120, "processEvent");
377 ResetEvent(updateEvent);
378
379 // - If the SDisplay isn't even started then quit now
380 if (!core) {
381 vlog.error("not start()ed");
382 return;
383 }
384
385 // - Ensure that the disableLocalInputs flag is respected
386 inputs->blockInputs(disableLocalInputs);
387
388 // - Only process updates if the server is ready
Pierre Ossmana3ac01e2011-11-07 21:13:54 +0000389 if (server) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000390 // - Check that the SDesktop doesn't need restarting
391 if (isRestartRequired()) {
392 restartCore();
393 return;
394 }
395
396 // - Flush any updates from the core
397 try {
398 core->flushUpdates();
399 } catch (rdr::Exception& e) {
Pierre Ossmanad8609a2012-04-26 09:04:14 +0000400 vlog.error("%s", e.str());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000401 restartCore();
402 return;
403 }
404
405 // Ensure the cursor is up to date
406 WMCursor::Info info = cursor->getCursorInfo();
407 if (old_cursor != info) {
408 // Update the cursor shape if the visibility has changed
409 bool set_cursor = info.visible != old_cursor.visible;
410 // OR if the cursor is visible and the shape has changed.
411 set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
412
413 // Update the cursor shape
414 if (set_cursor)
415 pb->setCursor(info.visible ? info.cursor : 0, server);
416
417 // Update the cursor position
418 // NB: First translate from Screen coordinates to Desktop
419 Point desktopPos = info.position.translate(screenRect.tl.negate());
420 server->setCursorPos(desktopPos);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000421
422 old_cursor = info;
423 }
424
425 // Flush any changes to the server
Pierre Ossmanbbf955e2011-11-08 12:44:10 +0000426 flushChangeTracker();
Rahul Kalec719e4a2017-07-13 00:36:02 +0200427
428 // Forward current LED state to the server
429 if (checkLedState())
430 server->setLEDState(ledState);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000431 }
432 return;
433 }
434 throw rdr::Exception("No such event");
435}
436
437
438// -=- Protected methods
439
440void
441SDisplay::recreatePixelBuffer(bool force) {
442 // Open the specified display device
443 // If no device is specified, open entire screen using GetDC().
444 // Opening the whole display with CreateDC doesn't work on multi-monitor
445 // systems for some reason.
446 DeviceContext* new_device = 0;
447 TCharArray deviceName(displayDevice.getData());
448 if (deviceName.buf[0]) {
449 vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
450 new_device = new DeviceDC(deviceName.buf);
451 }
452 if (!new_device) {
453 vlog.info("Attaching to virtual desktop");
454 new_device = new WindowDC(0);
455 }
456
457 // Get the coordinates of the specified dispay device
458 Rect newScreenRect;
459 if (deviceName.buf[0]) {
460 MonitorInfo info(CStr(deviceName.buf));
461 newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
462 info.rcMonitor.right, info.rcMonitor.bottom);
463 } else {
464 newScreenRect = new_device->getClipBox();
465 }
466
467 // If nothing has changed & a recreate has not been forced, delete
468 // the new device context and return
469 if (pb && !force &&
470 newScreenRect.equals(screenRect) &&
471 new_device->getPF().equal(pb->getPF())) {
472 delete new_device;
473 return;
474 }
475
476 // Flush any existing changes to the server
477 flushChangeTracker();
478
479 // Delete the old pixelbuffer and device context
480 vlog.debug("deleting old pixel buffer & device");
481 if (pb)
482 delete pb;
483 if (device)
484 delete device;
485
486 // Create a DeviceFrameBuffer attached to the new device
487 vlog.debug("creating pixel buffer");
488 DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
489
490 // Replace the old PixelBuffer
491 screenRect = newScreenRect;
492 pb = new_buffer;
493 device = new_device;
494
495 // Initialise the pixels
496 pb->grabRegion(pb->getRect());
497
498 // Prevent future grabRect operations from throwing exceptions
499 pb->setIgnoreGrabErrors(true);
500
501 // Update the clipping update tracker
502 clipper.setClipRect(pb->getRect());
503
504 // Inform the core of the changes
505 if (core)
506 core->setScreenRect(screenRect);
507
508 // Inform the server of the changes
509 if (server)
510 server->setPixelBuffer(pb);
511}
512
513bool SDisplay::flushChangeTracker() {
514 if (updates.is_empty())
515 return false;
516
517 vlog.write(120, "flushChangeTracker");
518
519 // Translate the update coordinates from Screen coords to Desktop
520 updates.translate(screenRect.tl.negate());
521
522 // Clip the updates & flush them to the server
523 updates.copyTo(&clipper);
524 updates.clear();
525 return true;
526}