/***************************************************************************** * * 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

. * * @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

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 * * 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 . * *****************************************************************************/ 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; }