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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  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 <rfb_win32/SDisplay.h>
  22. #include <rfb_win32/Service.h>
  23. #include <rfb_win32/TsSessions.h>
  24. #include <rfb_win32/CleanDesktop.h>
  25. #include <rfb_win32/CurrentUser.h>
  26. #include <rfb_win32/DynamicFn.h>
  27. #include <rfb_win32/MonitorInfo.h>
  28. #include <rfb_win32/SDisplayCorePolling.h>
  29. #include <rfb_win32/SDisplayCoreWMHooks.h>
  30. #include <rfb_win32/SDisplayCoreDriver.h>
  31. #include <rfb/Exception.h>
  32. #include <rfb/LogWriter.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::removePattern("RemovePattern",
  49. "Remove the desktop background pattern when the server is in use.", false);
  50. BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects",
  51. "Disable desktop user interface effects when the server is in use.", false);
  52. //////////////////////////////////////////////////////////////////////////////
  53. //
  54. // SDisplay
  55. //
  56. typedef BOOL (WINAPI *_LockWorkStation_proto)();
  57. DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation");
  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)
  64. {
  65. updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
  66. }
  67. SDisplay::~SDisplay()
  68. {
  69. // XXX when the VNCServer has been deleted with clients active, stop()
  70. // doesn't get called - this ought to be fixed in VNCServerST. In any event,
  71. // we should never call any methods on VNCServer once we're being deleted.
  72. // This is because it is supposed to be guaranteed that the SDesktop exists
  73. // throughout the lifetime of the VNCServer. So if we're being deleted, then
  74. // the VNCServer ought not to exist and therefore we shouldn't invoke any
  75. // methods on it. Setting server to zero here ensures that stop() doesn't
  76. // call setPixelBuffer(0) on the server.
  77. server = 0;
  78. if (core) stop();
  79. }
  80. // -=- SDesktop interface
  81. void SDisplay::start(VNCServer* vs)
  82. {
  83. vlog.debug("starting");
  84. // Try to make session zero the console session
  85. if (!inConsoleSession())
  86. setConsoleSession();
  87. // Start the SDisplay core
  88. server = vs;
  89. startCore();
  90. vlog.debug("started");
  91. if (statusLocation) *statusLocation = true;
  92. }
  93. void SDisplay::stop()
  94. {
  95. vlog.debug("stopping");
  96. // If we successfully start()ed then perform the DisconnectAction
  97. if (core) {
  98. CurrentUserToken cut;
  99. CharArray action(disconnectAction.getData());
  100. if (stricmp(action.buf, "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(action.buf, "Lock") == 0) {
  106. if (!cut.h) {
  107. vlog.info("ignoring DisconnectAction=Lock - no current user");
  108. } else {
  109. if (_LockWorkStation.isValid())
  110. (*_LockWorkStation)();
  111. else
  112. ExitWindowsEx(EWX_LOGOFF, 0);
  113. }
  114. }
  115. }
  116. // Stop the SDisplayCore
  117. if (server)
  118. server->setPixelBuffer(0);
  119. stopCore();
  120. server = 0;
  121. vlog.debug("stopped");
  122. if (statusLocation) *statusLocation = false;
  123. }
  124. void SDisplay::startCore() {
  125. // Currently, we just check whether we're in the console session, and
  126. // fail if not
  127. if (!inConsoleSession())
  128. throw rdr::Exception("Console is not session zero - oreconnect to restore Console sessin");
  129. // Switch to the current input desktop
  130. if (rfb::win32::desktopChangeRequired()) {
  131. if (!rfb::win32::changeDesktop())
  132. throw rdr::Exception("unable to switch into input desktop");
  133. }
  134. // Initialise the change tracker and clipper
  135. updates.clear();
  136. clipper.setUpdateTracker(server);
  137. // Create the framebuffer object
  138. recreatePixelBuffer(true);
  139. // Create the SDisplayCore
  140. updateMethod_ = updateMethod;
  141. int tryMethod = updateMethod_;
  142. while (!core) {
  143. try {
  144. if (tryMethod == 2)
  145. core = new SDisplayCoreDriver(this, &updates);
  146. else if (tryMethod == 1)
  147. core = new SDisplayCoreWMHooks(this, &updates);
  148. else
  149. core = new SDisplayCorePolling(this, &updates);
  150. core->setScreenRect(screenRect);
  151. } catch (rdr::Exception& e) {
  152. delete core; core = 0;
  153. if (tryMethod == 0)
  154. throw rdr::Exception("unable to access desktop");
  155. tryMethod--;
  156. vlog.error("%s", e.str());
  157. }
  158. }
  159. vlog.info("Started %s", core->methodName());
  160. // Start display monitor, clipboard handler and input handlers
  161. monitor = new WMMonitor;
  162. monitor->setNotifier(this);
  163. clipboard = new Clipboard;
  164. clipboard->setNotifier(this);
  165. ptr = new SPointer;
  166. kbd = new SKeyboard;
  167. inputs = new WMBlockInput;
  168. cursor = new WMCursor;
  169. // Apply desktop optimisations
  170. cleanDesktop = new CleanDesktop;
  171. if (removePattern)
  172. cleanDesktop->disablePattern();
  173. if (removeWallpaper)
  174. cleanDesktop->disableWallpaper();
  175. if (disableEffects)
  176. cleanDesktop->disableEffects();
  177. isWallpaperRemoved = removeWallpaper;
  178. isPatternRemoved = removePattern;
  179. areEffectsDisabled = disableEffects;
  180. }
  181. void SDisplay::stopCore() {
  182. if (core)
  183. vlog.info("Stopping %s", core->methodName());
  184. delete core; core = 0;
  185. delete pb; pb = 0;
  186. delete device; device = 0;
  187. delete monitor; monitor = 0;
  188. delete clipboard; clipboard = 0;
  189. delete inputs; inputs = 0;
  190. delete ptr; ptr = 0;
  191. delete kbd; kbd = 0;
  192. delete cleanDesktop; cleanDesktop = 0;
  193. delete cursor; cursor = 0;
  194. ResetEvent(updateEvent);
  195. }
  196. bool SDisplay::areHooksAvailable() {
  197. return WMHooks::areAvailable();
  198. }
  199. bool SDisplay::isDriverAvailable() {
  200. return SDisplayCoreDriver::isAvailable();
  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. (isPatternRemoved != removePattern) ||
  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 key, bool down) {
  253. // - Check that the SDesktop doesn't need restarting
  254. if (isRestartRequired())
  255. restartCore();
  256. if (kbd)
  257. kbd->keyEvent(key, down);
  258. }
  259. void SDisplay::clientCutText(const char* text, int len) {
  260. CharArray clip_sz(len+1);
  261. memcpy(clip_sz.buf, text, len);
  262. clip_sz.buf[len] = 0;
  263. clipboard->setClipText(clip_sz.buf);
  264. }
  265. Point SDisplay::getFbSize() {
  266. bool startAndStop = !core;
  267. // If not started, do minimal initialisation to get desktop size.
  268. if (startAndStop)
  269. recreatePixelBuffer();
  270. Point result = Point(pb->width(), pb->height());
  271. // Destroy the initialised structures.
  272. if (startAndStop)
  273. stopCore();
  274. return result;
  275. }
  276. void
  277. SDisplay::notifyClipboardChanged(const char* text, int len) {
  278. vlog.debug("clipboard text changed");
  279. if (server)
  280. server->serverCutText(text, len);
  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. case WMMonitor::Notifier::DisplayColourMapChanged:
  294. vlog.debug("desktop colormap changed");
  295. pb->updateColourMap();
  296. if (server)
  297. server->setColourMapEntries();
  298. break;
  299. default:
  300. vlog.error("unknown display event received");
  301. }
  302. }
  303. void
  304. SDisplay::processEvent(HANDLE event) {
  305. if (event == updateEvent) {
  306. vlog.write(120, "processEvent");
  307. ResetEvent(updateEvent);
  308. // - If the SDisplay isn't even started then quit now
  309. if (!core) {
  310. vlog.error("not start()ed");
  311. return;
  312. }
  313. // - Ensure that the disableLocalInputs flag is respected
  314. inputs->blockInputs(disableLocalInputs);
  315. // - Only process updates if the server is ready
  316. if (server) {
  317. // - Check that the SDesktop doesn't need restarting
  318. if (isRestartRequired()) {
  319. restartCore();
  320. return;
  321. }
  322. // - Flush any updates from the core
  323. try {
  324. core->flushUpdates();
  325. } catch (rdr::Exception& e) {
  326. vlog.error("%s", e.str());
  327. restartCore();
  328. return;
  329. }
  330. // Ensure the cursor is up to date
  331. WMCursor::Info info = cursor->getCursorInfo();
  332. if (old_cursor != info) {
  333. // Update the cursor shape if the visibility has changed
  334. bool set_cursor = info.visible != old_cursor.visible;
  335. // OR if the cursor is visible and the shape has changed.
  336. set_cursor |= info.visible && (old_cursor.cursor != info.cursor);
  337. // Update the cursor shape
  338. if (set_cursor)
  339. pb->setCursor(info.visible ? info.cursor : 0, server);
  340. // Update the cursor position
  341. // NB: First translate from Screen coordinates to Desktop
  342. Point desktopPos = info.position.translate(screenRect.tl.negate());
  343. server->setCursorPos(desktopPos);
  344. old_cursor = info;
  345. }
  346. // Flush any changes to the server
  347. flushChangeTracker();
  348. }
  349. return;
  350. }
  351. throw rdr::Exception("No such event");
  352. }
  353. // -=- Protected methods
  354. void
  355. SDisplay::recreatePixelBuffer(bool force) {
  356. // Open the specified display device
  357. // If no device is specified, open entire screen using GetDC().
  358. // Opening the whole display with CreateDC doesn't work on multi-monitor
  359. // systems for some reason.
  360. DeviceContext* new_device = 0;
  361. TCharArray deviceName(displayDevice.getData());
  362. if (deviceName.buf[0]) {
  363. vlog.info("Attaching to device %s", (const char*)CStr(deviceName.buf));
  364. new_device = new DeviceDC(deviceName.buf);
  365. }
  366. if (!new_device) {
  367. vlog.info("Attaching to virtual desktop");
  368. new_device = new WindowDC(0);
  369. }
  370. // Get the coordinates of the specified dispay device
  371. Rect newScreenRect;
  372. if (deviceName.buf[0]) {
  373. MonitorInfo info(CStr(deviceName.buf));
  374. newScreenRect = Rect(info.rcMonitor.left, info.rcMonitor.top,
  375. info.rcMonitor.right, info.rcMonitor.bottom);
  376. } else {
  377. newScreenRect = new_device->getClipBox();
  378. }
  379. // If nothing has changed & a recreate has not been forced, delete
  380. // the new device context and return
  381. if (pb && !force &&
  382. newScreenRect.equals(screenRect) &&
  383. new_device->getPF().equal(pb->getPF())) {
  384. delete new_device;
  385. return;
  386. }
  387. // Flush any existing changes to the server
  388. flushChangeTracker();
  389. // Delete the old pixelbuffer and device context
  390. vlog.debug("deleting old pixel buffer & device");
  391. if (pb)
  392. delete pb;
  393. if (device)
  394. delete device;
  395. // Create a DeviceFrameBuffer attached to the new device
  396. vlog.debug("creating pixel buffer");
  397. DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(*new_device);
  398. // Replace the old PixelBuffer
  399. screenRect = newScreenRect;
  400. pb = new_buffer;
  401. device = new_device;
  402. // Initialise the pixels
  403. pb->grabRegion(pb->getRect());
  404. // Prevent future grabRect operations from throwing exceptions
  405. pb->setIgnoreGrabErrors(true);
  406. // Update the clipping update tracker
  407. clipper.setClipRect(pb->getRect());
  408. // Inform the core of the changes
  409. if (core)
  410. core->setScreenRect(screenRect);
  411. // Inform the server of the changes
  412. if (server)
  413. server->setPixelBuffer(pb);
  414. }
  415. bool SDisplay::flushChangeTracker() {
  416. if (updates.is_empty())
  417. return false;
  418. vlog.write(120, "flushChangeTracker");
  419. // Translate the update coordinates from Screen coords to Desktop
  420. updates.translate(screenRect.tl.negate());
  421. // Clip the updates & flush them to the server
  422. updates.copyTo(&clipper);
  423. updates.clear();
  424. return true;
  425. }