Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

SDisplay.cxx 14KB

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