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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. #include <assert.h>
  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>
  28. #include <rfb_win32/MonitorInfo.h>
  29. #include <rfb_win32/SDisplayCorePolling.h>
  30. #include <rfb_win32/SDisplayCoreWMHooks.h>
  31. #include <rfb/Exception.h>
  32. #include <rfb/LogWriter.h>
  33. #include <rfb/ledStates.h>
  34. using namespace rdr;
  35. using namespace rfb;
  36. using namespace rfb::win32;
  37. static LogWriter vlog("SDisplay");
  38. // - SDisplay-specific configuration options
  39. IntParameter rfb::win32::SDisplay::updateMethod("UpdateMethod",
  40. "How to discover desktop updates; 0 - Polling, 1 - Application hooking, 2 - Driver hooking.", 1);
  41. BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs",
  42. "Disable local keyboard and pointer input while the server is in use", false);
  43. StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction",
  44. "Action to perform when all clients have disconnected. (None, Lock, Logoff)", "None");
  45. StringParameter displayDevice("DisplayDevice",
  46. "Display device name of the monitor to be remoted, or empty to export the whole desktop.", "");
  47. BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper",
  48. "Remove the desktop wallpaper when the server is in use.", false);
  49. BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
  50. "Disable desktop user interface effects when the server is in use.", false);
  51. //////////////////////////////////////////////////////////////////////////////
  52. //
  53. // SDisplay
  54. //
  55. // -=- Constructor/Destructor
  56. SDisplay::SDisplay()
  57. : server(0), pb(0), device(0),
  58. core(0), ptr(0), kbd(0), clipboard(0),
  59. inputs(0), monitor(0), cleanDesktop(0), cursor(0),
  60. statusLocation(0), queryConnectionHandler(0), ledState(0)
  61. {
  62. updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
  63. terminateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
  64. }
  65. SDisplay::~SDisplay()
  66. {
  67. // XXX when the VNCServer has been deleted with clients active, stop()
  68. // doesn't get called - this ought to be fixed in VNCServerST. In any event,
  69. // we should never call any methods on VNCServer once we're being deleted.
  70. // This is because it is supposed to be guaranteed that the SDesktop exists
  71. // throughout the lifetime of the VNCServer. So if we're being deleted, then
  72. // the VNCServer ought not to exist and therefore we shouldn't invoke any
  73. // methods on it. Setting server to zero here ensures that stop() doesn't
  74. // call setPixelBuffer(0) on the server.
  75. server = 0;
  76. if (core) stop();
  77. }
  78. // -=- SDesktop interface
  79. void SDisplay::start(VNCServer* vs)
  80. {
  81. vlog.debug("starting");
  82. // Try to make session zero the console session
  83. if (!inConsoleSession())
  84. setConsoleSession();
  85. // Start the SDisplay core
  86. server = vs;
  87. startCore();
  88. vlog.debug("started");
  89. if (statusLocation) *statusLocation = true;
  90. }
  91. void SDisplay::stop()
  92. {
  93. vlog.debug("stopping");
  94. // If we successfully start()ed then perform the DisconnectAction
  95. if (core) {
  96. CurrentUserToken cut;
  97. CharArray action(disconnectAction.getData());
  98. if (stricmp(action.buf, "Logoff") == 0) {
  99. if (!cut.h)
  100. vlog.info("ignoring DisconnectAction=Logoff - no current user");
  101. else
  102. ExitWindowsEx(EWX_LOGOFF, 0);
  103. } else if (stricmp(action.buf, "Lock") == 0) {
  104. if (!cut.h) {
  105. vlog.info("ignoring DisconnectAction=Lock - no current user");
  106. } else {
  107. LockWorkStation();
  108. }
  109. }
  110. }
  111. // Stop the SDisplayCore
  112. if (server)
  113. server->setPixelBuffer(0);
  114. stopCore();
  115. server = 0;
  116. vlog.debug("stopped");
  117. if (statusLocation) *statusLocation = false;
  118. }
  119. void SDisplay::terminate()
  120. {
  121. SetEvent(terminateEvent);
  122. }
  123. void SDisplay::queryConnection(network::Socket* sock,
  124. const char* userName)
  125. {
  126. assert(server != NULL);
  127. if (queryConnectionHandler) {
  128. queryConnectionHandler->queryConnection(sock, userName);
  129. return;
  130. }
  131. server->approveConnection(sock, true);
  132. }
  133. void SDisplay::startCore() {
  134. // Currently, we just check whether we're in the console session, and
  135. // fail if not
  136. if (!inConsoleSession())
  137. throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
  138. // Switch to the current input desktop
  139. if (rfb::win32::desktopChangeRequired()) {
  140. if (!rfb::win32::changeDesktop())
  141. throw rdr::Exception("unable to switch into input desktop");
  142. }
  143. // Initialise the change tracker and clipper
  144. updates.clear();
  145. clipper.setUpdateTracker(server);
  146. // Create the framebuffer object
  147. recreatePixelBuffer(true);
  148. // Create the SDisplayCore
  149. updateMethod_ = updateMethod;
  150. int tryMethod = updateMethod_;
  151. while (!core) {
  152. try {
  153. if (tryMethod == 1)
  154. core = new SDisplayCoreWMHooks(this, &updates);
  155. else
  156. core = new SDisplayCorePolling(this, &updates);
  157. core->setScreenRect(screenRect);
  158. } catch (rdr::Exception& e) {
  159. delete core; core = 0;
  160. if (tryMethod == 0)
  161. throw rdr::Exception("unable to access desktop");
  162. tryMethod--;
  163. vlog.error("%s", e.str());
  164. }
  165. }
  166. vlog.info("Started %s", core->methodName());
  167. // Start display monitor, clipboard handler and input handlers
  168. monitor = new WMMonitor;
  169. monitor->setNotifier(this);
  170. clipboard = new Clipboard;
  171. clipboard->setNotifier(this);
  172. ptr = new SPointer;
  173. kbd = new SKeyboard;
  174. inputs = new WMBlockInput;
  175. cursor = new WMCursor;
  176. // Apply desktop optimisations
  177. cleanDesktop = new CleanDesktop;
  178. if (removeWallpaper)
  179. cleanDesktop->disableWallpaper();
  180. if (disableEffects)
  181. cleanDesktop->disableEffects();
  182. isWallpaperRemoved = removeWallpaper;
  183. areEffectsDisabled = disableEffects;
  184. checkLedState();
  185. if (server)
  186. server->setLEDState(ledState);
  187. }
  188. void SDisplay::stopCore() {
  189. if (core)
  190. vlog.info("Stopping %s", core->methodName());
  191. delete core; core = 0;
  192. delete pb; pb = 0;
  193. delete device; device = 0;
  194. delete monitor; monitor = 0;
  195. delete clipboard; clipboard = 0;
  196. delete inputs; inputs = 0;
  197. delete ptr; ptr = 0;
  198. delete kbd; kbd = 0;
  199. delete cleanDesktop; cleanDesktop = 0;
  200. delete cursor; cursor = 0;
  201. ResetEvent(updateEvent);
  202. }
  203. bool SDisplay::isRestartRequired() {
  204. // - We must restart the SDesktop if:
  205. // 1. We are no longer in the input desktop.
  206. // 2. The any setting has changed.
  207. // - Check that our session is the Console
  208. if (!inConsoleSession())
  209. return true;
  210. // - Check that we are in the input desktop
  211. if (rfb::win32::desktopChangeRequired())
  212. return true;
  213. // - Check that the update method setting hasn't changed
  214. // NB: updateMethod reflects the *selected* update method, not
  215. // necessarily the one in use, since we fall back to simpler
  216. // methods if more advanced ones fail!
  217. if (updateMethod_ != updateMethod)
  218. return true;
  219. // - Check that the desktop optimisation settings haven't changed
  220. // This isn't very efficient, but it shouldn't change very often!
  221. if ((isWallpaperRemoved != removeWallpaper) ||
  222. (areEffectsDisabled != disableEffects))
  223. return true;
  224. return false;
  225. }
  226. void SDisplay::restartCore() {
  227. vlog.info("restarting");
  228. // Stop the existing Core related resources
  229. stopCore();
  230. try {
  231. // Start a new Core if possible
  232. startCore();
  233. vlog.info("restarted");
  234. } catch (rdr::Exception& e) {
  235. // If startCore() fails then we MUST disconnect all clients,
  236. // to cause the server to stop() the desktop.
  237. // Otherwise, the SDesktop is in an inconsistent state
  238. // and the server will crash.
  239. server->closeClients(e.str());
  240. }
  241. }
  242. void SDisplay::handleClipboardRequest() {
  243. CharArray data(clipboard->getClipText());
  244. server->sendClipboardData(data.buf);
  245. }
  246. void SDisplay::handleClipboardAnnounce(bool available) {
  247. // FIXME: Wait for an application to actually request it
  248. if (available)
  249. server->requestClipboard();
  250. }
  251. void SDisplay::handleClipboardData(const char* data) {
  252. clipboard->setClipText(data);
  253. }
  254. void SDisplay::pointerEvent(const Point& pos, int buttonmask) {
  255. if (pb->getRect().contains(pos)) {
  256. Point screenPos = pos.translate(screenRect.tl);
  257. // - Check that the SDesktop doesn't need restarting
  258. if (isRestartRequired())
  259. restartCore();
  260. if (ptr)
  261. ptr->pointerEvent(screenPos, buttonmask);
  262. }
  263. }
  264. void SDisplay::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
  265. // - Check that the SDesktop doesn't need restarting
  266. if (isRestartRequired())
  267. restartCore();
  268. if (kbd)
  269. kbd->keyEvent(keysym, keycode, down);
  270. }
  271. bool SDisplay::checkLedState() {
  272. unsigned state = 0;
  273. if (GetKeyState(VK_SCROLL) & 0x0001)
  274. state |= ledScrollLock;
  275. if (GetKeyState(VK_NUMLOCK) & 0x0001)
  276. state |= ledNumLock;
  277. if (GetKeyState(VK_CAPITAL) & 0x0001)
  278. state |= ledCapsLock;
  279. if (ledState != state) {
  280. ledState = state;
  281. return true;
  282. }
  283. return false;
  284. }
  285. void
  286. SDisplay::notifyClipboardChanged(bool available) {
  287. vlog.debug("clipboard text changed");
  288. if (server)
  289. server->announceClipboard(available);
  290. }
  291. void
  292. SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
  293. switch (evt) {
  294. case WMMonitor::Notifier::DisplaySizeChanged:
  295. vlog.debug("desktop size changed");
  296. recreatePixelBuffer();
  297. break;
  298. case WMMonitor::Notifier::DisplayPixelFormatChanged:
  299. vlog.debug("desktop format changed");
  300. recreatePixelBuffer();
  301. break;
  302. default:
  303. vlog.error("unknown display event received");
  304. }
  305. }
  306. void
  307. SDisplay::processEvent(HANDLE event) {
  308. if (event == updateEvent) {
  309. vlog.write(120, "processEvent");
  310. ResetEvent(updateEvent);
  311. // - If the SDisplay isn't even started then quit now
  312. if (!core) {
  313. vlog.error("not start()ed");
  314. return;
  315. }
  316. // - Ensure that the disableLocalInputs flag is respected
  317. inputs->blockInputs(disableLocalInputs);
  318. // - Only process updates if the server is ready
  319. if (server) {
  320. // - Check that the SDesktop doesn't need restarting
  321. if (isRestartRequired()) {
  322. restartCore();
  323. return;
  324. }
  325. // - Flush any updates from the core
  326. try {
  327. core->flushUpdates();
  328. } catch (rdr::Exception& e) {
  329. vlog.error("%s", e.str());
  330. restartCore();
  331. return;
  332. }
  333. // Ensure the cursor is up to date
  334. WMCursor::Info info = cursor->getCursorInfo();
  335. if (old_cursor != info) {
  336. // Update the cursor shape if the visibility has changed
  337. bool set_cursor = info.visible != old_cursor.visible;
  338. // OR if the cursor is visible and the shape has changed.
  339. set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
  340. // Update the cursor shape
  341. if (set_cursor)
  342. pb->setCursor(info.visible ? info.cursor : 0, server);
  343. // Update the cursor position
  344. // NB: First translate from Screen coordinates to Desktop
  345. Point desktopPos = info.position.translate(screenRect.tl.negate());
  346. server->setCursorPos(desktopPos);
  347. old_cursor = info;
  348. }
  349. // Flush any changes to the server
  350. flushChangeTracker();
  351. // Forward current LED state to the server
  352. if (checkLedState())
  353. server->setLEDState(ledState);
  354. }
  355. return;
  356. }
  357. throw rdr::Exception("No such event");
  358. }
  359. // -=- Protected methods
  360. void
  361. SDisplay::recreatePixelBuffer(bool force) {
  362. // Open the specified display device
  363. // If no device is specified, open entire screen using GetDC().
  364. // Opening the whole display with CreateDC doesn't work on multi-monitor
  365. // systems for some reason.
  366. DeviceContext* new_device = 0;
  367. TCharArray deviceName(displayDevice.getData());
  368. if (deviceName.buf[0]) {
  369. vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
  370. new_device = new DeviceDC(deviceName.buf);
  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 (deviceName.buf[0]) {
  379. MonitorInfo info(CStr(deviceName.buf));
  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. }