blob: 0e5e70a4eb9f21dba8be033859e0666c1a454927 [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>
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000022#include <rfb_win32/Service.h>
23#include <rfb_win32/MsgWindow.h>
24#include <rfb_win32/IntervalTimer.h>
25#include <rfb/Threading.h>
26#include <rfb/LogWriter.h>
27
28#include <list>
29
30using namespace rfb;
31using namespace rfb::win32;
32
33static LogWriter vlog("WMHooks");
34
35
Pierre Ossmanfc08bee2016-01-12 12:32:15 +010036static HMODULE hooksLibrary;
37
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000038typedef UINT (*WM_Hooks_WMVAL_proto)();
Pierre Ossmanfc08bee2016-01-12 12:32:15 +010039static WM_Hooks_WMVAL_proto WM_Hooks_WindowChanged;
40static WM_Hooks_WMVAL_proto WM_Hooks_WindowBorderChanged;
41static WM_Hooks_WMVAL_proto WM_Hooks_WindowClientAreaChanged;
42static WM_Hooks_WMVAL_proto WM_Hooks_RectangleChanged;
43#ifdef _DEBUG
44static WM_Hooks_WMVAL_proto WM_Hooks_Diagnostic;
45#endif
46
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000047typedef BOOL (*WM_Hooks_Install_proto)(DWORD owner, DWORD thread);
Pierre Ossmanfc08bee2016-01-12 12:32:15 +010048static WM_Hooks_Install_proto WM_Hooks_Install;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000049typedef BOOL (*WM_Hooks_Remove_proto)(DWORD owner);
Pierre Ossmanfc08bee2016-01-12 12:32:15 +010050static WM_Hooks_Remove_proto WM_Hooks_Remove;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000051#ifdef _DEBUG
52typedef void (*WM_Hooks_SetDiagnosticRange_proto)(UINT min, UINT max);
Pierre Ossmanfc08bee2016-01-12 12:32:15 +010053static WM_Hooks_SetDiagnosticRange_proto WM_Hooks_SetDiagnosticRange;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +000054#endif
55
Pierre Ossmanfc08bee2016-01-12 12:32:15 +010056typedef BOOL (*WM_Hooks_EnableRealInputs_proto)(BOOL pointer, BOOL keyboard);
57static WM_Hooks_EnableRealInputs_proto WM_Hooks_EnableRealInputs;
58
59
60static void LoadHooks()
61{
62 if (hooksLibrary != NULL)
63 return;
64
65 hooksLibrary = LoadLibrary("wm_hooks.dll");
66 if (hooksLibrary == NULL)
67 return;
68
69 WM_Hooks_WindowChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_WindowChanged");
70 if (WM_Hooks_WindowChanged == NULL)
71 goto error;
72 WM_Hooks_WindowBorderChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_WindowBorderChanged");
73 if (WM_Hooks_WindowBorderChanged == NULL)
74 goto error;
75 WM_Hooks_WindowClientAreaChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_WindowClientAreaChanged");
76 if (WM_Hooks_WindowClientAreaChanged == NULL)
77 goto error;
78 WM_Hooks_RectangleChanged = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_RectangleChanged");
79 if (WM_Hooks_RectangleChanged == NULL)
80 goto error;
81#ifdef _DEBUG
82 WM_Hooks_Diagnostic = (WM_Hooks_WMVAL_proto)GetProcAddress(hooksLibrary, "WM_Hooks_Diagnostic");
83 if (WM_Hooks_Diagnostic == NULL)
84 goto error;
85#endif
86
87 WM_Hooks_Install = (WM_Hooks_Install_proto)GetProcAddress(hooksLibrary, "WM_Hooks_Install");
88 if (WM_Hooks_Install == NULL)
89 goto error;
90 WM_Hooks_Remove = (WM_Hooks_Remove_proto)GetProcAddress(hooksLibrary, "WM_Hooks_Remove");
91 if (WM_Hooks_Remove == NULL)
92 goto error;
93#ifdef _DEBUG
94 WM_Hooks_SetDiagnosticRange = (WM_Hooks_SetDiagnosticRange_proto)GetProcAddress(hooksLibrary, "WM_Hooks_SetDiagnosticRange");
95 if (WM_Hooks_SetDiagnosticRange == NULL)
96 goto error;
97#endif
98
99 WM_Hooks_EnableRealInputs = (WM_Hooks_EnableRealInputs_proto)GetProcAddress(hooksLibrary, "WM_Hooks_EnableRealInputs");
100 if (WM_Hooks_EnableRealInputs == NULL)
101 goto error;
102
103 return;
104
105error:
106 FreeLibrary(hooksLibrary);
107 hooksLibrary = NULL;
108}
109
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000110
111class WMHooksThread : public Thread {
112public:
Peter Åstrand0a7700e2008-12-10 13:12:38 +0000113 WMHooksThread() : Thread("WMHookThread"),
Peter Åstrand0a7700e2008-12-10 13:12:38 +0000114 active(true) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000115 }
116 virtual void run();
117 virtual Thread* join();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000118protected:
119 bool active;
120};
121
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100122static WMHooksThread* hook_mgr = 0;
123static std::list<WMHooks*> hooks;
124static Mutex hook_mgr_lock;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000125
126
127static bool StartHookThread() {
128 if (hook_mgr)
129 return true;
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100130 if (hooksLibrary == NULL)
131 return false;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000132 vlog.debug("creating thread");
133 hook_mgr = new WMHooksThread();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000134 vlog.debug("installing hooks");
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100135 if (!WM_Hooks_Install(hook_mgr->getThreadId(), 0)) {
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000136 vlog.error("failed to initialise hooks");
137 delete hook_mgr->join();
138 hook_mgr = 0;
139 return false;
140 }
141 vlog.debug("starting thread");
142 hook_mgr->start();
143 return true;
144}
145
146static void StopHookThread() {
147 if (!hook_mgr)
148 return;
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100149 if (!hooks.empty())
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000150 return;
151 vlog.debug("closing thread");
152 delete hook_mgr->join();
153 hook_mgr = 0;
154}
155
156
157static bool AddHook(WMHooks* hook) {
158 vlog.debug("adding hook");
159 Lock l(hook_mgr_lock);
160 if (!StartHookThread())
161 return false;
162 hooks.push_back(hook);
163 return true;
164}
165
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000166static bool RemHook(WMHooks* hook) {
167 {
168 vlog.debug("removing hook");
169 Lock l(hook_mgr_lock);
170 hooks.remove(hook);
171 }
172 StopHookThread();
173 return true;
174}
175
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000176static void NotifyHooksRegion(const Region& r) {
177 Lock l(hook_mgr_lock);
178 std::list<WMHooks*>::iterator i;
179 for (i=hooks.begin(); i!=hooks.end(); i++)
180 (*i)->NotifyHooksRegion(r);
181}
182
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000183
184void
185WMHooksThread::run() {
186 // Obtain message ids for all supported hook messages
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100187 UINT windowMsg = WM_Hooks_WindowChanged();
188 UINT clientAreaMsg = WM_Hooks_WindowClientAreaChanged();
189 UINT borderMsg = WM_Hooks_WindowBorderChanged();
190 UINT rectangleMsg = WM_Hooks_RectangleChanged();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000191#ifdef _DEBUG
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100192 UINT diagnosticMsg = WM_Hooks_Diagnostic();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000193#endif
194 MSG msg;
195 RECT wrect;
196 HWND hwnd;
197 int count = 0;
198
199 // Update delay handling
200 // We delay updates by 40-80ms, so that the triggering application has time to
201 // actually complete them before we notify the hook callbacks & they go off
202 // capturing screen state.
203 const int updateDelayMs = 40;
204 MsgWindow updateDelayWnd(_T("WMHooks::updateDelay"));
205 IntervalTimer updateDelayTimer(updateDelayWnd.getHandle(), 1);
206 Region updates[2];
207 int activeRgn = 0;
208
209 vlog.debug("starting hook thread");
210
211 while (active && GetMessage(&msg, NULL, 0, 0)) {
212 count++;
213
214 if (msg.message == WM_TIMER) {
215 // Actually notify callbacks of graphical updates
216 NotifyHooksRegion(updates[1-activeRgn]);
217 if (updates[activeRgn].is_empty())
218 updateDelayTimer.stop();
219 activeRgn = 1-activeRgn;
220 updates[activeRgn].clear();
221
222 } else if (msg.message == windowMsg) {
223 // An entire window has (potentially) changed
224 hwnd = (HWND) msg.lParam;
225 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
226 GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) {
227 updates[activeRgn].assign_union(Rect(wrect.left, wrect.top,
228 wrect.right, wrect.bottom));
229 updateDelayTimer.start(updateDelayMs);
230 }
231
232 } else if (msg.message == clientAreaMsg) {
233 // The client area of a window has (potentially) changed
234 hwnd = (HWND) msg.lParam;
235 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
236 GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
237 {
238 POINT pt = {0,0};
239 if (ClientToScreen(hwnd, &pt)) {
240 updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,
241 wrect.right+pt.x, wrect.bottom+pt.y));
242 updateDelayTimer.start(updateDelayMs);
243 }
244 }
245
246 } else if (msg.message == borderMsg) {
247 hwnd = (HWND) msg.lParam;
248 if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
249 GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect))
250 {
251 Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
252 RECT crect;
253 POINT pt = {0,0};
254 if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) &&
255 !IsRectEmpty(&crect))
256 {
257 changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
258 crect.right+pt.x, crect.bottom+pt.y));
259 }
260 if (!changed.is_empty()) {
261 updates[activeRgn].assign_union(changed);
262 updateDelayTimer.start(updateDelayMs);
263 }
264 }
265 } else if (msg.message == rectangleMsg) {
266 Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam),
267 LOWORD(msg.lParam), HIWORD(msg.lParam));
268 if (!r.is_empty()) {
269 updates[activeRgn].assign_union(r);
270 updateDelayTimer.start(updateDelayMs);
271 }
272
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000273#ifdef _DEBUG
274 } else if (msg.message == diagnosticMsg) {
Pierre Ossman023df7e2015-09-29 15:42:58 +0200275 vlog.info("DIAG msg=%x(%d) wnd=%lx",
276 (unsigned)msg.wParam, (int)msg.wParam,
277 (unsigned long)msg.lParam);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000278#endif
279 }
280 }
281
282 vlog.debug("stopping hook thread - processed %d events", count);
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100283 WM_Hooks_Remove(getThreadId());
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000284}
285
286Thread*
287WMHooksThread::join() {
288 vlog.debug("stopping WMHooks thread");
289 active = false;
290 PostThreadMessage(thread_id, WM_QUIT, 0, 0);
291 vlog.debug("joining WMHooks thread");
292 return Thread::join();
293}
294
295// -=- WMHooks class
296
297rfb::win32::WMHooks::WMHooks() : updateEvent(0) {
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100298 LoadHooks();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000299}
300
301rfb::win32::WMHooks::~WMHooks() {
302 RemHook(this);
303}
304
305bool rfb::win32::WMHooks::setEvent(HANDLE ue) {
306 if (updateEvent)
307 RemHook(this);
308 updateEvent = ue;
309 return AddHook(this);
310}
311
312bool rfb::win32::WMHooks::getUpdates(UpdateTracker* ut) {
313 if (!updatesReady) return false;
314 Lock l(hook_mgr_lock);
315 updates.copyTo(ut);
316 updates.clear();
317 updatesReady = false;
318 return true;
319}
320
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000321#ifdef _DEBUG
322void
323rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) {
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100324 WM_Hooks_SetDiagnosticRange(min, max);
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000325}
326#endif
327
328void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {
329 // hook_mgr_lock is already held at this point
330 updates.add_changed(r);
331 updatesReady = true;
332 SetEvent(updateEvent);
333}
334
335
336// -=- WMBlockInput class
337
338rfb::win32::WMBlockInput::WMBlockInput() : active(false) {
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100339 LoadHooks();
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000340}
341
342rfb::win32::WMBlockInput::~WMBlockInput() {
343 blockInputs(false);
344}
345
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100346static bool blocking = false;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000347static bool blockRealInputs(bool block_) {
348 // NB: Requires blockMutex to be held!
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100349 if (hooksLibrary == NULL)
350 return false;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000351 if (block_) {
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100352 if (blocking)
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000353 return true;
354 // Enable blocking
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100355 if (!WM_Hooks_EnableRealInputs(false, false))
356 return false;
357 blocking = true;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000358 }
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100359 if (blocking) {
360 WM_Hooks_EnableRealInputs(true, true);
361 blocking = false;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000362 }
Pierre Ossmanfc08bee2016-01-12 12:32:15 +0100363 return block_ == blocking;
Constantin Kaplinsky729598c2006-05-25 05:12:25 +0000364}
365
366Mutex blockMutex;
367int blockCount = 0;
368
369bool rfb::win32::WMBlockInput::blockInputs(bool on) {
370 if (active == on) return true;
371 Lock l(blockMutex);
372 int newCount = on ? blockCount+1 : blockCount-1;
373 if (!blockRealInputs(newCount > 0))
374 return false;
375 blockCount = newCount;
376 active = on;
377 return true;
378}