Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1174 lines
34 KiB

/*****************************************************************************
*
* 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 *wrong*. (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 messes 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 defined(WINNT) && (_WIN32_WINNT >= 0x0500)
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
case WM_XBUTTONDBLCLK:
#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 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;
#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;
/*
* Prefix considers this memory leaked (mb:34652) because we
* don't save the pointer here. The memory is freed when the
* hook is removed so this is not really a problem.
*/
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 `*wrong*'
* 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 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
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 */