blob: 2d69053819da5485370bfa41e7b7ed8d65fc4812 [file] [log] [blame]
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +00001/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
2 *
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +00003 * 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
19// -=- WMHooks.cxx
20
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000021#include <rfb_win32/WMHooks.h>
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000022#include <rfb_win32/DynamicFn.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000023#include <rfb_win32/Service.h>
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000024#include <rfb_win32/MsgWindow.h>
25#include <rfb_win32/IntervalTimer.h>
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000026#include <rfb/Threading.h>
27#include <rfb/LogWriter.h>
28
29#include <list>
30
31using namespace rfb;
32using namespace rfb::win32;
33
34static LogWriter vlog("WMHooks");
35
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000036
37typedef UINT (*WM_Hooks_WMVAL_proto)();
38typedef BOOL (*WM_Hooks_Install_proto)(DWORD owner, DWORD thread);
39typedef BOOL (*WM_Hooks_Remove_proto)(DWORD owner);
40typedef BOOL (*WM_Hooks_EnableCursorShape_proto)(BOOL enable);
41#ifdef _DEBUG
42typedef void (*WM_Hooks_SetDiagnosticRange_proto)(UINT min, UINT max);
43DynamicFn<WM_Hooks_SetDiagnosticRange_proto> WM_Hooks_SetDiagnosticRange(_T("wm_hooks.dll"), "WM_Hooks_SetDiagnosticRange");
44#endif
45
46
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000047class WMHooksThread : public Thread {
48public:
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000049 WMHooksThread() : Thread("WMHookThread"), active(true),
50 WM_Hooks_Install(_T("wm_hooks.dll"), "WM_Hooks_Install"),
51 WM_Hooks_Remove(_T("wm_hooks.dll"), "WM_Hooks_Remove"),
52 WM_Hooks_EnableCursorShape(_T("wm_hooks.dll"), "WM_Hooks_EnableCursorShape") {
53 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000054 virtual void run();
55 virtual Thread* join();
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000056 DynamicFn<WM_Hooks_Install_proto> WM_Hooks_Install;;
57 DynamicFn<WM_Hooks_Remove_proto> WM_Hooks_Remove;
58 DynamicFn<WM_Hooks_EnableCursorShape_proto> WM_Hooks_EnableCursorShape;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000059protected:
60 bool active;
61};
62
63WMHooksThread* hook_mgr = 0;
64std::list<WMHooks*> hooks;
65std::list<WMCursorHooks*> cursor_hooks;
66Mutex hook_mgr_lock;
67HCURSOR hook_cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
68
69
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000070static bool StartHookThread() {
71 if (hook_mgr)
72 return true;
73 vlog.debug("creating thread");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000074 hook_mgr = new WMHooksThread();
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000075 if (!hook_mgr->WM_Hooks_Install.isValid() ||
76 !hook_mgr->WM_Hooks_Remove.isValid()) {
77 vlog.debug("hooks not available");
78 return false;
79 }
80 vlog.debug("installing hooks");
81 if (!(*hook_mgr->WM_Hooks_Install)(hook_mgr->getThreadId(), 0)) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000082 vlog.error("failed to initialise hooks");
83 delete hook_mgr->join();
84 hook_mgr = 0;
85 return false;
86 }
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000087 vlog.debug("starting thread");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000088 hook_mgr->start();
89 return true;
90}
91
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +000092static void StopHookThread() {
93 if (!hook_mgr)
94 return;
95 if (!hooks.empty() || !cursor_hooks.empty())
96 return;
97 vlog.debug("closing thread");
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +000098 delete hook_mgr->join();
99 hook_mgr = 0;
100}
101
102
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000103static bool AddHook(WMHooks* hook) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000104 vlog.debug("adding hook");
105 Lock l(hook_mgr_lock);
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000106 if (!StartHookThread())
107 return false;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000108 hooks.push_back(hook);
109 return true;
110}
111
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000112static bool AddCursorHook(WMCursorHooks* hook) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000113 vlog.debug("adding cursor hook");
114 Lock l(hook_mgr_lock);
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000115 if (!StartHookThread())
116 return false;
117 if (!hook_mgr->WM_Hooks_EnableCursorShape.isValid())
118 return false;
119 if (cursor_hooks.empty() && !(*hook_mgr->WM_Hooks_EnableCursorShape)(TRUE))
120 return false;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000121 cursor_hooks.push_back(hook);
122 return true;
123}
124
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000125static bool RemHook(WMHooks* hook) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000126 {
127 vlog.debug("removing hook");
128 Lock l(hook_mgr_lock);
129 hooks.remove(hook);
130 }
131 StopHookThread();
132 return true;
133}
134
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000135static bool RemCursorHook(WMCursorHooks* hook) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000136 {
137 vlog.debug("removing cursor hook");
138 Lock l(hook_mgr_lock);
139 cursor_hooks.remove(hook);
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000140 if (hook_mgr->WM_Hooks_EnableCursorShape.isValid() &&
141 cursor_hooks.empty())
142 (*hook_mgr->WM_Hooks_EnableCursorShape)(FALSE);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000143 }
144 StopHookThread();
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000145 return true;
146}
147
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000148static void NotifyHooksRegion(const Region& r) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000149 Lock l(hook_mgr_lock);
150 std::list<WMHooks*>::iterator i;
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000151 for (i=hooks.begin(); i!=hooks.end(); i++)
152 (*i)->NotifyHooksRegion(r);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000153}
154
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000155static void NotifyHooksCursor(HCURSOR c) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000156 Lock l(hook_mgr_lock);
157 hook_cursor = c;
158}
159
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000160
161static UINT GetMsgVal(DynamicFn<WM_Hooks_WMVAL_proto>& fn) {
162 if (fn.isValid())
163 return (*fn)();
164 return WM_NULL;
165}
166
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000167void
168WMHooksThread::run() {
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000169 // Obtain message ids for all supported hook messages
170 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowChanged");
171 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowBorderChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowBorderChanged");
172 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowClientAreaChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowClientAreaChanged");
173 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_RectangleChanged(_T("wm_hooks.dll"), "WM_Hooks_RectangleChanged");
174 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_CursorChanged(_T("wm_hooks.dll"), "WM_Hooks_CursorChanged");
175 UINT windowMsg = GetMsgVal(WM_Hooks_WindowChanged);
176 UINT clientAreaMsg = GetMsgVal(WM_Hooks_WindowClientAreaChanged);
177 UINT borderMsg = GetMsgVal(WM_Hooks_WindowBorderChanged);
178 UINT rectangleMsg = GetMsgVal(WM_Hooks_RectangleChanged);
179 UINT cursorMsg = GetMsgVal(WM_Hooks_CursorChanged);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000180#ifdef _DEBUG
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000181 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_Diagnostic(_T("wm_hooks.dll"), "WM_Hooks_Diagnostic");
182 UINT diagnosticMsg = GetMsgVal(WM_Hooks_Diagnostic);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000183#endif
184 MSG msg;
185 RECT wrect;
186 HWND hwnd;
187 int count = 0;
188
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000189 // Update delay handling
190 // We delay updates by 40-80ms, so that the triggering application has time to
191 // actually complete them before we notify the hook callbacks & they go off
192 // capturing screen state.
193 const int updateDelayMs = 40;
194 MsgWindow updateDelayWnd(_T("WMHooks::updateDelay"));
195 IntervalTimer updateDelayTimer(updateDelayWnd.getHandle(), 1);
196 Region updates[2];
197 int activeRgn = 0;
198
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000199 vlog.debug("starting hook thread");
200
201 while (active && GetMessage(&msg, NULL, 0, 0)) {
202 count++;
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000203
204 if (msg.message == WM_TIMER) {
205 // Actually notify callbacks of graphical updates
206 NotifyHooksRegion(updates[1-activeRgn]);
207 if (updates[activeRgn].is_empty())
208 updateDelayTimer.stop();
209 activeRgn = 1-activeRgn;
210 updates[activeRgn].clear();
211
212 } else if (msg.message == windowMsg) {
213 // An entire window has (potentially) changed
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000214 hwnd = (HWND) msg.lParam;
215 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000216 GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) {
217 updates[activeRgn].assign_union(Rect(wrect.left, wrect.top,
218 wrect.right, wrect.bottom));
219 updateDelayTimer.start(updateDelayMs);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000220 }
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000221
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000222 } else if (msg.message == clientAreaMsg) {
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000223 // The client area of a window has (potentially) changed
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000224 hwnd = (HWND) msg.lParam;
225 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
226 GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
227 {
228 POINT pt = {0,0};
229 if (ClientToScreen(hwnd, &pt)) {
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000230 updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,
231 wrect.right+pt.x, wrect.bottom+pt.y));
232 updateDelayTimer.start(updateDelayMs);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000233 }
234 }
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000235
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000236 } else if (msg.message == borderMsg) {
237 hwnd = (HWND) msg.lParam;
238 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
239 GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
240 {
241 Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
242 RECT crect;
243 POINT pt = {0,0};
244 if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) &&
245 !IsRectEmpty(&crect))
246 {
247 changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
248 crect.right+pt.x, crect.bottom+pt.y));
249 }
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000250 if (!changed.is_empty()) {
251 updates[activeRgn].assign_union(changed);
252 updateDelayTimer.start(updateDelayMs);
253 }
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000254 }
255 } else if (msg.message == rectangleMsg) {
256 Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
257 LOWORD(msg.lParam), HIWORD(msg.lParam));
258 if (!r.is_empty()) {
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000259 updates[activeRgn].assign_union(r);
260 updateDelayTimer.start(updateDelayMs);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000261 }
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000262
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000263 } else if (msg.message == cursorMsg) {
264 NotifyHooksCursor((HCURSOR)msg.lParam);
265#ifdef _DEBUG
266 } else if (msg.message == diagnosticMsg) {
267 vlog.info("DIAG msg=%x(%d) wnd=%lx", msg.wParam, msg.wParam, msg.lParam);
268#endif
269 }
270 }
271
272 vlog.debug("stopping hook thread - processed %d events", count);
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000273 (*WM_Hooks_Remove)(getThreadId());
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000274}
275
276Thread*
277WMHooksThread::join() {
278 vlog.debug("stopping WMHooks thread");
279 active = false;
280 PostThreadMessage(thread_id, WM_QUIT, 0, 0);
281 vlog.debug("joining WMHooks thread");
282 return Thread::join();
283}
284
285// -=- WMHooks class
286
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000287rfb::win32::WMHooks::WMHooks() : updateEvent(0) {
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000288}
289
290rfb::win32::WMHooks::~WMHooks() {
291 RemHook(this);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000292}
293
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000294bool rfb::win32::WMHooks::setEvent(HANDLE ue) {
295 if (updateEvent)
296 RemHook(this);
297 updateEvent = ue;
298 return AddHook(this);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000299}
300
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000301bool rfb::win32::WMHooks::getUpdates(UpdateTracker* ut) {
302 if (!updatesReady) return false;
303 Lock l(hook_mgr_lock);
304 updates.copyTo(ut);
305 updates.clear();
306 updatesReady = false;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000307 return true;
308}
309
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000310bool rfb::win32::WMHooks::areAvailable() {
311 WMHooksThread wmht;
312 return wmht.WM_Hooks_Install.isValid();
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000313}
314
315#ifdef _DEBUG
316void
317rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000318 if (WM_Hooks_SetDiagnosticRange.isValid())
319 (*WM_Hooks_SetDiagnosticRange)(min, max);
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000320}
321#endif
322
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000323void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {
324 // hook_mgr_lock is already held at this point
325 updates.add_changed(r);
326 updatesReady = true;
327 SetEvent(updateEvent);
328}
329
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000330
331// -=- WMBlockInput class
332
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000333rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
334}
335
336rfb::win32::WMBlockInput::~WMBlockInput() {
337 blockInputs(false);
338}
339
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000340typedef BOOL (*WM_Hooks_EnableRealInputs_proto)(BOOL pointer, BOOL keyboard);
341DynamicFn<WM_Hooks_EnableRealInputs_proto>* WM_Hooks_EnableRealInputs = 0;
342static bool blockRealInputs(bool block_) {
343 // NB: Requires blockMutex to be held!
344 if (block_) {
345 if (WM_Hooks_EnableRealInputs)
346 return true;
347 // Enable blocking
348 WM_Hooks_EnableRealInputs = new DynamicFn<WM_Hooks_EnableRealInputs_proto>(_T("wm_hooks.dll"), "WM_Hooks_EnableRealInputs");
349 if (WM_Hooks_EnableRealInputs->isValid() && (**WM_Hooks_EnableRealInputs)(false, false))
350 return true;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000351 }
Constantin Kaplinsky0a1eca12006-04-16 07:28:14 +0000352 if (WM_Hooks_EnableRealInputs) {
353 // Clean up the DynamicFn, either if init failed, or block_ is false
354 if (WM_Hooks_EnableRealInputs->isValid())
355 (**WM_Hooks_EnableRealInputs)(true, true);
356 delete WM_Hooks_EnableRealInputs;
357 WM_Hooks_EnableRealInputs = 0;
358 }
359 return block_ == (WM_Hooks_EnableRealInputs != 0);
360}
361
362Mutex blockMutex;
363int blockCount = 0;
364
365bool rfb::win32::WMBlockInput::blockInputs(bool on) {
366 if (active == on) return true;
367 Lock l(blockMutex);
368 int newCount = on ? blockCount+1 : blockCount-1;
369 if (!blockRealInputs(newCount > 0))
370 return false;
371 blockCount = newCount;
372 active = on;
373 return true;
Constantin Kaplinsky47ed8d32004-10-08 09:43:57 +0000374}
375
376
377// -=- WMCursorHooks class
378
379rfb::win32::WMCursorHooks::WMCursorHooks() {
380}
381
382rfb::win32::WMCursorHooks::~WMCursorHooks() {
383 RemCursorHook(this);
384}
385
386bool
387rfb::win32::WMCursorHooks::start() {
388 return AddCursorHook(this);
389}
390
391HCURSOR
392rfb::win32::WMCursorHooks::getCursor() const {
393 return hook_cursor;
394}