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