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