/* * 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 // // 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 // // 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 // // globals // #pragma comment(linker, "-section:shared,rws") #pragma data_seg("shared") // 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_cbt = NULL; static HHOOK g_getMessage = NULL; static HANDLE g_hookThreadLL = NULL; static DWORD g_hookThreadIDLL = 0; static HANDLE g_hookEventLL = NULL; static HHOOK g_keyboardLL = NULL; static HHOOK g_mouseLL = NULL; static bool g_screenSaver = false; static bool g_relay = false; 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 HCURSOR g_cursor = NULL; static DWORD g_cursorThread = 0; #pragma data_seg() // 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 hideCursor(DWORD thread) { // we should be running the context of the window who's cursor // we want to hide so we shouldn't have to attach thread input. g_cursor = GetCursor(); g_cursorThread = thread; SetCursor(NULL); } static void restoreCursor() { // restore the show cursor in the window we hid it last if (g_cursor != NULL && g_cursorThread != 0) { DWORD myThread = GetCurrentThreadId(); if (myThread != g_cursorThread) AttachThreadInput(myThread, g_cursorThread, TRUE); SetCursor(g_cursor); if (myThread != g_cursorThread) AttachThreadInput(myThread, g_cursorThread, FALSE); } g_cursor = NULL; g_cursorThread = 0; } static LRESULT CALLBACK keyboardHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_relay) { // forward message to our window PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); // 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; default: // discard event return 1; } } } return CallNextHookEx(g_keyboard, code, wParam, lParam); } static LRESULT CALLBACK mouseHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_relay) { switch (wParam) { case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, 0); return 1; case 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: { const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, static_cast( LOWORD(info->dwExtraInfo)), 0); break; } case kWheelWin2000: { const MOUSEHOOKSTRUCTWin2000* info = (const MOUSEHOOKSTRUCTWin2000*)lParam; PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, static_cast( HIWORD(info->mouseData)), 0); break; } default: break; } } return 1; case WM_MOUSEMOVE: { const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; // we want the cursor to be hidden at all times so we // hide the cursor on whatever window has it. but then // we have to show the cursor whenever we leave that // window (or at some later time before we stop relaying). // so check the window with the cursor. if it's not the // same window that had it before then show the cursor // in the last window and hide it in this window. DWORD thread = GetWindowThreadProcessId(info->hwnd, NULL); if (thread != g_cursorThread) { restoreCursor(); hideCursor(thread); } // get position. it seems the positions are not // in virtual desktop coordinates but in a similar // space with 0,0 at the upper-left. translate // into virtual desktop coordinates. SInt32 x = (SInt32)info->pt.x + g_xScreen; SInt32 y = (SInt32)info->pt.y + g_yScreen; // relay the motion PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); } return 1; } } else { // check for mouse inside jump zone. it seems the positions // are not in virtual desktop coordinates but in a similar // space with 0,0 at the upper-left. translate into virtual // desktop coordinates. bool inside = false; const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam; SInt32 x = (SInt32)info->pt.x; SInt32 y = (SInt32)info->pt.y; 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); } // if inside then eat event and notify our window if (inside) { x += g_xScreen; y += g_yScreen; restoreCursor(); PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); return 1; } else { x += g_xScreen; y += g_yScreen; PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); return 0; } } } return CallNextHookEx(g_mouse, code, wParam, lParam); } /* static LRESULT CALLBACK cbtHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_relay) { // do nothing for now. may add something later. } } return CallNextHookEx(g_cbt, 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_relay) { MSG* msg = reinterpret_cast(lParam); if (msg->message == g_wmMouseWheel) { // post message to our window PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, msg->wParam, 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) // // 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 compressed for us. // static LRESULT CALLBACK keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_relay) { KBDLLHOOKSTRUCT* info = reinterpret_cast(lParam); // let certain keys pass through switch (info->vkCode) { 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; default: // construct lParam for WM_KEYDOWN, etc. DWORD 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 // forward message to our window PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, info->vkCode, lParam); // discard event return 1; } } } return CallNextHookEx(g_keyboardLL, code, wParam, lParam); } // // low-level mouse hook -- this allows us to capture and handle mouse // wheel events on all windows NT platforms from NT SP3 and up. this // is both simpler than using the mouse hook but also supports windows // windows NT which does not report mouse wheel events. we need to // keep the mouse hook handling of mouse wheel events because the // windows 95 family doesn't support low-level hooks. // static LRESULT CALLBACK mouseLLHook(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (g_relay) { MSLLHOOKSTRUCT* info = reinterpret_cast(lParam); switch (wParam) { case WM_MOUSEWHEEL: // mouse wheel events are the same for entire NT family // (>=SP3, prior versions have no low level hooks) for // low-level mouse hook messages, unlike (regular) mouse // hook messages which are gratuitously different on // win2k and not sent at all for windows NT. // forward message to our window PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, HIWORD(info->mouseData), 0); // discard event return 1; default: // all other events are passed through break; } } } return CallNextHookEx(g_mouseLL, code, wParam, lParam); } static DWORD WINAPI getLowLevelProc(void*) { // thread proc for low-level keyboard/mouse hooks. this does // nothing but install the hook, process events, and uninstall // the hook. // force this thread to have a message queue MSG msg; PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); #if !NO_GRAB_KEYBOARD // install low-level keyboard hook g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL, &keyboardLLHook, g_hinstance, 0); if (g_keyboardLL == NULL) { // indicate failure and exit g_hookThreadIDLL = 0; SetEvent(g_hookEventLL); return 1; } #else // keep compiler quiet &keyboardLLHook; #endif // install low-level mouse hook g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL, &mouseLLHook, g_hinstance, 0); if (g_mouseLL == NULL) { // indicate failure and exit if (g_keyboardLL != NULL) { UnhookWindowsHookEx(g_keyboardLL); g_keyboardLL = NULL; } g_hookThreadIDLL = 0; SetEvent(g_hookEventLL); return 1; } // ready SetEvent(g_hookEventLL); // message loop bool done = false; while (!done) { switch (GetMessage(&msg, NULL, 0, 0)) { case -1: break; case 0: done = true; break; default: TranslateMessage(&msg); DispatchMessage(&msg); break; } } // uninstall hook UnhookWindowsHookEx(g_mouseLL); UnhookWindowsHookEx(g_keyboardLL); g_mouseLL = NULL; g_keyboardLL = NULL; return 0; } #else // (_WIN32_WINNT < 0x0400) static DWORD WINAPI getLowLevelProc(void*) { g_hookThreadIDLL = 0; SetEvent(g_hookEventLL); return 1; } #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. 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; } } } // 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()) { if (g_keyboard != NULL || g_mouse != NULL || g_cbt != NULL) { uninstall(); } g_processID = 0; g_hinstance = NULL; } } return TRUE; } extern "C" { 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("synrgyhk"); g_processID = GetCurrentProcessId(); g_wheelSupport = kWheelNone; g_threadID = 0; g_keyboard = NULL; g_mouse = NULL; g_cbt = NULL; g_getMessage = NULL; g_hookThreadLL = NULL; g_hookThreadIDLL = 0; g_hookEventLL = 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_relay = false; g_zoneSides = 0; g_zoneSize = 0; g_xScreen = 0; g_yScreen = 0; g_wScreen = 0; g_hScreen = 0; g_cursor = NULL; g_cursorThread = 0; return 1; } int cleanup(void) { assert(g_hinstance != NULL); if (g_processID == GetCurrentProcessId()) { g_threadID = 0; } return 1; } int install() { assert(g_hinstance != NULL); assert(g_keyboard == NULL); assert(g_mouse == NULL); assert(g_cbt == NULL); assert(g_getMessage == NULL || g_screenSaver); // must be initialized if (g_threadID == 0) { return 0; } // check for mouse wheel support g_wheelSupport = getWheelSupport(); // install keyboard hook #if !NO_GRAB_KEYBOARD g_keyboard = SetWindowsHookEx(WH_KEYBOARD, &keyboardHook, g_hinstance, 0); if (g_keyboard == NULL) { g_threadID = NULL; return 0; } #else // keep compiler quiet &keyboardHook; #endif // install mouse hook g_mouse = SetWindowsHookEx(WH_MOUSE, &mouseHook, g_hinstance, 0); if (g_mouse == NULL) { // uninstall keyboard hook before failing if (g_keyboard != NULL) { UnhookWindowsHookEx(g_keyboard); g_keyboard = NULL; } g_threadID = NULL; return 0; } /* // install CBT hook g_cbt = SetWindowsHookEx(WH_CBT, &cbtHook, g_hinstance, 0); if (g_cbt == NULL) { // uninstall keyboard and mouse hooks before failing if (g_keyboard != NULL) { UnhookWindowsHookEx(g_keyboard); g_keyboard = NULL; } UnhookWindowsHookEx(g_mouse); g_mouse = NULL; g_threadID = NULL; return 0; } */ // install GetMessage hook (unless already installed) if (g_wheelSupport == kWheelOld && g_getMessage == NULL) { g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, &getMessageHook, g_hinstance, 0); // ignore failure; we just won't get mouse wheel messages } // install low-level keyboard/mouse hooks, if possible. since these // hook are called in the context of the installing thread and that // thread must have a message loop but we don't want the caller's // message loop to do the work, we'll fire up a separate thread // just for the hooks. note that low-level hooks are only available // on windows NT SP3 and above. g_hookEventLL = CreateEvent(NULL, TRUE, FALSE, NULL); if (g_hookEventLL != NULL) { g_hookThreadLL = CreateThread(NULL, 0, &getLowLevelProc, 0, CREATE_SUSPENDED, &g_hookThreadIDLL); if (g_hookThreadLL != NULL) { // start the thread and wait for it to initialize ResumeThread(g_hookThreadLL); WaitForSingleObject(g_hookEventLL, INFINITE); ResetEvent(g_hookEventLL); // the thread clears g_hookThreadIDLL if it failed if (g_hookThreadIDLL == 0) { CloseHandle(g_hookThreadLL); g_hookThreadLL = NULL; } } if (g_hookThreadLL == NULL) { CloseHandle(g_hookEventLL); g_hookEventLL = NULL; } } return 1; } int uninstall(void) { assert(g_hinstance != NULL); // uninstall hooks if (g_hookThreadLL != NULL) { PostThreadMessage(g_hookThreadIDLL, WM_QUIT, 0, 0); WaitForSingleObject(g_hookThreadLL, INFINITE); CloseHandle(g_hookEventLL); CloseHandle(g_hookThreadLL); g_hookEventLL = NULL; g_hookThreadLL = NULL; g_hookThreadIDLL = 0; } if (g_keyboard != NULL) { UnhookWindowsHookEx(g_keyboard); } if (g_mouse != NULL) { UnhookWindowsHookEx(g_mouse); } if (g_cbt != NULL) { UnhookWindowsHookEx(g_cbt); } if (g_getMessage != NULL && !g_screenSaver) { UnhookWindowsHookEx(g_getMessage); g_getMessage = NULL; } g_keyboard = NULL; g_mouse = NULL; g_cbt = NULL; g_wheelSupport = kWheelNone; // show the cursor restoreCursor(); 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 == kWheelNone) { 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 setRelay(int enable) { g_relay = (enable != 0); if (!g_relay) { restoreCursor(); } } }