/***************************************************************************** * * 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 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 */ DWORD dwNativeData; /* delta for rel, dwData for abs */ 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; /* * Assert that if it's not a relative axis, its a POV or * an absolute axis, both of which should be absolute. */ CAssertF( DIDFT_DWORDOBJS == ( DIDFT_ABSAXIS | DIDFT_RELAXIS | DIDFT_POV ) ); /* * DX7 had series of bugs, the net result of which was * that emulated devices only report data in their native * mode. If that behavior is required make it so here. */ if( ped->pDevType[dwOfs] & DIDFT_RELAXIS ) { dwNativeData = ddwData; /* Always use relative */ } else { dwNativeData = dwData; /* Always use absolute */ } } else { goto nop; } } else { LPBYTE pb = pvAddPvCb(ped->pState, dwOfs); AssertF((dwData & ~0x80) == 0); if (*pb != (BYTE)dwData) { *pb = (BYTE)dwData; dwNativeData = 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_MODECOMPAT ) { pemNext = CEm_BufferEvent(pem, dwNativeData, dwOfs, tm, dwSeq); } else 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)); } /* * Since this happens only when the device is acquired, * we don't need to worry about the notify event changing * asynchronously. * * UPDATE 1/5/01 Winbug 270403 (jacklin): Moved call to CDIDev_SetNotifyEvent * below so the buffer is updated before the event is set. * * It would be easy to avoid setting the event if nothing * was buffered for better performance but people will be * relying on getting events now, even when they are not * using 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
. * * @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(IDirectInputDevice::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(IDirectInputDevice::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
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
*