1111 lines
28 KiB
C++
1111 lines
28 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>
|
||
|
#include <tchar.h>
|
||
|
|
||
|
#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<SInt16>(LOWORD(info->dwExtraInfo));
|
||
|
break;
|
||
|
|
||
|
case kWheelWin2000: {
|
||
|
const MOUSEHOOKSTRUCTWin2000* info2k =
|
||
|
(const MOUSEHOOKSTRUCTWin2000*)lParam;
|
||
|
w = static_cast<SInt16>(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<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_mode == kHOOK_RELAY_EVENTS) {
|
||
|
MSG* msg = reinterpret_cast<MSG*>(lParam);
|
||
|
if (g_wheelSupport == kWheelOld && msg->message == g_wmMouseWheel) {
|
||
|
// post message to our window
|
||
|
PostThreadMessage(g_threadID,
|
||
|
SYNERGY_MSG_MOUSE_WHEEL,
|
||
|
static_cast<SInt16>(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<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);
|
||
|
}
|
||
|
#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<MSLLHOOKSTRUCT*>(lParam);
|
||
|
SInt32 x = static_cast<SInt32>(info->pt.x);
|
||
|
SInt32 y = static_cast<SInt32>(info->pt.y);
|
||
|
SInt32 w = static_cast<SInt16>(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;
|
||
|
}
|
||
|
|
||
|
}
|