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.

WMHooks.cxx 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. // -=- WMHooks.cxx
  19. #include <os/Mutex.h>
  20. #include <os/Thread.h>
  21. #include <rfb_win32/WMHooks.h>
  22. #include <rfb_win32/Service.h>
  23. #include <rfb_win32/MsgWindow.h>
  24. #include <rfb_win32/IntervalTimer.h>
  25. #include <rfb/LogWriter.h>
  26. #include <list>
  27. using namespace rfb;
  28. using namespace rfb::win32;
  29. static LogWriter vlog("WMHooks");
  30. static HMODULE hooksLibrary;
  31. typedef UINT (*WM_Hooks_WMVAL_proto)();
  32. static WM_Hooks_WMVAL_proto WM_Hooks_WindowChanged;
  33. static WM_Hooks_WMVAL_proto WM_Hooks_WindowBorderChanged;
  34. static WM_Hooks_WMVAL_proto WM_Hooks_WindowClientAreaChanged;
  35. static WM_Hooks_WMVAL_proto WM_Hooks_RectangleChanged;
  36. #ifdef _DEBUG
  37. static WM_Hooks_WMVAL_proto WM_Hooks_Diagnostic;
  38. #endif
  39. typedef BOOL (*WM_Hooks_Install_proto)(DWORD owner, DWORD thread);
  40. static WM_Hooks_Install_proto WM_Hooks_Install;
  41. typedef BOOL (*WM_Hooks_Remove_proto)(DWORD owner);
  42. static WM_Hooks_Remove_proto WM_Hooks_Remove;
  43. #ifdef _DEBUG
  44. typedef void (*WM_Hooks_SetDiagnosticRange_proto)(UINT min, UINT max);
  45. static WM_Hooks_SetDiagnosticRange_proto WM_Hooks_SetDiagnosticRange;
  46. #endif
  47. typedef BOOL (*WM_Hooks_EnableRealInputs_proto)(BOOL pointer, BOOL keyboard);
  48. static WM_Hooks_EnableRealInputs_proto WM_Hooks_EnableRealInputs;
  49. static void LoadHooks()
  50. {
  51. if (hooksLibrary != NULL)
  52. return;
  53. hooksLibrary = LoadLibrary("wm_hooks.dll");
  54. if (hooksLibrary == NULL)
  55. return;
  56. WM_Hooks_WindowChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_WindowChanged");
  57. if (WM_Hooks_WindowChanged == NULL)
  58. goto error;
  59. WM_Hooks_WindowBorderChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_WindowBorderChanged");
  60. if (WM_Hooks_WindowBorderChanged == NULL)
  61. goto error;
  62. WM_Hooks_WindowClientAreaChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_WindowClientAreaChanged");
  63. if (WM_Hooks_WindowClientAreaChanged == NULL)
  64. goto error;
  65. WM_Hooks_RectangleChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_RectangleChanged");
  66. if (WM_Hooks_RectangleChanged == NULL)
  67. goto error;
  68. #ifdef _DEBUG
  69. WM_Hooks_Diagnostic = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_Diagnostic");
  70. if (WM_Hooks_Diagnostic == NULL)
  71. goto error;
  72. #endif
  73. WM_Hooks_Install = (WM_Hooks_Install_proto)GetProcAddress(hooksLibrary, "WM_Hooks_Install");
  74. if (WM_Hooks_Install == NULL)
  75. goto error;
  76. WM_Hooks_Remove = (WM_Hooks_Remove_proto)GetProcAddress(hooksLibrary, "WM_Hooks_Remove");
  77. if (WM_Hooks_Remove == NULL)
  78. goto error;
  79. #ifdef _DEBUG
  80. WM_Hooks_SetDiagnosticRange = (WM_Hooks_SetDiagnosticRange_proto)GetProcAddress(hooksLibrary, "WM_Hooks_SetDiagnosticRange");
  81. if (WM_Hooks_SetDiagnosticRange == NULL)
  82. goto error;
  83. #endif
  84. WM_Hooks_EnableRealInputs = (WM_Hooks_EnableRealInputs_proto)GetProcAddress(hooksLibrary, "WM_Hooks_EnableRealInputs");
  85. if (WM_Hooks_EnableRealInputs == NULL)
  86. goto error;
  87. return;
  88. error:
  89. FreeLibrary(hooksLibrary);
  90. hooksLibrary = NULL;
  91. }
  92. class WMHooksThread : public os::Thread {
  93. public:
  94. WMHooksThread() : active(true), thread_id(-1) { }
  95. void stop();
  96. DWORD getThreadId() { return thread_id; }
  97. protected:
  98. virtual void worker();
  99. protected:
  100. bool active;
  101. DWORD thread_id;
  102. };
  103. static WMHooksThread* hook_mgr = 0;
  104. static std::list<WMHooks*> hooks;
  105. static os::Mutex hook_mgr_lock;
  106. static bool StartHookThread() {
  107. if (hook_mgr)
  108. return true;
  109. if (hooksLibrary == NULL)
  110. return false;
  111. vlog.debug("creating thread");
  112. hook_mgr = new WMHooksThread();
  113. hook_mgr->start();
  114. while (hook_mgr->getThreadId() == (DWORD)-1)
  115. Sleep(0);
  116. vlog.debug("installing hooks");
  117. if (!WM_Hooks_Install(hook_mgr->getThreadId(), 0)) {
  118. vlog.error("failed to initialise hooks");
  119. hook_mgr->stop();
  120. delete hook_mgr;
  121. hook_mgr = 0;
  122. return false;
  123. }
  124. return true;
  125. }
  126. static void StopHookThread() {
  127. if (!hook_mgr)
  128. return;
  129. if (!hooks.empty())
  130. return;
  131. vlog.debug("closing thread");
  132. hook_mgr->stop();
  133. delete hook_mgr;
  134. hook_mgr = 0;
  135. }
  136. static bool AddHook(WMHooks* hook) {
  137. vlog.debug("adding hook");
  138. os::AutoMutex a(&hook_mgr_lock);
  139. if (!StartHookThread())
  140. return false;
  141. hooks.push_back(hook);
  142. return true;
  143. }
  144. static bool RemHook(WMHooks* hook) {
  145. {
  146. vlog.debug("removing hook");
  147. os::AutoMutex a(&hook_mgr_lock);
  148. hooks.remove(hook);
  149. }
  150. StopHookThread();
  151. return true;
  152. }
  153. static void NotifyHooksRegion(const Region& r) {
  154. os::AutoMutex a(&hook_mgr_lock);
  155. std::list<WMHooks*>::iterator i;
  156. for (i=hooks.begin(); i!=hooks.end(); i++)
  157. (*i)->NotifyHooksRegion(r);
  158. }
  159. void
  160. WMHooksThread::worker() {
  161. // Obtain message ids for all supported hook messages
  162. UINT windowMsg = WM_Hooks_WindowChanged();
  163. UINT clientAreaMsg = WM_Hooks_WindowClientAreaChanged();
  164. UINT borderMsg = WM_Hooks_WindowBorderChanged();
  165. UINT rectangleMsg = WM_Hooks_RectangleChanged();
  166. #ifdef _DEBUG
  167. UINT diagnosticMsg = WM_Hooks_Diagnostic();
  168. #endif
  169. MSG msg;
  170. RECT wrect;
  171. HWND hwnd;
  172. int count = 0;
  173. // Update delay handling
  174. // We delay updates by 40-80ms, so that the triggering application has time to
  175. // actually complete them before we notify the hook callbacks & they go off
  176. // capturing screen state.
  177. const int updateDelayMs = 40;
  178. MsgWindow updateDelayWnd(_T("WMHooks::updateDelay"));
  179. IntervalTimer updateDelayTimer(updateDelayWnd.getHandle(), 1);
  180. Region updates[2];
  181. int activeRgn = 0;
  182. vlog.debug("starting hook thread");
  183. thread_id = GetCurrentThreadId();
  184. while (active && GetMessage(&msg, NULL, 0, 0)) {
  185. count++;
  186. if (msg.message == WM_TIMER) {
  187. // Actually notify callbacks of graphical updates
  188. NotifyHooksRegion(updates[1-activeRgn]);
  189. if (updates[activeRgn].is_empty())
  190. updateDelayTimer.stop();
  191. activeRgn = 1-activeRgn;
  192. updates[activeRgn].clear();
  193. } else if (msg.message == windowMsg) {
  194. // An entire window has (potentially) changed
  195. hwnd = (HWND) msg.lParam;
  196. if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
  197. GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) {
  198. updates[activeRgn].assign_union(Rect(wrect.left, wrect.top,
  199. wrect.right, wrect.bottom));
  200. updateDelayTimer.start(updateDelayMs);
  201. }
  202. } else if (msg.message == clientAreaMsg) {
  203. // The client area of a window has (potentially) changed
  204. hwnd = (HWND) msg.lParam;
  205. if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
  206. GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
  207. {
  208. POINT pt = {0,0};
  209. if (ClientToScreen(hwnd, &pt)) {
  210. updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,
  211. wrect.right+pt.x, wrect.bottom+pt.y));
  212. updateDelayTimer.start(updateDelayMs);
  213. }
  214. }
  215. } else if (msg.message == borderMsg) {
  216. hwnd = (HWND) msg.lParam;
  217. if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
  218. GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
  219. {
  220. Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
  221. RECT crect;
  222. POINT pt = {0,0};
  223. if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) &&
  224. !IsRectEmpty(&crect))
  225. {
  226. changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
  227. crect.right+pt.x, crect.bottom+pt.y));
  228. }
  229. if (!changed.is_empty()) {
  230. updates[activeRgn].assign_union(changed);
  231. updateDelayTimer.start(updateDelayMs);
  232. }
  233. }
  234. } else if (msg.message == rectangleMsg) {
  235. Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
  236. LOWORD(msg.lParam), HIWORD(msg.lParam));
  237. if (!r.is_empty()) {
  238. updates[activeRgn].assign_union(r);
  239. updateDelayTimer.start(updateDelayMs);
  240. }
  241. #ifdef _DEBUG
  242. } else if (msg.message == diagnosticMsg) {
  243. vlog.info("DIAG msg=%x(%d) wnd=%lx",
  244. (unsigned)msg.wParam, (int)msg.wParam,
  245. (unsigned long)msg.lParam);
  246. #endif
  247. }
  248. }
  249. vlog.debug("stopping hook thread - processed %d events", count);
  250. WM_Hooks_Remove(getThreadId());
  251. }
  252. void
  253. WMHooksThread::stop() {
  254. vlog.debug("stopping WMHooks thread");
  255. active = false;
  256. PostThreadMessage(thread_id, WM_QUIT, 0, 0);
  257. vlog.debug("waiting for WMHooks thread");
  258. wait();
  259. }
  260. // -=- WMHooks class
  261. rfb::win32::WMHooks::WMHooks() : updateEvent(0) {
  262. LoadHooks();
  263. }
  264. rfb::win32::WMHooks::~WMHooks() {
  265. RemHook(this);
  266. }
  267. bool rfb::win32::WMHooks::setEvent(HANDLE ue) {
  268. if (updateEvent)
  269. RemHook(this);
  270. updateEvent = ue;
  271. return AddHook(this);
  272. }
  273. bool rfb::win32::WMHooks::getUpdates(UpdateTracker* ut) {
  274. if (!updatesReady) return false;
  275. os::AutoMutex a(&hook_mgr_lock);
  276. updates.copyTo(ut);
  277. updates.clear();
  278. updatesReady = false;
  279. return true;
  280. }
  281. #ifdef _DEBUG
  282. void
  283. rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
  284. WM_Hooks_SetDiagnosticRange(min, max);
  285. }
  286. #endif
  287. void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {
  288. // hook_mgr_lock is already held at this point
  289. updates.add_changed(r);
  290. updatesReady = true;
  291. SetEvent(updateEvent);
  292. }
  293. // -=- WMBlockInput class
  294. rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
  295. LoadHooks();
  296. }
  297. rfb::win32::WMBlockInput::~WMBlockInput() {
  298. blockInputs(false);
  299. }
  300. static bool blocking = false;
  301. static bool blockRealInputs(bool block_) {
  302. // NB: Requires blockMutex to be held!
  303. if (hooksLibrary == NULL)
  304. return false;
  305. if (block_) {
  306. if (blocking)
  307. return true;
  308. // Enable blocking
  309. if (!WM_Hooks_EnableRealInputs(false, false))
  310. return false;
  311. blocking = true;
  312. }
  313. if (blocking) {
  314. WM_Hooks_EnableRealInputs(true, true);
  315. blocking = false;
  316. }
  317. return block_ == blocking;
  318. }
  319. static os::Mutex blockMutex;
  320. static int blockCount = 0;
  321. bool rfb::win32::WMBlockInput::blockInputs(bool on) {
  322. if (active == on) return true;
  323. os::AutoMutex a(&blockMutex);
  324. int newCount = on ? blockCount+1 : blockCount-1;
  325. if (!blockRealInputs(newCount > 0))
  326. return false;
  327. blockCount = newCount;
  328. active = on;
  329. return true;
  330. }