You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SDisplay.cxx 14KB

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