Fixed a few win32 keyboard/mouse problems. First, the mouse hook

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.
This commit is contained in:
crs 2003-05-03 12:54:22 +00:00
parent 8d9134f93a
commit 75729cef46
4 changed files with 293 additions and 420 deletions

View File

@ -295,9 +295,9 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen(
m_receiver(primaryReceiver), m_receiver(primaryReceiver),
m_is95Family(CArchMiscWindows::isWindows95Family()), m_is95Family(CArchMiscWindows::isWindows95Family()),
m_threadID(0), m_threadID(0),
m_window(NULL),
m_mark(0), m_mark(0),
m_markReceived(0) m_markReceived(0),
m_lowLevel(false)
{ {
assert(m_receiver != NULL); assert(m_receiver != NULL);
@ -333,7 +333,6 @@ CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen(
CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen() CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen()
{ {
assert(m_hookLibrary != NULL); assert(m_hookLibrary != NULL);
assert(m_window == NULL);
delete m_screen; delete m_screen;
FreeLibrary(m_hookLibrary); FreeLibrary(m_hookLibrary);
@ -385,8 +384,8 @@ KeyModifierMask
CMSWindowsPrimaryScreen::getToggleMask() const CMSWindowsPrimaryScreen::getToggleMask() const
{ {
KeyModifierMask mask = 0; KeyModifierMask mask = 0;
if (isActive()) { if (!m_lowLevel) {
// get key state // get key state from our shadow state
if ((m_keys[VK_CAPITAL] & 0x01) != 0) if ((m_keys[VK_CAPITAL] & 0x01) != 0)
mask |= KeyModifierCapsLock; mask |= KeyModifierCapsLock;
if ((m_keys[VK_NUMLOCK] & 0x01) != 0) if ((m_keys[VK_NUMLOCK] & 0x01) != 0)
@ -395,33 +394,13 @@ CMSWindowsPrimaryScreen::getToggleMask() const
mask |= KeyModifierScrollLock; mask |= KeyModifierScrollLock;
} }
else { else {
// show the window, but make it very small. we must do this // get key state from the system when using low level hooks
// because GetKeyState() reports the key state according to
// processed messages and until the window is visible the
// system won't update the state of the toggle keys reported
// by that function. unfortunately, this slows this method
// down significantly and, for some reason i don't understand,
// causes everything on the screen to redraw.
if (m_window != NULL) {
MoveWindow(m_window, 1, 1, 1, 1, FALSE);
const_cast<CMSWindowsPrimaryScreen*>(this)->showWindow();
}
// get key state
if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) if ((GetKeyState(VK_CAPITAL) & 0x01) != 0)
mask |= KeyModifierCapsLock; mask |= KeyModifierCapsLock;
if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0)
mask |= KeyModifierNumLock; mask |= KeyModifierNumLock;
if ((GetKeyState(VK_SCROLL) & 0x01) != 0) if ((GetKeyState(VK_SCROLL) & 0x01) != 0)
mask |= KeyModifierScrollLock; mask |= KeyModifierScrollLock;
// make the window hidden again and restore its size
if (m_window != NULL) {
const_cast<CMSWindowsPrimaryScreen*>(this)->hideWindow();
SInt32 x, y, w, h;
m_screen->getShape(x, y, w, h);
MoveWindow(m_window, x, y, w, h, FALSE);
}
} }
return mask; return mask;
@ -444,12 +423,12 @@ CMSWindowsPrimaryScreen::isLockedToScreen() const
0x7ffffe5f 0x7ffffe5f
}; };
// check each key. note that we cannot use GetKeyboardState() here // check each key. if we're capturing events at a low level we
// since it reports the state of keys according to key messages // can query the keyboard state using GetKeyState(). if not we
// that have been pulled off the queue. in general, we won't get // resort to using our shadow keyboard state since the system's
// these key messages because they're not for our window. if any // shadow state won't be in sync (because our window is not
// key (or mouse button) is down then we're locked to the screen. // getting keyboard events).
if (isActive()) { if (!m_lowLevel) {
// use shadow keyboard state in m_keys // use shadow keyboard state in m_keys
for (UInt32 i = 0; i < 256; ++i) { for (UInt32 i = 0; i < 256; ++i) {
if ((m_keys[i] & 0x80) != 0) { if ((m_keys[i] & 0x80) != 0) {
@ -462,7 +441,7 @@ CMSWindowsPrimaryScreen::isLockedToScreen() const
for (UInt32 i = 0; i < 256 / 32; ++i) { for (UInt32 i = 0; i < 256 / 32; ++i) {
for (UInt32 b = 1, j = 0; j < 32; b <<= 1, ++j) { for (UInt32 b = 1, j = 0; j < 32; b <<= 1, ++j) {
if ((s_mappedKeys[i] & b) != 0) { if ((s_mappedKeys[i] & b) != 0) {
if (GetAsyncKeyState(i * 32 + j) < 0) { if (GetKeyState(i * 32 + j) < 0) {
LOG((CLOG_DEBUG "locked by \"%s\"", g_vkToName[i * 32 + j])); LOG((CLOG_DEBUG "locked by \"%s\"", g_vkToName[i * 32 + j]));
return true; return true;
} }
@ -573,9 +552,6 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event)
LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button)); LOG((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x button=0x%04x", key, mask, button));
m_receiver->onKeyDown(key, mask, button); m_receiver->onKeyDown(key, mask, button);
} }
// update key state
updateKey(msg->wParam, true);
} }
else { else {
// key release. if the key isn't down according to // key release. if the key isn't down according to
@ -594,51 +570,76 @@ CMSWindowsPrimaryScreen::onPreDispatch(const CEvent* event)
// do key up // do key up
LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button)); LOG((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x button=0x%04x", key, mask, button));
m_receiver->onKeyUp(key, mask, button); m_receiver->onKeyUp(key, mask, button);
// update key state
updateKey(msg->wParam, false);
} }
} }
else { else {
LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", msg->wParam, msg->lParam)); LOG((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", msg->wParam, msg->lParam));
} }
} }
// keep our shadow key state up to date
updateKey(msg->wParam, ((msg->lParam & 0x80000000) == 0));
return true; return true;
case SYNERGY_MSG_MOUSE_BUTTON: case SYNERGY_MSG_MOUSE_BUTTON: {
static const int s_vkButton[] = {
0, // kButtonNone
VK_LBUTTON, // kButtonLeft, etc.
VK_MBUTTON,
VK_RBUTTON
};
// get which button
bool pressed = false;
const ButtonID button = mapButton(msg->wParam);
// ignore message if posted prior to last mark change // ignore message if posted prior to last mark change
if (!ignore()) { if (!ignore()) {
static const int s_vkButton[] = {
0, // kButtonNone
VK_LBUTTON, // kButtonLeft, etc.
VK_MBUTTON,
VK_RBUTTON
};
const ButtonID button = mapButton(msg->wParam);
switch (msg->wParam) { switch (msg->wParam) {
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN: case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN: case WM_RBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_NCLBUTTONDOWN:
case WM_NCMBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
LOG((CLOG_DEBUG1 "event: button press button=%d", button)); LOG((CLOG_DEBUG1 "event: button press button=%d", button));
if (button != kButtonNone) { if (button != kButtonNone) {
m_receiver->onMouseDown(button); m_receiver->onMouseDown(button);
m_keys[s_vkButton[button]] |= 0x80; m_keys[s_vkButton[button]] |= 0x80;
} }
pressed = true;
break; break;
case WM_LBUTTONUP: case WM_LBUTTONUP:
case WM_MBUTTONUP: case WM_MBUTTONUP:
case WM_RBUTTONUP: case WM_RBUTTONUP:
case WM_NCLBUTTONUP:
case WM_NCMBUTTONUP:
case WM_NCRBUTTONUP:
LOG((CLOG_DEBUG1 "event: button release button=%d", button)); LOG((CLOG_DEBUG1 "event: button release button=%d", button));
if (button != kButtonNone) { if (button != kButtonNone) {
m_receiver->onMouseUp(button); m_receiver->onMouseUp(button);
m_keys[s_vkButton[button]] &= ~0x80; m_keys[s_vkButton[button]] &= ~0x80;
} }
pressed = false;
break; break;
} }
} }
// keep our shadow key state up to date
if (button != kButtonNone) {
updateKey(s_vkButton[button], pressed);
}
return true; return true;
}
case SYNERGY_MSG_MOUSE_WHEEL: case SYNERGY_MSG_MOUSE_WHEEL:
// ignore message if posted prior to last mark change // ignore message if posted prior to last mark change
@ -765,29 +766,25 @@ CMSWindowsPrimaryScreen::getJumpZoneSize() const
} }
void void
CMSWindowsPrimaryScreen::postCreateWindow(HWND window) CMSWindowsPrimaryScreen::postCreateWindow(HWND)
{ {
// save window
m_window = window;
// install hooks // install hooks
m_install(); switch (m_install()) {
case kHOOK_FAILED:
// FIXME -- can't install hook so we won't work; report error
m_lowLevel = false;
break;
// resize window case kHOOK_OKAY:
// note -- we use a fullscreen window to grab input. it should m_lowLevel = false;
// be possible to use a 1x1 window but i've run into problems break;
// with losing keyboard input (focus?) in that case.
// unfortunately, hiding the full screen window (when entering
// the screen) causes all other windows to redraw.
SInt32 x, y, w, h;
m_screen->getShape(x, y, w, h);
MoveWindow(m_window, x, y, w, h, FALSE);
if (isActive()) { case kHOOK_OKAY_LL:
// hide the cursor m_lowLevel = true;
showWindow(); break;
} }
else {
if (!isActive()) {
// watch jump zones // watch jump zones
m_setRelay(false); m_setRelay(false);
@ -799,11 +796,6 @@ CMSWindowsPrimaryScreen::postCreateWindow(HWND window)
void void
CMSWindowsPrimaryScreen::preDestroyWindow(HWND) CMSWindowsPrimaryScreen::preDestroyWindow(HWND)
{ {
// hide the window if it's visible
if (isActive()) {
hideWindow();
}
// uninstall hooks // uninstall hooks
m_uninstall(); m_uninstall();
} }
@ -813,14 +805,11 @@ CMSWindowsPrimaryScreen::onPreMainLoop()
{ {
// must call mainLoop() from same thread as open() // must call mainLoop() from same thread as open()
assert(m_threadID == GetCurrentThreadId()); assert(m_threadID == GetCurrentThreadId());
assert(m_window != NULL);
} }
void void
CMSWindowsPrimaryScreen::onPreOpen() CMSWindowsPrimaryScreen::onPreOpen()
{ {
assert(m_window == NULL);
// initialize hook library // initialize hook library
m_threadID = GetCurrentThreadId(); m_threadID = GetCurrentThreadId();
if (m_init(m_threadID) == 0) { if (m_init(m_threadID) == 0) {
@ -857,8 +846,6 @@ CMSWindowsPrimaryScreen::onPostClose()
void void
CMSWindowsPrimaryScreen::onPreEnter() CMSWindowsPrimaryScreen::onPreEnter()
{ {
assert(m_window != NULL);
// enable ctrl+alt+del, alt+tab, etc // enable ctrl+alt+del, alt+tab, etc
if (m_is95Family) { if (m_is95Family) {
DWORD dummy = 0; DWORD dummy = 0;
@ -879,8 +866,6 @@ CMSWindowsPrimaryScreen::onPostEnter()
void void
CMSWindowsPrimaryScreen::onPreLeave() CMSWindowsPrimaryScreen::onPreLeave()
{ {
assert(m_window != NULL);
// all messages prior to now are invalid // all messages prior to now are invalid
nextMark(); nextMark();
} }
@ -904,19 +889,10 @@ void
CMSWindowsPrimaryScreen::createWindow() CMSWindowsPrimaryScreen::createWindow()
{ {
// open the desktop and the window // open the desktop and the window
m_window = m_screen->openDesktop(); HWND window = m_screen->openDesktop();
if (m_window == NULL) { if (window == NULL) {
throw XScreenOpenFailure(); throw XScreenOpenFailure();
} }
// note -- we use a fullscreen window to grab input. it should
// be possible to use a 1x1 window but i've run into problems
// with losing keyboard input (focus?) in that case.
// unfortunately, hiding the full screen window (when entering
// the scren causes all other windows to redraw).
SInt32 x, y, w, h;
m_screen->getShape(x, y, w, h);
MoveWindow(m_window, x, y, w, h, FALSE);
} }
void void
@ -924,104 +900,19 @@ CMSWindowsPrimaryScreen::destroyWindow()
{ {
// close the desktop and the window // close the desktop and the window
m_screen->closeDesktop(); m_screen->closeDesktop();
m_window = NULL;
} }
bool bool
CMSWindowsPrimaryScreen::showWindow() CMSWindowsPrimaryScreen::showWindow()
{ {
// remember the active window before we leave. GetActiveWindow() // do nothing. we don't need to show a window to capture input.
// will only return the active window for the thread's queue (i.e.
// our app) but we need the globally active window. get that by
// attaching input to the foreground window's thread then calling
// GetActiveWindow() and then detaching our input.
m_lastActiveWindow = NULL;
m_lastForegroundWindow = GetForegroundWindow();
m_lastActiveThread = GetWindowThreadProcessId(
m_lastForegroundWindow, NULL);
DWORD myThread = GetCurrentThreadId();
if (m_lastActiveThread != 0) {
if (myThread != m_lastActiveThread) {
if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) {
m_lastActiveWindow = GetActiveWindow();
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
}
}
// show our window
ShowWindow(m_window, SW_SHOW);
// force our window to the foreground. this is necessary to
// capture input but is complicated by microsoft's misguided
// attempt to prevent applications from changing the
// foreground window. (the user should be in control of that
// under normal circumstances but there are exceptions; the
// good folks at microsoft, after abusing the previously
// available ability to switch foreground tasks in many of
// their apps, changed the behavior to prevent it. maybe
// it was easier than fixing the applications.)
//
// anyway, simply calling SetForegroundWindow() doesn't work
// unless there is no foreground window or we already are the
// foreground window. so we AttachThreadInput() to the
// foreground process then call SetForegroundWindow(); that
// makes Windows think the foreground process changed the
// foreground window which is allowed since the foreground
// is "voluntarily" yielding control. then we unattach the
// thread input and go about our business.
//
// unfortunately, this still doesn't work for console windows
// on the windows 95 family. if a console is the foreground
// app on the server when the user leaves the server screen
// then the keyboard will not be captured by synergy.
if (m_lastActiveThread != myThread) {
if (m_lastActiveThread != 0) {
AttachThreadInput(myThread, m_lastActiveThread, TRUE);
}
SetForegroundWindow(m_window);
if (m_lastActiveThread != 0) {
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
}
// get keyboard input and capture mouse
SetActiveWindow(m_window);
SetFocus(m_window);
SetCapture(m_window);
return true; return true;
} }
void void
CMSWindowsPrimaryScreen::hideWindow() CMSWindowsPrimaryScreen::hideWindow()
{ {
// restore the active window and hide our window. we can only set // do nothing. we don't need to show a window to capture input.
// the active window for another thread if we first attach our input
// to that thread.
ReleaseCapture();
if (m_lastActiveWindow != NULL) {
DWORD myThread = GetCurrentThreadId();
if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) {
// FIXME -- shouldn't raise window if X-Mouse is enabled
// but i have no idea how to do that or check if enabled.
SetActiveWindow(m_lastActiveWindow);
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
}
// hide the window. do not wait for it, though, since ShowWindow()
// waits for the event loop to process the show-window event, but
// that thread may need to lock the mutex that this thread has
// already locked. in particular, that deadlock will occur unless
// we use the asynchronous version of show window when a client
// disconnects: thread A will lock the mutex and enter the primary
// screen which warps the mouse and calls this method while thread B
// will handle the mouse warp event and call methods that try to
// lock the mutex. thread A owns the mutex and is waiting for the
// event loop, thread B owns the event loop and is waiting for the
// mutex causing deadlock.
ShowWindowAsync(m_window, SW_HIDE);
} }
void void
@ -1520,15 +1411,27 @@ CMSWindowsPrimaryScreen::mapButton(WPARAM button) const
{ {
switch (button) { switch (button) {
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_LBUTTONUP: case WM_LBUTTONUP:
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONDBLCLK:
case WM_NCLBUTTONUP:
return kButtonLeft; return kButtonLeft;
case WM_MBUTTONDOWN: case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
case WM_MBUTTONUP: case WM_MBUTTONUP:
case WM_NCMBUTTONDOWN:
case WM_NCMBUTTONDBLCLK:
case WM_NCMBUTTONUP:
return kButtonMiddle; return kButtonMiddle;
case WM_RBUTTONDOWN: case WM_RBUTTONDOWN:
case WM_RBUTTONDBLCLK:
case WM_RBUTTONUP: case WM_RBUTTONUP:
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONDBLCLK:
case WM_NCRBUTTONUP:
return kButtonRight; return kButtonRight;
default: default:

View File

@ -101,9 +101,6 @@ private:
// the main loop's thread id // the main loop's thread id
DWORD m_threadID; DWORD m_threadID;
// our window
HWND m_window;
// used to discard queued messages that are no longer needed // used to discard queued messages that are no longer needed
UInt32 m_mark; UInt32 m_mark;
UInt32 m_markReceived; UInt32 m_markReceived;
@ -126,6 +123,7 @@ private:
SetSidesFunc m_setSides; SetSidesFunc m_setSides;
SetZoneFunc m_setZone; SetZoneFunc m_setZone;
SetRelayFunc m_setRelay; SetRelayFunc m_setRelay;
bool m_lowLevel;
// stuff for restoring active window // stuff for restoring active window
HWND m_lastForegroundWindow; HWND m_lastForegroundWindow;

View File

@ -61,7 +61,6 @@ static UINT g_wmMouseWheel = 0;
static DWORD g_threadID = 0; static DWORD g_threadID = 0;
static HHOOK g_keyboard = NULL; static HHOOK g_keyboard = NULL;
static HHOOK g_mouse = NULL; static HHOOK g_mouse = NULL;
static HHOOK g_cbt = NULL;
static HHOOK g_getMessage = NULL; static HHOOK g_getMessage = NULL;
static HANDLE g_hookThreadLL = NULL; static HANDLE g_hookThreadLL = NULL;
static DWORD g_hookThreadIDLL = 0; static DWORD g_hookThreadIDLL = 0;
@ -122,117 +121,92 @@ restoreCursor()
} }
static static
LRESULT CALLBACK bool
keyboardHook(int code, WPARAM wParam, LPARAM lParam) keyboardHookHandler(WPARAM wParam, LPARAM lParam)
{ {
if (code >= 0) { // forward message to our window. do this whether or not we're
if (g_relay) { // forwarding events to clients because this'll keep our thread's
// forward message to our window // key state table up to date. that's important for querying
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam); // the scroll lock toggle state.
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam);
// let certain keys pass through if (g_relay) {
switch (wParam) { // let certain keys pass through
case VK_CAPITAL: switch (wParam) {
case VK_NUMLOCK: case VK_CAPITAL:
case VK_SCROLL: case VK_NUMLOCK:
// pass event on. we want to let these through to case VK_SCROLL:
// the window proc because otherwise the keyboard // pass event on. we want to let these through to
// lights may not stay synchronized. // the window proc because otherwise the keyboard
break; // lights may not stay synchronized.
break;
default: default:
// discard event // discard event
return 1; return true;
}
} }
} }
return CallNextHookEx(g_keyboard, code, wParam, lParam); return false;
} }
static static
LRESULT CALLBACK bool
mouseHook(int code, WPARAM wParam, LPARAM lParam) mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 wheel)
{ {
if (code >= 0) { 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) { if (g_relay) {
switch (wParam) { // relay event
case WM_LBUTTONDOWN: PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, wheel, 0);
case WM_MBUTTONDOWN: }
case WM_RBUTTONDOWN: return g_relay;
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
PostThreadMessage(g_threadID,
SYNERGY_MSG_MOUSE_BUTTON, wParam, 0);
return 1;
case WM_MOUSEWHEEL: case WM_NCMOUSEMOVE:
{ case WM_MOUSEMOVE:
// win2k and other systems supporting WM_MOUSEWHEEL in if (g_relay) {
// the mouse hook are gratuitously different (and poorly // we want the cursor to be hidden at all times so we
// documented). if a low-level mouse hook is in place // hide the cursor on whatever window has it. but then
// it should capture these events so we'll never see // we have to show the cursor whenever we leave that
// them. // window (or at some later time before we stop relaying).
switch (g_wheelSupport) { // so check the window with the cursor. if it's not the
case kWheelModern: { // same window that had it before then show the cursor
const MOUSEHOOKSTRUCT* info = // in the last window and hide it in this window.
(const MOUSEHOOKSTRUCT*)lParam; DWORD thread = GetCurrentThreadId();
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, if (thread != g_cursorThread) {
static_cast<short>( restoreCursor();
LOWORD(info->dwExtraInfo)), 0); hideCursor(thread);
break;
}
case kWheelWin2000: {
const MOUSEHOOKSTRUCTWin2000* info =
(const MOUSEHOOKSTRUCTWin2000*)lParam;
PostThreadMessage(g_threadID, 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;
// we want the cursor to be hidden at all times so we
// hide the cursor on whatever window has it. but then
// we have to show the cursor whenever we leave that
// window (or at some later time before we stop relaying).
// so check the window with the cursor. if it's not the
// same window that had it before then show the cursor
// in the last window and hide it in this window.
DWORD thread = GetWindowThreadProcessId(info->hwnd, NULL);
if (thread != g_cursorThread) {
restoreCursor();
hideCursor(thread);
}
// get position
SInt32 x = (SInt32)info->pt.x;
SInt32 y = (SInt32)info->pt.y;
// relay the motion
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
}
return 1;
} }
// relay and eat event
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
return true;
} }
else { else {
// check for mouse inside jump zone // check for mouse inside jump zone
bool inside = false; bool inside = false;
const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam;
SInt32 x = (SInt32)info->pt.x;
SInt32 y = (SInt32)info->pt.y;
if (!inside && (g_zoneSides & kLeftMask) != 0) { if (!inside && (g_zoneSides & kLeftMask) != 0) {
inside = (x < g_xScreen + g_zoneSize); inside = (x < g_xScreen + g_zoneSize);
} }
@ -246,36 +220,71 @@ mouseHook(int code, WPARAM wParam, LPARAM lParam)
inside = (y >= g_yScreen + g_hScreen - g_zoneSize); inside = (y >= g_yScreen + g_hScreen - g_zoneSize);
} }
// if inside then eat event and notify our window // relay the event
if (inside) { PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
restoreCursor();
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y); // if inside then eat the event
return 1; 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;
} }
else {
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
} }
} }
// handle the message
if (mouseHookHandler(wParam, x, y, w)) {
return 1;
}
} }
return CallNextHookEx(g_mouse, code, wParam, lParam); return CallNextHookEx(g_mouse, code, wParam, lParam);
} }
/*
static
LRESULT CALLBACK
cbtHook(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0) {
if (g_relay) {
// do nothing for now. may add something later.
}
}
return CallNextHookEx(g_cbt, code, wParam, lParam);
}
*/
static static
LRESULT CALLBACK LRESULT CALLBACK
getMessageHook(int code, WPARAM wParam, LPARAM lParam) getMessageHook(int code, WPARAM wParam, LPARAM lParam)
@ -312,7 +321,7 @@ getMessageHook(int code, WPARAM wParam, LPARAM lParam)
// //
// low-level keyboard hook -- this allows us to capture and handle // 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 // alt+tab, alt+esc, ctrl+esc, and windows key hot keys. on the down
// side, key repeats are not compressed for us. // side, key repeats are not reported to us.
// //
static static
@ -320,41 +329,27 @@ LRESULT CALLBACK
keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
{ {
if (code >= 0) { if (code >= 0) {
if (g_relay) { // decode the message
KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam); 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.
// let certain keys pass through // handle the message
switch (info->vkCode) { if (keyboardHookHandler(wParam, lParam)) {
case VK_CAPITAL: return 1;
case VK_NUMLOCK:
case VK_SCROLL:
// pass event on. we want to let these through to
// the window proc because otherwise the keyboard
// lights may not stay synchronized.
break;
default:
// construct lParam for WM_KEYDOWN, etc.
DWORD lParam = 1; // repeat code
lParam |= (info->scanCode << 16); // scan code
if (info->flags & LLKHF_EXTENDED) {
lParam |= (1lu << 24); // extended key
}
if (info->flags & LLKHF_ALTDOWN) {
lParam |= (1lu << 29); // context code
}
if (info->flags & LLKHF_UP) {
lParam |= (1lu << 31); // transition
}
// FIXME -- bit 30 should be set if key was already down
// forward message to our window
PostThreadMessage(g_threadID,
SYNERGY_MSG_KEY, info->vkCode, lParam);
// discard event
return 1;
}
} }
} }
@ -363,11 +358,7 @@ keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
// //
// low-level mouse hook -- this allows us to capture and handle mouse // low-level mouse hook -- this allows us to capture and handle mouse
// wheel events on all windows NT platforms from NT SP3 and up. this // events very early. the earlier the better.
// is both simpler than using the mouse hook and also supports windows
// windows NT which does not report mouse wheel events. we need to
// keep the mouse hook handling of mouse wheel events because the
// windows 95 family doesn't support low-level hooks.
// //
static static
@ -375,28 +366,15 @@ LRESULT CALLBACK
mouseLLHook(int code, WPARAM wParam, LPARAM lParam) mouseLLHook(int code, WPARAM wParam, LPARAM lParam)
{ {
if (code >= 0) { if (code >= 0) {
if (g_relay) { // decode the message
MSLLHOOKSTRUCT* info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam); MSLLHOOKSTRUCT* info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
SInt32 x = (SInt32)info->pt.x;
SInt32 y = (SInt32)info->pt.y;
SInt32 w = (SInt32)HIWORD(info->mouseData);
switch (wParam) { // handle the message
case WM_MOUSEWHEEL: if (mouseHookHandler(wParam, x, y, w)) {
// mouse wheel events are the same for entire NT family return 1;
// (>=SP3, prior versions have no low level hooks) for
// low-level mouse hook messages, unlike (regular) mouse
// hook messages which are gratuitously different on
// win2k and not sent at all for windows NT.
// forward message to our window
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL,
HIWORD(info->mouseData), 0);
// discard event
return 1;
default:
// all other events are passed through
break;
}
} }
} }
@ -592,7 +570,6 @@ init(DWORD threadID)
g_threadID = 0; g_threadID = 0;
g_keyboard = NULL; g_keyboard = NULL;
g_mouse = NULL; g_mouse = NULL;
g_cbt = NULL;
g_getMessage = NULL; g_getMessage = NULL;
g_hookThreadLL = NULL; g_hookThreadLL = NULL;
g_hookThreadIDLL = 0; g_hookThreadIDLL = 0;
@ -632,79 +609,28 @@ cleanup(void)
return 1; return 1;
} }
int EHookResult
install() install()
{ {
assert(g_hinstance != NULL); assert(g_hinstance != NULL);
assert(g_keyboard == NULL); assert(g_keyboard == NULL);
assert(g_mouse == NULL); assert(g_mouse == NULL);
assert(g_cbt == NULL);
assert(g_getMessage == NULL || g_screenSaver); assert(g_getMessage == NULL || g_screenSaver);
// must be initialized // must be initialized
if (g_threadID == 0) { if (g_threadID == 0) {
return 0; return kHOOK_FAILED;
} }
// check for mouse wheel support // check for mouse wheel support
g_wheelSupport = getWheelSupport(); g_wheelSupport = getWheelSupport();
// install keyboard hook
#if !NO_GRAB_KEYBOARD
g_keyboard = SetWindowsHookEx(WH_KEYBOARD,
&keyboardHook,
g_hinstance,
0);
if (g_keyboard == NULL) {
g_threadID = NULL;
return 0;
}
#else
// keep compiler quiet
&keyboardHook;
#endif
// install mouse hook
g_mouse = SetWindowsHookEx(WH_MOUSE,
&mouseHook,
g_hinstance,
0);
if (g_mouse == NULL) {
// uninstall keyboard hook before failing
if (g_keyboard != NULL) {
UnhookWindowsHookEx(g_keyboard);
g_keyboard = NULL;
}
g_threadID = NULL;
return 0;
}
/*
// install CBT hook
g_cbt = SetWindowsHookEx(WH_CBT,
&cbtHook,
g_hinstance,
0);
if (g_cbt == NULL) {
// uninstall keyboard and mouse hooks before failing
if (g_keyboard != NULL) {
UnhookWindowsHookEx(g_keyboard);
g_keyboard = NULL;
}
UnhookWindowsHookEx(g_mouse);
g_mouse = NULL;
g_threadID = NULL;
return 0;
}
*/
// install GetMessage hook (unless already installed) // install GetMessage hook (unless already installed)
if (g_wheelSupport == kWheelOld && g_getMessage == NULL) { if (g_wheelSupport == kWheelOld && g_getMessage == NULL) {
g_getMessage = SetWindowsHookEx(WH_GETMESSAGE, g_getMessage = SetWindowsHookEx(WH_GETMESSAGE,
&getMessageHook, &getMessageHook,
g_hinstance, g_hinstance,
0); 0);
// ignore failure; we just won't get mouse wheel messages
} }
// install low-level keyboard/mouse hooks, if possible. since these // install low-level keyboard/mouse hooks, if possible. since these
@ -735,7 +661,47 @@ install()
} }
} }
return 1; // 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 int
@ -755,20 +721,16 @@ uninstall(void)
} }
if (g_keyboard != NULL) { if (g_keyboard != NULL) {
UnhookWindowsHookEx(g_keyboard); UnhookWindowsHookEx(g_keyboard);
g_keyboard = NULL;
} }
if (g_mouse != NULL) { if (g_mouse != NULL) {
UnhookWindowsHookEx(g_mouse); UnhookWindowsHookEx(g_mouse);
} g_mouse = NULL;
if (g_cbt != NULL) {
UnhookWindowsHookEx(g_cbt);
} }
if (g_getMessage != NULL && !g_screenSaver) { if (g_getMessage != NULL && !g_screenSaver) {
UnhookWindowsHookEx(g_getMessage); UnhookWindowsHookEx(g_getMessage);
g_getMessage = NULL; g_getMessage = NULL;
} }
g_keyboard = NULL;
g_mouse = NULL;
g_cbt = NULL;
g_wheelSupport = kWheelNone; g_wheelSupport = kWheelNone;
// show the cursor // show the cursor
@ -837,6 +799,10 @@ setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize)
void void
setRelay(int enable) setRelay(int enable)
{ {
if ((enable != 0) == g_relay) {
// no change
return;
}
g_relay = (enable != 0); g_relay = (enable != 0);
if (!g_relay) { if (!g_relay) {
restoreCursor(); restoreCursor();

View File

@ -43,9 +43,15 @@
extern "C" { extern "C" {
enum EHookResult {
kHOOK_FAILED,
kHOOK_OKAY,
kHOOK_OKAY_LL
};
typedef int (*InitFunc)(DWORD targetQueueThreadID); typedef int (*InitFunc)(DWORD targetQueueThreadID);
typedef int (*CleanupFunc)(void); typedef int (*CleanupFunc)(void);
typedef int (*InstallFunc)(void); typedef EHookResult (*InstallFunc)(void);
typedef int (*UninstallFunc)(void); typedef int (*UninstallFunc)(void);
typedef int (*InstallScreenSaverFunc)(void); typedef int (*InstallScreenSaverFunc)(void);
typedef int (*UninstallScreenSaverFunc)(void); typedef int (*UninstallScreenSaverFunc)(void);
@ -55,7 +61,7 @@ typedef void (*SetRelayFunc)(int);
CSYNERGYHOOK_API int init(DWORD); CSYNERGYHOOK_API int init(DWORD);
CSYNERGYHOOK_API int cleanup(void); CSYNERGYHOOK_API int cleanup(void);
CSYNERGYHOOK_API int install(void); CSYNERGYHOOK_API EHookResult install(void);
CSYNERGYHOOK_API int uninstall(void); CSYNERGYHOOK_API int uninstall(void);
CSYNERGYHOOK_API int installScreenSaver(void); CSYNERGYHOOK_API int installScreenSaver(void);
CSYNERGYHOOK_API int uninstallScreenSaver(void); CSYNERGYHOOK_API int uninstallScreenSaver(void);