1177 lines
34 KiB
C
1177 lines
34 KiB
C
/*****************************************************************************
|
|
*
|
|
* 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 */
|