blob: 2a3da0d9d390927b9d144db5f6405502248d9a46 [file] [log] [blame]
Constantin Kaplinsky729598c2006-05-25 05:12:25 +00001/* 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
19// -=- WMHooks.cxx
20
21#include <rfb_win32/WMHooks.h>
22#include <rfb_win32/DynamicFn.h>
23#include <rfb_win32/Service.h>
24#include <rfb_win32/MsgWindow.h>
25#include <rfb_win32/IntervalTimer.h>
26#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
36
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
47class WMHooksThread : public Thread {
48public:
Peter Åstrand0a7700e2008-12-10 13:12:38 +000049 WMHooksThread() : Thread("WMHookThread"),
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000050 WM_Hooks_Install(_T("wm_hooks.dll"), "WM_Hooks_Install"),
51 WM_Hooks_Remove(_T("wm_hooks.dll"), "WM_Hooks_Remove"),
Peter Åstrand0a7700e2008-12-10 13:12:38 +000052 WM_Hooks_EnableCursorShape(_T("wm_hooks.dll"), "WM_Hooks_EnableCursorShape"),
53 active(true) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000054 }
55 virtual void run();
56 virtual Thread* join();
57 DynamicFn<WM_Hooks_Install_proto> WM_Hooks_Install;;
58 DynamicFn<WM_Hooks_Remove_proto> WM_Hooks_Remove;
59 DynamicFn<WM_Hooks_EnableCursorShape_proto> WM_Hooks_EnableCursorShape;
60protected:
61 bool active;
62};
63
64WMHooksThread* hook_mgr = 0;
65std::list<WMHooks*> hooks;
66std::list<WMCursorHooks*> cursor_hooks;
67Mutex hook_mgr_lock;
68HCURSOR hook_cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
69
70
71static bool StartHookThread() {
72 if (hook_mgr)
73 return true;
74 vlog.debug("creating thread");
75 hook_mgr = new WMHooksThread();
76 if (!hook_mgr->WM_Hooks_Install.isValid() ||
77 !hook_mgr->WM_Hooks_Remove.isValid()) {
78 vlog.debug("hooks not available");
79 return false;
80 }
81 vlog.debug("installing hooks");
82 if (!(*hook_mgr->WM_Hooks_Install)(hook_mgr->getThreadId(), 0)) {
83 vlog.error("failed to initialise hooks");
84 delete hook_mgr->join();
85 hook_mgr = 0;
86 return false;
87 }
88 vlog.debug("starting thread");
89 hook_mgr->start();
90 return true;
91}
92
93static void StopHookThread() {
94 if (!hook_mgr)
95 return;
96 if (!hooks.empty() || !cursor_hooks.empty())
97 return;
98 vlog.debug("closing thread");
99 delete hook_mgr->join();
100 hook_mgr = 0;
101}
102
103
104static bool AddHook(WMHooks* hook) {
105 vlog.debug("adding hook");
106 Lock l(hook_mgr_lock);
107 if (!StartHookThread())
108 return false;
109 hooks.push_back(hook);
110 return true;
111}
112
113static bool AddCursorHook(WMCursorHooks* hook) {
114 vlog.debug("adding cursor hook");
115 Lock l(hook_mgr_lock);
116 if (!StartHookThread())
117 return false;
118 if (!hook_mgr->WM_Hooks_EnableCursorShape.isValid())
119 return false;
120 if (cursor_hooks.empty() && !(*hook_mgr->WM_Hooks_EnableCursorShape)(TRUE))
121 return false;
122 cursor_hooks.push_back(hook);
123 return true;
124}
125
126static bool RemHook(WMHooks* hook) {
127 {
128 vlog.debug("removing hook");
129 Lock l(hook_mgr_lock);
130 hooks.remove(hook);
131 }
132 StopHookThread();
133 return true;
134}
135
136static bool RemCursorHook(WMCursorHooks* hook) {
137 {
138 vlog.debug("removing cursor hook");
139 Lock l(hook_mgr_lock);
140 cursor_hooks.remove(hook);
141 if (hook_mgr->WM_Hooks_EnableCursorShape.isValid() &&
142 cursor_hooks.empty())
143 (*hook_mgr->WM_Hooks_EnableCursorShape)(FALSE);
144 }
145 StopHookThread();
146 return true;
147}
148
149static void NotifyHooksRegion(const Region& r) {
150 Lock l(hook_mgr_lock);
151 std::list<WMHooks*>::iterator i;
152 for (i=hooks.begin(); i!=hooks.end(); i++)
153 (*i)->NotifyHooksRegion(r);
154}
155
156static void NotifyHooksCursor(HCURSOR c) {
157 Lock l(hook_mgr_lock);
158 hook_cursor = c;
159}
160
161
162static UINT GetMsgVal(DynamicFn<WM_Hooks_WMVAL_proto>& fn) {
163 if (fn.isValid())
164 return (*fn)();
165 return WM_NULL;
166}
167
168void
169WMHooksThread::run() {
170 // Obtain message ids for all supported hook messages
171 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowChanged");
172 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowBorderChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowBorderChanged");
173 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowClientAreaChanged(_T("wm_hooks.dll"), "WM_Hooks_WindowClientAreaChanged");
174 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_RectangleChanged(_T("wm_hooks.dll"), "WM_Hooks_RectangleChanged");
175 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_CursorChanged(_T("wm_hooks.dll"), "WM_Hooks_CursorChanged");
176 UINT windowMsg = GetMsgVal(WM_Hooks_WindowChanged);
177 UINT clientAreaMsg = GetMsgVal(WM_Hooks_WindowClientAreaChanged);
178 UINT borderMsg = GetMsgVal(WM_Hooks_WindowBorderChanged);
179 UINT rectangleMsg = GetMsgVal(WM_Hooks_RectangleChanged);
180 UINT cursorMsg = GetMsgVal(WM_Hooks_CursorChanged);
181#ifdef _DEBUG
182 DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_Diagnostic(_T("wm_hooks.dll"), "WM_Hooks_Diagnostic");
183 UINT diagnosticMsg = GetMsgVal(WM_Hooks_Diagnostic);
184#endif
185 MSG msg;
186 RECT wrect;
187 HWND hwnd;
188 int count = 0;
189
190 // Update delay handling
191 // We delay updates by 40-80ms, so that the triggering application has time to
192 // actually complete them before we notify the hook callbacks & they go off
193 // capturing screen state.
194 const int updateDelayMs = 40;
195 MsgWindow updateDelayWnd(_T("WMHooks::updateDelay"));
196 IntervalTimer updateDelayTimer(updateDelayWnd.getHandle(), 1);
197 Region updates[2];
198 int activeRgn = 0;
199
200 vlog.debug("starting hook thread");
201
202 while (active && GetMessage(&msg, NULL, 0, 0)) {
203 count++;
204
205 if (msg.message == WM_TIMER) {
206 // Actually notify callbacks of graphical updates
207 NotifyHooksRegion(updates[1-activeRgn]);
208 if (updates[activeRgn].is_empty())
209 updateDelayTimer.stop();
210 activeRgn = 1-activeRgn;
211 updates[activeRgn].clear();
212
213 } else if (msg.message == windowMsg) {
214 // An entire window has (potentially) changed
215 hwnd = (HWND) msg.lParam;
216 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
217 GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) {
218 updates[activeRgn].assign_union(Rect(wrect.left, wrect.top,
219 wrect.right, wrect.bottom));
220 updateDelayTimer.start(updateDelayMs);
221 }
222
223 } else if (msg.message == clientAreaMsg) {
224 // The client area of a window has (potentially) changed
225 hwnd = (HWND) msg.lParam;
226 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
227 GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
228 {
229 POINT pt = {0,0};
230 if (ClientToScreen(hwnd, &pt)) {
231 updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,
232 wrect.right+pt.x, wrect.bottom+pt.y));
233 updateDelayTimer.start(updateDelayMs);
234 }
235 }
236
237 } else if (msg.message == borderMsg) {
238 hwnd = (HWND) msg.lParam;
239 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
240 GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
241 {
242 Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
243 RECT crect;
244 POINT pt = {0,0};
245 if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) &&
246 !IsRectEmpty(&crect))
247 {
248 changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
249 crect.right+pt.x, crect.bottom+pt.y));
250 }
251 if (!changed.is_empty()) {
252 updates[activeRgn].assign_union(changed);
253 updateDelayTimer.start(updateDelayMs);
254 }
255 }
256 } else if (msg.message == rectangleMsg) {
257 Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
258 LOWORD(msg.lParam), HIWORD(msg.lParam));
259 if (!r.is_empty()) {
260 updates[activeRgn].assign_union(r);
261 updateDelayTimer.start(updateDelayMs);
262 }
263
264 } else if (msg.message == cursorMsg) {
265 NotifyHooksCursor((HCURSOR)msg.lParam);
266#ifdef _DEBUG
267 } else if (msg.message == diagnosticMsg) {
Pierre Ossman023df7e2015-09-29 15:42:58 +0200268 vlog.info("DIAG msg=%x(%d) wnd=%lx",
269 (unsigned)msg.wParam, (int)msg.wParam,
270 (unsigned long)msg.lParam);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000271#endif
272 }
273 }
274
275 vlog.debug("stopping hook thread - processed %d events", count);
276 (*WM_Hooks_Remove)(getThreadId());
277}
278
279Thread*
280WMHooksThread::join() {
281 vlog.debug("stopping WMHooks thread");
282 active = false;
283 PostThreadMessage(thread_id, WM_QUIT, 0, 0);
284 vlog.debug("joining WMHooks thread");
285 return Thread::join();
286}
287
288// -=- WMHooks class
289
290rfb::win32::WMHooks::WMHooks() : updateEvent(0) {
291}
292
293rfb::win32::WMHooks::~WMHooks() {
294 RemHook(this);
295}
296
297bool rfb::win32::WMHooks::setEvent(HANDLE ue) {
298 if (updateEvent)
299 RemHook(this);
300 updateEvent = ue;
301 return AddHook(this);
302}
303
304bool rfb::win32::WMHooks::getUpdates(UpdateTracker* ut) {
305 if (!updatesReady) return false;
306 Lock l(hook_mgr_lock);
307 updates.copyTo(ut);
308 updates.clear();
309 updatesReady = false;
310 return true;
311}
312
313bool rfb::win32::WMHooks::areAvailable() {
314 WMHooksThread wmht;
315 return wmht.WM_Hooks_Install.isValid();
316}
317
318#ifdef _DEBUG
319void
320rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
321 if (WM_Hooks_SetDiagnosticRange.isValid())
322 (*WM_Hooks_SetDiagnosticRange)(min, max);
323}
324#endif
325
326void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {
327 // hook_mgr_lock is already held at this point
328 updates.add_changed(r);
329 updatesReady = true;
330 SetEvent(updateEvent);
331}
332
333
334// -=- WMBlockInput class
335
336rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
337}
338
339rfb::win32::WMBlockInput::~WMBlockInput() {
340 blockInputs(false);
341}
342
343typedef BOOL (*WM_Hooks_EnableRealInputs_proto)(BOOL pointer, BOOL keyboard);
344DynamicFn<WM_Hooks_EnableRealInputs_proto>* WM_Hooks_EnableRealInputs = 0;
345static bool blockRealInputs(bool block_) {
346 // NB: Requires blockMutex to be held!
347 if (block_) {
348 if (WM_Hooks_EnableRealInputs)
349 return true;
350 // Enable blocking
351 WM_Hooks_EnableRealInputs = new DynamicFn<WM_Hooks_EnableRealInputs_proto>(_T("wm_hooks.dll"), "WM_Hooks_EnableRealInputs");
352 if (WM_Hooks_EnableRealInputs->isValid() && (**WM_Hooks_EnableRealInputs)(false, false))
353 return true;
354 }
355 if (WM_Hooks_EnableRealInputs) {
356 // Clean up the DynamicFn, either if init failed, or block_ is false
357 if (WM_Hooks_EnableRealInputs->isValid())
358 (**WM_Hooks_EnableRealInputs)(true, true);
359 delete WM_Hooks_EnableRealInputs;
360 WM_Hooks_EnableRealInputs = 0;
361 }
362 return block_ == (WM_Hooks_EnableRealInputs != 0);
363}
364
365Mutex blockMutex;
366int blockCount = 0;
367
368bool rfb::win32::WMBlockInput::blockInputs(bool on) {
369 if (active == on) return true;
370 Lock l(blockMutex);
371 int newCount = on ? blockCount+1 : blockCount-1;
372 if (!blockRealInputs(newCount > 0))
373 return false;
374 blockCount = newCount;
375 active = on;
376 return true;
377}
378
379
380// -=- WMCursorHooks class
381
382rfb::win32::WMCursorHooks::WMCursorHooks() {
383}
384
385rfb::win32::WMCursorHooks::~WMCursorHooks() {
386 RemCursorHook(this);
387}
388
389bool
390rfb::win32::WMCursorHooks::start() {
391 return AddCursorHook(this);
392}
393
394HCURSOR
395rfb::win32::WMCursorHooks::getCursor() const {
396 return hook_cursor;
397}