440 lines
13 KiB
C
440 lines
13 KiB
C
|
/*****************************************************************************
|
||
|
*
|
||
|
* DIEm.h
|
||
|
*
|
||
|
* Copyright (c) 1996-1997 Microsoft Corporation. All Rights Reserved.
|
||
|
*
|
||
|
* Abstract:
|
||
|
*
|
||
|
* DirectInput internal header file for emulation.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @struct CEd |
|
||
|
*
|
||
|
* Emulation descriptor. One of these is created for each
|
||
|
* device. It is never destroyed, so the variable must
|
||
|
* be a global variable or memory allocated inside a
|
||
|
* container that will eventually be destroyed.
|
||
|
*
|
||
|
* ISSUE-2001/03/29-timgill Need a better destructor function
|
||
|
*
|
||
|
* @field LPVOID const | pState |
|
||
|
*
|
||
|
* State buffer that everybody parties into.
|
||
|
*
|
||
|
* It too is never destroyed, so once again it should be
|
||
|
* a global variable or live inside something else that
|
||
|
* will be destroyed.
|
||
|
*
|
||
|
* @field LPDWORD const | pDevType |
|
||
|
*
|
||
|
* Array of device type descriptors, indexed by data format
|
||
|
* offset. Used to determine whether a particular piece of
|
||
|
* data belongs to an axis, button, or POV.
|
||
|
*
|
||
|
* @field EMULATIONPROC | Acquire |
|
||
|
*
|
||
|
* Callback function for acquisition and loss thereof.
|
||
|
* It is called once when the first client acquires,
|
||
|
* and again when the last app unacquires. It is not
|
||
|
* informed of nested acquisition.
|
||
|
*
|
||
|
* @field LONG | cAcquire |
|
||
|
*
|
||
|
* Number of times the device emulation has been acquired (minus one).
|
||
|
*
|
||
|
* @field DWORD | cbData |
|
||
|
*
|
||
|
* Size of the device data type. In other words, size of
|
||
|
* <p pState> in bytes.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
typedef STDMETHOD(EMULATIONPROC)(struct CEm *, BOOL fAcquire);
|
||
|
|
||
|
typedef struct CEd {
|
||
|
|
||
|
LPVOID const pState;
|
||
|
LPDWORD const pDevType;
|
||
|
EMULATIONPROC Acquire;
|
||
|
LONG cAcquire;
|
||
|
DWORD cbData;
|
||
|
ULONG cRef;
|
||
|
} CEd, ED, *PED;
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @struct CEm |
|
||
|
*
|
||
|
* Emulation state information.
|
||
|
*
|
||
|
* @field VXDINSTANCE | vi |
|
||
|
*
|
||
|
* Information shared with parent device.
|
||
|
*
|
||
|
* @field PEM | pemNext |
|
||
|
*
|
||
|
* Next item in linked list of all active device instances.
|
||
|
*
|
||
|
* @field LPDWORD | rgdwDf |
|
||
|
*
|
||
|
* Array of items (one for each byte in the device
|
||
|
* data format). This maps each device data format byte
|
||
|
* into an application device data offset, or -1 if the
|
||
|
* application doesn't care about the corresponding object.
|
||
|
*
|
||
|
* @field ULONG_PTR | dwExtra |
|
||
|
*
|
||
|
* Extra information passed in the <t VXDDEVICEFORMAT>
|
||
|
* when the device was created. This is used by each
|
||
|
* particular device to encode additional instance infomation.
|
||
|
*
|
||
|
* @field PED | ped |
|
||
|
*
|
||
|
* The device that owns this instance. Multiple instances
|
||
|
* of the same device share the same <e CEm.ped>.
|
||
|
*
|
||
|
* @field LONG | cRef |
|
||
|
*
|
||
|
* Reference count.
|
||
|
*
|
||
|
*
|
||
|
* @field LONG | cAcquire |
|
||
|
*
|
||
|
* Number of times the device instance has been acquired (minus one).
|
||
|
*
|
||
|
*
|
||
|
* @field BOOL | fWorkerThread |
|
||
|
*
|
||
|
* This is used by low-level hooks and HID devices, which
|
||
|
* require a worker thread to collect the data.
|
||
|
* This is not cheap, so
|
||
|
* instead, we spin up the thread on the first acquire, and
|
||
|
* on the unacquire, we keep the thread around so that the next
|
||
|
* acquire is fast. When the last object is released, we finally
|
||
|
* kill the thread.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
typedef struct CEm {
|
||
|
|
||
|
VXDINSTANCE vi; /* This must be first */
|
||
|
struct CEm *pemNext;
|
||
|
LPDWORD rgdwDf;
|
||
|
ULONG_PTR dwExtra;
|
||
|
PED ped;
|
||
|
LONG cAcquire;
|
||
|
LONG cRef;
|
||
|
#ifdef WORKER_THREAD
|
||
|
BOOL fWorkerThread;
|
||
|
#endif
|
||
|
#ifdef DEBUG
|
||
|
DWORD dwSignature;
|
||
|
#endif
|
||
|
BOOL fHidden;
|
||
|
} CEm, EM, *PEM;
|
||
|
|
||
|
#define CEM_SIGNATURE 0x4D4D4545 /* "EEMM" */
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @func PEM | pemFromPvi |
|
||
|
*
|
||
|
* Given an interior pointer to a <t VXDINSTANCE>, retrieve
|
||
|
* a pointer to the parent <t CEm>.
|
||
|
*
|
||
|
* @parm PVXDINSTANCE | pvi |
|
||
|
*
|
||
|
* The pointer to convert.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
PEM INLINE
|
||
|
pemFromPvi(PVXDINSTANCE pvi)
|
||
|
{
|
||
|
return pvSubPvCb(pvi, FIELD_OFFSET(CEm, vi));
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* NT low-level hook support
|
||
|
*
|
||
|
* Low-level hooks live on a separate thread which we spin
|
||
|
* up when first requested and take down when the last
|
||
|
* DirectInput device that used a thread has been destroyed.
|
||
|
*
|
||
|
* If we wanted, we could destroy the thread when the
|
||
|
* device is unacquired (rather than when the device is
|
||
|
* destroyed), but we cache the thread instead, because
|
||
|
* a device that once has been acquired will probably be
|
||
|
* acquired again.
|
||
|
*
|
||
|
* To prevent race conditions from crashing us, we addref
|
||
|
* our DLL when the thread exists and have the thread
|
||
|
* perform a FreeLibrary as its final act.
|
||
|
*
|
||
|
* Note that this helper thread is also used by the HID data
|
||
|
* collector.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
#ifdef USE_SLOW_LL_HOOKS
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @struct LLHOOKSTATE |
|
||
|
*
|
||
|
* Low-level hook information about a single hook.
|
||
|
*
|
||
|
* @field int | cHook |
|
||
|
*
|
||
|
* Number of times the hook has been requested. If zero,
|
||
|
* then there should be no hook. All modifications to
|
||
|
* this field must be interlocked to avoid race conditions
|
||
|
* when two threads try to hook or unhook simultaneously.
|
||
|
*
|
||
|
* @field int | cExcl |
|
||
|
*
|
||
|
* Number of times the hook has been requested in an exclusive
|
||
|
* mode. This value should always be less than or equal to the
|
||
|
* cHook value. All modifications to this field must be
|
||
|
* interlocked to avoid race conditions when two threads try to
|
||
|
* hook or unhook simultaneously.
|
||
|
*
|
||
|
* @field HHOOK | hhk |
|
||
|
*
|
||
|
* The actual hook, if it is installed. Only the hook thread
|
||
|
* touches this field, so it does not need to be protected.
|
||
|
*
|
||
|
* @field BOOLEAN | fExcluded |
|
||
|
*
|
||
|
* Flag to indicate whether or not exclusivity has been applied.
|
||
|
* Only the hook thread touches this field, so it does not need to
|
||
|
* be protected.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
typedef struct LLHOOKSTATE {
|
||
|
|
||
|
int cHook;
|
||
|
int cExcl;
|
||
|
HHOOK hhk;
|
||
|
BOOLEAN fExcluded;
|
||
|
} LLHOOKSTATE, *PLLHOOKSTATE;
|
||
|
|
||
|
LRESULT CALLBACK CEm_LL_KbdHook(int nCode, WPARAM wp, LPARAM lp);
|
||
|
LRESULT CALLBACK CEm_LL_MseHook(int nCode, WPARAM wp, LPARAM lp);
|
||
|
|
||
|
#endif /* USE_SLOW_LL_HOOKS */
|
||
|
|
||
|
#ifdef WORKER_THREAD
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @struct LLTHREADSTATE |
|
||
|
*
|
||
|
* Low-level hook state for a thread. Note that this is
|
||
|
* a dynamically
|
||
|
* allocated structure instead of a static. This avoids various
|
||
|
* race conditions where, for example, somebody terminates the
|
||
|
* worker thread and somebody else starts it up before the
|
||
|
* worker thread is completely gone.
|
||
|
*
|
||
|
* A pointer to the hThread is passed as the pointer to an array
|
||
|
* of two handles in calls to WaitForMultipleObject so hEvent must
|
||
|
* follow it directly.
|
||
|
*
|
||
|
* @field DWORD | idThread |
|
||
|
*
|
||
|
* The ID of the worker thread.
|
||
|
*
|
||
|
* @field LONG | cRef |
|
||
|
*
|
||
|
* Thread reference count. The thread kills itself when this
|
||
|
* drops to zero.
|
||
|
*
|
||
|
* @field LLHOOKSTATE | rglhs[2] |
|
||
|
*
|
||
|
* Hook states, indexed by LLTS_* values.
|
||
|
*
|
||
|
* These are used only if low-level hooks are enabled.
|
||
|
*
|
||
|
* @field HANDLE | hThread |
|
||
|
*
|
||
|
* The handle (from the create) of the worker thread.
|
||
|
*
|
||
|
* This is used only if HID support is enabled.
|
||
|
*
|
||
|
* @field HANDLE | hEvent |
|
||
|
*
|
||
|
* The handle to the event used to synchronize with the worker thread.
|
||
|
*
|
||
|
* This is used only if HID support is enabled.
|
||
|
*
|
||
|
* @field GPA | gpaHid |
|
||
|
*
|
||
|
* Pointer array of HID devices which are acquired.
|
||
|
*
|
||
|
* This is used only if HID support is enabled.
|
||
|
*
|
||
|
* @field PEM | pemCheck |
|
||
|
*
|
||
|
* Pointer to Emulation state information.
|
||
|
*
|
||
|
* This is used only if HID support is enabled.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
#define LLTS_KBD 0
|
||
|
#define LLTS_MSE 1
|
||
|
#define LLTS_MAX 2
|
||
|
|
||
|
typedef struct LLTHREADSTATE {
|
||
|
DWORD idThread;
|
||
|
LONG cRef;
|
||
|
#ifdef USE_SLOW_LL_HOOKS
|
||
|
LLHOOKSTATE rglhs[LLTS_MAX];
|
||
|
#endif
|
||
|
HANDLE hThread; /* MUST be followed by hEvent, see above */
|
||
|
HANDLE hEvent; /* MUST follow hThread, see above */
|
||
|
GPA gpaHid;
|
||
|
PEM pemCheck;
|
||
|
} LLTHREADSTATE, *PLLTHREADSTATE;
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @topic Communicating with the worker thread |
|
||
|
*
|
||
|
* Communication with the worker thread is performed via
|
||
|
* <c WM_NULL> messages. Extra care must be taken to make
|
||
|
* sure that someone isn't randomly sending messages to us.
|
||
|
*
|
||
|
* We use the <c WM_NULL> message because there are race
|
||
|
* windows where we might post a message to a thread after
|
||
|
* it is gone. During this window, the thread ID might get
|
||
|
* recycled, and we end up posting the message to some random
|
||
|
* thread that isn't ours. By using the <c WM_NULL> message,
|
||
|
* we are safe in knowing that the target thread won't barf
|
||
|
* on the unexpected message.
|
||
|
*
|
||
|
* The <t WPARAM> of the <c WM_NULL> is the magic value
|
||
|
* <c WT_WPARAM>.
|
||
|
*
|
||
|
* The <t LPARAM> of the <c WM_NULL> is either a pointer
|
||
|
* to the <t CEm> that needs to be refreshed or is
|
||
|
* zero if we merely want to check our bearings.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
#define WT_WPARAM 0
|
||
|
|
||
|
#define PostWorkerMessage(thid, lp) \
|
||
|
PostThreadMessage(thid, WM_NULL, WT_WPARAM, (LPARAM)(lp)) \
|
||
|
|
||
|
#define NudgeWorkerThread(thid) \
|
||
|
PostThreadMessage(thid, WM_NULL, WT_WPARAM, (LPARAM)NULL)
|
||
|
|
||
|
HRESULT EXTERNAL NudgeWorkerThreadPem( PLLTHREADSTATE plts, PEM pem );
|
||
|
|
||
|
HRESULT EXTERNAL NotifyWorkerThreadPem(DWORD idThread, PEM pem);
|
||
|
|
||
|
STDMETHODIMP CEm_GetWorkerThread(PEM pem, PLLTHREADSTATE *pplts);
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @global PLLTHREADSTATE | g_plts |
|
||
|
*
|
||
|
* The thread state of the currently-active thread.
|
||
|
*
|
||
|
* This variable needs to be externally accessible
|
||
|
* because you can't pass instance data to a windows
|
||
|
* hook function. (Whose idea was that?)
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
extern PLLTHREADSTATE g_plts;
|
||
|
|
||
|
void EXTERNAL CEm_Mouse_OnMouseChange(void);
|
||
|
|
||
|
#endif /* WORKER_THREAD */
|
||
|
|
||
|
/*
|
||
|
* Private helper functions in diem.c
|
||
|
*/
|
||
|
|
||
|
#define FDUFL_NORMAL 0x0000 /* Nothing unusual */
|
||
|
#define FDUFL_UNPLUGGED VIFL_UNPLUGGED /* Device disconnected */
|
||
|
|
||
|
void EXTERNAL CEm_ForceDeviceUnacquire(PED ped, UINT fdufl);
|
||
|
void EXTERNAL CEm_AddState(PED ped, LPVOID pvData, DWORD tm);
|
||
|
DWORD EXTERNAL CEm_AddEvent(PED ped, DWORD dwData, DWORD dwOfs, DWORD tm);
|
||
|
BOOL EXTERNAL CEm_ContinueEvent(PED ped, DWORD dwData, DWORD dwOfs, DWORD tm, DWORD dwSeq);
|
||
|
|
||
|
STDMETHODIMP CEm_LL_Acquire(PEM this, BOOL fAcquire, ULONG fl, UINT ilts);
|
||
|
|
||
|
HRESULT EXTERNAL
|
||
|
CEm_CreateInstance(PVXDDEVICEFORMAT pdevf, PVXDINSTANCE *ppviOut, PED ped);
|
||
|
|
||
|
void EXTERNAL CEm_FreeInstance(PEM this);
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @func void | CEm_AddRef |
|
||
|
*
|
||
|
* Bump the reference count because we're doing something with it.
|
||
|
*
|
||
|
* @parm PEM | this |
|
||
|
*
|
||
|
* The victim.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
void INLINE
|
||
|
CEm_AddRef(PEM this)
|
||
|
{
|
||
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
||
|
InterlockedIncrement(&this->cRef);
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
*
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @func void | CEm_Release |
|
||
|
*
|
||
|
* Drop the reference count and blow it away if it's gone.
|
||
|
*
|
||
|
* @parm PEM | this |
|
||
|
*
|
||
|
* The victim.
|
||
|
*
|
||
|
*****************************************************************************/
|
||
|
|
||
|
void INLINE
|
||
|
CEm_Release(PEM this)
|
||
|
{
|
||
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
||
|
if (InterlockedDecrement(&this->cRef) == 0) {
|
||
|
CEm_FreeInstance(this);
|
||
|
}
|
||
|
}
|