mirror of
https://github.com/debauchee/barrier.git
synced 2024-12-29 14:12:37 +03:00
d2135af0d9
CServer to the primary screen when the configuration changes so it can make necessary adjustments (the win32 primary screen must tell the hook dll about the new jump zones). changed includes of some std c++ library files to go through our own include files. these wrap the include with stuff to keep vc++ quiet when compiling at warning level 4, which is what it does now. it also works around missing <istream> and <ostream> on g++2.96. added missing std:: where necessary. g++ doesn't really support namespaces so it lets references without the namespace slip through. added workaround or fix. not sure if istringstream::str(string) should reset eofbit. it does on g++ but does not on vc++. added clear() after str() so it works either way. added low-level keyboard hook to win32. if available (it's only available on NT SP3 and up) it allows us to catch and handle alt+tab, alt+esc, ctrl+esc, and windows key hot keys. i think that leaves only ctrl+alt+del and accessibility functions uncaught on those systems.
595 lines
14 KiB
C++
595 lines
14 KiB
C++
#include "CSynergyHook.h"
|
|
#include "CConfig.h"
|
|
#include <assert.h>
|
|
#include <zmouse.h>
|
|
|
|
//
|
|
// 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_process = NULL;
|
|
static EWheelSupport g_wheelSupport = kWheelNone;
|
|
static UINT g_wmMouseWheel = 0;
|
|
static HWND g_hwnd = NULL;
|
|
static HHOOK g_keyboard = NULL;
|
|
static HHOOK g_mouse = NULL;
|
|
static HHOOK g_cbt = NULL;
|
|
static HHOOK g_getMessage = NULL;
|
|
static HANDLE g_keyHookThread = NULL;
|
|
static DWORD g_keyHookThreadID = 0;
|
|
static HANDLE g_keyHookEvent = NULL;
|
|
static HHOOK g_keyboardLL = NULL;
|
|
static bool g_relay = false;
|
|
static SInt32 g_zoneSize = 0;
|
|
static UInt32 g_zoneSides = 0;
|
|
static SInt32 g_wScreen = 0;
|
|
static SInt32 g_hScreen = 0;
|
|
static HCURSOR g_cursor = NULL;
|
|
static DWORD g_cursorThread = 0;
|
|
|
|
static int foo = 0;
|
|
|
|
#pragma data_seg()
|
|
|
|
//
|
|
// 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
|
|
PostMessage(g_hwnd, SYNERGY_MSG_KEY, wParam, lParam);
|
|
|
|
/* XXX -- this doesn't seem to work/help with lost key events
|
|
// if the active window isn't our window then make it
|
|
// active.
|
|
const bool wrongFocus = (GetActiveWindow() != g_hwnd);
|
|
if (wrongFocus) {
|
|
SetForegroundWindow(g_hwnd);
|
|
}
|
|
*/
|
|
|
|
// 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:
|
|
PostMessage(g_hwnd, 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).
|
|
switch (g_wheelSupport) {
|
|
case kWheelModern: {
|
|
const MOUSEHOOKSTRUCT* info =
|
|
(const MOUSEHOOKSTRUCT*)lParam;
|
|
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL,
|
|
static_cast<short>(LOWORD(info->dwExtraInfo)), 0);
|
|
break;
|
|
}
|
|
|
|
case kWheelWin2000: {
|
|
const MOUSEHOOKSTRUCTWin2000* info =
|
|
(const MOUSEHOOKSTRUCTWin2000*)lParam;
|
|
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL,
|
|
static_cast<short>(HIWORD(info->mouseData)), 0);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
case WM_MOUSEMOVE: {
|
|
const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam;
|
|
SInt32 x = (SInt32)info->pt.x;
|
|
SInt32 y = (SInt32)info->pt.y;
|
|
|
|
// 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);
|
|
}
|
|
|
|
// relay the motion
|
|
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_MOVE, x, y);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// check for mouse inside jump zone
|
|
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 & CConfig::kLeftMask) != 0) {
|
|
inside = (x < g_zoneSize);
|
|
}
|
|
if (!inside && (g_zoneSides & CConfig::kRightMask) != 0) {
|
|
inside = (x >= g_wScreen - g_zoneSize);
|
|
}
|
|
if (!inside && (g_zoneSides & CConfig::kTopMask) != 0) {
|
|
inside = (y < g_zoneSize);
|
|
}
|
|
if (!inside && (g_zoneSides & CConfig::kBottomMask) != 0) {
|
|
inside = (y >= g_hScreen - g_zoneSize);
|
|
}
|
|
|
|
// if inside then eat event and notify our window
|
|
if (inside) {
|
|
restoreCursor();
|
|
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_MOVE, x, y);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CallNextHookEx(g_mouse, code, wParam, lParam);
|
|
}
|
|
|
|
static LRESULT CALLBACK cbtHook(int code, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (code >= 0) {
|
|
if (g_relay) {
|
|
switch (code) {
|
|
case HCBT_ACTIVATE:
|
|
case HCBT_SETFOCUS:
|
|
// discard unless activating our window
|
|
if (reinterpret_cast<HWND>(wParam) != g_hwnd) {
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CallNextHookEx(g_cbt, code, wParam, lParam);
|
|
}
|
|
|
|
static LRESULT CALLBACK getMessageHook(int code, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (code >= 0) {
|
|
if (g_relay) {
|
|
MSG* msg = reinterpret_cast<MSG*>(lParam);
|
|
if (msg->message == g_wmMouseWheel) {
|
|
// post message to our window
|
|
PostMessage(g_hwnd, 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<KBDLLHOOKSTRUCT*>(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
|
|
PostMessage(g_hwnd, SYNERGY_MSG_KEY, info->vkCode, lParam);
|
|
|
|
// discard event
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CallNextHookEx(g_keyboardLL, code, wParam, lParam);
|
|
}
|
|
|
|
static DWORD WINAPI getKeyboardLLProc(void*)
|
|
{
|
|
// thread proc for low-level keyboard hook. 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);
|
|
|
|
// install low-level keyboard hook
|
|
g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL,
|
|
&keyboardLLHook,
|
|
g_hinstance,
|
|
0);
|
|
if (g_keyboardLL == NULL) {
|
|
// indicate failure and exit
|
|
g_keyHookThreadID = 0;
|
|
SetEvent(g_keyHookEvent);
|
|
return 1;
|
|
}
|
|
|
|
// ready
|
|
SetEvent(g_keyHookEvent);
|
|
|
|
// 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_keyboardLL);
|
|
g_keyboardLL = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else // (_WIN32_WINNT < 0x0400)
|
|
|
|
#error foo
|
|
|
|
static DWORD WINAPI getKeyboardLLProc(void*)
|
|
{
|
|
g_keyHookThreadID = 0;
|
|
SetEvent(g_keyHookEvent);
|
|
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) {
|
|
if (g_hinstance == NULL) {
|
|
g_hinstance = instance;
|
|
g_process = GetCurrentProcessId();
|
|
}
|
|
}
|
|
else if (reason == DLL_PROCESS_DETACH) {
|
|
if (g_process == GetCurrentProcessId()) {
|
|
if (g_keyboard != NULL || g_mouse != NULL || g_cbt != NULL) {
|
|
uninstall();
|
|
}
|
|
g_process = NULL;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
int install(HWND hwnd)
|
|
{
|
|
assert(g_hinstance != NULL);
|
|
assert(g_keyboard == NULL);
|
|
assert(g_mouse == NULL);
|
|
assert(g_cbt == NULL);
|
|
assert(g_wheelSupport != kWheelOld || g_getMessage == NULL);
|
|
|
|
// save window
|
|
g_hwnd = hwnd;
|
|
|
|
// set defaults
|
|
g_relay = false;
|
|
g_zoneSize = 0;
|
|
g_zoneSides = 0;
|
|
g_wScreen = 0;
|
|
g_hScreen = 0;
|
|
g_cursor = NULL;
|
|
g_cursorThread = 0;
|
|
|
|
// check for mouse wheel support
|
|
g_wheelSupport = GetWheelSupport();
|
|
|
|
// install keyboard hook
|
|
g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
|
|
&keyboardHook,
|
|
g_hinstance,
|
|
0);
|
|
if (g_keyboard == NULL) {
|
|
g_hwnd = NULL;
|
|
return 0;
|
|
}
|
|
|
|
// install mouse hook
|
|
g_mouse = SetWindowsHookEx(WH_MOUSE,
|
|
&mouseHook,
|
|
g_hinstance,
|
|
0);
|
|
if (g_mouse == NULL) {
|
|
// uninstall keyboard hook before failing
|
|
UnhookWindowsHookEx(g_keyboard);
|
|
g_keyboard = NULL;
|
|
g_hwnd = 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
|
|
UnhookWindowsHookEx(g_keyboard);
|
|
UnhookWindowsHookEx(g_mouse);
|
|
g_keyboard = NULL;
|
|
g_mouse = NULL;
|
|
g_hwnd = NULL;
|
|
return 0;
|
|
}
|
|
|
|
// install GetMessage hook
|
|
if (g_wheelSupport == kWheelOld) {
|
|
g_getMessage = SetWindowsHookEx(WH_GETMESSAGE,
|
|
&getMessageHook,
|
|
g_hinstance,
|
|
0);
|
|
// ignore failure; we just won't get mouse wheel messages
|
|
}
|
|
|
|
// install low-level keyboard hook, if possible. since this hook
|
|
// is 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 hook. note that low-level keyboard hooks are only
|
|
// available on windows NT SP3 and above.
|
|
g_keyHookEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
if (g_keyHookEvent != NULL) {
|
|
g_keyHookThread = CreateThread(NULL, 0, &getKeyboardLLProc, 0,
|
|
CREATE_SUSPENDED, &g_keyHookThreadID);
|
|
if (g_keyHookThread != NULL) {
|
|
// start the thread and wait for it to initialize
|
|
ResumeThread(g_keyHookThread);
|
|
WaitForSingleObject(g_keyHookEvent, INFINITE);
|
|
ResetEvent(g_keyHookEvent);
|
|
|
|
// the thread clears g_keyHookThreadID if it failed
|
|
if (g_keyHookThreadID == 0) {
|
|
CloseHandle(g_keyHookThread);
|
|
g_keyHookThread = NULL;
|
|
}
|
|
}
|
|
if (g_keyHookThread == NULL) {
|
|
CloseHandle(g_keyHookEvent);
|
|
g_keyHookEvent = NULL;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int uninstall(void)
|
|
{
|
|
assert(g_keyboard != NULL);
|
|
assert(g_mouse != NULL);
|
|
assert(g_cbt != NULL);
|
|
|
|
// uninstall hooks
|
|
if (g_keyHookThread != NULL) {
|
|
PostThreadMessage(g_keyHookThreadID, WM_QUIT, 0, 0);
|
|
WaitForSingleObject(g_keyHookThread, INFINITE);
|
|
CloseHandle(g_keyHookEvent);
|
|
CloseHandle(g_keyHookThread);
|
|
g_keyHookEvent = NULL;
|
|
g_keyHookThread = NULL;
|
|
g_keyHookThreadID = 0;
|
|
}
|
|
UnhookWindowsHookEx(g_keyboard);
|
|
UnhookWindowsHookEx(g_mouse);
|
|
UnhookWindowsHookEx(g_cbt);
|
|
if (g_getMessage != NULL) {
|
|
UnhookWindowsHookEx(g_getMessage);
|
|
}
|
|
g_keyboard = NULL;
|
|
g_mouse = NULL;
|
|
g_cbt = NULL;
|
|
g_getMessage = NULL;
|
|
g_hwnd = NULL;
|
|
|
|
// show the cursor
|
|
restoreCursor();
|
|
|
|
return 1;
|
|
}
|
|
|
|
void setZone(UInt32 sides,
|
|
SInt32 w, SInt32 h, SInt32 jumpZoneSize)
|
|
{
|
|
g_zoneSize = jumpZoneSize;
|
|
g_zoneSides = sides;
|
|
g_wScreen = w;
|
|
g_hScreen = h;
|
|
g_relay = false;
|
|
restoreCursor();
|
|
}
|
|
|
|
void setRelay(void)
|
|
{
|
|
g_relay = true;
|
|
g_zoneSize = 0;
|
|
g_zoneSides = 0;
|
|
g_wScreen = 0;
|
|
g_hScreen = 0;
|
|
}
|
|
|
|
}
|