Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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