/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2002 Chris Schoeneman * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "CSynergyHook.h" #include "ProtocolTypes.h" #include #include #if _MSC_VER >= 1400 // VS2005 hack - we don't use assert here because we don't want to link with the CRT. #undef assert #if _DEBUG #define assert(_X_) if (!(_X_)) __debugbreak() #else #define assert(_X_) __noop() #endif // VS2005 is a bit more smart than VC6 and optimize simple copy loop to // intrinsic memcpy. #pragma function(memcpy) #endif // // debugging compile flag. when not zero the server doesn't grab // the keyboard when the mouse leaves the server screen. this // makes it possible to use the debugger (via the keyboard) when // all user input would normally be caught by the hook procedures. // #define NO_GRAB_KEYBOARD 0 // // debugging compile flag. when not zero the server will not // install low level hooks. // #define NO_LOWLEVEL_HOOKS 0 // // extra mouse wheel stuff // enum EWheelSupport { kWheelNone, kWheelOld, kWheelWin2000, kWheelModern }; // declare extended mouse hook struct. useable on win2k typedef struct tagMOUSEHOOKSTRUCTWin2000 { MOUSEHOOKSTRUCT mhs; DWORD mouseData; } MOUSEHOOKSTRUCTWin2000; #if !defined(SM_MOUSEWHEELPRESENT) #define SM_MOUSEWHEELPRESENT 75 #endif // X button stuff #if !defined(WM_XBUTTONDOWN) #define WM_XBUTTONDOWN 0x020B #define WM_XBUTTONUP 0x020C #define WM_XBUTTONDBLCLK 0x020D #define WM_NCXBUTTONDOWN 0x00AB #define WM_NCXBUTTONUP 0x00AC #define WM_NCXBUTTONDBLCLK 0x00AD #define MOUSEEVENTF_XDOWN 0x0080 #define MOUSEEVENTF_XUP 0x0100 #define XBUTTON1 0x0001 #define XBUTTON2 0x0002 #endif // // globals // #if defined(_MSC_VER) #pragma comment(linker, "-section:shared,rws") #pragma data_seg("shared") #endif // all data in this shared section *must* be initialized static HINSTANCE g_hinstance = NULL; static DWORD g_processID = 0; static EWheelSupport g_wheelSupport = kWheelNone; static UINT g_wmMouseWheel = 0; static DWORD g_threadID = 0; static HHOOK g_keyboard = NULL; static HHOOK g_mouse = NULL; static HHOOK g_getMessage = NULL; static HHOOK g_keyboardLL = NULL; static HHOOK g_mouseLL = NULL; static bool g_screenSaver = false; static EHookMode g_mode = kHOOK_DISABLE; static UInt32 g_zoneSides = 0; static SInt32 g_zoneSize = 0; static SInt32 g_xScreen = 0; static SInt32 g_yScreen = 0; static SInt32 g_wScreen = 0; static SInt32 g_hScreen = 0; static WPARAM g_deadVirtKey = 0; static LPARAM g_deadLParam = 0; static BYTE g_deadKeyState[256] = { 0 }; static DWORD g_hookThread = 0; static DWORD g_attachedThread = 0; static bool g_fakeInput = false; #if defined(_MSC_VER) #pragma data_seg() #endif // keep linker quiet about floating point stuff. we don't use any // floating point operations but our includes may define some // (unused) floating point values. #ifndef _DEBUG extern "C" { int _fltused=0; } #endif // // internal functions // static void detachThread() { if (g_attachedThread != 0 && g_hookThread != g_attachedThread) { AttachThreadInput(g_hookThread, g_attachedThread, FALSE); g_attachedThread = 0; } } static bool attachThreadToForeground() { // only attach threads if using low level hooks. a low level hook // runs in the thread that installed the hook but we have to make // changes that require being attached to the target thread (which // should be the foreground window). a regular hook runs in the // thread that just removed the event from its queue so we're // already in the right thread. if (g_hookThread != 0) { HWND window = GetForegroundWindow(); DWORD threadID = GetWindowThreadProcessId(window, NULL); // skip if no change if (g_attachedThread != threadID) { // detach from previous thread detachThread(); // attach to new thread if (threadID != 0 && threadID != g_hookThread) { AttachThreadInput(g_hookThread, threadID, TRUE); g_attachedThread = threadID; } return true; } } return false; } #if !NO_GRAB_KEYBOARD static WPARAM makeKeyMsg(UINT virtKey, char c, bool noAltGr) { return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0); } static void keyboardGetState(BYTE keys[256]) { // we have to use GetAsyncKeyState() rather than GetKeyState() because // we don't pass through most keys so the event synchronous state // doesn't get updated. we do that because certain modifier keys have // side effects, like alt and the windows key. SHORT key; for (int i = 0; i < 256; ++i) { key = GetAsyncKeyState(i); keys[i] = (BYTE)((key < 0) ? 0x80u : 0); } key = GetKeyState(VK_CAPITAL); keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1)); } static bool doKeyboardHookHandler(WPARAM wParam, LPARAM lParam) { // check for special events indicating if we should start or stop // passing events through and not report them to the server. this // is used to allow the server to synthesize events locally but // not pick them up as user events. if (wParam == SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY && ((lParam >> 16) & 0xffu) == SYNERGY_HOOK_FAKE_INPUT_SCANCODE) { // update flag g_fakeInput = ((lParam & 0x80000000u) == 0); PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, 0xff000000u | wParam, lParam); // discard event return true; } // if we're expecting fake input then just pass the event through // and do not forward to the server if (g_fakeInput) { PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, 0xfe000000u | wParam, lParam); return false; } // VK_RSHIFT may be sent with an extended scan code but right shift // is not an extended key so we reset that bit. if (wParam == VK_RSHIFT) { lParam &= ~0x01000000u; } // tell server about event PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam, lParam); // ignore dead key release if (g_deadVirtKey == wParam && (lParam & 0x80000000u) != 0) { PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam | 0x04000000, lParam); return false; } // we need the keyboard state for ToAscii() BYTE keys[256]; keyboardGetState(keys); // ToAscii() maps ctrl+letter to the corresponding control code // and ctrl+backspace to delete. we don't want those translations // so clear the control modifier state. however, if we want to // simulate AltGr (which is ctrl+alt) then we must not clear it. UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL]; UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU]; if ((control & 0x80) == 0 || (menu & 0x80) == 0) { keys[VK_LCONTROL] = 0; keys[VK_RCONTROL] = 0; keys[VK_CONTROL] = 0; } else { keys[VK_LCONTROL] = 0x80; keys[VK_RCONTROL] = 0x80; keys[VK_CONTROL] = 0x80; keys[VK_LMENU] = 0x80; keys[VK_RMENU] = 0x80; keys[VK_MENU] = 0x80; } // ToAscii() needs to know if a menu is active for some reason. // we don't know and there doesn't appear to be any way to find // out. so we'll just assume a menu is active if the menu key // is down. // FIXME -- figure out some way to check if a menu is active UINT flags = 0; if ((menu & 0x80) != 0) flags |= 1; // if we're on the server screen then just pass numpad keys with alt // key down as-is. we won't pick up the resulting character but the // local app will. if on a client screen then grab keys as usual; // if the client is a windows system it'll synthesize the expected // character. if not then it'll probably just do nothing. if (g_mode != kHOOK_RELAY_EVENTS) { // we don't use virtual keys because we don't know what the // state of the numlock key is. we'll hard code the scan codes // instead. hopefully this works across all keyboards. UINT sc = (lParam & 0x01ff0000u) >> 16; if (menu && (sc >= 0x47u && sc <= 0x52u && sc != 0x4au && sc != 0x4eu)) { return false; } } // map the key event to a character. we have to put the dead // key back first and this has the side effect of removing it. if (g_deadVirtKey != 0) { WORD c = 0; ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, g_deadKeyState, &c, flags); } WORD c = 0; UINT scanCode = ((lParam & 0x10ff0000u) >> 16); int n = ToAscii(wParam, scanCode, keys, &c, flags); // if mapping failed and ctrl and alt are pressed then try again // with both not pressed. this handles the case where ctrl and // alt are being used as individual modifiers rather than AltGr. // we note that's the case in the message sent back to synergy // because there's no simple way to deduce it after the fact. // we have to put the dead key back first, if there was one. bool noAltGr = false; if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) { noAltGr = true; PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam | 0x05000000, lParam); if (g_deadVirtKey != 0) { ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, g_deadKeyState, &c, flags); } BYTE keys2[256]; for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { keys2[i] = keys[i]; } keys2[VK_LCONTROL] = 0; keys2[VK_RCONTROL] = 0; keys2[VK_CONTROL] = 0; keys2[VK_LMENU] = 0; keys2[VK_RMENU] = 0; keys2[VK_MENU] = 0; n = ToAscii(wParam, scanCode, keys2, &c, flags); } PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam | ((c & 0xff) << 8) | ((n & 0xff) << 16) | 0x06000000, lParam); WPARAM charAndVirtKey = 0; bool clearDeadKey = false; switch (n) { default: // key is a dead key g_deadVirtKey = wParam; g_deadLParam = lParam; for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { g_deadKeyState[i] = keys[i]; } break; case 0: // key doesn't map to a character. this can happen if // non-character keys are pressed after a dead key. charAndVirtKey = makeKeyMsg(wParam, (char)0, noAltGr); break; case 1: // key maps to a character composed with dead key charAndVirtKey = makeKeyMsg(wParam, (char)LOBYTE(c), noAltGr); clearDeadKey = true; break; case 2: { // previous dead key not composed. send a fake key press // and release for the dead key to our window. WPARAM deadCharAndVirtKey = makeKeyMsg(g_deadVirtKey, (char)LOBYTE(c), noAltGr); PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, deadCharAndVirtKey, g_deadLParam & 0x7fffffffu); PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, deadCharAndVirtKey, g_deadLParam | 0x80000000u); // use uncomposed character charAndVirtKey = makeKeyMsg(wParam, (char)HIBYTE(c), noAltGr); clearDeadKey = true; break; } } // put back the dead key, if any, for the application to use if (g_deadVirtKey != 0) { ToAscii(g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, g_deadKeyState, &c, flags); } // clear out old dead key state if (clearDeadKey) { g_deadVirtKey = 0; g_deadLParam = 0; } // forward message to our window. do this whether or not we're // forwarding events to clients because this'll keep our thread's // key state table up to date. that's important for querying // the scroll lock toggle state. // XXX -- with hot keys for actions we may only need to do this when // forwarding. if (charAndVirtKey != 0) { PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, charAndVirtKey | 0x07000000, lParam); PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam); } if (g_mode == kHOOK_RELAY_EVENTS) { // let certain keys pass through switch (wParam) { case VK_CAPITAL: case VK_NUMLOCK: case VK_SCROLL: // pass event on. we want to let these through to // the window proc because otherwise the keyboard // lights may not stay synchronized. break; case VK_HANGUL: // pass these modifiers if using a low level hook, discard // them if not. if (g_hookThread == 0) { return true; } break; default: // discard return true; } } return false; } static bool keyboardHookHandler(WPARAM wParam, LPARAM lParam) { attachThreadToForeground(); return doKeyboardHookHandler(wParam, lParam); } #endif static bool doMouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) { switch (wParam) { case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_XBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_MBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_XBUTTONDBLCLK: case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: case WM_XBUTTONUP: case WM_NCLBUTTONDOWN: case WM_NCMBUTTONDOWN: case WM_NCRBUTTONDOWN: case WM_NCXBUTTONDOWN: case WM_NCLBUTTONDBLCLK: case WM_NCMBUTTONDBLCLK: case WM_NCRBUTTONDBLCLK: case WM_NCXBUTTONDBLCLK: case WM_NCLBUTTONUP: case WM_NCMBUTTONUP: case WM_NCRBUTTONUP: case WM_NCXBUTTONUP: // always relay the event. eat it if relaying. PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, data); return (g_mode == kHOOK_RELAY_EVENTS); case WM_MOUSEWHEEL: if (g_mode == kHOOK_RELAY_EVENTS) { // relay event PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, data, 0); } return (g_mode == kHOOK_RELAY_EVENTS); case WM_NCMOUSEMOVE: case WM_MOUSEMOVE: if (g_mode == kHOOK_RELAY_EVENTS) { // relay and eat event PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); return true; } else if (g_mode == kHOOK_WATCH_JUMP_ZONE) { // low level hooks can report bogus mouse positions that are // outside of the screen. jeez. naturally we end up getting // fake motion in the other direction to get the position back // on the screen, which plays havoc with switch on double tap. // CServer deals with that. we'll clamp positions onto the // screen. also, if we discard events for positions outside // of the screen then the mouse appears to get a bit jerky // near the edge. we can either accept that or pass the bogus // events. we'll try passing the events. bool bogus = false; if (x < g_xScreen) { x = g_xScreen; bogus = true; } else if (x >= g_xScreen + g_wScreen) { x = g_xScreen + g_wScreen - 1; bogus = true; } if (y < g_yScreen) { y = g_yScreen; bogus = true; } else if (y >= g_yScreen + g_hScreen) { y = g_yScreen + g_hScreen - 1; bogus = true; } // check for mouse inside jump zone bool inside = false; if (!inside && (g_zoneSides & kLeftMask) != 0) { inside = (x < g_xScreen + g_zoneSize); } if (!inside && (g_zoneSides & kRightMask) != 0) { inside = (x >= g_xScreen + g_wScreen - g_zoneSize); } if (!inside && (g_zoneSides & kTopMask) != 0) { inside = (y < g_yScreen + g_zoneSize); } if (!inside && (g_zoneSides & kBottomMask) != 0) { inside = (y >= g_yScreen + g_hScreen - g_zoneSize); } // relay the event PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); // if inside and not bogus then eat the event return inside && !bogus; } } // pass the event return false; } static bool mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) { // attachThreadToForeground(); return doMouseHookHandler(wParam, x, y, data); } #if !NO_GRAB_KEYBOARD static LRESULT CALLBACK keyboardHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { // handle the message if (keyboardHookHandler(wParam, lParam)) { return 1; } } return CallNextHookEx(g_keyboard, code, wParam, lParam); } #endif static LRESULT CALLBACK mouseHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { // decode message const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; SInt32 x = (SInt32)info->pt.x; SInt32 y = (SInt32)info->pt.y; SInt32 w = 0; if (wParam == WM_MOUSEWHEEL) { // win2k and other systems supporting WM_MOUSEWHEEL in // the mouse hook are gratuitously different (and poorly // documented). if a low-level mouse hook is in place // it should capture these events so we'll never see // them. switch (g_wheelSupport) { case kWheelModern: w = static_cast(LOWORD(info->dwExtraInfo)); break; case kWheelWin2000: { const MOUSEHOOKSTRUCTWin2000* info2k = (const MOUSEHOOKSTRUCTWin2000*)lParam; w = static_cast(HIWORD(info2k->mouseData)); break; } default: break; } } // handle the message. note that we don't handle X buttons // here. that's okay because they're only supported on // win2k and winxp and up and on those platforms we'll get // get the mouse events through the low level hook. if (mouseHookHandler(wParam, x, y, w)) { return 1; } } return CallNextHookEx(g_mouse, code, wParam, lParam); } static LRESULT CALLBACK getMessageHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_screenSaver) { MSG* msg = reinterpret_cast(lParam); if (msg->message == WM_SYSCOMMAND && msg->wParam == SC_SCREENSAVE) { // broadcast screen saver started message PostThreadMessage(g_threadID, SYNERGY_MSG_SCREEN_SAVER, TRUE, 0); } } if (g_mode == kHOOK_RELAY_EVENTS) { MSG* msg = reinterpret_cast(lParam); if (g_wheelSupport == kWheelOld && msg->message == g_wmMouseWheel) { // post message to our window PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, static_cast(msg->wParam & 0xffffu), 0); // zero out the delta in the message so it's (hopefully) // ignored msg->wParam = 0; } } } return CallNextHookEx(g_getMessage, code, wParam, lParam); } #if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS // // low-level keyboard hook -- this allows us to capture and handle // alt+tab, alt+esc, ctrl+esc, and windows key hot keys. on the down // side, key repeats are not reported to us. // #if !NO_GRAB_KEYBOARD static LRESULT CALLBACK keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { // decode the message KBDLLHOOKSTRUCT* info = reinterpret_cast(lParam); WPARAM wParam = info->vkCode; LPARAM lParam = 1; // repeat code lParam |= (info->scanCode << 16); // scan code if (info->flags & LLKHF_EXTENDED) { lParam |= (1lu << 24); // extended key } if (info->flags & LLKHF_ALTDOWN) { lParam |= (1lu << 29); // context code } if (info->flags & LLKHF_UP) { lParam |= (1lu << 31); // transition } // FIXME -- bit 30 should be set if key was already down but // we don't know that info. as a result we'll never generate // key repeat events. // handle the message if (keyboardHookHandler(wParam, lParam)) { return 1; } } return CallNextHookEx(g_keyboardLL, code, wParam, lParam); } #endif // // low-level mouse hook -- this allows us to capture and handle mouse // events very early. the earlier the better. // static LRESULT CALLBACK mouseLLHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { // decode the message MSLLHOOKSTRUCT* info = reinterpret_cast(lParam); SInt32 x = static_cast(info->pt.x); SInt32 y = static_cast(info->pt.y); SInt32 w = static_cast(HIWORD(info->mouseData)); // handle the message if (mouseHookHandler(wParam, x, y, w)) { return 1; } } return CallNextHookEx(g_mouseLL, code, wParam, lParam); } #endif static EWheelSupport getWheelSupport() { // get operating system OSVERSIONINFO info; info.dwOSVersionInfoSize = sizeof(info); if (!GetVersionEx(&info)) { return kWheelNone; } // see if modern wheel is present if (GetSystemMetrics(SM_MOUSEWHEELPRESENT)) { // note if running on win2k if (info.dwPlatformId == VER_PLATFORM_WIN32_NT && info.dwMajorVersion == 5 && info.dwMinorVersion == 0) { return kWheelWin2000; } return kWheelModern; } // not modern. see if we've got old-style support. #if defined(MSH_WHEELSUPPORT) UINT wheelSupportMsg = RegisterWindowMessage(MSH_WHEELSUPPORT); HWND wheelSupportWindow = FindWindow(MSH_WHEELMODULE_CLASS, MSH_WHEELMODULE_TITLE); if (wheelSupportWindow != NULL && wheelSupportMsg != 0) { if (SendMessage(wheelSupportWindow, wheelSupportMsg, 0, 0) != 0) { g_wmMouseWheel = RegisterWindowMessage(MSH_MOUSEWHEEL); if (g_wmMouseWheel != 0) { return kWheelOld; } } } #endif // assume modern. we don't do anything special in this case // except respond to WM_MOUSEWHEEL messages. GetSystemMetrics() // can apparently return FALSE even if a mouse wheel is present // though i'm not sure exactly when it does that (WinME returns // FALSE for my logitech USB trackball). return kWheelModern; } // // external functions // BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(instance); if (g_processID == 0) { g_hinstance = instance; g_processID = GetCurrentProcessId(); } } else if (reason == DLL_PROCESS_DETACH) { if (g_processID == GetCurrentProcessId()) { uninstall(); uninstallScreenSaver(); g_processID = 0; g_hinstance = NULL; } } return TRUE; } extern "C" { // VS2005 hack to not link with the CRT #if _MSC_VER >= 1400 BOOL WINAPI _DllMainCRTStartup( HINSTANCE instance, DWORD reason, LPVOID lpreserved) { return DllMain(instance, reason, lpreserved); } // VS2005 is a bit more bright than VC6 and optimize simple copy loop to // intrinsic memcpy. void * __cdecl memcpy(void * _Dst, const void * _Src, size_t _MaxCount) { void * _DstBackup = _Dst; switch (_MaxCount & 3) { case 3: ((char*)_Dst)[0] = ((char*)_Src)[0]; ++(char*&)_Dst; ++(char*&)_Src; --_MaxCount; case 2: ((char*)_Dst)[0] = ((char*)_Src)[0]; ++(char*&)_Dst; ++(char*&)_Src; --_MaxCount; case 1: ((char*)_Dst)[0] = ((char*)_Src)[0]; ++(char*&)_Dst; ++(char*&)_Src; --_MaxCount; break; case 0: break; default: __assume(0); break; } // I think it's faster on intel to deference than modify the pointer. const size_t max = _MaxCount / sizeof(UINT_PTR); for (size_t i = 0; i < max; ++i) { ((UINT_PTR*)_Dst)[i] = ((UINT_PTR*)_Src)[i]; } (UINT_PTR*&)_Dst += max; (UINT_PTR*&)_Src += max; switch (_MaxCount & 3) { case 3: ((char*)_Dst)[0] = ((char*)_Src)[0]; ++(char*&)_Dst; ++(char*&)_Src; case 2: ((char*)_Dst)[0] = ((char*)_Src)[0]; ++(char*&)_Dst; ++(char*&)_Src; case 1: ((char*)_Dst)[0] = ((char*)_Src)[0]; ++(char*&)_Dst; ++(char*&)_Src; break; case 0: break; default: __assume(0); break; } return _DstBackup; } #endif int init(DWORD threadID) { assert(g_hinstance != NULL); // try to open process that last called init() to see if it's // still running or if it died without cleaning up. if (g_processID != 0 && g_processID != GetCurrentProcessId()) { HANDLE process = OpenProcess(STANDARD_RIGHTS_REQUIRED, FALSE, g_processID); if (process != NULL) { // old process (probably) still exists so refuse to // reinitialize this DLL (and thus steal it from the // old process). CloseHandle(process); return 0; } // clean up after old process. the system should've already // removed the hooks so we just need to reset our state. g_hinstance = GetModuleHandle(_T("synrgyhk")); g_processID = GetCurrentProcessId(); g_wheelSupport = kWheelNone; g_threadID = 0; g_keyboard = NULL; g_mouse = NULL; g_getMessage = NULL; g_keyboardLL = NULL; g_mouseLL = NULL; g_screenSaver = false; } // save thread id. we'll post messages to this thread's // message queue. g_threadID = threadID; // set defaults g_mode = kHOOK_DISABLE; g_zoneSides = 0; g_zoneSize = 0; g_xScreen = 0; g_yScreen = 0; g_wScreen = 0; g_hScreen = 0; return 1; } int cleanup(void) { assert(g_hinstance != NULL); if (g_processID == GetCurrentProcessId()) { g_threadID = 0; } return 1; } EHookResult install() { assert(g_hinstance != NULL); assert(g_keyboard == NULL); assert(g_mouse == NULL); assert(g_getMessage == NULL || g_screenSaver); // must be initialized if (g_threadID == 0) { return kHOOK_FAILED; } // discard old dead keys g_deadVirtKey = 0; g_deadLParam = 0; // reset fake input flag g_fakeInput = false; // check for mouse wheel support g_wheelSupport = getWheelSupport(); // install GetMessage hook (unless already installed) if (g_wheelSupport == kWheelOld && g_getMessage == NULL) { g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, &getMessageHook, g_hinstance, 0); } // install low-level hooks. we require that they both get installed. #if (_WIN32_WINNT >= 0x0400) && defined(_MSC_VER) && !NO_LOWLEVEL_HOOKS g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL, &mouseLLHook, g_hinstance, 0); #if !NO_GRAB_KEYBOARD g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL, &keyboardLLHook, g_hinstance, 0); if (g_mouseLL == NULL || g_keyboardLL == NULL) { if (g_keyboardLL != NULL) { UnhookWindowsHookEx(g_keyboardLL); g_keyboardLL = NULL; } if (g_mouseLL != NULL) { UnhookWindowsHookEx(g_mouseLL); g_mouseLL = NULL; } } #endif #endif // install regular hooks if (g_mouseLL == NULL) { g_mouse = SetWindowsHookEx(WH_MOUSE, &mouseHook, g_hinstance, 0); } #if !NO_GRAB_KEYBOARD if (g_keyboardLL == NULL) { g_keyboard = SetWindowsHookEx(WH_KEYBOARD, &keyboardHook, g_hinstance, 0); } #endif // check that we got all the hooks we wanted if ((g_getMessage == NULL && g_wheelSupport == kWheelOld) || #if !NO_GRAB_KEYBOARD (g_keyboardLL == NULL && g_keyboard == NULL) || #endif (g_mouseLL == NULL && g_mouse == NULL)) { uninstall(); return kHOOK_FAILED; } if (g_keyboardLL != NULL || g_mouseLL != NULL) { g_hookThread = GetCurrentThreadId(); return kHOOK_OKAY_LL; } return kHOOK_OKAY; } int uninstall(void) { assert(g_hinstance != NULL); // discard old dead keys g_deadVirtKey = 0; g_deadLParam = 0; // detach from thread detachThread(); // uninstall hooks if (g_keyboardLL != NULL) { UnhookWindowsHookEx(g_keyboardLL); g_keyboardLL = NULL; } if (g_mouseLL != NULL) { UnhookWindowsHookEx(g_mouseLL); g_mouseLL = NULL; } if (g_keyboard != NULL) { UnhookWindowsHookEx(g_keyboard); g_keyboard = NULL; } if (g_mouse != NULL) { UnhookWindowsHookEx(g_mouse); g_mouse = NULL; } if (g_getMessage != NULL && !g_screenSaver) { UnhookWindowsHookEx(g_getMessage); g_getMessage = NULL; } g_wheelSupport = kWheelNone; return 1; } int installScreenSaver(void) { assert(g_hinstance != NULL); // must be initialized if (g_threadID == 0) { return 0; } // generate screen saver messages g_screenSaver = true; // install hook unless it's already installed if (g_getMessage == NULL) { g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, &getMessageHook, g_hinstance, 0); } return (g_getMessage != NULL) ? 1 : 0; } int uninstallScreenSaver(void) { assert(g_hinstance != NULL); // uninstall hook unless the mouse wheel hook is installed if (g_getMessage != NULL && g_wheelSupport != kWheelOld) { UnhookWindowsHookEx(g_getMessage); g_getMessage = NULL; } // screen saver hook is no longer installed g_screenSaver = false; return 1; } void setSides(UInt32 sides) { g_zoneSides = sides; } void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize) { g_zoneSize = jumpZoneSize; g_xScreen = x; g_yScreen = y; g_wScreen = w; g_hScreen = h; } void setMode(EHookMode mode) { if (mode == g_mode) { // no change return; } g_mode = mode; } }