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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  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::pointerEvent(const Point& pos, int buttonmask) {
  243. if (pb->getRect().contains(pos)) {
  244. Point screenPos = pos.translate(screenRect.tl);
  245. // - Check that the SDesktop doesn't need restarting
  246. if (isRestartRequired())
  247. restartCore();
  248. if (ptr)
  249. ptr->pointerEvent(screenPos, buttonmask);
  250. }
  251. }
  252. void SDisplay::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
  253. // - Check that the SDesktop doesn't need restarting
  254. if (isRestartRequired())
  255. restartCore();
  256. if (kbd)
  257. kbd->keyEvent(keysym, keycode, down);
  258. }
  259. bool SDisplay::checkLedState() {
  260. unsigned state = 0;
  261. if (GetKeyState(VK_SCROLL) & 0x0001)
  262. state |= ledScrollLock;
  263. if (GetKeyState(VK_NUMLOCK) & 0x0001)
  264. state |= ledNumLock;
  265. if (GetKeyState(VK_CAPITAL) & 0x0001)
  266. state |= ledCapsLock;
  267. if (ledState != state) {
  268. ledState = state;
  269. return true;
  270. }
  271. return false;
  272. }
  273. void SDisplay::clientCutText(const char* text) {
  274. clipboard->setClipText(text);
  275. }
  276. void
  277. SDisplay::notifyClipboardChanged(const char* text) {
  278. vlog.debug("clipboard text changed");
  279. if (server)
  280. server->serverCutText(text);
  281. }
  282. void
  283. SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) {
  284. switch (evt) {
  285. case WMMonitor::Notifier::DisplaySizeChanged:
  286. vlog.debug("desktop size changed");
  287. recreatePixelBuffer();
  288. break;
  289. case WMMonitor::Notifier::DisplayPixelFormatChanged:
  290. vlog.debug("desktop format changed");
  291. recreatePixelBuffer();
  292. break;
  293. default:
  294. vlog.error("unknown display event received");
  295. }
  296. }
  297. void
  298. SDisplay::processEvent(HANDLE event) {
  299. if (event == updateEvent) {
  300. vlog.write(120, "processEvent");
  301. ResetEvent(updateEvent);
  302. // - If the SDisplay isn't even started then quit now
  303. if (!core) {
  304. vlog.error("not start()ed");
  305. return;
  306. }
  307. // - Ensure that the disableLocalInputs flag is respected
  308. inputs->blockInputs(disableLocalInputs);
  309. // - Only process updates if the server is ready
  310. if (server) {
  311. // - Check that the SDesktop doesn't need restarting
  312. if (isRestartRequired()) {
  313. restartCore();
  314. return;
  315. }
  316. // - Flush any updates from the core
  317. try {
  318. core->flushUpdates();
  319. } catch (rdr::Exception& e) {
  320. vlog.error("%s", e.str());
  321. restartCore();
  322. return;
  323. }
  324. // Ensure the cursor is up to date
  325. WMCursor::Info info = cursor->getCursorInfo();
  326. if (old_cursor != info) {
  327. // Update the cursor shape if the visibility has changed
  328. bool set_cursor = info.visible != old_cursor.visible;
  329. // OR if the cursor is visible and the shape has changed.
  330. set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
  331. // Update the cursor shape
  332. if (set_cursor)
  333. pb->setCursor(info.visible ? info.cursor : 0, server);
  334. // Update the cursor position
  335. // NB: First translate from Screen coordinates to Desktop
  336. Point desktopPos = info.position.translate(screenRect.tl.negate());
  337. server->setCursorPos(desktopPos);
  338. old_cursor = info;
  339. }
  340. // Flush any changes to the server
  341. flushChangeTracker();
  342. // Forward current LED state to the server
  343. if (checkLedState())
  344. server->setLEDState(ledState);
  345. }
  346. return;
  347. }
  348. throw rdr::Exception("No such event");
  349. }
  350. // -=- Protected methods
  351. void
  352. SDisplay::recreatePixelBuffer(bool force) {
  353. // Open the specified display device
  354. // If no device is specified, open entire screen using GetDC().
  355. // Opening the whole display with CreateDC doesn't work on multi-monitor
  356. // systems for some reason.
  357. DeviceContext* new_device = 0;
  358. TCharArray deviceName(displayDevice.getData());
  359. if (deviceName.buf[0]) {
  360. vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
  361. new_device = new DeviceDC(deviceName.buf);
  362. }
  363. if (!new_device) {
  364. vlog.info("Attaching to virtual desktop");
  365. new_device = new WindowDC(0);
  366. }
  367. // Get the coordinates of the specified dispay device
  368. Rect newScreenRect;
  369. if (deviceName.buf[0]) {
  370. MonitorInfo info(CStr(deviceName.buf));
  371. newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
  372. info.rcMonitor.right, info.rcMonitor.bottom);
  373. } else {
  374. newScreenRect = new_device->getClipBox();
  375. }
  376. // If nothing has changed & a recreate has not been forced, delete
  377. // the new device context and return
  378. if (pb && !force &&
  379. newScreenRect.equals(screenRect) &&
  380. new_device->getPF().equal(pb->getPF())) {
  381. delete new_device;
  382. return;
  383. }
  384. // Flush any existing changes to the server
  385. flushChangeTracker();
  386. // Delete the old pixelbuffer and device context
  387. vlog.debug("deleting old pixel buffer & device");
  388. if (pb)
  389. delete pb;
  390. if (device)
  391. delete device;
  392. // Create a DeviceFrameBuffer attached to the new device
  393. vlog.debug("creating pixel buffer");
  394. DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
  395. // Replace the old PixelBuffer
  396. screenRect = newScreenRect;
  397. pb = new_buffer;
  398. device = new_device;
  399. // Initialise the pixels
  400. pb->grabRegion(pb->getRect());
  401. // Prevent future grabRect operations from throwing exceptions
  402. pb->setIgnoreGrabErrors(true);
  403. // Update the clipping update tracker
  404. clipper.setClipRect(pb->getRect());
  405. // Inform the core of the changes
  406. if (core)
  407. core->setScreenRect(screenRect);
  408. // Inform the server of the changes
  409. if (server)
  410. server->setPixelBuffer(pb);
  411. }
  412. bool SDisplay::flushChangeTracker() {
  413. if (updates.is_empty())
  414. return false;
  415. vlog.write(120, "flushChangeTracker");
  416. // Translate the update coordinates from Screen coords to Desktop
  417. updates.translate(screenRect.tl.negate());
  418. // Clip the updates & flush them to the server
  419. updates.copyTo(&clipper);
  420. updates.clear();
  421. return true;
  422. }