source: trunk/synergy/lib/platform/CSynergyHook.cpp

Last change on this file was 2749, checked in by bird, 19 years ago

synergy v1.3.1 sources (zip).

File size: 25.8 KB
Line 
1/*
2 * synergy -- mouse and keyboard sharing utility
3 * Copyright (C) 2002 Chris Schoeneman
4 *
5 * This package is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * found in the file COPYING that should have accompanied this file.
8 *
9 * This package is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include "CSynergyHook.h"
16#include "ProtocolTypes.h"
17#include <zmouse.h>
18
19//
20// debugging compile flag. when not zero the server doesn't grab
21// the keyboard when the mouse leaves the server screen. this
22// makes it possible to use the debugger (via the keyboard) when
23// all user input would normally be caught by the hook procedures.
24//
25#define NO_GRAB_KEYBOARD 0
26
27//
28// debugging compile flag. when not zero the server will not
29// install low level hooks.
30//
31#define NO_LOWLEVEL_HOOKS 0
32
33//
34// extra mouse wheel stuff
35//
36
37enum EWheelSupport {
38 kWheelNone,
39 kWheelOld,
40 kWheelWin2000,
41 kWheelModern
42};
43
44// declare extended mouse hook struct. useable on win2k
45typedef struct tagMOUSEHOOKSTRUCTWin2000 {
46 MOUSEHOOKSTRUCT mhs;
47 DWORD mouseData;
48} MOUSEHOOKSTRUCTWin2000;
49
50#if !defined(SM_MOUSEWHEELPRESENT)
51#define SM_MOUSEWHEELPRESENT 75
52#endif
53
54// X button stuff
55#if !defined(WM_XBUTTONDOWN)
56#define WM_XBUTTONDOWN 0x020B
57#define WM_XBUTTONUP 0x020C
58#define WM_XBUTTONDBLCLK 0x020D
59#define WM_NCXBUTTONDOWN 0x00AB
60#define WM_NCXBUTTONUP 0x00AC
61#define WM_NCXBUTTONDBLCLK 0x00AD
62#define MOUSEEVENTF_XDOWN 0x0080
63#define MOUSEEVENTF_XUP 0x0100
64#define XBUTTON1 0x0001
65#define XBUTTON2 0x0002
66#endif
67
68
69//
70// globals
71//
72
73#if defined(_MSC_VER)
74#pragma comment(linker, "-section:shared,rws")
75#pragma data_seg("shared")
76#endif
77// all data in this shared section *must* be initialized
78
79static HINSTANCE g_hinstance = NULL;
80static DWORD g_processID = 0;
81static EWheelSupport g_wheelSupport = kWheelNone;
82static UINT g_wmMouseWheel = 0;
83static DWORD g_threadID = 0;
84static HHOOK g_keyboard = NULL;
85static HHOOK g_mouse = NULL;
86static HHOOK g_getMessage = NULL;
87static HHOOK g_keyboardLL = NULL;
88static HHOOK g_mouseLL = NULL;
89static bool g_screenSaver = false;
90static EHookMode g_mode = kHOOK_DISABLE;
91static UInt32 g_zoneSides = 0;
92static SInt32 g_zoneSize = 0;
93static SInt32 g_xScreen = 0;
94static SInt32 g_yScreen = 0;
95static SInt32 g_wScreen = 0;
96static SInt32 g_hScreen = 0;
97static WPARAM g_deadVirtKey = 0;
98static LPARAM g_deadLParam = 0;
99static BYTE g_deadKeyState[256] = { 0 };
100static DWORD g_hookThread = 0;
101static DWORD g_attachedThread = 0;
102static bool g_fakeInput = false;
103
104#if defined(_MSC_VER)
105#pragma data_seg()
106#endif
107
108// keep linker quiet about floating point stuff. we don't use any
109// floating point operations but our includes may define some
110// (unused) floating point values.
111#ifndef _DEBUG
112extern "C" {
113int _fltused=0;
114}
115#endif
116
117
118//
119// internal functions
120//
121
122static
123void
124detachThread()
125{
126 if (g_attachedThread != 0 && g_hookThread != g_attachedThread) {
127 AttachThreadInput(g_hookThread, g_attachedThread, FALSE);
128 g_attachedThread = 0;
129 }
130}
131
132static
133bool
134attachThreadToForeground()
135{
136 // only attach threads if using low level hooks. a low level hook
137 // runs in the thread that installed the hook but we have to make
138 // changes that require being attached to the target thread (which
139 // should be the foreground window). a regular hook runs in the
140 // thread that just removed the event from its queue so we're
141 // already in the right thread.
142 if (g_hookThread != 0) {
143 HWND window = GetForegroundWindow();
144 DWORD threadID = GetWindowThreadProcessId(window, NULL);
145 // skip if no change
146 if (g_attachedThread != threadID) {
147 // detach from previous thread
148 detachThread();
149
150 // attach to new thread
151 if (threadID != 0 && threadID != g_hookThread) {
152 AttachThreadInput(g_hookThread, threadID, TRUE);
153 g_attachedThread = threadID;
154 }
155 return true;
156 }
157 }
158 return false;
159}
160
161#if !NO_GRAB_KEYBOARD
162static
163WPARAM
164makeKeyMsg(UINT virtKey, char c, bool noAltGr)
165{
166 return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0);
167}
168
169static
170void
171keyboardGetState(BYTE keys[256])
172{
173 // we have to use GetAsyncKeyState() rather than GetKeyState() because
174 // we don't pass through most keys so the event synchronous state
175 // doesn't get updated. we do that because certain modifier keys have
176 // side effects, like alt and the windows key.
177 SHORT key;
178 for (int i = 0; i < 256; ++i) {
179 key = GetAsyncKeyState(i);
180 keys[i] = (BYTE)((key < 0) ? 0x80u : 0);
181 }
182 key = GetKeyState(VK_CAPITAL);
183 keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1));
184}
185
186static
187bool
188doKeyboardHookHandler(WPARAM wParam, LPARAM lParam)
189{
190 // check for special events indicating if we should start or stop
191 // passing events through and not report them to the server. this
192 // is used to allow the server to synthesize events locally but
193 // not pick them up as user events.
194 if (wParam == SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY &&
195 ((lParam >> 16) & 0xffu) == SYNERGY_HOOK_FAKE_INPUT_SCANCODE) {
196 // update flag
197 g_fakeInput = ((lParam & 0x80000000u) == 0);
198 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
199 0xff000000u | wParam, lParam);
200
201 // discard event
202 return true;
203 }
204
205 // if we're expecting fake input then just pass the event through
206 // and do not forward to the server
207 if (g_fakeInput) {
208 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
209 0xfe000000u | wParam, lParam);
210 return false;
211 }
212
213 // VK_RSHIFT may be sent with an extended scan code but right shift
214 // is not an extended key so we reset that bit.
215 if (wParam == VK_RSHIFT) {
216 lParam &= ~0x01000000u;
217 }
218
219 // tell server about event
220 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam, lParam);
221
222 // ignore dead key release
223 if (g_deadVirtKey == wParam &&
224 (lParam & 0x80000000u) != 0) {
225 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
226 wParam | 0x04000000, lParam);
227 return false;
228 }
229
230 // we need the keyboard state for ToAscii()
231 BYTE keys[256];
232 keyboardGetState(keys);
233
234 // ToAscii() maps ctrl+letter to the corresponding control code
235 // and ctrl+backspace to delete. we don't want those translations
236 // so clear the control modifier state. however, if we want to
237 // simulate AltGr (which is ctrl+alt) then we must not clear it.
238 UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL];
239 UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU];
240 if ((control & 0x80) == 0 || (menu & 0x80) == 0) {
241 keys[VK_LCONTROL] = 0;
242 keys[VK_RCONTROL] = 0;
243 keys[VK_CONTROL] = 0;
244 }
245 else {
246 keys[VK_LCONTROL] = 0x80;
247 keys[VK_RCONTROL] = 0x80;
248 keys[VK_CONTROL] = 0x80;
249 keys[VK_LMENU] = 0x80;
250 keys[VK_RMENU] = 0x80;
251 keys[VK_MENU] = 0x80;
252 }
253
254 // ToAscii() needs to know if a menu is active for some reason.
255 // we don't know and there doesn't appear to be any way to find
256 // out. so we'll just assume a menu is active if the menu key
257 // is down.
258 // FIXME -- figure out some way to check if a menu is active
259 UINT flags = 0;
260 if ((menu & 0x80) != 0)
261 flags |= 1;
262
263 // if we're on the server screen then just pass numpad keys with alt
264 // key down as-is. we won't pick up the resulting character but the
265 // local app will. if on a client screen then grab keys as usual;
266 // if the client is a windows system it'll synthesize the expected
267 // character. if not then it'll probably just do nothing.
268 if (g_mode != kHOOK_RELAY_EVENTS) {
269 // we don't use virtual keys because we don't know what the
270 // state of the numlock key is. we'll hard code the scan codes
271 // instead. hopefully this works across all keyboards.
272 UINT sc = (lParam & 0x01ff0000u) >> 16;
273 if (menu &&
274 (sc >= 0x47u && sc <= 0x52u && sc != 0x4au && sc != 0x4eu)) {
275 return false;
276 }
277 }
278
279 // map the key event to a character. we have to put the dead
280 // key back first and this has the side effect of removing it.
281 if (g_deadVirtKey != 0) {
282 WORD c = 0;
283 ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
284 g_deadKeyState, &c, flags);
285 }
286 WORD c = 0;
287 UINT scanCode = ((lParam & 0x10ff0000u) >> 16);
288 int n = ToAscii(wParam, scanCode, keys, &c, flags);
289
290 // if mapping failed and ctrl and alt are pressed then try again
291 // with both not pressed. this handles the case where ctrl and
292 // alt are being used as individual modifiers rather than AltGr.
293 // we note that's the case in the message sent back to synergy
294 // because there's no simple way to deduce it after the fact.
295 // we have to put the dead key back first, if there was one.
296 bool noAltGr = false;
297 if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) {
298 noAltGr = true;
299 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
300 wParam | 0x05000000, lParam);
301 if (g_deadVirtKey != 0) {
302 ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
303 g_deadKeyState, &c, flags);
304 }
305 BYTE keys2[256];
306 for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
307 keys2[i] = keys[i];
308 }
309 keys2[VK_LCONTROL] = 0;
310 keys2[VK_RCONTROL] = 0;
311 keys2[VK_CONTROL] = 0;
312 keys2[VK_LMENU] = 0;
313 keys2[VK_RMENU] = 0;
314 keys2[VK_MENU] = 0;
315 n = ToAscii(wParam, scanCode, keys2, &c, flags);
316 }
317
318 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
319 wParam | ((c & 0xff) << 8) |
320 ((n & 0xff) << 16) | 0x06000000,
321 lParam);
322 WPARAM charAndVirtKey = 0;
323 bool clearDeadKey = false;
324 switch (n) {
325 default:
326 // key is a dead key
327 g_deadVirtKey = wParam;
328 g_deadLParam = lParam;
329 for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
330 g_deadKeyState[i] = keys[i];
331 }
332 break;
333
334 case 0:
335 // key doesn't map to a character. this can happen if
336 // non-character keys are pressed after a dead key.
337 charAndVirtKey = makeKeyMsg(wParam, (char)0, noAltGr);
338 break;
339
340 case 1:
341 // key maps to a character composed with dead key
342 charAndVirtKey = makeKeyMsg(wParam, (char)LOBYTE(c), noAltGr);
343 clearDeadKey = true;
344 break;
345
346 case 2: {
347 // previous dead key not composed. send a fake key press
348 // and release for the dead key to our window.
349 WPARAM deadCharAndVirtKey =
350 makeKeyMsg(g_deadVirtKey, (char)LOBYTE(c), noAltGr);
351 PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
352 deadCharAndVirtKey, g_deadLParam & 0x7fffffffu);
353 PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
354 deadCharAndVirtKey, g_deadLParam | 0x80000000u);
355
356 // use uncomposed character
357 charAndVirtKey = makeKeyMsg(wParam, (char)HIBYTE(c), noAltGr);
358 clearDeadKey = true;
359 break;
360 }
361 }
362
363 // put back the dead key, if any, for the application to use
364 if (g_deadVirtKey != 0) {
365 ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
366 g_deadKeyState, &c, flags);
367 }
368
369 // clear out old dead key state
370 if (clearDeadKey) {
371 g_deadVirtKey = 0;
372 g_deadLParam = 0;
373 }
374
375 // forward message to our window. do this whether or not we're
376 // forwarding events to clients because this'll keep our thread's
377 // key state table up to date. that's important for querying
378 // the scroll lock toggle state.
379 // XXX -- with hot keys for actions we may only need to do this when
380 // forwarding.
381 if (charAndVirtKey != 0) {
382 PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
383 charAndVirtKey | 0x07000000, lParam);
384 PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam);
385 }
386
387 if (g_mode == kHOOK_RELAY_EVENTS) {
388 // let certain keys pass through
389 switch (wParam) {
390 case VK_CAPITAL:
391 case VK_NUMLOCK:
392 case VK_SCROLL:
393 // pass event on. we want to let these through to
394 // the window proc because otherwise the keyboard
395 // lights may not stay synchronized.
396 break;
397
398 case VK_HANGUL:
399 // pass these modifiers if using a low level hook, discard
400 // them if not.
401 if (g_hookThread == 0) {
402 return true;
403 }
404 break;
405
406 default:
407 // discard
408 return true;
409 }
410 }
411
412 return false;
413}
414
415static
416bool
417keyboardHookHandler(WPARAM wParam, LPARAM lParam)
418{
419 attachThreadToForeground();
420 return doKeyboardHookHandler(wParam, lParam);
421}
422#endif
423
424static
425bool
426doMouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
427{
428 switch (wParam) {
429 case WM_LBUTTONDOWN:
430 case WM_MBUTTONDOWN:
431 case WM_RBUTTONDOWN:
432 case WM_XBUTTONDOWN:
433 case WM_LBUTTONDBLCLK:
434 case WM_MBUTTONDBLCLK:
435 case WM_RBUTTONDBLCLK:
436 case WM_XBUTTONDBLCLK:
437 case WM_LBUTTONUP:
438 case WM_MBUTTONUP:
439 case WM_RBUTTONUP:
440 case WM_XBUTTONUP:
441 case WM_NCLBUTTONDOWN:
442 case WM_NCMBUTTONDOWN:
443 case WM_NCRBUTTONDOWN:
444 case WM_NCXBUTTONDOWN:
445 case WM_NCLBUTTONDBLCLK:
446 case WM_NCMBUTTONDBLCLK:
447 case WM_NCRBUTTONDBLCLK:
448 case WM_NCXBUTTONDBLCLK:
449 case WM_NCLBUTTONUP:
450 case WM_NCMBUTTONUP:
451 case WM_NCRBUTTONUP:
452 case WM_NCXBUTTONUP:
453 // always relay the event. eat it if relaying.
454 PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, data);
455 return (g_mode == kHOOK_RELAY_EVENTS);
456
457 case WM_MOUSEWHEEL:
458 if (g_mode == kHOOK_RELAY_EVENTS) {
459 // relay event
460 PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, data, 0);
461 }
462 return (g_mode == kHOOK_RELAY_EVENTS);
463
464 case WM_NCMOUSEMOVE:
465 case WM_MOUSEMOVE:
466 if (g_mode == kHOOK_RELAY_EVENTS) {
467 // relay and eat event
468 PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
469 return true;
470 }
471 else if (g_mode == kHOOK_WATCH_JUMP_ZONE) {
472 // low level hooks can report bogus mouse positions that are
473 // outside of the screen. jeez. naturally we end up getting
474 // fake motion in the other direction to get the position back
475 // on the screen, which plays havoc with switch on double tap.
476 // CServer deals with that. we'll clamp positions onto the
477 // screen. also, if we discard events for positions outside
478 // of the screen then the mouse appears to get a bit jerky
479 // near the edge. we can either accept that or pass the bogus
480 // events. we'll try passing the events.
481 bool bogus = false;
482 if (x < g_xScreen) {
483 x = g_xScreen;
484 bogus = true;
485 }
486 else if (x >= g_xScreen + g_wScreen) {
487 x = g_xScreen + g_wScreen - 1;
488 bogus = true;
489 }
490 if (y < g_yScreen) {
491 y = g_yScreen;
492 bogus = true;
493 }
494 else if (y >= g_yScreen + g_hScreen) {
495 y = g_yScreen + g_hScreen - 1;
496 bogus = true;
497 }
498
499 // check for mouse inside jump zone
500 bool inside = false;
501 if (!inside && (g_zoneSides & kLeftMask) != 0) {
502 inside = (x < g_xScreen + g_zoneSize);
503 }
504 if (!inside && (g_zoneSides & kRightMask) != 0) {
505 inside = (x >= g_xScreen + g_wScreen - g_zoneSize);
506 }
507 if (!inside && (g_zoneSides & kTopMask) != 0) {
508 inside = (y < g_yScreen + g_zoneSize);
509 }
510 if (!inside && (g_zoneSides & kBottomMask) != 0) {
511 inside = (y >= g_yScreen + g_hScreen - g_zoneSize);
512 }
513
514 // relay the event
515 PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
516
517 // if inside and not bogus then eat the event
518 return inside && !bogus;
519 }
520 }
521
522 // pass the event
523 return false;
524}
525
526static
527bool
528mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
529{
530// attachThreadToForeground();
531 return doMouseHookHandler(wParam, x, y, data);
532}
533
534#if !NO_GRAB_KEYBOARD
535static
536LRESULT CALLBACK
537keyboardHook(int code, WPARAM wParam, LPARAM lParam)
538{
539 if (code >= 0) {
540 // handle the message
541 if (keyboardHookHandler(wParam, lParam)) {
542 return 1;
543 }
544 }
545
546 return CallNextHookEx(g_keyboard, code, wParam, lParam);
547}
548#endif
549
550static
551LRESULT CALLBACK
552mouseHook(int code, WPARAM wParam, LPARAM lParam)
553{
554 if (code >= 0) {
555 // decode message
556 const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam;
557 SInt32 x = (SInt32)info->pt.x;
558 SInt32 y = (SInt32)info->pt.y;
559 SInt32 w = 0;
560 if (wParam == WM_MOUSEWHEEL) {
561 // win2k and other systems supporting WM_MOUSEWHEEL in
562 // the mouse hook are gratuitously different (and poorly
563 // documented). if a low-level mouse hook is in place
564 // it should capture these events so we'll never see
565 // them.
566 switch (g_wheelSupport) {
567 case kWheelModern:
568 w = static_cast<SInt16>(LOWORD(info->dwExtraInfo));
569 break;
570
571 case kWheelWin2000: {
572 const MOUSEHOOKSTRUCTWin2000* info2k =
573 (const MOUSEHOOKSTRUCTWin2000*)lParam;
574 w = static_cast<SInt16>(HIWORD(info2k->mouseData));
575 break;
576 }
577
578 default:
579 break;
580 }
581 }
582
583 // handle the message. note that we don't handle X buttons
584 // here. that's okay because they're only supported on
585 // win2k and winxp and up and on those platforms we'll get
586 // get the mouse events through the low level hook.
587 if (mouseHookHandler(wParam, x, y, w)) {
588 return 1;
589 }
590 }
591
592 return CallNextHookEx(g_mouse, code, wParam, lParam);
593}
594
595static
596LRESULT CALLBACK
597getMessageHook(int code, WPARAM wParam, LPARAM lParam)
598{
599 if (code >= 0) {
600 if (g_screenSaver) {
601 MSG* msg = reinterpret_cast<MSG*>(lParam);
602 if (msg->message == WM_SYSCOMMAND &&
603 msg->wParam == SC_SCREENSAVE) {
604 // broadcast screen saver started message
605 PostThreadMessage(g_threadID,
606 SYNERGY_MSG_SCREEN_SAVER, TRUE, 0);
607 }
608 }
609 if (g_mode == kHOOK_RELAY_EVENTS) {
610 MSG* msg = reinterpret_cast<MSG*>(lParam);
611 if (g_wheelSupport == kWheelOld && msg->message == g_wmMouseWheel) {
612 // post message to our window
613 PostThreadMessage(g_threadID,
614 SYNERGY_MSG_MOUSE_WHEEL,
615 static_cast<SInt16>(msg->wParam & 0xffffu), 0);
616
617 // zero out the delta in the message so it's (hopefully)
618 // ignored
619 msg->wParam = 0;
620 }
621 }
622 }
623
624 return CallNextHookEx(g_getMessage, code, wParam, lParam);
625}
626
627#if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS
628
629//
630// low-level keyboard hook -- this allows us to capture and handle
631// alt+tab, alt+esc, ctrl+esc, and windows key hot keys. on the down
632// side, key repeats are not reported to us.
633//
634
635#if !NO_GRAB_KEYBOARD
636static
637LRESULT CALLBACK
638keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
639{
640 if (code >= 0) {
641 // decode the message
642 KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
643 WPARAM wParam = info->vkCode;
644 LPARAM lParam = 1; // repeat code
645 lParam |= (info->scanCode << 16); // scan code
646 if (info->flags & LLKHF_EXTENDED) {
647 lParam |= (1lu << 24); // extended key
648 }
649 if (info->flags & LLKHF_ALTDOWN) {
650 lParam |= (1lu << 29); // context code
651 }
652 if (info->flags & LLKHF_UP) {
653 lParam |= (1lu << 31); // transition
654 }
655 // FIXME -- bit 30 should be set if key was already down but
656 // we don't know that info. as a result we'll never generate
657 // key repeat events.
658
659 // handle the message
660 if (keyboardHookHandler(wParam, lParam)) {
661 return 1;
662 }
663 }
664
665 return CallNextHookEx(g_keyboardLL, code, wParam, lParam);
666}
667#endif
668
669//
670// low-level mouse hook -- this allows us to capture and handle mouse
671// events very early. the earlier the better.
672//
673
674static
675LRESULT CALLBACK
676mouseLLHook(int code, WPARAM wParam, LPARAM lParam)
677{
678 if (code >= 0) {
679 // decode the message
680 MSLLHOOKSTRUCT* info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
681 SInt32 x = static_cast<SInt32>(info->pt.x);
682 SInt32 y = static_cast<SInt32>(info->pt.y);
683 SInt32 w = static_cast<SInt16>(HIWORD(info->mouseData));
684
685 // handle the message
686 if (mouseHookHandler(wParam, x, y, w)) {
687 return 1;
688 }
689 }
690
691 return CallNextHookEx(g_mouseLL, code, wParam, lParam);
692}
693
694#endif
695
696static
697EWheelSupport
698getWheelSupport()
699{
700 // get operating system
701 OSVERSIONINFO info;
702 info.dwOSVersionInfoSize = sizeof(info);
703 if (!GetVersionEx(&info)) {
704 return kWheelNone;
705 }
706
707 // see if modern wheel is present
708 if (GetSystemMetrics(SM_MOUSEWHEELPRESENT)) {
709 // note if running on win2k
710 if (info.dwPlatformId == VER_PLATFORM_WIN32_NT &&
711 info.dwMajorVersion == 5 &&
712 info.dwMinorVersion == 0) {
713 return kWheelWin2000;
714 }
715 return kWheelModern;
716 }
717
718 // not modern. see if we've got old-style support.
719#if defined(MSH_WHEELSUPPORT)
720 UINT wheelSupportMsg = RegisterWindowMessage(MSH_WHEELSUPPORT);
721 HWND wheelSupportWindow = FindWindow(MSH_WHEELMODULE_CLASS,
722 MSH_WHEELMODULE_TITLE);
723 if (wheelSupportWindow != NULL && wheelSupportMsg != 0) {
724 if (SendMessage(wheelSupportWindow, wheelSupportMsg, 0, 0) != 0) {
725 g_wmMouseWheel = RegisterWindowMessage(MSH_MOUSEWHEEL);
726 if (g_wmMouseWheel != 0) {
727 return kWheelOld;
728 }
729 }
730 }
731#endif
732
733 // assume modern. we don't do anything special in this case
734 // except respond to WM_MOUSEWHEEL messages. GetSystemMetrics()
735 // can apparently return FALSE even if a mouse wheel is present
736 // though i'm not sure exactly when it does that (WinME returns
737 // FALSE for my logitech USB trackball).
738 return kWheelModern;
739}
740
741
742//
743// external functions
744//
745
746BOOL WINAPI
747DllMain(HINSTANCE instance, DWORD reason, LPVOID)
748{
749 if (reason == DLL_PROCESS_ATTACH) {
750 DisableThreadLibraryCalls(instance);
751 if (g_processID == 0) {
752 g_hinstance = instance;
753 g_processID = GetCurrentProcessId();
754 }
755 }
756 else if (reason == DLL_PROCESS_DETACH) {
757 if (g_processID == GetCurrentProcessId()) {
758 uninstall();
759 uninstallScreenSaver();
760 g_processID = 0;
761 g_hinstance = NULL;
762 }
763 }
764 return TRUE;
765}
766
767extern "C" {
768
769int
770init(DWORD threadID)
771{
772 assert(g_hinstance != NULL);
773
774 // try to open process that last called init() to see if it's
775 // still running or if it died without cleaning up.
776 if (g_processID != 0 && g_processID != GetCurrentProcessId()) {
777 HANDLE process = OpenProcess(STANDARD_RIGHTS_REQUIRED,
778 FALSE, g_processID);
779 if (process != NULL) {
780 // old process (probably) still exists so refuse to
781 // reinitialize this DLL (and thus steal it from the
782 // old process).
783 CloseHandle(process);
784 return 0;
785 }
786
787 // clean up after old process. the system should've already
788 // removed the hooks so we just need to reset our state.
789 g_hinstance = GetModuleHandle("synrgyhk");
790 g_processID = GetCurrentProcessId();
791 g_wheelSupport = kWheelNone;
792 g_threadID = 0;
793 g_keyboard = NULL;
794 g_mouse = NULL;
795 g_getMessage = NULL;
796 g_keyboardLL = NULL;
797 g_mouseLL = NULL;
798 g_screenSaver = false;
799 }
800
801 // save thread id. we'll post messages to this thread's
802 // message queue.
803 g_threadID = threadID;
804
805 // set defaults
806 g_mode = kHOOK_DISABLE;
807 g_zoneSides = 0;
808 g_zoneSize = 0;
809 g_xScreen = 0;
810 g_yScreen = 0;
811 g_wScreen = 0;
812 g_hScreen = 0;
813
814 return 1;
815}
816
817int
818cleanup(void)
819{
820 assert(g_hinstance != NULL);
821
822 if (g_processID == GetCurrentProcessId()) {
823 g_threadID = 0;
824 }
825
826 return 1;
827}
828
829EHookResult
830install()
831{
832 assert(g_hinstance != NULL);
833 assert(g_keyboard == NULL);
834 assert(g_mouse == NULL);
835 assert(g_getMessage == NULL || g_screenSaver);
836
837 // must be initialized
838 if (g_threadID == 0) {
839 return kHOOK_FAILED;
840 }
841
842 // discard old dead keys
843 g_deadVirtKey = 0;
844 g_deadLParam = 0;
845
846 // reset fake input flag
847 g_fakeInput = false;
848
849 // check for mouse wheel support
850 g_wheelSupport = getWheelSupport();
851
852 // install GetMessage hook (unless already installed)
853 if (g_wheelSupport == kWheelOld && g_getMessage == NULL) {
854 g_getMessage = SetWindowsHookEx(WH_GETMESSAGE,
855 &getMessageHook,
856 g_hinstance,
857 0);
858 }
859
860 // install low-level hooks. we require that they both get installed.
861#if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS
862 g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL,
863 &mouseLLHook,
864 g_hinstance,
865 0);
866#if !NO_GRAB_KEYBOARD
867 g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL,
868 &keyboardLLHook,
869 g_hinstance,
870 0);
871 if (g_mouseLL == NULL || g_keyboardLL == NULL) {
872 if (g_keyboardLL != NULL) {
873 UnhookWindowsHookEx(g_keyboardLL);
874 g_keyboardLL = NULL;
875 }
876 if (g_mouseLL != NULL) {
877 UnhookWindowsHookEx(g_mouseLL);
878 g_mouseLL = NULL;
879 }
880 }
881#endif
882#endif
883
884 // install regular hooks
885 if (g_mouseLL == NULL) {
886 g_mouse = SetWindowsHookEx(WH_MOUSE,
887 &mouseHook,
888 g_hinstance,
889 0);
890 }
891#if !NO_GRAB_KEYBOARD
892 if (g_keyboardLL == NULL) {
893 g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
894 &keyboardHook,
895 g_hinstance,
896 0);
897 }
898#endif
899
900 // check that we got all the hooks we wanted
901 if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) ||
902#if !NO_GRAB_KEYBOARD
903 (g_keyboardLL == NULL && g_keyboard == NULL) ||
904#endif
905 (g_mouseLL == NULL && g_mouse == NULL)) {
906 uninstall();
907 return kHOOK_FAILED;
908 }
909
910 if (g_keyboardLL != NULL || g_mouseLL != NULL) {
911 g_hookThread = GetCurrentThreadId();
912 return kHOOK_OKAY_LL;
913 }
914
915 return kHOOK_OKAY;
916}
917
918int
919uninstall(void)
920{
921 assert(g_hinstance != NULL);
922
923 // discard old dead keys
924 g_deadVirtKey = 0;
925 g_deadLParam = 0;
926
927 // detach from thread
928 detachThread();
929
930 // uninstall hooks
931 if (g_keyboardLL != NULL) {
932 UnhookWindowsHookEx(g_keyboardLL);
933 g_keyboardLL = NULL;
934 }
935 if (g_mouseLL != NULL) {
936 UnhookWindowsHookEx(g_mouseLL);
937 g_mouseLL = NULL;
938 }
939 if (g_keyboard != NULL) {
940 UnhookWindowsHookEx(g_keyboard);
941 g_keyboard = NULL;
942 }
943 if (g_mouse != NULL) {
944 UnhookWindowsHookEx(g_mouse);
945 g_mouse = NULL;
946 }
947 if (g_getMessage != NULL && !g_screenSaver) {
948 UnhookWindowsHookEx(g_getMessage);
949 g_getMessage = NULL;
950 }
951 g_wheelSupport = kWheelNone;
952
953 return 1;
954}
955
956int
957installScreenSaver(void)
958{
959 assert(g_hinstance != NULL);
960
961 // must be initialized
962 if (g_threadID == 0) {
963 return 0;
964 }
965
966 // generate screen saver messages
967 g_screenSaver = true;
968
969 // install hook unless it's already installed
970 if (g_getMessage == NULL) {
971 g_getMessage = SetWindowsHookEx(WH_GETMESSAGE,
972 &getMessageHook,
973 g_hinstance,
974 0);
975 }
976
977 return (g_getMessage != NULL) ? 1 : 0;
978}
979
980int
981uninstallScreenSaver(void)
982{
983 assert(g_hinstance != NULL);
984
985 // uninstall hook unless the mouse wheel hook is installed
986 if (g_getMessage != NULL && g_wheelSupport != kWheelOld) {
987 UnhookWindowsHookEx(g_getMessage);
988 g_getMessage = NULL;
989 }
990
991 // screen saver hook is no longer installed
992 g_screenSaver = false;
993
994 return 1;
995}
996
997void
998setSides(UInt32 sides)
999{
1000 g_zoneSides = sides;
1001}
1002
1003void
1004setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize)
1005{
1006 g_zoneSize = jumpZoneSize;
1007 g_xScreen = x;
1008 g_yScreen = y;
1009 g_wScreen = w;
1010 g_hScreen = h;
1011}
1012
1013void
1014setMode(EHookMode mode)
1015{
1016 if (mode == g_mode) {
1017 // no change
1018 return;
1019 }
1020 g_mode = mode;
1021}
1022
1023}
Note: See TracBrowser for help on using the repository browser.