mirror of
https://github.com/debauchee/barrier.git
synced 2026-02-08 12:53:53 +08:00
- Fixed taking the address of begin() on an empty std::vector. - Fixed nsis makefile to use %ProgramFiles% environment variable. - Fixed nsis makefile to pass the output directory and file to makensis. - Fixed synergy.nsi to get the files from the output directory. That enables a debug build of the installer. - Fixes to compile under VS2005. I did not apply VS2005 project files, instead adding nmake files. nmake is pretty weak but the makefiles can be modified without having visual studio. Also modified the .rc files to not use winres.h. This plus nmake means synergy can now be built using the freely downloadable Microsoft Windows SDK for Vista, available from microsoft's web site. This change removes all of the old VC++6 project files in favor of the nmake files. It also removes the XCode project in favor of ./configure and make. All of the nmake files are named nmake.mak. Only the top level makefile is directly useful (the rest are included by it) so all builds are from the top level directory. nmake knows the following targets: all: build synergy.exe, synergyc.exe and synergys.exe clean: remove all intermediate files, keep programs clobber: clean and remove programs installer: build programs and an installer debug: build a debug version of 'all' release: build a release version of 'all' debug-installer: build an installer of the debug build release-installer: build an installer of the release build The default build version is release so 'all' and 'installer' will build a release version. The installer itself never has debug symbols, just the stuff it installs. The default target is 'all'. To build use: nmake /nologo /f nmake.mak <target> VC++ and VisualStudio users may need to manually run vcvars.bat in a command.exe or cmd.exe window before invoking nmake. The Window 98/Me command.exe may not handle potentially long command lines; I haven't tried to verify if that works.
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;
|
|
}
|
|
|
|
}
|