diff --git a/client/CMSWindowsSecondaryScreen.cpp b/client/CMSWindowsSecondaryScreen.cpp index c6cadd15..0602a05f 100644 --- a/client/CMSWindowsSecondaryScreen.cpp +++ b/client/CMSWindowsSecondaryScreen.cpp @@ -9,6 +9,14 @@ #include "CLog.h" #include +// these are only defined when WINVER >= 0x0500 +#if !defined(SPI_GETMOUSESPEED) +#define SPI_GETMOUSESPEED 112 +#endif +#if !defined(SPI_SETMOUSESPEED) +#define SPI_SETMOUSESPEED 113 +#endif + // // CMSWindowsSecondaryScreen // @@ -135,7 +143,7 @@ CMSWindowsSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask) toggleKey(VK_SCROLL, KeyModifierScrollLock); } - // hide mouse + // show mouse onEnter(x, y); } @@ -338,13 +346,7 @@ CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y) CLock lock(&m_mutex); assert(m_window != NULL); syncDesktop(); - - SInt32 x0, y0, w, h; - getScreenShape(x0, y0, w, h); - mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65535.99 * (x - x0)) / (w - 1)), - (DWORD)((65535.99 * (y - y0)) / (h - 1)), - 0, 0); + warpCursor(x, y); } void @@ -427,6 +429,9 @@ CMSWindowsSecondaryScreen::onOpenDisplay() { assert(m_window == NULL); + // note if using multiple monitors + m_multimon = isMultimon(); + // save thread id. we'll need to pass this to the hook library. m_threadID = GetCurrentThreadId(); @@ -546,6 +551,7 @@ CMSWindowsSecondaryScreen::onEvent(HWND hwnd, UINT msg, case WM_DISPLAYCHANGE: // screen resolution has changed updateScreenShape(); + m_multimon = isMultimon(); m_client->onResolutionChanged(); return 0; } @@ -557,12 +563,7 @@ void CMSWindowsSecondaryScreen::onEnter(SInt32 x, SInt32 y) { // warp to requested location - SInt32 x0, y0, w, h; - getScreenShape(x0, y0, w, h); - mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, - (DWORD)((65535.99 * (x - x0)) / (w - 1)), - (DWORD)((65535.99 * (y - y0)) / (h - 1)), - 0, 0); + warpCursor(x, y); // show cursor ShowWindow(m_window, SW_HIDE); @@ -736,6 +737,92 @@ CMSWindowsSecondaryScreen::getCurrentDesktopName() const return m_deskName; } +void +CMSWindowsSecondaryScreen::warpCursor(SInt32 x, SInt32 y) +{ + // move the mouse directly to target position on NT family or if + // not using multiple monitors. + if (!m_multimon || !m_is95Family) { + SInt32 x0, y0, w, h; + getScreenShape(x0, y0, w, h); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.99 * (x - x0)) / (w - 1)), + (DWORD)((65535.99 * (y - y0)) / (h - 1)), + 0, 0); + } + + // windows 98 (and Me?) is broken. you cannot set the absolute + // position of the mouse except on the primary monitor but you + // can do relative moves onto any monitor. this is, in microsoft's + // words, "by design." apparently the designers of windows 2000 + // we're a little less lazy and did it right. + // + // we use the microsoft recommendation (Q193003): set the absolute + // position on the primary monitor, disable mouse acceleration, + // relative move the mouse to the final location, restore mouse + // acceleration. to avoid one kind of race condition (the user + // clicking the mouse or pressing a key between the absolute and + // relative move) we'll use SendInput() which guarantees that the + // events are delivered uninterrupted. we cannot prevent changes + // to the mouse acceleration at inopportune times, though. + // + // point-to-activate (x-mouse) doesn't seem to be bothered by the + // absolute/relative combination. a window over the absolute + // position (0,0) does *not* get activated (at least not on win2k) + // if the relative move puts the cursor elsewhere. similarly, the + // app under the final mouse position does *not* get deactivated + // by the absolute move to 0,0. + else { + // save mouse speed & acceleration + int oldSpeed[4]; + bool accelChanged = + SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed,0) && + SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); + + // use 1:1 motion + if (accelChanged) { + int newSpeed[4] = { 0, 0, 0, 1 }; + accelChanged = + SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || + SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); + } + + // send events + INPUT events[2]; + events[0].type = INPUT_MOUSE; + events[0].mi.dx = 0; + events[0].mi.dy = 0; + events[0].mi.mouseData = 0; + events[0].mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[0].mi.time = GetTickCount(); + events[0].mi.dwExtraInfo = 0; + events[1].type = INPUT_MOUSE; + events[1].mi.dx = x; + events[1].mi.dy = y; + events[1].mi.mouseData = 0; + events[1].mi.dwFlags = MOUSEEVENTF_MOVE; + events[1].mi.time = events[0].mi.time; + events[1].mi.dwExtraInfo = 0; + SendInput(sizeof(events) / sizeof(events[0]), + events, sizeof(events[0])); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } + } +} + +bool +CMSWindowsSecondaryScreen::isMultimon() const +{ + SInt32 x0, y0, w, h; + getScreenShape(x0, y0, w, h); + return (w != GetSystemMetrics(SM_CXSCREEN) || + h != GetSystemMetrics(SM_CYSCREEN)); +} + // these tables map KeyID (a X windows KeySym) to virtual key codes. // if the key is an extended key then the entry is the virtual key // code | 0x100. keys that map to normal characters have a 0 entry diff --git a/client/CMSWindowsSecondaryScreen.h b/client/CMSWindowsSecondaryScreen.h index e845d49a..34d456f0 100644 --- a/client/CMSWindowsSecondaryScreen.h +++ b/client/CMSWindowsSecondaryScreen.h @@ -1,6 +1,12 @@ #ifndef CMSWINDOWSSECONDARYSCREEN_H #define CMSWINDOWSSECONDARYSCREEN_H +// ensure that we get SendInput() +#if _WIN32_WINNT <= 0x400 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x401 +#endif + #include "CMSWindowsScreen.h" #include "ISecondaryScreen.h" #include "CMutex.h" @@ -66,6 +72,12 @@ private: // get calling thread to use the input desktop void syncDesktop() const; + // warp the mouse to the specified position + void warpCursor(SInt32 x, SInt32 y); + + // returns true iff there appear to be multiple monitors + bool isMultimon() const; + // key and button queries and operations DWORD mapButton(ButtonID button, bool press) const; KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID, @@ -86,6 +98,9 @@ private: // true if windows 95/98/me bool m_is95Family; + // true if system appears to have multiple monitors + bool m_multimon; + // the main loop's thread id DWORD m_threadID;