/***************************************************************************** * * DIEmM.c * * Copyright (c) 1996 Microsoft Corporation. All Rights Reserved. * * Abstract: * * Emulation module for mouse. * * Contents: * * CEm_Mouse_CreateInstance * CEm_Mouse_InitButtons * CEm_LL_MseHook * *****************************************************************************/ #include "dinputpr.h" /***************************************************************************** * * The sqiffle for this file. * *****************************************************************************/ #define sqfl sqflEm /***************************************************************************** * * Mouse globals * *****************************************************************************/ STDMETHODIMP CEm_Mouse_Acquire(PEM this, BOOL fAcquire); DIMOUSESTATE_INT s_msEd; ED s_edMouse = { &s_msEd, 0, CEm_Mouse_Acquire, -1, cbX(DIMOUSESTATE_INT), 0x0, }; /***************************************************************************** * * The algorithm for applying acceleration is: * * dxC = dxR * if A >= 1 and abs(dxR) > T1 then * dxC = dxR * 2 * if A >= 2 and abs(dxR) > Thres2 then * dxC = dxR * 4 * end if * end if * * where * dxR is the raw mouse motion * dxC is the cooked mouse motion * A is the acceleration * T1 is the first threshold * T2 is the second threshold * * Repeat for dy instead of dx. * * We can optimize this by simply setting the thresholds to MAXLONG * if they are disabled; that way, abs(dx) will never exceed it. * * The result is the following piecewise linear function: * * if 0 < abs(dxR) <= T1: dxC = dxR * if T1 < abs(dxR) <= T2: dxC = dxR * 2 * if T2 < abs(dxR): dxC = dxR * 4 * * If you graph this function, you'll see that it's discontinuous! * * The inverse mapping of this function is what concerns us. * It looks like this: * * if 0 < abs(dxC) <= T1: dxR = dxC * if T1 * 2 < abs(dxC) <= T2 * 2: dxR = dxC / 2 * if T2 * 4 < abs(dxC): dxR = dxC / 4 * * Notice that there are gaps in the graph, so we can fill them in * any way we want, as long as it isn't blatantly unintelegent. (In the * case where we are using emulation, it is possible to get relative * mouse motions that live in the "impossible" limbo zone due to * clipping.) * * if 0 < abs(dxC) <= T1: dxR = dxC * if T1 < abs(dxC) <= T2 * 2: dxR = dxC / 2 * if T2 * 2 < abs(dxC): dxR = dxC / 4 * * Therefore: (you knew the punch line was coming) * * s_rgiMouseThresh[0] = T1 (or MAXLONG) * s_rgiMouseThresh[1] = T2 * 2 (or MAXLONG) * * *****************************************************************************/ static int s_rgiMouseThresh[2]; /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_Mouse_OnMouseChange | * * The mouse acceleration changed. Go recompute the * unacceleration variables. * *****************************************************************************/ void EXTERNAL CEm_Mouse_OnMouseChange(void) { int rgi[3]; /* Mouse acceleration information */ /* * See the huge comment block at the definition of * s_rgiMouseThresh for an explanation of the math * that is happening here. * * If acceleration is enabled at all... */ if (SystemParametersInfo(SPI_GETMOUSE, 0, &rgi, 0) && rgi[2]) { s_rgiMouseThresh[0] = rgi[0]; if (rgi[2] >= 2) { s_rgiMouseThresh[1] = rgi[1] * 2; } else { /* Disable level 2 acceleration */ s_rgiMouseThresh[1] = MAXLONG; } } else { /* Disable all acceleration */ s_rgiMouseThresh[0] = MAXLONG; } SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_OnMouseChange: ") TEXT("New accelerations %d / %d"), s_rgiMouseThresh[0], s_rgiMouseThresh[1]); } /***************************************************************************** * * Mouse emulation * * Mouse emulation is done by subclassing the window that * captured the mouse. We then do the following things: * * (1) Hide the cursor for the entire vwi. * * (2) Capture the mouse. * * (3) Clip the cursor to the window. (If we let the cursor * leave our window, then it screws up capture.) * * (4) Keep re-centering the mouse whenever it moves. * * (5) Release the capture on WM_SYSCOMMAND so we don't * mess up menus, Alt+F4, etc. * * If we are using NT low-level hooks then mouse emulation * is done by spinning a thread to service ll hook * notifications. The victim window is not subclassed. * *****************************************************************************/ #define dxMinMouse 10 #define dyMinMouse 10 typedef struct MOUSEEMULATIONINFO { POINT ptCenter; /* Center of client rectangle (screen coords) */ POINT ptCenterCli; /* Center of client rectangle (client coords) */ LPARAM lpCenter; /* ptCenter in the form of an LPARAM */ BOOL fInitialized:1; /* Have we gotten started? */ BOOL fNeedExit:1; /* Should we leave now? */ BOOL fExiting:1; /* Are we trying to leave already? */ BOOL fCaptured:1; /* Have we captured the mouse? */ BOOL fHidden:1; /* Have we hidden the mouse? */ BOOL fClipped:1; /* Have we clipped the mouse? */ RECT rcClip; /* ClipCursor rectangle */ } MOUSEEMULATIONINFO, *PMOUSEEMULATIONINFO; LRESULT CALLBACK CEm_Mouse_SubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp, UINT_PTR uid, ULONG_PTR dwRef); /***************************************************************************** * * CEm_Mouse_InitCoords * * *****************************************************************************/ BOOL INTERNAL CEm_Mouse_InitCoords(HWND hwnd, PMOUSEEMULATIONINFO this) { RECT rcClient; RECT rcDesk; GetClientRect(hwnd, &rcClient); MapWindowPoints(hwnd, 0, (LPPOINT)&rcClient, 2); SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_InitCoords: Client (%d,%d)-(%d,%d)"), rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); /* * Clip this with the screen, in case the window extends * off-screen. * * Someday: This will need to change when we get multiple monitors. */ GetWindowRect(GetDesktopWindow(), &rcDesk); SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_InitCoords: Desk (%d,%d)-(%d,%d)"), rcDesk.left, rcDesk.top, rcDesk.right, rcDesk.bottom); IntersectRect(&this->rcClip, &rcDesk, &rcClient); SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_InitCoords: Clip (%d,%d)-(%d,%d)"), this->rcClip.left, this->rcClip.top, this->rcClip.right, this->rcClip.bottom); this->ptCenter.x = (this->rcClip.left + this->rcClip.right) >> 1; this->ptCenter.y = (this->rcClip.top + this->rcClip.bottom) >> 1; this->ptCenterCli.x = this->ptCenter.x - rcClient.left; this->ptCenterCli.y = this->ptCenter.y - rcClient.top; this->lpCenter = MAKELPARAM(this->ptCenterCli.x, this->ptCenterCli.y); SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_InitCoords: lpCenter (%d, %d)"), MAKEPOINTS(this->lpCenter).x, MAKEPOINTS(this->lpCenter).y); return this->rcClip.bottom - this->rcClip.top > dyMinMouse && this->rcClip.right - this->rcClip.left > dxMinMouse; } /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_Mouse_OnSettingChange | * * If the mouse acceleration changed, then update our globals * so we can unaccelerate the mouse properly. * * @parm WPARAM | wp | * * SystemParametersInfo value. * * @parm LPARAM | lp | * * Name of section that changed. * *****************************************************************************/ void INTERNAL CEm_Mouse_OnSettingChange(WPARAM wp, LPARAM lp) { /* * If wp is nonzero, then it is an SPI value. * * If wp is zero, then be paranoid if lp == 0 or lp = "windows". */ switch (wp) { case 0: /* wp == 0; must test lp */ if (lp == 0) { CEm_Mouse_OnMouseChange(); } else if (lstrcmpi((LPTSTR)lp, TEXT("windows")) == 0) { CEm_Mouse_OnMouseChange(); } break; case SPI_SETMOUSE: CEm_Mouse_OnMouseChange(); break; default: /* Some other SPI */ break; } } /***************************************************************************** * * CEm_Mouse_Subclass_OnNull * * WM_NULL is a nudge message that makes us reconsider our * place in the world. * * We need this special signal because you cannot call * SetCapture() or ReleaseCapture() from the wrong thread. * *****************************************************************************/ void INTERNAL CEm_Mouse_Subclass_OnNull(HWND hwnd, PMOUSEEMULATIONINFO this) { /* * Initialize me if I haven't been already. */ if (!this->fInitialized) { this->fInitialized = 1; if (!this->fCaptured) { this->fCaptured = 1; SetCapture(hwnd); } if (!this->fHidden) { this->fHidden = 1; SquirtSqflPtszV(sqflCursor, TEXT("CEm_Mouse_Subclass: Hiding mouse")); ShowCursor(0); } /* * Remove any clipping we performed so our math * comes out right again. */ if (this->fClipped) { this->fClipped = 0; ClipCursor(0); } /* * (Re)compute mouse acceleration information. */ CEm_Mouse_OnMouseChange(); if (CEm_Mouse_InitCoords(hwnd, this)) { /* * Force the LBUTTON up during the recentering move. * * Otherwise, if the user activates the app by clicking * the title bar, USER sees the cursor move while the * left button is down on the title bar and moves the * window. Oops. * * We don't bother forcing the mouse back down after we * have recentered. I can't figure out how, and it's * not worth it. * */ if (GetAsyncKeyState(VK_LBUTTON) < 0) { mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); } SetCursorPos(this->ptCenter.x, this->ptCenter.y); this->fClipped = 1; ClipCursor(&this->rcClip); } else { /* Can't emulate; window too small */ this->fNeedExit = 1; } } if (this->fNeedExit && !this->fExiting) { /* * Must do this first! ReleaseCapture() will re-enter us, * and if we continued onward, we'd end up partying on freed * memory. */ this->fExiting = 1; if (this->fCaptured) { ReleaseCapture(); } if (this->fHidden) { SquirtSqflPtszV(sqflCursor, TEXT("CEm_Mouse_Subclass: Showing mouse")); ShowCursor(1); } if (this->fClipped) { ClipCursor(0); } CEm_ForceDeviceUnacquire(&s_edMouse, FDUFL_NORMAL); // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_Subclass %p unhook"), hwnd); ConfirmF(RemoveWindowSubclass(hwnd, CEm_Mouse_SubclassProc, 0)); FreePv(this); } } /***************************************************************************** * * @doc INTERNAL * * @func void | CEm_Mouse_RemoveAccel | * * Remove any acceleration from the mouse motion. * * See the huge comment block at s_rgiMouseThresh * for an explanation of what we are doing. * * @parm int | dx | * * Change in coordinate, either dx or dy. * *****************************************************************************/ int INTERNAL CEm_Mouse_RemoveAccel(int dx) { int x = abs(dx); if (x > s_rgiMouseThresh[0]) { dx /= 2; if (x > s_rgiMouseThresh[1]) { dx /= 2; } } return dx; } /***************************************************************************** * * @doc EXTERNAL * * @func void | CEm_Mouse_AddState | * * Add a mouse state change. * * The mouse coordinates are relative, not absolute. * * @parm LPDIMOUSESTATE_INT | pms | * * New mouse state, except that coordinates are relative. * * @parm DWORD | tm | * * Time the state change was generated. * *****************************************************************************/ void EXTERNAL CEm_Mouse_AddState(LPDIMOUSESTATE_INT pms, DWORD tm) { /* Sanity check: Make sure the device has been initialized */ if( s_edMouse.pDevType ) { pms->lX = s_msEd.lX + pms->lX; pms->lY = s_msEd.lY + pms->lY; /* * HACK! * * Memphis and NT5 USER both mess up the case where the presence * of a wheel mouse changes dynamically. So if we do not have * a wheel in our data format, then don't record it. * * The consequence of this is that we will not see any more * buttons or wheels than were present when we queried the number * of buttons in the first place. */ /* If we use Subclassing, the movement of wheel can't be accumulated. * Otherwise, you will see the number keep increasing. Fix bug: 182774. * However, if we use low level hook, we need the code. Fix bug: 238987 */ #ifdef USE_SLOW_LL_HOOKS if (s_edMouse.pDevType[DIMOFS_Z]) { pms->lZ = s_msEd.lZ + pms->lZ; } #endif CEm_AddState(&s_edMouse, pms, tm); } } /***************************************************************************** * * Mouse window subclass procedure * *****************************************************************************/ #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL (WM_MOUSELAST + 1) #endif #define WM_SETACQUIRE WM_USER #define WM_QUITSELF (WM_USER+1) LRESULT CALLBACK CEm_Mouse_SubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp, UINT_PTR uid, ULONG_PTR dwRef) { PMOUSEEMULATIONINFO this = (PMOUSEEMULATIONINFO)dwRef; DIMOUSESTATE_INT ms; static BOOL fWheelScrolling = FALSE; switch (wm) { case WM_NCDESTROY: SquirtSqflPtszV(sqfl, TEXT("CEm_Subclass: window destroyed while acquired")); goto unhook; case WM_CAPTURECHANGED: /* * "An application should not attempt to set the mouse capture * in response to [WM_CAPTURECHANGED]." * * So we just unhook. */ SquirtSqflPtszV(sqfl, TEXT("CEm_Subclass: %04x lost to %04x"), hwnd, lp); goto unhook; case WM_SYSCOMMAND: /* * We've got to unhook because WM_SYSCOMMAND will punt if * the mouse is captured. Otherwise, you couldn't type Alt+F4 * to exit the app, which is kind of a bummer. */ unhook:; // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_Acquire: %p ") TEXT("exiting because of %04x"), hwnd, wm); this->fNeedExit = 1; CEm_Mouse_Subclass_OnNull(hwnd, this); break; case WM_NULL: CEm_Mouse_Subclass_OnNull(hwnd, this); break; /* * Note that we use WM_WINDOWPOSCHANGED and not WM_SIZE, because * an application which doesn't send WM_WINDOWPOSCHANGED to * DefWindowProc will will never receive a WM_SIZE message. * * We need to start over to handle the new screen dimensions, * recenter the mouse, and possibly abandon the operation if * things don't look right. */ case WM_WINDOWPOSCHANGED: case WM_DISPLAYCHANGE: this->fInitialized = 0; CEm_Mouse_Subclass_OnNull(hwnd, this); break; /* * The mouse acceleration may have changed. */ case WM_SETTINGCHANGE: CEm_Mouse_OnSettingChange(wp, lp); break; case WM_MOUSEWHEEL: SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_SubclassProc: (%d,%d,%d)"), MAKEPOINTS(lp).x, MAKEPOINTS(lp).y, (short)HIWORD(wp)); ms.lZ = (short)HIWORD(wp); fWheelScrolling = TRUE; goto lparam; case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: #if DIRECTINPUT_VERSION >= 0x0700 #if defined(WINNT) && (_WIN32_WINNT >= 0x0500) case WM_XBUTTONDOWN: case WM_XBUTTONUP: case WM_XBUTTONDBLCLK: #endif #endif SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_SubclassProc: (%d,%d)"), MAKEPOINTS(lp).x, MAKEPOINTS(lp).y); ms.lZ = 0; lparam:; /* * Don't move the cursor if it hasn't moved. * Otherwise, we recurse ourselves to death. * * In fact, if the cursor hasn't moved, ignore this * motion and do only buttons. Otherwise, you get * into the situation where we end up reacting to * our own recentering. (D'oh!) */ ms.lX = 0; ms.lY = 0; if (lp != this->lpCenter && !fWheelScrolling ) { SetCursorPos(this->ptCenter.x, this->ptCenter.y); ms.lX = MAKEPOINTS(lp).x - this->ptCenterCli.x; ms.lY = MAKEPOINTS(lp).y - this->ptCenterCli.y; } fWheelScrolling = FALSE; /* * Note that these return unswapped mouse button data. * Arguably a bug, but it's documented, so it's now a * feature. */ #define GetButton(n) ((GetAsyncKeyState(n) & 0x8000) >> 8) ms.rgbButtons[0] = GetButton(VK_LBUTTON); ms.rgbButtons[1] = GetButton(VK_RBUTTON); ms.rgbButtons[2] = GetButton(VK_MBUTTON); #if DIRECTINPUT_VERSION >= 0x0700 #if defined(WINNT) && (_WIN32_WINNT >= 0x0500) ms.rgbButtons[3] = GetButton(VK_XBUTTON1); ms.rgbButtons[4] = GetButton(VK_XBUTTON2); #else ms.rgbButtons[3] = 0; ms.rgbButtons[4] = 0; #endif ms.rgbButtons[5] = 0; ms.rgbButtons[6] = 0; ms.rgbButtons[7] = 0; #else ms.rgbButtons[3] = 0; #endif #undef GetButton /* * Note that we cannot unaccelerate the mouse when using * mouse capture, because we don't know what sort of * coalescing USER has done for us. */ CEm_Mouse_AddState(&ms, GetMessageTime()); return 0; } return DefSubclassProc(hwnd, wm, wp, lp); } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | CEm_Mouse_Subclass_Acquire | * * Acquire/unacquire a mouse via subclassing. * * @parm PEM | pem | * * Device being acquired. * * @parm BOOL | fAcquire | * * Whether the device is being acquired or unacquired. * *****************************************************************************/ STDMETHODIMP CEm_Mouse_Subclass_Acquire(PEM this, BOOL fAcquire) { HRESULT hres; EnterProc(CEm_Mouse_Subclass_Acquire, (_ "pu", this, fAcquire)); AssertF(this->dwSignature == CEM_SIGNATURE); if (fAcquire) { /* Install the hook */ if (this->vi.hwnd && (this->vi.fl & VIFL_CAPTURED)) { PMOUSEEMULATIONINFO pmei; hres = AllocCbPpv(cbX(MOUSEEMULATIONINFO), &pmei); if (SUCCEEDED(hres)) { if (SetWindowSubclass(this->vi.hwnd, CEm_Mouse_SubclassProc, 0, (ULONG_PTR)pmei)) { /* Nudge it */ SendNotifyMessage(this->vi.hwnd, WM_NULL, 0, 0L); hres = S_OK; } else { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("Mouse::Acquire: ") TEXT("Window %p is not valid"), this->vi.hwnd); FreePv(pmei); hres = E_INVALIDARG; } } } else { RPF("Mouse::Acquire: Non-exclusive mode not supported"); hres = E_FAIL; } } else { /* Remove the hook */ PMOUSEEMULATIONINFO pmei; if (GetWindowSubclass(this->vi.hwnd, CEm_Mouse_SubclassProc, 0, (PULONG_PTR)&pmei)) { // 7/19/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers. SquirtSqflPtszV(sqfl, TEXT("CEm_Mouse_Acquire: ") TEXT("Telling %p to exit"), this->vi.hwnd); pmei->fNeedExit = 1; SendNotifyMessage(this->vi.hwnd, WM_NULL, 0, 0L); } else { /* Window was already unhooked */ } hres = S_OK; } ExitOleProc(); return hres; } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | CEm_Mouse_Acquire | * * Acquire/unacquire a mouse. * * @parm PEM | pem | * * Device being acquired. * * Whether the device is being acquired or unacquired. * *****************************************************************************/ STDMETHODIMP CEm_Mouse_Acquire(PEM this, BOOL fAcquire) { HRESULT hres; EnterProc(CEm_Mouse_Acquire, (_ "pu", this, fAcquire)); AssertF(this->dwSignature == CEM_SIGNATURE); #ifdef USE_SLOW_LL_HOOKS AssertF(DIGETEMFL(this->vi.fl) == DIEMFL_MOUSE || DIGETEMFL(this->vi.fl) == DIEMFL_MOUSE2); if (this->vi.fl & DIMAKEEMFL(DIEMFL_MOUSE)) { /* * This used to use the subclass technique for exclusive mode * even if low-level hooks were available because low-level * hooks turn out to be even slower that subclassing. However, * subclassing is not transparent as it uses SetCapture which * causes Accellerator translation to be disabled for the app * which would be a more serious regression from Win9x than * performance being even worse than we thought. */ AssertF(g_fUseLLHooks); hres = CEm_LL_Acquire(this, fAcquire, this->vi.fl, LLTS_MSE); if( SUCCEEDED(hres) ) { if( fAcquire && this->vi.fl & VIFL_CAPTURED ) { if( !this->fHidden ) { ShowCursor(0); this->fHidden = TRUE; } } else { if( this->fHidden ) { ShowCursor(1); this->fHidden = FALSE; } } } } else { hres = CEm_Mouse_Subclass_Acquire(this, fAcquire); } #else AssertF(DIGETEMFL(this->vi.fl) == DIEMFL_MOUSE2); hres = CEm_Mouse_Subclass_Acquire(this, fAcquire); #endif ExitOleProc(); return hres; } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | CEm_Mouse_CreateInstance | * * Create a mouse thing. Also record what emulation * level we ended up with so the caller knows. * * @parm PVXDDEVICEFORMAT | pdevf | * * What the object should look like. * * @parm PVXDINSTANCE * | ppviOut | * * The answer goes here. * *****************************************************************************/ HRESULT EXTERNAL CEm_Mouse_CreateInstance(PVXDDEVICEFORMAT pdevf, PVXDINSTANCE *ppviOut) { HRESULT hres; #ifdef USE_SLOW_LL_HOOKS /* * Note carefully the test. It handles the cases where * * 0. The app did not ask for emulation, so we give it the * best we can. (dwEmulation == 0) * 1. The app explicitly asked for emulation 1. * (dwEmulation == DIEMFL_MOUSE) * 2. The app explicitly asked for emulation 2. * (dwEmulation == DIEMFL_MOUSE2) * 3. The registry explicitly asked for both emulation modes. * (dwEmulation == DIEMFL_MOUSE | DIEMFL_MOUSE2) * Give it the best we can. (I.e., same as case 0.) * * All platforms support emulation 2. Not all platforms support * emulation 1. If we want emulation 1 but can't get it, then * we fall back on emulation 2. */ /* * First, if we don't have support for emulation 1, then clearly * we have to use emulation 2. */ if (!g_fUseLLHooks #ifdef DEBUG || (g_flEmulation & DIEMFL_MOUSE2) #endif ) { pdevf->dwEmulation = DIEMFL_MOUSE2; } else /* * Otherwise, we have to choose between 1 and 2. The only case * where we choose 2 is if 2 is explicitly requested. */ if (pdevf->dwEmulation == DIEMFL_MOUSE2) { /* Do nothing */ } else /* * All other cases get 1. */ { pdevf->dwEmulation = DIEMFL_MOUSE; } /* * Assert that we never give emulation 1 when it doesn't exist. */ AssertF(fLimpFF(pdevf->dwEmulation & DIEMFL_MOUSE, g_fUseLLHooks)); #else /* * We are being compiled for "emulation 2 only", so that simplifies * matters immensely. */ pdevf->dwEmulation = DIEMFL_MOUSE2; #endif hres = CEm_CreateInstance(pdevf, ppviOut, &s_edMouse); return hres; } /***************************************************************************** * * @doc INTERNAL * * @func HRESULT | CEm_Mouse_InitButtons | * * Initialize the mouse button state in preparation for * acquisition. * * @parm PVXDDWORDDATA | pvdd | * * The button states. * *****************************************************************************/ HRESULT EXTERNAL CEm_Mouse_InitButtons(PVXDDWORDDATA pvdd) { /* Do this only when nothing is yet acquired */ if (s_edMouse.cAcquire < 0) { *(LPDWORD)&s_msEd.rgbButtons = pvdd->dw; /* randomly initializing axes as well as mouse buttons X and Y are not buttons Randomize initial values of X and Y */ while( !s_msEd.lX ) { s_msEd.lX = GetTickCount(); s_msEd.lY = (s_msEd.lX << 16) | (s_msEd.lX >> 16); s_msEd.lX = s_msEd.lY * (DWORD)((UINT_PTR)&pvdd); } } return S_OK; } #ifdef USE_SLOW_LL_HOOKS /***************************************************************************** * * @doc INTERNAL * * @func LRESULT | CEm_LL_MseHook | * * Low-level mouse hook filter. * * @parm int | nCode | * * Notification code. * * @parm WPARAM | wp | * * WM_* mouse message. * * @parm LPARAM | lp | * * Mouse message information. * * @returns * * Always chains to the next hook. * *****************************************************************************/ LRESULT CALLBACK CEm_LL_MseHook(int nCode, WPARAM wp, LPARAM lp) { PLLTHREADSTATE plts; if (nCode == HC_ACTION) { DIMOUSESTATE_INT ms; POINT pt; PMSLLHOOKSTRUCT pllhs = (PV)lp; /* * We are called only on mouse messages, so we may as * well prepare ourselves up front. * * Note! that we *cannot* use GetAsyncKeyState on the * buttons, because the buttons haven't been pressed yet! * Instead, we must update the button state based on the * received message. */ ms.lX = 0; ms.lY = 0; ms.lZ = 0; memcpy(ms.rgbButtons, s_msEd.rgbButtons, cbX(ms.rgbButtons)); /* * * Annoying! We receive swapped buttons, so we need to * unswap them. I mark this as `annoying' because * GetAsyncKeyState returns unswapped buttons, so sometimes * I do and sometimes I don't. But it isn't unintelegent * because it is the right thing. Arguably, GetAsyncKeyState * is the one that is broken. */ if (GetSystemMetrics(SM_SWAPBUTTON)) { /* * Assert that the left and right button messages * run in parallel. */ CAssertF(WM_RBUTTONDOWN - WM_LBUTTONDOWN == WM_RBUTTONDBLCLK - WM_LBUTTONDBLCLK && WM_RBUTTONDBLCLK - WM_LBUTTONDBLCLK == WM_RBUTTONUP - WM_LBUTTONUP); switch (wp) { case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_LBUTTONUP: wp = (wp - WM_LBUTTONUP) + WM_RBUTTONUP; break; case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_RBUTTONUP: wp = (wp - WM_RBUTTONUP) + WM_LBUTTONUP; break; } } switch (wp) { /* wp = message number */ case WM_MOUSEWHEEL: SquirtSqflPtszV(sqfl, TEXT("CEm_LL_MseHook: (%d,%d,%d)"), pllhs->pt.x, pllhs->pt.y, pllhs->mouseData); ms.lZ = (short int)HIWORD(pllhs->mouseData); goto lparam; case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: ms.rgbButtons[0] = 0x80; goto move; case WM_LBUTTONUP: ms.rgbButtons[0] = 0x00; goto move; case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: ms.rgbButtons[1] = 0x80; goto move; case WM_RBUTTONUP: ms.rgbButtons[1] = 0x00; goto move; case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: ms.rgbButtons[2] = 0x80; goto move; case WM_MBUTTONUP: ms.rgbButtons[2] = 0x00; goto move; #if DIRECTINPUT_VERSION >= 0x0700 #if defined(WINNT) && (_WIN32_WINNT >= 0x0500) case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: /* * Using switch can be easily extended to support more buttons. */ switch ( HIWORD(pllhs->mouseData) ) { case XBUTTON1: ms.rgbButtons[3] = 0x80; break; case XBUTTON2: ms.rgbButtons[4] = 0x80; break; /* * When we support more than 5 buttons, take care of them. case XBUTTON3: ms.rgbButtons[5] = 0x80; break; case XBUTTON4: ms.rgbButtons[6] = 0x80; break; case XBUTTON5: ms.rgbButtons[7] = 0x80; break; */ } goto move; case WM_XBUTTONUP: /* * Using switch can be easily extended to support more buttons. */ switch ( HIWORD(pllhs->mouseData) ) { case XBUTTON1: ms.rgbButtons[3] = 0x00; break; case XBUTTON2: ms.rgbButtons[4] = 0x00; break; /* * When we support more than 5 buttons, take care of them. case XBUTTON3: ms.rgbButtons[5] = 0x00; break; case XBUTTON4: ms.rgbButtons[6] = 0x00; break; case XBUTTON5: ms.rgbButtons[7] = 0x00; break; */ } goto move; #endif #endif case WM_MOUSEMOVE: SquirtSqflPtszV(sqfl, TEXT("CEm_LL_MseHook: (%d,%d)"), pllhs->pt.x, pllhs->pt.y); move:; lparam:; GetCursorPos(&pt); ms.lX = CEm_Mouse_RemoveAccel(pllhs->pt.x - pt.x); ms.lY = CEm_Mouse_RemoveAccel(pllhs->pt.y - pt.y); CEm_Mouse_AddState(&ms, GetTickCount()); } } /* * Eat the message by returning non-zero if at least one client * is exclusive. */ plts = g_plts; if (plts) { LRESULT rc; rc = CallNextHookEx(plts->rglhs[LLTS_MSE].hhk, nCode, wp, lp); if (!plts->rglhs[LLTS_MSE].cExcl) { return rc; } } else { /* * This can happen if a message gets posted to the hook after * releasing the last acquire but before we completely unhook. */ RPF( "DINPUT: Mouse hook not passed on to next hook" ); } return 1; } #endif /* USE_SLOW_LL_HOOKS */