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