1938 lines
53 KiB
C
1938 lines
53 KiB
C
|
|
/*****************************************************************************
|
|
*
|
|
* DIEm.c
|
|
*
|
|
* Copyright (c) 1996 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
* Abstract:
|
|
*
|
|
* DirectInput VxD emulation layer. (I.e., do the things that
|
|
* dinput.vxd normally does.) You may find large chunks of this
|
|
* code familiar: It's exactly the same thing that happens in
|
|
* the VxD.
|
|
*
|
|
* Contents:
|
|
*
|
|
* CEm_AcquireInstance
|
|
* CEm_UnacquireInstance
|
|
* CEm_SetBufferSize
|
|
* CEm_DestroyInstance
|
|
* CEm_SetDataFormat
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#include "dinputpr.h"
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* The sqiffle for this file.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define sqfl sqflEm
|
|
|
|
#define ThisClass CEm
|
|
|
|
#define CEM_SIGNATURE 0x4D4D4545 /* "EEMM" */
|
|
|
|
PEM g_pemFirst;
|
|
|
|
#ifdef WORKER_THREAD
|
|
|
|
PLLTHREADSTATE g_plts; /* The currently active input thread */
|
|
|
|
#ifdef USE_WM_INPUT
|
|
BOOL g_fFromKbdMse;
|
|
#endif
|
|
|
|
#endif /* WORKER_THREAD */
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func void | CEm_FreeInstance |
|
|
*
|
|
* It's really gone now.
|
|
*
|
|
* @parm PEM | this |
|
|
*
|
|
* The victim.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void EXTERNAL
|
|
CEm_FreeInstance(PEM this)
|
|
{
|
|
PEM *ppem;
|
|
EnterProc(CEm_FreeInstance, (_ "p", this));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
AssertF(this->cRef == 0);
|
|
|
|
/*
|
|
* It is the owner's responsibility to unacquire before releasing.
|
|
*/
|
|
AssertF(!(this->vi.fl & VIFL_ACQUIRED));
|
|
|
|
/*
|
|
* If this device has a reference to a hook, then remove
|
|
* the reference.
|
|
*/
|
|
#ifdef WORKER_THREAD
|
|
if (this->fWorkerThread) {
|
|
PLLTHREADSTATE plts;
|
|
DWORD idThread;
|
|
|
|
/*
|
|
* Protect test and access of g_plts with DLLCrit
|
|
*/
|
|
DllEnterCrit();
|
|
plts = g_plts;
|
|
|
|
if (plts ) {
|
|
AssertF(plts->cRef);
|
|
|
|
/*
|
|
* Note that we need to keep the thread ID because
|
|
* the InterlockedDecrement might cause us to lose
|
|
* the object.
|
|
*
|
|
* Note that this opens a race condition where the
|
|
* thread might decide to kill itself before we
|
|
* post it the nudge message. That's okay, because
|
|
* even if the thread ID gets recycled, the message
|
|
* that appears is a dummy WM_NULL message that
|
|
* causes no harm.
|
|
*/
|
|
|
|
idThread = plts->idThread; /* Must save before we dec */
|
|
if( InterlockedDecrement(&plts->cRef) == 0 ) {
|
|
g_plts = 0;
|
|
}
|
|
}
|
|
|
|
DllLeaveCrit();
|
|
|
|
if( plts )
|
|
{
|
|
NudgeWorkerThread(idThread);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Unlink the node from the master list.
|
|
*/
|
|
DllEnterCrit();
|
|
for (ppem = &g_pemFirst; *ppem; ppem = &(*ppem)->pemNext) {
|
|
AssertF((*ppem)->dwSignature == CEM_SIGNATURE);
|
|
if (*ppem == this) {
|
|
*ppem = (*ppem)->pemNext;
|
|
break;
|
|
}
|
|
}
|
|
AssertF(ppem);
|
|
DllLeaveCrit();
|
|
|
|
FreePpv(&this->rgdwDf);
|
|
FreePpv(&this->vi.pBuffer);
|
|
|
|
if( InterlockedDecrement(&this->ped->cRef) == 0x0 )
|
|
{
|
|
FreePpv(&this->ped->pDevType);
|
|
}
|
|
|
|
D(this->dwSignature++);
|
|
|
|
FreePv(this);
|
|
|
|
ExitProc();
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_CreateInstance |
|
|
*
|
|
* Create a device thing.
|
|
*
|
|
* @parm PVXDDEVICEFORMAT | pdevf |
|
|
*
|
|
* What the object should look like.
|
|
*
|
|
* @parm PVXDINSTANCE * | ppviOut |
|
|
*
|
|
* The answer goes here.
|
|
*
|
|
* @parm PED | ped |
|
|
*
|
|
* Descriptor.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT EXTERNAL
|
|
CEm_CreateInstance(PVXDDEVICEFORMAT pdevf, PVXDINSTANCE *ppviOut, PED ped)
|
|
{
|
|
HRESULT hres;
|
|
EnterProc(CEm_CreateInstance, (_ "pp", pdevf, ped));
|
|
|
|
AssertF(pdevf->cbData == ped->cbData);
|
|
|
|
CAssertF(FIELD_OFFSET(CEm, vi) == 0);
|
|
|
|
hres = AllocCbPpv(cbX(CEm), ppviOut);
|
|
if (SUCCEEDED(hres)) {
|
|
PEM pem = (PV)*ppviOut;
|
|
|
|
D(pem->dwSignature = CEM_SIGNATURE);
|
|
pem->dwExtra = pdevf->dwExtra;
|
|
pem->ped = ped;
|
|
pem->cAcquire = -1;
|
|
/*
|
|
* Make sure these functions are inverses.
|
|
*/
|
|
AssertF(DIGETEMFL(DIMAKEEMFL(pdevf->dwEmulation)) ==
|
|
pdevf->dwEmulation);
|
|
|
|
pem->vi.fl = VIFL_EMULATED | DIMAKEEMFL(pdevf->dwEmulation);
|
|
pem->vi.pState = ped->pState;
|
|
CEm_AddRef(pem);
|
|
|
|
DllEnterCrit();
|
|
/*
|
|
* Build the devtype array. This consists of one dword
|
|
* for each byte in the data format.
|
|
*
|
|
* Someday: Do the button thing too.
|
|
*/
|
|
if (ped->pDevType == 0) {
|
|
hres = ReallocCbPpv(cbCdw(pdevf->cbData), &ped->pDevType);
|
|
if (SUCCEEDED(hres)) {
|
|
UINT iobj;
|
|
|
|
/*
|
|
* If HID is messed up, we will end up with
|
|
* entries whose dwType is zero (because HID
|
|
* said they existed, but when we went around
|
|
* enumerating, they never showed up).
|
|
*
|
|
* And don't put no-data items into the array!
|
|
*/
|
|
for (iobj = 0; iobj < pdevf->cObj; iobj++) {
|
|
if (pdevf->rgodf[iobj].dwType &&
|
|
!(pdevf->rgodf[iobj].dwType & DIDFT_NODATA)) {
|
|
ped->pDevType[pdevf->rgodf[iobj].dwOfs] =
|
|
pdevf->rgodf[iobj].dwType;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
hres = S_OK;
|
|
}
|
|
|
|
if (SUCCEEDED(hres)) {
|
|
/*
|
|
* Link this node into the list. This must be done
|
|
* under the critical section.
|
|
*/
|
|
pem->pemNext = g_pemFirst;
|
|
g_pemFirst = pem;
|
|
|
|
InterlockedIncrement(&ped->cRef);
|
|
|
|
*ppviOut = &pem->vi;
|
|
} else {
|
|
FreePpv(ppviOut);
|
|
}
|
|
DllLeaveCrit();
|
|
}
|
|
|
|
ExitOleProcPpv(ppviOut);
|
|
return hres;
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func DWORD | CEm_NextSequence |
|
|
*
|
|
* Increment the sequence number wherever it may be.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DWORD INTERNAL
|
|
CEm_NextSequence(void)
|
|
{
|
|
/*
|
|
* Stashing the value into a local tells the compiler that
|
|
* the value can be cached. Otherwise, the compiler has
|
|
* to assume that InterlockedIncrement can modify g_pdwSequence
|
|
* so it keeps reloading it.
|
|
*/
|
|
LPDWORD pdwSequence = g_pdwSequence;
|
|
|
|
AssertF(pdwSequence);
|
|
|
|
/*
|
|
* Increment through zero.
|
|
*/
|
|
if (InterlockedIncrement((LPLONG)pdwSequence) == 0) {
|
|
InterlockedIncrement((LPLONG)pdwSequence);
|
|
}
|
|
|
|
return *pdwSequence;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func PEM | CEm_BufferEvent |
|
|
*
|
|
* Add a single event to the device, returning the next device
|
|
* on the global list.
|
|
*
|
|
* This routine is entered with the global critical section
|
|
* taken exactly once.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
PEM INTERNAL
|
|
CEm_BufferEvent(PEM pem, DWORD dwData, DWORD dwOfs, DWORD tm, DWORD dwSeq)
|
|
{
|
|
PEM pemNext;
|
|
|
|
/*
|
|
* We must release the global critical section in order to take
|
|
* the device critical section.
|
|
*/
|
|
CEm_AddRef(pem); /* Make sure it doesn't vanish */
|
|
|
|
DllLeaveCrit();
|
|
AssertF(!InCrit());
|
|
|
|
/*
|
|
* ---Windows Bug 238305---
|
|
* Run the buffering code in __try block so that if an
|
|
* input is receive after the device is released, we can
|
|
* catch the AV and clean up from there.
|
|
*/
|
|
__try
|
|
{
|
|
CDIDev_EnterCrit(pem->vi.pdd);
|
|
|
|
AssertF(dwOfs < pem->ped->cbData);
|
|
AssertF(pem->rgdwDf);
|
|
|
|
/*
|
|
* If the user cares about the object...
|
|
*/
|
|
if (pem->rgdwDf[dwOfs] != 0xFFFFFFFF) {
|
|
LPDIDEVICEOBJECTDATA_DX3 pdod = pem->vi.pHead;
|
|
|
|
/*
|
|
* Set the node value.
|
|
*/
|
|
|
|
pdod->dwOfs = pem->rgdwDf[dwOfs];
|
|
pdod->dwData = dwData;
|
|
pdod->dwTimeStamp = tm;
|
|
pdod->dwSequence = dwSeq;
|
|
|
|
/*
|
|
* Append the node to the list if there is room.
|
|
* Note that we rely above on the fact that the list is
|
|
* never totally full.
|
|
*/
|
|
pdod++;
|
|
|
|
AssertF(pdod <= pem->vi.pEnd);
|
|
|
|
if (pdod >= pem->vi.pEnd) {
|
|
pdod = pem->vi.pBuffer;
|
|
}
|
|
|
|
/*
|
|
* always keep the new data
|
|
*/
|
|
pem->vi.pHead = pdod;
|
|
|
|
if (pdod == pem->vi.pTail) {
|
|
if (!pem->vi.fOverflow) {
|
|
RPF("Buffer overflow; discard old data");
|
|
}
|
|
|
|
pem->vi.pTail++;
|
|
if (pem->vi.pTail == pem->vi.pEnd) {
|
|
pem->vi.pTail = pem->vi.pBuffer;
|
|
}
|
|
|
|
pem->vi.fOverflow = 1;
|
|
}
|
|
|
|
}
|
|
|
|
CDIDev_LeaveCrit(pem->vi.pdd);
|
|
}
|
|
/*
|
|
* If we get an AV, most likely input is received after the device has
|
|
* been released. In this case, we clean up the thread and exit as
|
|
* soon as possible.
|
|
*/
|
|
__except( GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
|
|
EXCEPTION_EXECUTE_HANDLER :
|
|
EXCEPTION_CONTINUE_SEARCH )
|
|
{
|
|
/* Do nothing here, so we clean up the thread and exit below. */
|
|
RPF("CEm_BufferEvent: Access Violation catched! Most likely the device has been released");
|
|
}
|
|
|
|
DllEnterCrit();
|
|
pemNext = pem->pemNext;
|
|
AssertF(fLimpFF(pemNext, pemNext->dwSignature == CEM_SIGNATURE));
|
|
CEm_Release(pem);
|
|
return pemNext;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc EXTERNAL
|
|
*
|
|
* @func HRESULT | CEm_ContinueEvent |
|
|
*
|
|
* Add a single event to the queues of all acquired devices
|
|
* of the indicated type.
|
|
*
|
|
* @returns
|
|
*
|
|
* TRUE if someone is interested in this data (even if they are not
|
|
* buffered).
|
|
*
|
|
*****************************************************************************/
|
|
|
|
BOOL EXTERNAL
|
|
CEm_ContinueEvent(PED ped, DWORD dwData, DWORD dwOfs, DWORD tm, DWORD dwSeq)
|
|
{
|
|
DWORD ddwData; /* delta in dwData */
|
|
BOOL fRtn = FALSE;
|
|
|
|
AssertF(!InCrit());
|
|
|
|
/* Sanity check: Make sure the ped has been initialized */
|
|
if (ped->pDevType) {
|
|
PEM pem, pemNext;
|
|
|
|
if (ped->pDevType[dwOfs] & DIDFT_DWORDOBJS) {
|
|
DWORD UNALIGNED *pdw = pvAddPvCb(ped->pState, dwOfs);
|
|
if (*pdw != dwData) {
|
|
if (ped->pDevType[dwOfs] & DIDFT_POV ) {
|
|
ddwData = dwData; /* Don't do deltas for POV */
|
|
} else {
|
|
ddwData = dwData - *pdw;
|
|
}
|
|
*pdw = dwData;
|
|
} else {
|
|
goto nop;
|
|
}
|
|
} else {
|
|
LPBYTE pb = pvAddPvCb(ped->pState, dwOfs);
|
|
|
|
AssertF((dwData & ~0x80) == 0);
|
|
|
|
if (*pb != (BYTE)dwData) {
|
|
*pb = (BYTE)dwData;
|
|
ddwData = dwData; /* Don't do deltas for buttons */
|
|
/* Someday: Button sequences go here */
|
|
} else {
|
|
goto nop;
|
|
}
|
|
}
|
|
|
|
AssertF(!InCrit()); /* You can never be too paranoid */
|
|
|
|
DllEnterCrit();
|
|
for (pem = g_pemFirst; pem; pem = pemNext) {
|
|
AssertF(pem->dwSignature == CEM_SIGNATURE);
|
|
if ((pem->vi.fl & (VIFL_ACQUIRED|VIFL_INITIALIZE)) && pem->ped == ped) {
|
|
|
|
if (pem->vi.pBuffer) {
|
|
if( pem->vi.fl & VIFL_RELATIVE )
|
|
{
|
|
pemNext = CEm_BufferEvent(pem, ddwData, dwOfs, tm, dwSeq);
|
|
}
|
|
else
|
|
{
|
|
pemNext = CEm_BufferEvent(pem, dwData, dwOfs, tm, dwSeq);
|
|
}
|
|
AssertF(fLimpFF(pemNext,
|
|
pemNext->dwSignature == CEM_SIGNATURE));
|
|
} else {
|
|
pemNext = pem->pemNext;
|
|
AssertF(fLimpFF(pemNext,
|
|
pemNext->dwSignature == CEM_SIGNATURE));
|
|
}
|
|
/*
|
|
* It would be easy to avoid setting the event if nothing
|
|
* was buffered but somebody would rely on getting them
|
|
* without setting a buffer.
|
|
*/
|
|
fRtn = TRUE;
|
|
} else {
|
|
pemNext = pem->pemNext;
|
|
AssertF(fLimpFF(pemNext,
|
|
pemNext->dwSignature == CEM_SIGNATURE));
|
|
}
|
|
}
|
|
DllLeaveCrit();
|
|
}
|
|
|
|
nop:;
|
|
return fRtn;
|
|
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func DWORD | CEm_AddEvent |
|
|
*
|
|
* Increment the DirectInput sequence number, then
|
|
* add a single event to the queues of all acquired devices
|
|
* of the indicated type.
|
|
*
|
|
* @parm PED | ped |
|
|
*
|
|
* Device which is adding the event.
|
|
*
|
|
* @parm DWORD | dwData |
|
|
*
|
|
* The event data.
|
|
*
|
|
* @parm DWORD | dwOfs |
|
|
*
|
|
* Device data format-relative offset for <p dwData>.
|
|
*
|
|
* @parm DWORD | tm |
|
|
*
|
|
* Time the event was generated.
|
|
*
|
|
* @returns
|
|
*
|
|
* Returns the sequence number added, so that it may be
|
|
* continued.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DWORD EXTERNAL
|
|
CEm_AddEvent(PED ped, DWORD dwData, DWORD dwOfs, DWORD tm)
|
|
{
|
|
PEM pem, pemNext;
|
|
|
|
DWORD dwSeq = CEm_NextSequence();
|
|
|
|
AssertF(!InCrit()); /* You can never be too paranoid */
|
|
|
|
if( CEm_ContinueEvent(ped, dwData, dwOfs, tm, dwSeq) )
|
|
{
|
|
DllEnterCrit();
|
|
for (pem = g_pemFirst; pem; pem = pemNext) {
|
|
AssertF(pem->dwSignature == CEM_SIGNATURE);
|
|
if ((pem->vi.fl & VIFL_ACQUIRED) && pem->ped == ped) {
|
|
CDIDev_SetNotifyEvent(pem->vi.pdd);
|
|
}
|
|
pemNext = pem->pemNext;
|
|
AssertF(fLimpFF(pemNext,
|
|
pemNext->dwSignature == CEM_SIGNATURE));
|
|
}
|
|
DllLeaveCrit();
|
|
}
|
|
|
|
return dwSeq;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_AddState |
|
|
*
|
|
* Record a brand new device state.
|
|
*
|
|
* @parm PED | ped |
|
|
*
|
|
* Device which has changed state.
|
|
*
|
|
* @parm DWORD | dwData |
|
|
*
|
|
* The value to record.
|
|
*
|
|
* @parm DWORD | tm |
|
|
*
|
|
* Time the state change was generated.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void EXTERNAL
|
|
CEm_AddState(PED ped, LPVOID pvData, DWORD tm)
|
|
{
|
|
DWORD dwSeq = CEm_NextSequence();
|
|
|
|
/* Sanity check: Make sure the ped has been initialized */
|
|
if (ped->pDevType) {
|
|
DWORD dwOfs;
|
|
BOOL fEvent = FALSE;
|
|
|
|
/*
|
|
* Note, it is too late to improve performance by only doing events
|
|
* if somebody is listening.
|
|
*/
|
|
dwOfs = 0;
|
|
while (dwOfs < ped->cbData) {
|
|
/*
|
|
* There shouldn't be any no-data items.
|
|
*/
|
|
AssertF(!(ped->pDevType[dwOfs] & DIDFT_NODATA));
|
|
|
|
if (ped->pDevType[dwOfs] & DIDFT_DWORDOBJS) {
|
|
DWORD UNALIGNED *pdw = pvAddPvCb(pvData, dwOfs);
|
|
if( CEm_ContinueEvent(ped, *pdw, dwOfs, tm, dwSeq) ){
|
|
fEvent = TRUE;
|
|
}
|
|
dwOfs += cbX(DWORD);
|
|
} else {
|
|
LPBYTE pb = pvAddPvCb(pvData, dwOfs);
|
|
if( CEm_ContinueEvent(ped, *pb, dwOfs, tm, dwSeq) ) {
|
|
fEvent = TRUE;
|
|
}
|
|
dwOfs++;
|
|
}
|
|
}
|
|
|
|
if( fEvent ) {
|
|
PEM pem, pemNext;
|
|
|
|
AssertF(!InCrit()); /* You can never be too paranoid */
|
|
|
|
DllEnterCrit();
|
|
for (pem = g_pemFirst; pem; pem = pemNext) {
|
|
AssertF(pem->dwSignature == CEM_SIGNATURE);
|
|
if ((pem->vi.fl & VIFL_ACQUIRED) && pem->ped == ped) {
|
|
CDIDev_SetNotifyEvent(pem->vi.pdd);
|
|
}
|
|
pemNext = pem->pemNext;
|
|
AssertF(fLimpFF(pemNext,
|
|
pemNext->dwSignature == CEM_SIGNATURE));
|
|
}
|
|
DllLeaveCrit();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_InputLost |
|
|
*
|
|
* Remove global hooks because something weird happened.
|
|
*
|
|
* We don't need to do anything because our hooks are local.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INLINE
|
|
CEm_InputLost(LPVOID pvIn, LPVOID pvOut)
|
|
{
|
|
return S_OK;
|
|
}
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_UnacquirePem |
|
|
*
|
|
* Unacquire the device in the device-specific way.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Information about the gizmo being mangled.
|
|
*
|
|
* @parm UINT | fdufl |
|
|
*
|
|
* Assorted flags describing why we are being unacquired.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INTERNAL
|
|
CEm_UnacquirePem(PEM this, UINT fdufl)
|
|
{
|
|
HRESULT hres;
|
|
#ifdef DEBUG
|
|
EnterProcR(CEm_UnacquirePem, (_ "px", this, fdufl));
|
|
#else
|
|
EnterProcR(IDirectInputDevice8::Unacquire, (_ "p", this));
|
|
#endif
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
|
|
AssertF((fdufl & ~FDUFL_UNPLUGGED) == 0);
|
|
CAssertF(FDUFL_UNPLUGGED == VIFL_UNPLUGGED);
|
|
|
|
if (this->vi.fl & VIFL_ACQUIRED) {
|
|
this->vi.fl &= ~VIFL_ACQUIRED;
|
|
this->vi.fl |= fdufl;
|
|
if (InterlockedDecrement(&this->cAcquire) < 0) {
|
|
InterlockedDecrement(&this->ped->cAcquire);
|
|
hres = this->ped->Acquire(this, 0);
|
|
} else {
|
|
SquirtSqflPtszV(sqfl, TEXT("%S: Still acquired %d"),
|
|
s_szProc, this->cAcquire);
|
|
hres = S_OK;
|
|
}
|
|
} else {
|
|
SquirtSqflPtszV(sqfl, TEXT("%S: Not acquired %d"),
|
|
s_szProc, this->cAcquire);
|
|
hres = S_OK;
|
|
}
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func void | CEm_ForceDeviceUnacquire |
|
|
*
|
|
* Force all users of a device to unacquire.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Information about the gizmo being mangled.
|
|
*
|
|
* @parm UINT | fdufl |
|
|
*
|
|
* Assorted flags describing why we are being unacquired.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void EXTERNAL
|
|
CEm_ForceDeviceUnacquire(PED ped, UINT fdufl)
|
|
{
|
|
PEM pem, pemNext;
|
|
|
|
AssertF((fdufl & ~FDUFL_UNPLUGGED) == 0);
|
|
|
|
AssertF(!DllInCrit());
|
|
|
|
DllEnterCrit();
|
|
for (pem = g_pemFirst; pem; pem = pemNext) {
|
|
AssertF(pem->dwSignature == CEM_SIGNATURE);
|
|
if (pem->ped == ped && (pem->vi.fl & VIFL_ACQUIRED)) {
|
|
CEm_AddRef(pem);
|
|
DllLeaveCrit();
|
|
CEm_UnacquirePem(pem, fdufl);
|
|
|
|
CDIDev_SetForcedUnacquiredFlag(pem->vi.pdd);
|
|
/*
|
|
* Since this happens only when the device is acquired,
|
|
* we don't need to worry about the notify event changing
|
|
* asynchronously.
|
|
*/
|
|
CDIDev_SetNotifyEvent(pem->vi.pdd);
|
|
DllEnterCrit();
|
|
pemNext = pem->pemNext;
|
|
AssertF(pem->dwSignature == CEM_SIGNATURE);
|
|
CEm_Release(pem);
|
|
} else {
|
|
pemNext = pem->pemNext;
|
|
AssertF(pem->dwSignature == CEM_SIGNATURE);
|
|
}
|
|
}
|
|
DllLeaveCrit();
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_DestroyInstance |
|
|
*
|
|
* Clean up an instance.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT EXTERNAL
|
|
CEm_DestroyInstance(PVXDINSTANCE *ppvi)
|
|
{
|
|
HRESULT hres;
|
|
PEM this = _thisPvNm(*ppvi, vi);
|
|
EnterProc(CEm_DestroyInstance, (_ "p", *ppvi));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
AssertF((PV)this == (PV)*ppvi);
|
|
|
|
if (this) {
|
|
CEm_Release(this);
|
|
}
|
|
hres = S_OK;
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_SetDataFormat |
|
|
*
|
|
* Record the application data format in the device so that
|
|
* we can translate it for buffering purposes.
|
|
*
|
|
* @parm PVXDDATAFORMAT | pvdf |
|
|
*
|
|
* Information about the gizmo being mangled.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INTERNAL
|
|
CEm_SetDataFormat(PVXDDATAFORMAT pvdf)
|
|
{
|
|
HRESULT hres;
|
|
PEM this = _thisPvNm(pvdf->pvi, vi);
|
|
EnterProc(CEm_SetDataFormat, (_ "p", pvdf->pvi));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
hres = ReallocCbPpv( cbCdw(pvdf->cbData), &this->rgdwDf);
|
|
if (SUCCEEDED(hres)) {
|
|
AssertF(pvdf->cbData == this->ped->cbData);
|
|
memcpy(this->rgdwDf, pvdf->pDfOfs, cbCdw(pvdf->cbData) );
|
|
}
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_AcquireInstance |
|
|
*
|
|
* Acquire the device in the device-specific way.
|
|
*
|
|
* @parm PVXDINSTANCE * | ppvi |
|
|
*
|
|
* The instance to acquire.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INTERNAL
|
|
CEm_AcquireInstance(PVXDINSTANCE *ppvi)
|
|
{
|
|
HRESULT hres;
|
|
PEM this = _thisPvNm(*ppvi, vi);
|
|
#ifdef DEBUG
|
|
EnterProc(CEm_AcquireInstance, (_ "p", *ppvi));
|
|
#else
|
|
EnterProcR(IDirectInputDevice8::Acquire, (_ "p", *ppvi));
|
|
#endif
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
this->vi.fl |= VIFL_ACQUIRED;
|
|
if (InterlockedIncrement(&this->cAcquire) == 0) {
|
|
InterlockedIncrement(&this->ped->cAcquire);
|
|
hres = this->ped->Acquire(this, 1);
|
|
if (FAILED(hres)) {
|
|
this->vi.fl &= ~VIFL_ACQUIRED;
|
|
InterlockedDecrement(&this->cAcquire);
|
|
}
|
|
} else {
|
|
SquirtSqflPtszV(sqfl, TEXT("%S: Already acquired %d"),
|
|
s_szProc, this->cAcquire);
|
|
hres = S_OK;
|
|
}
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_UnacquireInstance |
|
|
*
|
|
* Unacquire the device in the device-specific way.
|
|
*
|
|
* @parm PVXDINSTANCE * | ppvi |
|
|
*
|
|
* Information about the gizmo being mangled.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INTERNAL
|
|
CEm_UnacquireInstance(PVXDINSTANCE *ppvi)
|
|
{
|
|
HRESULT hres;
|
|
PEM this = _thisPvNm(*ppvi, vi);
|
|
EnterProc(CEm_UnacquireInstance, (_ "p", *ppvi));
|
|
|
|
hres = CEm_UnacquirePem(this, FDUFL_NORMAL);
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_SetBufferSize |
|
|
*
|
|
* Allocate a buffer of the appropriate size.
|
|
*
|
|
* @parm PVXDDWORDDATA | pvdd |
|
|
*
|
|
* The <p dwData> is the buffer size.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INTERNAL
|
|
CEm_SetBufferSize(PVXDDWORDDATA pvdd)
|
|
{
|
|
HRESULT hres;
|
|
PEM this = _thisPvNm(pvdd->pvi, vi);
|
|
EnterProc(CEm_SetBufferSize, (_ "px", pvdd->pvi, pvdd->dw));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
|
|
hres = ReallocCbPpv(cbCxX(pvdd->dw, DIDEVICEOBJECTDATA),
|
|
&this->vi.pBuffer);
|
|
if (SUCCEEDED(hres)) {
|
|
this->vi.pHead = this->vi.pBuffer;
|
|
this->vi.pTail = this->vi.pBuffer;
|
|
this->vi.pEnd = &this->vi.pBuffer[pvdd->dw];
|
|
}
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @struct LLHOOKINFO |
|
|
*
|
|
* Information about how to install a low-level hook.
|
|
*
|
|
* @field int | idHook |
|
|
*
|
|
* The Windows hook identifier.
|
|
*
|
|
* @field HOOKPROC | hp |
|
|
*
|
|
* The hook procedure itself.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef struct LLHOOKINFO {
|
|
|
|
int idHook;
|
|
HOOKPROC hp;
|
|
|
|
} LLHOOKINFO, *PLLHOOKINFO;
|
|
typedef const LLHOOKINFO *PCLLHOOKINFO;
|
|
|
|
#pragma BEGIN_CONST_DATA
|
|
|
|
const LLHOOKINFO c_rgllhi[] = {
|
|
{ WH_KEYBOARD_LL, CEm_LL_KbdHook }, /* LLTS_KBD */
|
|
{ WH_MOUSE_LL, CEm_LL_MseHook }, /* LLTS_MSE */
|
|
};
|
|
|
|
#pragma END_CONST_DATA
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func void | CEm_LL_SyncHook |
|
|
*
|
|
* Install or remove a hook as needed.
|
|
*
|
|
* @parm UINT | ilts |
|
|
*
|
|
* Which hook is being handled?
|
|
*
|
|
* @parm PLLTHREADSTATE | plts |
|
|
*
|
|
* Thread hook state containing hook information to synchronize.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
void INTERNAL
|
|
CEm_LL_SyncHook(PLLTHREADSTATE plts, UINT ilts)
|
|
{
|
|
PLLHOOKSTATE plhs = &plts->rglhs[ilts];
|
|
|
|
if (!fLeqvFF(plhs->cHook, plhs->hhk)) {
|
|
if (plhs->hhk) {
|
|
UnhookWindowsHookEx(plhs->hhk);
|
|
plhs->hhk = 0;
|
|
} else {
|
|
PCLLHOOKINFO pllhi = &c_rgllhi[ilts];
|
|
plhs->hhk = SetWindowsHookEx(pllhi->idHook, pllhi->hp, g_hinst, 0);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* USE_SLOW_LL_HOOKS */
|
|
|
|
#ifdef WORKER_THREAD
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func DWORD | FakeMsgWaitForMultipleObjectsEx |
|
|
*
|
|
* Stub function which emulates
|
|
* <f MsgWaitForMultipleObjectsEx>
|
|
* on platforms that do not support it.
|
|
*
|
|
* Such platforms (namely, Windows 95) do not support HID
|
|
* and therefore the inability to go into an alertable
|
|
* wait state constitutes no loss of amenity.
|
|
*
|
|
* @parm DWORD | nCount |
|
|
*
|
|
* Number of handles in handle array.
|
|
*
|
|
* @parm LPHANDLE | pHandles |
|
|
*
|
|
* Pointer to an object-handle array.
|
|
*
|
|
* @parm DWORD | ms |
|
|
*
|
|
* Time-out interval in milliseconds.
|
|
*
|
|
* @parm DWORD | dwWakeMask |
|
|
*
|
|
* Type of input events to wait for.
|
|
*
|
|
* @parm DWORD | dwFlags |
|
|
*
|
|
* Wait flags.
|
|
*
|
|
* @returns
|
|
*
|
|
* Same as <f MsgWaitForMultipleObjectsEx>.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
DWORD WINAPI
|
|
FakeMsgWaitForMultipleObjectsEx(
|
|
DWORD nCount,
|
|
LPHANDLE pHandles,
|
|
DWORD ms,
|
|
DWORD dwWakeMask,
|
|
DWORD dwFlags)
|
|
{
|
|
/*
|
|
* We merely call the normal MsgWaitForMultipleObjects because
|
|
* the only way we can get here is on a platform that doesn't
|
|
* support HID.
|
|
*/
|
|
return MsgWaitForMultipleObjects(nCount, pHandles,
|
|
dwFlags & MWMO_WAITALL, ms, dwWakeMask);
|
|
}
|
|
|
|
#ifdef WINNT
|
|
// On win2k non-exclusive mode user thinks the Dinput thread is hung.
|
|
// In order to fix this we set a TimerEvent and wake up every so
|
|
// often and execute the FakeTimerProc. This keeps user happy and
|
|
// keeps dinput thread from being marked as hung and we can get
|
|
// events to our low level hooks
|
|
VOID CALLBACK FakeTimerProc(
|
|
HWND hwnd, // handle to window
|
|
UINT uMsg, // WM_TIMER message
|
|
UINT_PTR idEvent, // timer identifier
|
|
DWORD dwTime // current system time
|
|
)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_WM_INPUT
|
|
|
|
#pragma BEGIN_CONST_DATA
|
|
TCHAR c_szEmClassName[] = TEXT("DIEmWin");
|
|
#pragma END_CONST_DATA
|
|
|
|
/****************************************************************************
|
|
*
|
|
* CEm_WndProc
|
|
*
|
|
* Window procedure for simple sample.
|
|
*
|
|
****************************************************************************/
|
|
|
|
LRESULT CALLBACK
|
|
CEm_WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
|
|
{
|
|
switch (msg) {
|
|
//case WM_INPUT:
|
|
// RPF("in WM_INPUT message");
|
|
// break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
HWND
|
|
CEm_InitWindow(void)
|
|
{
|
|
HWND hwnd;
|
|
WNDCLASS wc;
|
|
static BOOL fFirstTime = TRUE;
|
|
|
|
if( fFirstTime ) {
|
|
wc.hCursor = LoadCursor(0, IDC_ARROW);
|
|
wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = c_szEmClassName;
|
|
wc.hbrBackground = 0;
|
|
wc.hInstance = g_hinst;
|
|
wc.style = 0;
|
|
wc.lpfnWndProc = CEm_WndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
|
|
if (!RegisterClass(&wc)) {
|
|
return NULL;
|
|
}
|
|
|
|
fFirstTime = FALSE;
|
|
}
|
|
|
|
hwnd = CreateWindow(
|
|
c_szEmClassName, // Class name
|
|
TEXT("DIEmWin"), // Caption
|
|
WS_OVERLAPPEDWINDOW, // Style
|
|
-1, -1, // Position
|
|
1, 1, // Size
|
|
NULL, //parent
|
|
NULL, // No menu
|
|
g_hinst, // inst handle
|
|
0 // no params
|
|
);
|
|
|
|
if( !hwnd ) {
|
|
RPF("CreateWindow failed.");
|
|
}
|
|
|
|
return hwnd;
|
|
}
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func VOID | CEm_LL_ThreadProc |
|
|
*
|
|
* The thread that manages our low-level hooks.
|
|
*
|
|
* ThreadProcs are prototyped to return a DWORD but since the return
|
|
* would follow some form of ExitThread, it will never be reached so
|
|
* this function is declared to return VOID and cast.
|
|
*
|
|
* When we get started, and whenever we receive any message
|
|
* whatsoever, re-check to see which hooks should be installed
|
|
* and re-synchronize ourselves with them.
|
|
*
|
|
* Note that restarting can be slow, since it happens only
|
|
* when we get nudged by a client.
|
|
*
|
|
* @parm PLLTHREADSTATE | plts |
|
|
*
|
|
* The thread state to use.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
VOID INTERNAL
|
|
CEm_LL_ThreadProc(PLLTHREADSTATE plts)
|
|
{
|
|
MSG msg;
|
|
DWORD dwRc;
|
|
#ifdef USE_WM_INPUT
|
|
HWND hwnd = NULL;
|
|
#endif
|
|
|
|
AssertF(plts->idThread == GetCurrentThreadId());
|
|
SquirtSqflPtszV(sqflLl, TEXT("CEm_LL_ThreadProc: Thread started"));
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
/*
|
|
* Refresh the mouse acceleration values.
|
|
*
|
|
* ISSUE-2001/03/29-timgill Need a window to listen for WM_SETTINGCHANGE
|
|
* We need to create a window to listen for
|
|
* WM_SETTINGCHANGE so we can refresh the mouse acceleration
|
|
* as needed.
|
|
*/
|
|
CEm_Mouse_OnMouseChange();
|
|
#endif
|
|
|
|
/*
|
|
* Create ourselves a queue before we go into our "hey what happened
|
|
* before I got here?" phase. The thread that created us is waiting on
|
|
* the thread event, holding DLLCrit, so let it go as soon as the queue
|
|
* is ready. We create the queue by calling a function that requires a
|
|
* queue. We use this very simple one.
|
|
*/
|
|
GetInputState();
|
|
|
|
#ifdef WINNT
|
|
// Look at comment block in FakeTimerProc
|
|
SetTimer(NULL, 0, 2 * 1000 /*2 seconds*/, FakeTimerProc);
|
|
#endif
|
|
|
|
SetEvent(plts->hEvent);
|
|
|
|
#ifdef USE_WM_INPUT
|
|
ResetEvent(g_hEventThread);
|
|
|
|
if( g_fRawInput ) {
|
|
hwnd = CEm_InitWindow();
|
|
|
|
if (!hwnd) {
|
|
g_fRawInput = FALSE;
|
|
}
|
|
}
|
|
|
|
g_hwndThread = hwnd;
|
|
|
|
// Tell CEm_LL_Acquire that windows has been created.
|
|
SetEvent( g_hEventAcquire );
|
|
|
|
if( g_fFromKbdMse ) {
|
|
DWORD rc;
|
|
rc = WaitForSingleObject(g_hEventThread, INFINITE);
|
|
g_fFromKbdMse = FALSE;
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
/*
|
|
* Note carefully that we sync the hooks before entering our
|
|
* fake GetMessage loop. This is necessary to avoid the race
|
|
* condition when CEm_LL_Acquire posts us a thread message
|
|
* before our thread gets a queue. By sync'ing the hooks
|
|
* first, we do what the lost message would've had us do
|
|
* anyway.
|
|
* ISSUE-2001/03/29-timgill Following branch should be no longer necessary
|
|
* This is should not be needed now that CEm_GetWorkerThread waits for
|
|
* this thread to respond before continuing on to post any messages.
|
|
*/
|
|
#endif /* USE_SLOW_LL_HOOKS */
|
|
|
|
do {
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
if( !g_fRawInput ) {
|
|
CEm_LL_SyncHook(plts, LLTS_KBD);
|
|
CEm_LL_SyncHook(plts, LLTS_MSE);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* We can wake up for three reasons.
|
|
*
|
|
* 1. We received an APC due to an I/o completion.
|
|
* Just go back to sleep.
|
|
*
|
|
* 2. We need to call Peek/GetMessage so that
|
|
* USER can dispatch a low-level hook or SendMessage.
|
|
* Go into a PeekMessage loop to let that happen.
|
|
*
|
|
* 3. A message got posted to us.
|
|
* Go into a PeekMessage loop to process it.
|
|
*/
|
|
|
|
do {
|
|
dwRc = _MsgWaitForMultipleObjectsEx(0, 0, INFINITE, QS_ALLINPUT,
|
|
MWMO_ALERTABLE);
|
|
} while (dwRc == WAIT_IO_COMPLETION);
|
|
|
|
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
|
|
if (msg.hwnd == 0 && msg.message == WM_NULL && msg.lParam)
|
|
{
|
|
/*
|
|
* See if maybe the lParam is a valid PEM that we're
|
|
* processing.
|
|
*/
|
|
PEM pem = (PEM)msg.lParam;
|
|
|
|
if( pem && pem == plts->pemCheck )
|
|
{
|
|
AssertF(GPA_FindPtr(&plts->gpaHid, pem));
|
|
|
|
CEm_HID_Sync(plts, pem);
|
|
plts->pemCheck = NULL;
|
|
|
|
SetEvent(plts->hEvent);
|
|
|
|
#ifdef USE_WM_INPUT
|
|
if( g_fRawInput ) {
|
|
SetEvent(g_hEventHid);
|
|
}
|
|
#endif
|
|
|
|
continue;
|
|
}
|
|
}
|
|
#ifdef USE_WM_INPUT
|
|
else if ( g_fRawInput && msg.message == WM_INPUT &&
|
|
(msg.wParam == RIM_INPUT || msg.wParam == RIM_INPUTSINK) )
|
|
{
|
|
CDIRaw_OnInput(&msg);
|
|
}
|
|
#endif
|
|
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
} while (plts->cRef);
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
/*
|
|
* Remove our hooks before we go.
|
|
*
|
|
* It is possible that there was a huge flurry of disconnects,
|
|
* causing us to notice that our refcount disappeared before
|
|
* we got a chance to remove the hooks in our message loop.
|
|
*/
|
|
|
|
AssertF(plts->rglhs[LLTS_KBD].cHook == 0);
|
|
AssertF(plts->rglhs[LLTS_KBD].cExcl == 0);
|
|
AssertF(plts->rglhs[LLTS_MSE].cHook == 0);
|
|
AssertF(plts->rglhs[LLTS_MSE].cExcl == 0);
|
|
|
|
if( !g_fRawInput ) {
|
|
if (plts->rglhs[LLTS_KBD].hhk) {
|
|
UnhookWindowsHookEx(plts->rglhs[LLTS_KBD].hhk);
|
|
}
|
|
|
|
if (plts->rglhs[LLTS_MSE].hhk) {
|
|
UnhookWindowsHookEx(plts->rglhs[LLTS_MSE].hhk);
|
|
}
|
|
}
|
|
#endif /* USE_SLOW_LL_HOOKS */
|
|
|
|
#ifdef USE_WM_INPUT
|
|
if( g_hwndThread ) {
|
|
DestroyWindow( g_hwndThread );
|
|
g_hwndThread = NULL;
|
|
}
|
|
|
|
ResetEvent( g_hEventAcquire );
|
|
ResetEvent( g_hEventHid );
|
|
#endif
|
|
|
|
if( plts->gpaHid.rgpv ) {
|
|
FreePpv(&plts->gpaHid.rgpv);
|
|
}
|
|
|
|
if( plts->hEvent ) {
|
|
CloseHandle( plts->hEvent );
|
|
}
|
|
|
|
if( plts->hThread) {
|
|
CloseHandle(plts->hThread);
|
|
}
|
|
|
|
FreePpv( &plts );
|
|
|
|
SquirtSqflPtszV(sqflLl, TEXT("CEm_LL_ThreadProc: Thread terminating"));
|
|
|
|
FreeLibraryAndExitThread(g_hinst, 0);
|
|
/*NOTREACHED*/
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_GetWorkerThread |
|
|
*
|
|
* Piggyback off the existing worker thread if possible;
|
|
* else create a new one.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Emulation state which requires a worker thread.
|
|
*
|
|
* @parm PLLTHREADSTATE * | pplts |
|
|
*
|
|
* Receives thread state for worker thread.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
CEm_GetWorkerThread(PEM pem, PLLTHREADSTATE *pplts)
|
|
{
|
|
PLLTHREADSTATE plts;
|
|
HRESULT hres;
|
|
|
|
DllEnterCrit();
|
|
|
|
/*
|
|
* Normally, we can piggyback off the one we already have.
|
|
*/
|
|
plts = g_plts;
|
|
|
|
/*
|
|
* If we already have a ref to a worker thread, then use it.
|
|
*/
|
|
if (pem->fWorkerThread) {
|
|
|
|
/*
|
|
* The reference we created when we created the worker thread
|
|
* ensures that g_plts is valid.
|
|
*/
|
|
AssertF(plts);
|
|
AssertF(plts->cRef);
|
|
if (plts) {
|
|
hres = S_OK;
|
|
} else {
|
|
AssertF(0); /* Can't happen */
|
|
hres = E_FAIL;
|
|
}
|
|
} else
|
|
|
|
if (plts) {
|
|
/*
|
|
* Create a reference to the existing thread.
|
|
*/
|
|
pem->fWorkerThread = TRUE;
|
|
InterlockedIncrement(&plts->cRef);
|
|
hres = S_OK;
|
|
} else {
|
|
|
|
/*
|
|
* There is no worker thread (or it is irretrievably
|
|
* on its way out) so create a new one.
|
|
*/
|
|
hres = AllocCbPpv(cbX(LLTHREADSTATE), &plts);
|
|
if (SUCCEEDED(hres)) {
|
|
DWORD dwRc = 0;
|
|
TCHAR tsz[MAX_PATH];
|
|
|
|
/*
|
|
* Assume the worst unless we find otherwise
|
|
*/
|
|
hres = E_FAIL;
|
|
|
|
if( GetModuleFileName(g_hinst, tsz, cA(tsz))
|
|
&& ( LoadLibrary(tsz) == g_hinst ) )
|
|
{
|
|
|
|
/*
|
|
* Must set up everything to avoid racing with
|
|
* the incoming thread.
|
|
*/
|
|
g_plts = plts;
|
|
InterlockedIncrement(&plts->cRef);
|
|
plts->hEvent = CreateEvent(0x0, 0, 0, 0x0);
|
|
if( plts->hEvent )
|
|
{
|
|
plts->hThread= CreateThread(0, 0, (LPTHREAD_START_ROUTINE)CEm_LL_ThreadProc, plts,
|
|
0, &plts->idThread);
|
|
if( plts->hThread )
|
|
{
|
|
/*
|
|
* Boost our priority to make sure we
|
|
* can handle the messages.
|
|
*
|
|
* RaymondC commented this out saying that it does not
|
|
* help but we're hoping that it may on Win2k.
|
|
*/
|
|
SetThreadPriority(plts->hThread, THREAD_PRIORITY_HIGHEST);
|
|
|
|
/*
|
|
* Wait for the thread to signal that it is up and running
|
|
* or for it to terminate.
|
|
* This means that we don't have to consider the
|
|
* possibility that the thread is not yet running in
|
|
* NotifyWorkerThreadPem so we know a failure there is
|
|
* terminal and don't retry.
|
|
*
|
|
* Assert that the handle fields make a two handle array.
|
|
*/
|
|
CAssertF( FIELD_OFFSET( LLTHREADSTATE, hThread) + sizeof(plts->hThread)
|
|
== FIELD_OFFSET( LLTHREADSTATE, hEvent) );
|
|
|
|
/*
|
|
* According to a comment in CEm_LL_ThreadProc Win95 may
|
|
* fail with an invalid parameter error, so if it does,
|
|
* keep trying. (Assume no valid case will occur.)
|
|
*
|
|
* ISSUE-2001/03/29-timgill Need to minimise waits while holding sync. objects
|
|
* Waiting whilst holding DLLCrit is bad.
|
|
*/
|
|
do
|
|
{
|
|
dwRc = WaitForMultipleObjects( 2, &plts->hThread, FALSE, INFINITE);
|
|
} while ( ( dwRc == WAIT_FAILED ) && ( GetLastError() == ERROR_INVALID_PARAMETER ) );
|
|
|
|
if( dwRc == WAIT_OBJECT_0 ) {
|
|
SquirtSqflPtszV(sqfl | sqflError,
|
|
TEXT("CEm_GetWorkerThread: Created Thread terminated on first wait") );
|
|
} else {
|
|
pem->fWorkerThread = TRUE;
|
|
hres = S_OK;
|
|
if( dwRc != WAIT_OBJECT_0 + 1 )
|
|
{
|
|
/*
|
|
* This would be a bad thing if it ever happened
|
|
* but we have to assume that the thread is still
|
|
* running so we return a success anyway.
|
|
*/
|
|
SquirtSqflPtszV(sqfl | sqflError,
|
|
TEXT("CEm_GetWorkerThread: First wait returned 0x%08x with LastError %d"),
|
|
dwRc, GetLastError() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SquirtSqflPtszV(sqfl | sqflError,
|
|
TEXT("CEm_GetWorkerThread: CreateThread failed with error %d"),
|
|
GetLastError() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SquirtSqflPtszV(sqfl | sqflError,
|
|
TEXT("CEm_GetWorkerThread: CreateEvent failed with error %d"),
|
|
GetLastError() );
|
|
}
|
|
|
|
|
|
if( FAILED( hres ) )
|
|
{
|
|
if( plts->hEvent ) {
|
|
CloseHandle( plts->hEvent );
|
|
}
|
|
FreeLibrary(g_hinst);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
RPF( "CEm_GetWorkerThread: failed to LoadLibrary( self ), le = %d", GetLastError() );
|
|
}
|
|
|
|
if( FAILED( hres ) )
|
|
{
|
|
FreePv(plts);
|
|
g_plts = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
DllLeaveCrit();
|
|
|
|
*pplts = plts;
|
|
return hres;
|
|
}
|
|
|
|
#endif /* WORKER_THREAD */
|
|
|
|
#ifdef USE_SLOW_LL_HOOKS
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_LL_Acquire |
|
|
*
|
|
* Acquire/unacquire a mouse or keyboard via low-level hooks.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Device being acquired.
|
|
*
|
|
* @parm BOOL | fAcquire |
|
|
*
|
|
* Whether the device is being acquired or unacquired.
|
|
*
|
|
* @parm ULONG | fl |
|
|
*
|
|
* Flags in VXDINSTANCE (vi.fl).
|
|
*
|
|
* @parm UINT | ilts |
|
|
*
|
|
* LLTS_KBD or LLTS_MSE, depending on which is happening.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
CEm_LL_Acquire(PEM this, BOOL fAcquire, ULONG fl, UINT ilts)
|
|
{
|
|
PLLTHREADSTATE plts;
|
|
BOOL fExclusive = fl & VIFL_CAPTURED;
|
|
BOOL fNoWinkey = fl & VIFL_NOWINKEY;
|
|
HRESULT hres = S_OK;
|
|
|
|
EnterProc(CEm_LL_Acquire, (_ "puuu", this, fAcquire, fExclusive, ilts));
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
AssertF(ilts==LLTS_KBD || ilts==LLTS_MSE);
|
|
|
|
#ifdef USE_WM_INPUT
|
|
g_fFromKbdMse = fAcquire ? TRUE : FALSE;
|
|
ResetEvent( g_hEventAcquire );
|
|
#endif
|
|
|
|
hres = CEm_GetWorkerThread(this, &plts);
|
|
|
|
if (SUCCEEDED(hres)) {
|
|
AssertF( plts->rglhs[ilts].cHook >= plts->rglhs[ilts].cExcl );
|
|
|
|
#ifdef USE_WM_INPUT
|
|
if( g_fRawInput && !g_hwndThread) {
|
|
DWORD dwRc;
|
|
dwRc = WaitForSingleObject(g_hEventAcquire, INFINITE);
|
|
}
|
|
#endif
|
|
|
|
if (fAcquire) {
|
|
InterlockedIncrement(&plts->rglhs[ilts].cHook);
|
|
|
|
if (fExclusive) {
|
|
InterlockedIncrement(&plts->rglhs[ilts].cExcl);
|
|
}
|
|
|
|
#ifdef USE_WM_INPUT
|
|
if( g_hwndThread ) {
|
|
if( fExclusive ) {
|
|
hres = CDIRaw_RegisterRawInputDevice(1-ilts, DIRAW_EXCL, g_hwndThread);
|
|
}
|
|
else if( fNoWinkey ) {
|
|
AssertF( ilts == 0 );
|
|
if( ilts == 0 ) {
|
|
hres = CDIRaw_RegisterRawInputDevice(1-ilts, DIRAW_NOHOTKEYS, g_hwndThread);
|
|
} else {
|
|
hres = E_FAIL;
|
|
}
|
|
}
|
|
else {
|
|
hres = CDIRaw_RegisterRawInputDevice(1-ilts, DIRAW_NONEXCL, g_hwndThread);
|
|
}
|
|
|
|
if(FAILED(hres)) {
|
|
hres = S_FALSE;
|
|
g_fRawInput = FALSE;
|
|
RPF("CEm_LL_Acquire: RegisterRawInput failed. LL will be used.");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
} else { /* Remove the hook */
|
|
AssertF(plts->cRef);
|
|
|
|
if (fExclusive) {
|
|
InterlockedDecrement(&plts->rglhs[ilts].cExcl);
|
|
}
|
|
|
|
InterlockedDecrement(&plts->rglhs[ilts].cHook);
|
|
|
|
#ifdef USE_WM_INPUT
|
|
if( g_fRawInput ) {
|
|
CDIRaw_UnregisterRawInputDevice(1-ilts, g_hwndThread);
|
|
|
|
if( plts->rglhs[ilts].cHook ) {
|
|
CDIRaw_RegisterRawInputDevice(1-ilts, 0, g_hwndThread);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
NudgeWorkerThread(plts->idThread);
|
|
|
|
#ifdef USE_WM_INPUT
|
|
// tell CEm_LL_ThreadProc that acquire finished.
|
|
SetEvent( g_hEventThread );
|
|
#endif
|
|
|
|
}
|
|
|
|
ExitOleProc();
|
|
return hres;
|
|
}
|
|
|
|
#endif /* USE_SLOW_LL_HOOKS */
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Joystick emulation
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Joy_Acquire |
|
|
*
|
|
* Acquire a joystick. Nothing happens.
|
|
*
|
|
* @parm PEM | pem |
|
|
*
|
|
* Device being acquired.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
STDMETHODIMP
|
|
CEm_Joy_Acquire(PEM this, BOOL fAcquire)
|
|
{
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
return S_OK;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Joystick globals
|
|
*
|
|
* Since we don't use joystick emulation by default, we allocate
|
|
* the emulation variables dynamically so we don't blow a page
|
|
* of memory on them.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
typedef struct JOYEMVARS {
|
|
ED rged[cJoyMax];
|
|
DIJOYSTATE2 rgjs2[cJoyMax];
|
|
} JOYEMVARS, *PJOYEMVARS;
|
|
|
|
static PJOYEMVARS s_pjev;
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Joy_CreateInstance |
|
|
*
|
|
* Create a joystick thing.
|
|
*
|
|
* @parm PVXDDEVICEFORMAT | pdevf |
|
|
*
|
|
* What the object should look like.
|
|
*
|
|
* @parm PVXDINSTANCE * | ppviOut |
|
|
*
|
|
* The answer goes here.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
#define OBJAT(T, v) (*(T *)(v))
|
|
#define PUN(T, v) OBJAT(T, &(v))
|
|
|
|
HRESULT INTERNAL
|
|
CEm_Joy_CreateInstance(PVXDDEVICEFORMAT pdevf, PVXDINSTANCE *ppviOut)
|
|
{
|
|
HRESULT hres;
|
|
|
|
DllEnterCrit();
|
|
if (s_pjev == 0) {
|
|
DWORD uiJoy;
|
|
|
|
hres = AllocCbPpv(cbX(JOYEMVARS), &s_pjev);
|
|
if (SUCCEEDED(hres)) {
|
|
for (uiJoy = 0; uiJoy < cJoyMax; uiJoy++) {
|
|
PUN(PV, s_pjev->rged[uiJoy].pState) = &s_pjev->rgjs2[uiJoy];
|
|
s_pjev->rged[uiJoy].Acquire = CEm_Joy_Acquire;
|
|
s_pjev->rged[uiJoy].cbData = cbX(s_pjev->rgjs2[uiJoy]);
|
|
s_pjev->rged[uiJoy].cRef = 0x0;
|
|
}
|
|
}
|
|
} else {
|
|
hres = S_OK;
|
|
}
|
|
DllLeaveCrit();
|
|
|
|
if (SUCCEEDED(hres)) {
|
|
hres = CEm_CreateInstance(pdevf, ppviOut,
|
|
&s_pjev->rged[pdevf->dwExtra]);
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* @doc INTERNAL
|
|
*
|
|
* @func HRESULT | CEm_Joy_Ping |
|
|
*
|
|
* Read data from the joystick.
|
|
*
|
|
* @parm PVXDINSTANCE * | ppvi |
|
|
*
|
|
* Information about the gizmo being mangled.
|
|
*
|
|
*****************************************************************************/
|
|
|
|
HRESULT INTERNAL
|
|
CEm_Joy_Ping(PVXDINSTANCE *ppvi)
|
|
{
|
|
HRESULT hres;
|
|
JOYINFOEX ji;
|
|
MMRESULT mmrc;
|
|
PEM this = _thisPvNm(*ppvi, vi);
|
|
|
|
AssertF(this->dwSignature == CEM_SIGNATURE);
|
|
ji.dwSize = cbX(ji);
|
|
ji.dwFlags = JOY_RETURNALL + JOY_RETURNRAWDATA;
|
|
ji.dwPOV = JOY_POVCENTERED; /* joyGetPosEx forgets to set this */
|
|
|
|
mmrc = joyGetPosEx((DWORD)(UINT_PTR)this->dwExtra, &ji);
|
|
if (mmrc == JOYERR_NOERROR) {
|
|
DIJOYSTATE2 js;
|
|
UINT uiButtons;
|
|
|
|
ZeroX(js); /* Wipe out the bogus things */
|
|
|
|
js.lX = ji.dwXpos;
|
|
js.lY = ji.dwYpos;
|
|
js.lZ = ji.dwZpos;
|
|
js.lRz = ji.dwRpos;
|
|
js.rglSlider[0] = ji.dwUpos;
|
|
js.rglSlider[1] = ji.dwVpos;
|
|
js.rgdwPOV[0] = ji.dwPOV;
|
|
js.rgdwPOV[1] = JOY_POVCENTERED;
|
|
js.rgdwPOV[2] = JOY_POVCENTERED;
|
|
js.rgdwPOV[3] = JOY_POVCENTERED;
|
|
|
|
for (uiButtons = 0; uiButtons < 32; uiButtons++) {
|
|
if (ji.dwButtons & (1 << uiButtons)) {
|
|
js.rgbButtons[uiButtons] = 0x80;
|
|
}
|
|
}
|
|
|
|
CEm_AddState(&s_pjev->rged[this->dwExtra], &js, GetTickCount());
|
|
|
|
hres = S_OK;
|
|
} else {
|
|
/*
|
|
* dinput.dll forces the device unacquired here
|
|
* in DX8 we just return an error
|
|
*/
|
|
hres = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32,
|
|
ERROR_DEV_NOT_EXIST);
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|
|
HRESULT EXTERNAL
|
|
NotifyWorkerThreadPem(DWORD idThread, PEM pem)
|
|
{
|
|
PLLTHREADSTATE plts;
|
|
HRESULT hres;
|
|
|
|
hres = CEm_GetWorkerThread(pem, &plts);
|
|
|
|
if( SUCCEEDED(hres) )
|
|
{
|
|
AssertF(plts->idThread == idThread);
|
|
|
|
hres = NudgeWorkerThreadPem( plts, pem );
|
|
}
|
|
return hres;
|
|
}
|
|
|
|
|
|
HRESULT EXTERNAL
|
|
NudgeWorkerThreadPem( PLLTHREADSTATE plts, PEM pem )
|
|
{
|
|
HRESULT hres = S_FALSE;
|
|
|
|
plts->pemCheck = pem;
|
|
|
|
if( !PostWorkerMessage(plts->idThread, pem))
|
|
{
|
|
SquirtSqflPtszV(sqfl | sqflBenign,
|
|
TEXT("NudgeWorkerThreadPem: PostThreadMessage Failed with error %d"),
|
|
GetLastError() );
|
|
}
|
|
else if( pem )
|
|
{
|
|
DWORD dwRc;
|
|
|
|
SquirtSqflPtszV(sqfl | sqflVerbose,
|
|
TEXT("NudgeWorkerThreadPem: PostThreadMessage SUCCEEDED, waiting for event ... "));
|
|
|
|
|
|
/*
|
|
* According to a comment in CEm_LL_ThreadProc Win95 may
|
|
* fail with an invalid parameter error, so if it does,
|
|
* keep trying. (Assume no valid case will occur.)
|
|
*/
|
|
do
|
|
{
|
|
dwRc = WaitForMultipleObjects( 2, &plts->hThread, FALSE, INFINITE);
|
|
} while ( ( dwRc == WAIT_FAILED ) && ( GetLastError() == ERROR_INVALID_PARAMETER ) );
|
|
|
|
switch( dwRc )
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
SquirtSqflPtszV(sqfl | sqflBenign,
|
|
TEXT("NotifyWorkerThreadPem: Not expecting response from dead worker thread") );
|
|
break;
|
|
case WAIT_OBJECT_0 + 1:
|
|
/*
|
|
* The worker thread responded OK
|
|
*/
|
|
hres = S_OK;
|
|
AssertF(plts->pemCheck == NULL );
|
|
break;
|
|
default:
|
|
SquirtSqflPtszV(sqfl | sqflError,
|
|
TEXT("NotifyWorkerThreadPem: WaitForMultipleObjects returned 0x%08x with LastError %d"),
|
|
dwRc, GetLastError() );
|
|
hres = E_FAIL;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
|