mirror of
https://github.com/debauchee/barrier.git
synced 2024-12-18 16:31:44 +03:00
75729cef46
now captures non-client area mouse messages. Previously, these were ignored (because i forgot about them) and they caused all kinds of problems because they weren't forwarded. For example, clicking on a window border would cause the window to start resizing when the mouse came back to the server screen. Moving inside a title bar meant that the mouse wouldn't move on the client screen. Second, because non-client messages are now handled, the full screen transparent window is no longer necessary to capture input so it's never displayed. (The window is still necessary for clipboard ownership so it's still created.) No transparent window means no screen flashing. It also means we don't have to become the foreground and active window. This plays better with apps that minimize or restore when they're no longer the foreground application/active window. Third, fixed the low level keyboard hook to forward toggle key updates, which it was neglecting to do. Finally, keyboard and mouse input is always forwarded from the hook to the primary screen handler which then shadows the current key and mouse button state. If we're using low level hooks then this isn't really necessary and GetKeyState() always returns the right info but without low level hooks it means we can just use the shadow state. It also means we don't have to show our window in order to get the system's key state table up to date, fixing the screen flash when checking for the scroll lock state.
813 lines
19 KiB
C++
813 lines
19 KiB
C++
/*
|
|
* 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 <zmouse.h>
|
|
|
|
//
|
|
// 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_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
|
|
bool
|
|
keyboardHookHandler(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// 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.
|
|
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam);
|
|
|
|
if (g_relay) {
|
|
// 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 true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static
|
|
bool
|
|
mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 wheel)
|
|
{
|
|
switch (wParam) {
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_MBUTTONDBLCLK:
|
|
case WM_RBUTTONDBLCLK:
|
|
case WM_LBUTTONUP:
|
|
case WM_MBUTTONUP:
|
|
case WM_RBUTTONUP:
|
|
case WM_NCLBUTTONDOWN:
|
|
case WM_NCMBUTTONDOWN:
|
|
case WM_NCRBUTTONDOWN:
|
|
case WM_NCLBUTTONDBLCLK:
|
|
case WM_NCMBUTTONDBLCLK:
|
|
case WM_NCRBUTTONDBLCLK:
|
|
case WM_NCLBUTTONUP:
|
|
case WM_NCMBUTTONUP:
|
|
case WM_NCRBUTTONUP:
|
|
// always relay the event. eat it if relaying.
|
|
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, 0);
|
|
return g_relay;
|
|
|
|
case WM_MOUSEWHEEL:
|
|
if (g_relay) {
|
|
// relay event
|
|
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, wheel, 0);
|
|
}
|
|
return g_relay;
|
|
|
|
case WM_NCMOUSEMOVE:
|
|
case WM_MOUSEMOVE:
|
|
if (g_relay) {
|
|
// 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 = GetCurrentThreadId();
|
|
if (thread != g_cursorThread) {
|
|
restoreCursor();
|
|
hideCursor(thread);
|
|
}
|
|
|
|
// relay and eat event
|
|
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
|
|
return true;
|
|
}
|
|
else {
|
|
// 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 then eat the event
|
|
return inside;
|
|
}
|
|
}
|
|
|
|
// pass the event
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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<SInt32>(LOWORD(info->dwExtraInfo));
|
|
break;
|
|
|
|
case kWheelWin2000: {
|
|
const MOUSEHOOKSTRUCTWin2000* info2k =
|
|
(const MOUSEHOOKSTRUCTWin2000*)lParam;
|
|
w = static_cast<SInt32>(HIWORD(info2k->mouseData));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle the message
|
|
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<MSG*>(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<MSG*>(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 reported to us.
|
|
//
|
|
|
|
static
|
|
LRESULT CALLBACK
|
|
keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (code >= 0) {
|
|
// decode the message
|
|
KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(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);
|
|
}
|
|
|
|
//
|
|
// 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<MSLLHOOKSTRUCT*>(lParam);
|
|
SInt32 x = (SInt32)info->pt.x;
|
|
SInt32 y = (SInt32)info->pt.y;
|
|
SInt32 w = (SInt32)HIWORD(info->mouseData);
|
|
|
|
// handle the message
|
|
if (mouseHookHandler(wParam, x, y, w)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
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_getMessage != NULL) {
|
|
uninstall();
|
|
uninstallScreenSaver();
|
|
}
|
|
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_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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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 keyboard/mouse hooks, if possible. since these
|
|
// hooks 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;
|
|
}
|
|
}
|
|
|
|
// install non-low-level hooks if the low-level hooks are not installed
|
|
if (g_hookThreadLL == NULL) {
|
|
#if !NO_GRAB_KEYBOARD
|
|
g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
|
|
&keyboardHook,
|
|
g_hinstance,
|
|
0);
|
|
#else
|
|
// keep compiler quiet
|
|
&keyboardHook;
|
|
#endif
|
|
g_mouse = SetWindowsHookEx(WH_MOUSE,
|
|
&mouseHook,
|
|
g_hinstance,
|
|
0);
|
|
}
|
|
|
|
// check for any failures. uninstall all hooks on failure.
|
|
if (g_hookThreadLL == NULL &&
|
|
#if !NO_GRAB_KEYBOARD
|
|
(g_keyboard == NULL || g_mouse == NULL)) {
|
|
#else
|
|
(g_mouse == NULL)) {
|
|
#endif
|
|
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_threadID = NULL;
|
|
return kHOOK_FAILED;
|
|
}
|
|
|
|
return (g_hookThreadLL == NULL) ? kHOOK_OKAY : kHOOK_OKAY_LL;
|
|
}
|
|
|
|
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);
|
|
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;
|
|
|
|
// 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 != 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
|
|
setRelay(int enable)
|
|
{
|
|
if ((enable != 0) == g_relay) {
|
|
// no change
|
|
return;
|
|
}
|
|
g_relay = (enable != 0);
|
|
if (!g_relay) {
|
|
restoreCursor();
|
|
}
|
|
}
|
|
|
|
}
|