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

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