blob: 4916c4866c3d03b7bd7fa6d33c448620002e5fa0 [file] [log] [blame]
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00001/* Copyright (C) 2002-2004 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 <assert.h>
24
25#include <rfb_win32/SDisplay.h>
26#include <rfb_win32/Service.h>
27#include <rfb_win32/WMShatter.h>
28#include <rfb_win32/osVersion.h>
29#include <rfb_win32/Win32Util.h>
30#include <rfb_win32/IntervalTimer.h>
31#include <rfb_win32/CleanDesktop.h>
32
33#include <rfb/util.h>
34#include <rfb/LogWriter.h>
35#include <rfb/Exception.h>
36
37#include <rfb/Configuration.h>
38
39using namespace rdr;
40using namespace rfb;
41using namespace rfb::win32;
42
43static LogWriter vlog("SDisplay");
44
45// - SDisplay-specific configuration options
46
47BoolParameter rfb::win32::SDisplay::use_hooks("UseHooks",
48 "Set hooks in the operating system to capture display updates more efficiently", true);
49BoolParameter 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");
53
54BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
55 "Remove the desktop wallpaper when the server in in use.", false);
56BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern",
57 "Remove the desktop background pattern when the server in in use.", false);
58BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
59 "Disable desktop user interface effects when the server is in use.", false);
60
61
62// - WM_TIMER ID values
63
64#define TIMER_CURSOR 1
65#define TIMER_UPDATE 2
66#define TIMER_UPDATE_AND_POLL 3
67
68
69// -=- Polling settings
70
71const int POLLING_SEGMENTS = 16;
72
73const int FG_POLLING_FPS = 20;
74const int FG_POLLING_FS_INTERVAL = 1000 / FG_POLLING_FPS;
75const int FG_POLLING_INTERVAL = FG_POLLING_FS_INTERVAL / POLLING_SEGMENTS;
76
77const int BG_POLLING_FS_INTERVAL = 5000;
78const int BG_POLLING_INTERVAL = BG_POLLING_FS_INTERVAL / POLLING_SEGMENTS;
79
80
81//////////////////////////////////////////////////////////////////////////////
82//
83// SDisplayCore
84//
85
86// The SDisplay Core object is created by SDisplay's start() method
87// and deleted by its stop() method.
88// The Core must be created in the current input desktop in order
89// to operate - SDisplay is responsible for ensuring that.
90// The structures contained in the Core are manipulated directly
91// by the SDisplay, which is also responsible for detecting when
92// a desktop-switch is required.
93
94class rfb::win32::SDisplayCore : public MsgWindow {
95public:
96 SDisplayCore(SDisplay* display);
97 ~SDisplayCore();
98
99 void setPixelBuffer(DeviceFrameBuffer* pb_);
100
101 bool isRestartRequired();
102
103 virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam);
104
105 // -=- Timers
106 IntervalTimer pollTimer;
107 IntervalTimer cursorTimer;
108
109 // -=- Input handling
110 rfb::win32::SPointer ptr;
111 rfb::win32::SKeyboard kbd;
112 rfb::win32::Clipboard clipboard;
113
114 // -=- Hook handling objects used outside thread run() method
115 WMCopyRect wm_copyrect;
116 WMPoller wm_poller;
117 WMCursor cursor;
118 WMMonitor wm_monitor;
119 WMHooks wm_hooks;
120 WMBlockInput wm_input;
121
122 // -=- Tidying the desktop
123 CleanDesktop cleanDesktop;
124 bool isWallpaperRemoved;
125 bool isPatternRemoved;
126 bool areEffectsDisabled;
127
128 // -=- Full screen polling
129 int poll_next_y;
130 int poll_y_increment;
131
132 // Are we using hooks?
133 bool use_hooks;
134 bool using_hooks;
135
136 // State of the display object
137 SDisplay* display;
138};
139
140SDisplayCore::SDisplayCore(SDisplay* display_)
141: MsgWindow(_T("SDisplayCore")), display(display_),
142 using_hooks(0), use_hooks(rfb::win32::SDisplay::use_hooks),
143 isWallpaperRemoved(rfb::win32::SDisplay::removeWallpaper),
144 isPatternRemoved(rfb::win32::SDisplay::removePattern),
145 areEffectsDisabled(rfb::win32::SDisplay::disableEffects),
146 pollTimer(getHandle(), TIMER_UPDATE_AND_POLL),
147 cursorTimer(getHandle(), TIMER_CURSOR) {
148 setPixelBuffer(display->pb);
149}
150
151SDisplayCore::~SDisplayCore() {
152}
153
154void SDisplayCore::setPixelBuffer(DeviceFrameBuffer* pb) {
155 poll_y_increment = (display->pb->height()+POLLING_SEGMENTS-1)/POLLING_SEGMENTS;
156 poll_next_y = display->screenRect.tl.y;
157 wm_hooks.setClipRect(display->screenRect);
158 wm_copyrect.setClipRect(display->screenRect);
159 wm_poller.setClipRect(display->screenRect);
160}
161
162
163bool SDisplayCore::isRestartRequired() {
164 // - We must restart the SDesktop if:
165 // 1. We are no longer in the input desktop.
166 // 2. The use_hooks setting has changed.
167
168 // - Check that we are in the input desktop
169 if (rfb::win32::desktopChangeRequired())
170 return true;
171
172 // - Check that the hooks setting hasn't changed
173 // NB: We can't just check using_hooks because that can be false
174 // because they failed, even though use_hooks is true!
175 if (use_hooks != rfb::win32::SDisplay::use_hooks)
176 return true;
177
178 // - Check that the desktop optimisation settings haven't changed
179 // This isn't very efficient, but it shouldn't change very often!
180 if ((isWallpaperRemoved != rfb::win32::SDisplay::removeWallpaper) ||
181 (isPatternRemoved != rfb::win32::SDisplay::removePattern) ||
182 (areEffectsDisabled != rfb::win32::SDisplay::disableEffects))
183 return true;
184
185 return false;
186}
187
188LRESULT SDisplayCore::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) {
189 switch (msg) {
190
191 case WM_TIMER:
192
193 if (display->server && display->server->clientsReadyForUpdate()) {
194
195 // - Check that the SDesktop doesn't need restarting
196 if (isRestartRequired()) {
197 display->restart();
198 return 0;
199 }
200
201 // - Action depends on the timer message type
202 switch (wParam) {
203
204 // POLL THE SCREEN
205 case TIMER_UPDATE_AND_POLL:
206 // Handle window dragging, polling of consoles, etc.
207 while (wm_poller.processEvent()) {}
208
209 // Poll the next strip of the screen (in Screen coordinates)
210 {
211 Rect pollrect = display->screenRect;
212 if (poll_next_y >= pollrect.br.y) {
213 // Yes. Reset the counter and return
214 poll_next_y = pollrect.tl.y;
215 } else {
216 // No. Poll the next section
217 pollrect.tl.y = poll_next_y;
218 poll_next_y += poll_y_increment;
219 pollrect.br.y = min(poll_next_y, pollrect.br.y);
220 display->add_changed(pollrect);
221 }
222 }
223 break;
224
225 case TIMER_CURSOR:
226 display->triggerUpdate();
227 break;
228
229 };
230
231 }
232 return 0;
233
234 };
235
236 return MsgWindow::processMessage(msg, wParam, lParam);
237}
238
239//////////////////////////////////////////////////////////////////////////////
240//
241// SDisplay
242//
243
244// -=- Constructor/Destructor
245
246SDisplay::SDisplay(const TCHAR* devName)
247 : server(0), change_tracker(true), pb(0),
248 deviceName(tstrDup(devName)), device(0), releaseDevice(false),
249 core(0), statusLocation(0)
250{
251 updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
252}
253
254SDisplay::~SDisplay()
255{
256 // XXX when the VNCServer has been deleted with clients active, stop()
257 // doesn't get called - this ought to be fixed in VNCServerST. In any event,
258 // we should never call any methods on VNCServer once we're being deleted.
259 // This is because it is supposed to be guaranteed that the SDesktop exists
260 // throughout the lifetime of the VNCServer. So if we're being deleted, then
261 // the VNCServer ought not to exist and therefore we shouldn't invoke any
262 // methods on it. Setting server to zero here ensures that stop() doesn't
263 // call setPixelBuffer(0) on the server.
264 server = 0;
265 if (core) stop();
266}
267
268
269// -=- SDesktop interface
270
271void SDisplay::start(VNCServer* vs)
272{
273 vlog.debug("starting");
274 server = vs;
275
276 // Switch to the current input desktop
277 // ***
278 if (rfb::win32::desktopChangeRequired()) {
279 if (!rfb::win32::changeDesktop())
280 throw rdr::Exception("unable to switch into input desktop");
281 }
282
283 // Clear the change tracker
284 change_tracker.clear();
285
286 // Create the framebuffer object
287 recreatePixelBuffer();
288
289 // Create the SDisplayCore
290 core = new SDisplayCore(this);
291 assert(core);
292
293 // Start display monitor and clipboard handler
294 core->wm_monitor.setNotifier(this);
295 core->clipboard.setNotifier(this);
296
297 // Apply desktop optimisations
298 if (removePattern)
299 core->cleanDesktop.disablePattern();
300 if (removeWallpaper)
301 core->cleanDesktop.disableWallpaper();
302 if (disableEffects)
303 core->cleanDesktop.disableEffects();
304
305 // Start hooks
306 core->wm_hooks.setClipRect(screenRect);
307 if (core->use_hooks) {
308 // core->wm_hooks.setDiagnosticRange(0, 0x400-1);
309 core->using_hooks = core->wm_hooks.setUpdateTracker(this);
310 if (!core->using_hooks)
311 vlog.debug("hook subsystem failed to initialise");
312 }
313
314 // Set up timers
315 core->pollTimer.start(core->using_hooks ? BG_POLLING_INTERVAL : FG_POLLING_INTERVAL);
316 core->cursorTimer.start(10);
317
318 // Register an interest in faked copyrect events
319 core->wm_copyrect.setUpdateTracker(&change_tracker);
320 core->wm_copyrect.setClipRect(screenRect);
321
322 // Polling of particular windows on the desktop
323 core->wm_poller.setUpdateTracker(&change_tracker);
324 core->wm_poller.setClipRect(screenRect);
325
326 vlog.debug("started");
327
328 if (statusLocation) *statusLocation = true;
329}
330
331void SDisplay::stop()
332{
333 vlog.debug("stopping");
334 if (core) {
335 // If SDisplay was actually active then perform the disconnect action
336 CharArray action = disconnectAction.getData();
337 if (stricmp(action.buf, "Logoff") == 0) {
338 ExitWindowsEx(EWX_LOGOFF, 0);
339 } else if (stricmp(action.buf, "Lock") == 0) {
340 typedef BOOL (WINAPI *_LockWorkStation_proto)();
341 DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
342 if (_LockWorkStation.isValid())
343 (*_LockWorkStation)();
344 else
345 ExitWindowsEx(EWX_LOGOFF, 0);
346 }
347 }
348 delete core;
349 core = 0;
350 delete pb;
351 pb = 0;
352 if (device) {
353 if (releaseDevice)
354 ReleaseDC(0, device);
355 else
356 DeleteDC(device);
357 }
358 device = 0;
359 if (server)
360 server->setPixelBuffer(0);
361
362 server = 0;
363 vlog.debug("stopped");
364
365 if (statusLocation) *statusLocation = false;
366}
367
368void SDisplay::restart() {
369 vlog.debug("restarting");
370 // Close down the hooks
371 delete core;
372 core = 0;
373 try {
374 // Re-start the hooks if possible
375 start(server);
376 vlog.debug("restarted");
377 } catch (rdr::Exception& e) {
378 // If start() fails then we MUST disconnect all clients,
379 // to cause the server to stop using the desktop.
380 // Otherwise, the SDesktop is in an inconsistent state
381 // and the server will crash
382 server->closeClients(e.str());
383 }
384}
385
386
387void SDisplay::pointerEvent(const Point& pos, rdr::U8 buttonmask) {
388 if (pb->getRect().contains(pos)) {
389 Point screenPos = pos.translate(screenRect.tl);
390 core->ptr.pointerEvent(screenPos, buttonmask);
391 }
392}
393
394void SDisplay::keyEvent(rdr::U32 key, bool down) {
395 core->kbd.keyEvent(key, down);
396}
397
398void SDisplay::clientCutText(const char* text, int len) {
399 CharArray clip_sz(len+1);
400 memcpy(clip_sz.buf, text, len);
401 clip_sz.buf[len] = 0;
402 core->clipboard.setClipText(clip_sz.buf);
403}
404
405
406void SDisplay::framebufferUpdateRequest()
407{
408 triggerUpdate();
409}
410
411Point SDisplay::getFbSize() {
412 bool startAndStop = !core;
413 // If not started, do minimal initialisation to get desktop size.
414 if (startAndStop) recreatePixelBuffer();
415 Point result = Point(pb->width(), pb->height());
416 // Destroy the initialised structures.
417 if (startAndStop) stop();
418 return result;
419}
420
421
422void
423SDisplay::add_changed(const Region& rgn) {
424 change_tracker.add_changed(rgn);
425 triggerUpdate();
426}
427
428void
429SDisplay::add_copied(const Region& dest, const Point& delta) {
430 change_tracker.add_copied(dest, delta);
431 triggerUpdate();
432}
433
434
435void
436SDisplay::notifyClipboardChanged(const char* text, int len) {
437 vlog.debug("clipboard text changed");
438 if (server)
439 server->serverCutText(text, len);
440}
441
442
443void
444SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
445 switch (evt) {
446 case WMMonitor::Notifier::DisplaySizeChanged:
447 vlog.debug("desktop size changed");
448 recreatePixelBuffer();
449 break;
450 case WMMonitor::Notifier::DisplayPixelFormatChanged:
451 vlog.debug("desktop format changed");
452 recreatePixelBuffer();
453 break;
454 case WMMonitor::Notifier::DisplayColourMapChanged:
Peter Åstrandc81a6522004-12-30 11:32:08 +0000455 vlog.debug("desktop colormap changed");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000456 pb->updateColourMap();
457 if (server)
458 server->setColourMapEntries();
459 break;
460 default:
461 vlog.error("unknown display event received");
462 }
463}
464
465bool
466SDisplay::processEvent(HANDLE event) {
467 if (event == updateEvent) {
468 vlog.info("processEvent");
469 ResetEvent(updateEvent);
470
471 // - If the SDisplay isn't even started then quit now
472 if (!core) {
473 vlog.error("not start()ed");
474 return true;
475 }
476
477 // - Ensure that the disableLocalInputs flag is respected
478 core->wm_input.blockInputs(SDisplay::disableLocalInputs);
479
480 // - Only process updates if the server is ready
481 if (server && server->clientsReadyForUpdate()) {
482 bool try_update = false;
483
484 // - Check that the SDesktop doesn't need restarting
485 if (core->isRestartRequired()) {
486 restart();
487 return true;
488 }
489
490 // *** window dragging can be improved - more frequent, more cunning about updates
491 while (core->wm_copyrect.processEvent()) {}
492
493 // Ensure the cursor is up to date
494 WMCursor::Info info = core->cursor.getCursorInfo();
495 if (old_cursor != info) {
496 // Update the cursor shape if the visibility has changed
497 bool set_cursor = info.visible != old_cursor.visible;
498 // OR if the cursor is visible and the shape has changed.
499 set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
500
501 // Update the cursor shape
502 if (set_cursor)
503 pb->setCursor(info.visible ? info.cursor : 0, server);
504
505 // Update the cursor position
506 // NB: First translate from Screen coordinates to Desktop
507 Point desktopPos = info.position.translate(screenRect.tl.negate());
508 server->setCursorPos(desktopPos.x, desktopPos.y);
509 try_update = true;
510
511 old_cursor = info;
512 }
513
514 // Flush any changes to the server
515 try_update = flushChangeTracker() || try_update;
516 if (try_update)
517 server->tryUpdate();
518 }
519 } else {
520 CloseHandle(event);
521 return false;
522 }
523 return true;
524}
525
526
527// -=- Protected methods
528
529void
530SDisplay::recreatePixelBuffer() {
531 vlog.debug("attaching to device %s", deviceName);
532
533 // Open the specified display device
534 HDC new_device;
535 if (deviceName.buf) {
536 new_device = ::CreateDC(_T("DISPLAY"), deviceName.buf, NULL, NULL);
537 releaseDevice = false;
538 } else {
539 // If no device is specified, open entire screen.
540 // Doing this with CreateDC creates problems on multi-monitor systems.
541 new_device = ::GetDC(0);
542 releaseDevice = true;
543 }
544 if (!new_device)
545 throw SystemException("cannot open the display", GetLastError());
546
547 // Get the coordinates of the entire virtual display
548 Rect newScreenRect;
549 {
550 WindowDC rootDC(0);
551 RECT r;
552 if (!GetClipBox(rootDC, &r))
553 throw rdr::SystemException("GetClipBox", GetLastError());
554 newScreenRect = Rect(r.left, r.top, r.right, r.bottom);
555 }
556
557 // Create a DeviceFrameBuffer attached to it
558 DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(new_device);
559
560 // Has anything actually changed about the screen or the buffer?
561 if (!pb ||
562 (!newScreenRect.equals(screenRect)) ||
563 (!new_buffer->getPF().equal(pb->getPF())))
564 {
565 // Yes. Update the buffer state.
566 screenRect = newScreenRect;
567 vlog.debug("creating pixel buffer for device");
568
569 // Flush any existing changes to the server
570 flushChangeTracker();
571
572 // Replace the old PixelBuffer
573 if (pb) delete pb;
574 if (device) DeleteDC(device);
575 pb = new_buffer;
576 device = new_device;
577
578 // Initialise the pixels
579 pb->grabRegion(pb->getRect());
580
581 // Prevent future grabRect operations from throwing exceptions
582 pb->setIgnoreGrabErrors(true);
583
584 // Update the SDisplayCore if required
585 if (core)
586 core->setPixelBuffer(pb);
587
588 // Inform the server of the changes
589 if (server)
590 server->setPixelBuffer(pb);
591
592 } else {
593 delete new_buffer;
594 DeleteDC(new_device);
595 }
596}
597
598bool SDisplay::flushChangeTracker() {
599 if (change_tracker.is_empty())
600 return false;
601 // Translate the update coordinates from Screen coords to Desktop
602 change_tracker.translate(screenRect.tl.negate());
603 // Flush the updates through
604 change_tracker.get_update(*server);
605 change_tracker.clear();
606 return true;
607}
608
609void SDisplay::triggerUpdate() {
610 if (core)
611 SetEvent(updateEvent);
612}