windows-nt/Source/XPSP1/NT/multimedia/directx/dinput/pid/effdrv.c
2020-09-26 16:20:57 +08:00

816 lines
21 KiB
C

/*****************************************************************************
*
* EffDrv.c
*
* Copyright (c) 1999 Microsoft Corporation. All Rights Reserved.
*
* Abstract:
*
* Effect driver.
*
* WARNING! Since the effect driver is marked ThreadingModel="Both",
* all methods must be thread-safe.
*
*****************************************************************************/
#include "PIDpr.h"
#define sqfl (sqflEffDrv)
/*****************************************************************************
*
* CPidDrv - Effect driver
*
*****************************************************************************/
/*****************************************************************************
*
* PID_AddRef
*
* Increment our object reference count (thread-safely) and return
* the new reference count.
*
*****************************************************************************/
STDMETHODIMP_(ULONG)
PID_AddRef(IDirectInputEffectDriver *ped)
{
CPidDrv *this = (CPidDrv *)ped;
InterlockedIncrement((LPLONG)&this->cRef);
return this->cRef;
}
/*****************************************************************************
*
* PID_Release
*
* Decrement our object reference count (thread-safely) and
* destroy ourselves if there are no more references.
*
*****************************************************************************/
STDMETHODIMP_(ULONG)
PID_Release(IDirectInputEffectDriver *ped)
{
ULONG ulRc;
CPidDrv *this = (CPidDrv *)ped;
if(InterlockedDecrement((LPLONG)&this->cRef) == 0)
{
DllRelease();
PID_Finalize(ped);
LocalFree(this);
ulRc = 0;
} else
{
ulRc = this->cRef;
}
return ulRc;
}
/*****************************************************************************
*
* PID_QueryInterface
*
* Our QI is very simple because we support no interfaces beyond
* ourselves.
*
* riid - Interface being requested
* ppvOut - receives new interface (if successful)
*
*****************************************************************************/
STDMETHODIMP
PID_QueryInterface(IDirectInputEffectDriver *ped, REFIID riid, LPVOID *ppvOut)
{
HRESULT hres;
if(IsEqualIID(riid, &IID_IUnknown) ||
IsEqualIID(riid, &IID_IDirectInputEffectDriver))
{
PID_AddRef(ped);
*ppvOut = ped;
hres = S_OK;
} else
{
*ppvOut = 0;
hres = E_NOINTERFACE;
}
return hres;
}
/*****************************************************************************
*
* PID_DeviceID
*
* DirectInput uses this method to inform us of
* the identity of the device.
*
* For example, if a device driver is passed
* dwExternalID = 2 and dwInternalID = 1,
* then this means the interface will be used to
* communicate with joystick ID number 2, which
* corresonds to physical unit 1 in VJOYD.
*
* dwDirectInputVersion
*
* The version of DirectInput that loaded the
* effect driver.
*
* dwExternalID
*
* The joystick ID number being used.
* The Windows joystick subsystem allocates external IDs.
*
* fBegin
*
* Nonzero if access to the device is beginning.
* Zero if the access to the device is ending.
*
* dwInternalID
*
* Internal joystick id. The device driver manages
* internal IDs.
*
* lpReserved
*
* Reserved for future use (HID).
*
* Returns:
*
* S_OK if the operation completed successfully.
*
* Any DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*****************************************************************************/
STDMETHODIMP
PID_DeviceID(IDirectInputEffectDriver *ped,
DWORD dwDirectInputVersion,
DWORD dwExternalID, DWORD fBegin,
DWORD dwInternalID, LPVOID pvReserved)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres = S_OK;
LPDIHIDFFINITINFO init = (DIHIDFFINITINFO*)pvReserved;
EnterProcI(PID_DeviceID, (_"xxxxxx", ped, dwDirectInputVersion, dwExternalID, fBegin, dwInternalID, pvReserved));
DllEnterCrit();
if( init == NULL )
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL init == NULL "),
s_tszProc );
hres = DIERR_PID_NOTINITIALIZED;
}
if( SUCCEEDED(hres)
&& (init->dwSize < cbX(*init) ) )
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL init->dwSize(%d) expecting(%d) "),
s_tszProc, init->dwSize, cbX(*init) );
hres = DIERR_PID_NOTINITIALIZED;
}
if( SUCCEEDED(hres) )
{
#ifdef UNICODE
lstrcpy(this->tszDeviceInterface, init->pwszDeviceInterface );
#else // !UNICODE
{
TCHAR tszDeviceInterface[MAX_DEVICEINTERFACE];
UToA(tszDeviceInterface, MAX_DEVICEINTERFACE, init->pwszDeviceInterface);
lstrcpy(this->tszDeviceInterface, tszDeviceInterface);
}
#endif
if( FAILED(hres) )
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL:0x%x Invalid string(%s) "),
s_tszProc, hres, init->pwszDeviceInterface );
}
if( SUCCEEDED(hres) && IsEqualGUID(&init->GuidInstance, &GUID_NULL ) )
{
hres = DIERR_PID_NOTINITIALIZED;
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL:init->GuidInstance is NULL "),
s_tszProc );
}
this->GuidInstance = init->GuidInstance;
/* Record the DI version number */
this->dwDirectInputVersion = dwDirectInputVersion;
/* Keep the external ID as a cookie for access to the driver functionality */
this->dwID = dwExternalID;
}
if( SUCCEEDED(hres) )
{
/* Ping the device to make sure it is fine */
hres = PID_Init(ped);
}
/*
* Remember the unit number because that tells us which of
* our devices we are talking to. The DirectInput external
* joystick number is useless to us. (We don't care if we
* are joystick 1 or joystick 2.)
*
* Note that although our other methods are given an external
* joystick Id, we don't use it. Instead, we use the unit
* number that we were given here.
*
* Our hardware supports only MAX_UNITS units.
*/
DllLeaveCrit();
ExitOleProc();
return hres;
}
/*****************************************************************************
*
* PID_GetVersions
*
* Obtain version information about the force feedback
* hardware and driver.
*
* pvers
*
* A structure which should be filled in with version information
* describing the hardware, firmware, and driver.
*
* DirectInput will set the dwSize field
* to sizeof(DIDRIVERVERSIONS) before calling this method.
*
* Returns:
*
* S_OK if the operation completed successfully.
*
* E_NOTIMPL to indicate that DirectInput should retrieve
* version information from the VxD driver instead.
*
* Any DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*****************************************************************************/
STDMETHODIMP
PID_GetVersions(IDirectInputEffectDriver *ped, LPDIDRIVERVERSIONS pvers)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres;
EnterProc(PID_GetVersions, (_"xx", ped, pvers));
DllEnterCrit();
if(pvers->dwSize >= sizeof(DIDRIVERVERSIONS))
{
/*
* Tell DirectInput how much of the structure we filled in.
*/
pvers->dwSize = sizeof(DIDRIVERVERSIONS);
/*
* In real life, we would detect the version of the hardware
* that is connected to unit number this->dwUnit.
*/
pvers->dwFirmwareRevision = 0x0;
pvers->dwHardwareRevision = this->attr.ProductID;
pvers->dwFFDriverVersion = PID_DRIVER_VERSION;
hres = S_OK;
} else
{
hres = E_INVALIDARG;
}
DllLeaveCrit();
ExitOleProc();
return hres;
}
/*****************************************************************************
*
* PID_Escape
*
* DirectInput uses this method to communicate
* IDirectInputDevice2::Escape and
* IDirectInputEFfect::Escape methods to the driver.
*
* dwId
*
* The joystick ID number being used.
*
* dwEffect
*
* If the application invoked the
* IDirectInputEffect::Escape method, then
* dwEffect contains the handle (returned by
* mf IDirectInputEffectDriver::DownloadEffect)
* of the effect at which the command is directed.
*
* If the application invoked the
* mf IDirectInputDevice2::Escape method, then
* dwEffect is zero.
*
* pesc
*
* Pointer to a DIEFFESCAPE structure which describes
* the command to be sent. On success, the
* cbOutBuffer field contains the number
* of bytes of the output buffer actually used.
*
* DirectInput has already validated that the
* lpvOutBuffer and lpvInBuffer and fields
* point to valid memory.
*
* Returns:
*
* S_OK if the operation completed successfully.
*
* Any DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*****************************************************************************/
STDMETHODIMP
PID_Escape(IDirectInputEffectDriver *ped,
DWORD dwId, DWORD dwEffect, LPDIEFFESCAPE pesc)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres;
EnterProc(PID_Escape, (_"xxxx", ped, dwId, dwEffect, pesc));
hres = E_NOTIMPL;
ExitOleProc();
return hres;
}
/*****************************************************************************
*
* PID_GetForceFeedbackState
*
* Retrieve the force feedback state for the device.
*
* dwId
*
* The external joystick number being addressed.
*
* pds
*
* Receives device state.
*
* DirectInput will set the dwSize field
* to sizeof(DIDEVICESTATE) before calling this method.
*
* Returns:
*
* S_OK on success.
*
* Any DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*****************************************************************************/
STDMETHODIMP
PID_GetForceFeedbackState(IDirectInputEffectDriver *ped,
DWORD dwId, LPDIDEVICESTATE pds)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres;
USHORT LinkCollection;
EnterProcI(PID_GetFFState, (_"xxx", ped, dwId, pds));
DllEnterCrit();
hres = PID_GetLinkCollectionIndex(ped,g_PoolReport.UsagePage,g_PoolReport.Collection,0x0,&LinkCollection);
if (SUCCEEDED(hres))
{
hres = PID_GetReport
(ped,
&g_PoolReport,
LinkCollection,
this->pReport[g_PoolReport.HidP_Type],
this->cbReport[g_PoolReport.HidP_Type]
);
if (SUCCEEDED(hres))
{
if (FAILED(PID_ParseReport
(
ped,
&g_PoolReport,
LinkCollection,
&this->ReportPool,
cbX(this->ReportPool),
this->pReport[g_PoolReport.HidP_Type],
this->cbReport[g_PoolReport.HidP_Type]
)))
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL to parse report."),
s_tszProc);
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL to get report."),
s_tszProc);
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: FAIL to get Link Collection Index."),
s_tszProc);
}
if( ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded != this->ReportPool.uRomETCount )
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: PID driver downloaded %d effects, device claims it has %d"),
s_tszProc, ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded, this->ReportPool.uRomETCount );
}
if(SUCCEEDED(hres))
{
/*
* Start out empty and then work our way up.
*/
pds->dwState = this->dwState;
/*
* If there are no effects, then DIGFFS_EMPTY.
*/
// ISSUE-2001/03/29-timgill Should use this->ReportPool.uRomETCount == 0x0
if(((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cEfDownloaded == 0x0 )
{
pds->dwState |= DIGFFS_EMPTY;
// No effects playing and device is not paused
if(!( pds->dwState & DIGFFS_PAUSED ) )
{
pds->dwState |= DIGFFS_STOPPED;
}
}
//if everything has succeeded, this->ReportPool.uRamPoolSz shouldn't be 0.
if (this->ReportPool.uRamPoolSz != 0)
{
if( this->uDeviceManaged & PID_DEVICEMANAGED )
{
pds->dwLoad = 100 * ( this->dwUsedMem / this->ReportPool.uRamPoolSz );
}else
{
pds->dwLoad = 100 * ( ((PUNITSTATE)(g_pshmem + this->iUnitStateOffset))->cbAlloc / this->ReportPool.uRamPoolSz );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("%s: this->ReportPool.uRamPoolSz = 0."),
s_tszProc);
hres = E_FAIL;
}
}
DllLeaveCrit();
ExitOleProc();
return hres;
}
/*****************************************************************************
*
* PID_StartEffect
*
* Begin playback of an effect.
*
* If the effect is already playing, then it is restarted
* from the beginning.
*
* @cwrap LPDIRECTINPUTEFFECTDRIVER | lpEffectDriver
*
* @parm DWORD | dwId |
*
* The external joystick number being addressed.
*
* @parm DWORD | dwEffect |
*
* The effect to be played.
*
* @parm DWORD | dwMode |
*
* How the effect is to affect other effects.
*
* This parameter consists of zero or more
* DIES_* flags. Note, however, that the driver
* will never receive the DIES_NODOWNLOAD flag;
* the DIES_NODOWNLOAD flag is managed by
* DirectInput and not the driver.
*
* @parm DWORD | dwCount |
*
* Number of times the effect is to be played.
*
* Returns:
*
* S_OK on success.
*
* Any other DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*
*****************************************************************************/
STDMETHODIMP
PID_StartEffect(IDirectInputEffectDriver *ped, DWORD dwId, DWORD dwEffect,
DWORD dwMode, DWORD dwCount)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres;
EnterProc(PID_StartEffect, (_"xxxxx", ped, dwId, dwEffect, dwMode, dwCount));
DllEnterCrit();
hres = PID_EffectOperation
(
ped,
dwId,
dwEffect,
dwMode | PID_DIES_START,
dwCount,
TRUE,
0,
1
);
if (SUCCEEDED(hres))
{
//set the status to DIEGES_PLAYING.
//we do this because of the following: if an app calls Start(), and then immediately
//calls GetEffectStatus(), it might happen that our second thread (pidrd.c)
//would not have time to update the status of the effect to DIEGES_PLAYING
//(see Whistler bug 287035).
//GetEffectStatus() returns (pEffectState->lEfState & DIEGES_PLAYING).
//at this point, we know that the call to WriteFile() has succeeded, and that
//all the data has been written (see PID_SendReportBl() in pidhid.c) --
//so we might as well set the status.
PEFFECTSTATE pEffectState = PeffectStateFromBlockIndex(this, dwEffect);
pEffectState->lEfState |= DIEGES_PLAYING;
}
DllLeaveCrit();
return hres;
ExitOleProc();
}
/*****************************************************************************
*
* PID_StopEffect
*
* Halt playback of an effect.
*
* dwId
*
* The external joystick number being addressed.
*
* dwEffect
*
* The effect to be stopped.
*
* Returns:
*
* S_OK on success.
*
* Any other DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*
*****************************************************************************/
STDMETHODIMP
PID_StopEffect(IDirectInputEffectDriver *ped, DWORD dwId, DWORD dwEffect)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres;
EnterProc(PID_StopEffect, (_"xxxx", ped, dwId, dwEffect));
DllEnterCrit();
hres = PID_EffectOperation
(
ped,
dwId,
dwEffect,
PID_DIES_STOP,
0x0,
TRUE,
0,
1
);
if (SUCCEEDED(hres))
{
//set the status to ~(DIEGES_PLAYING).
//we do this because of the following: if an app calls Stop(), and then immediately
//calls GetEffectStatus(), it might happen that our second thread (pidrd.c)
//would not have time to update the status of the effect to DIEGES_PLAYING
//(see Whistler bug 287035).
//GetEffectStatus() returns (pEffectState->lEfState & DIEGES_PLAYING).
//at this point, we know that the call to WriteFile() has succeeded, and that
//all the data has been written (see PID_SendReportBl() in pidhid.c) --
//so we might as well set the status.
PEFFECTSTATE pEffectState = PeffectStateFromBlockIndex(this, dwEffect);
pEffectState->lEfState &= ~(DIEGES_PLAYING);
}
ExitOleProc();
DllLeaveCrit();
return hres;
}
/*****************************************************************************
*
* PID_GetEffectStatus
*
* Obtain information about an effect.
*
* dwId
*
* The external joystick number being addressed.
*
* dwEffect
*
* The effect to be queried.
*
* pdwStatus
*
* Receives the effect status in the form of zero
* or more DIEGES_* flags.
*
* Returns:
*
* S_OK on success.
*
* Any other DIERR_* error code may be returned.
*
* Private driver-specific error codes in the range
* DIERR_DRIVERFIRST through DIERR_DRIVERLAST
* may be returned.
*
*
*****************************************************************************/
STDMETHODIMP
PID_GetEffectStatus(IDirectInputEffectDriver *ped, DWORD dwId, DWORD dwEffect,
LPDWORD pdwStatus)
{
CPidDrv *this = (CPidDrv *)ped;
HRESULT hres;
EnterProc(PID_GetEffectStatus, (_"xxxx", ped, dwId, dwEffect, pdwStatus));
DllEnterCrit();
*pdwStatus = 0x0;
hres = PID_ValidateEffectIndex(ped, dwEffect);
if(SUCCEEDED(hres) )
{
PEFFECTSTATE pEffectState = PeffectStateFromBlockIndex(this,dwEffect);
*pdwStatus = (pEffectState->lEfState & DIEGES_PLAYING);
}
DllLeaveCrit();
ExitOleProc();
return hres;
}
/*****************************************************************************
*
* The VTBL for our effect driver
*
*****************************************************************************/
IDirectInputEffectDriverVtbl PID_Vtbl = {
PID_QueryInterface,
PID_AddRef,
PID_Release,
PID_DeviceID,
PID_GetVersions,
PID_Escape,
PID_SetGain,
PID_SendForceFeedbackCommand,
PID_GetForceFeedbackState,
PID_DownloadEffect,
PID_DestroyEffect,
PID_StartEffect,
PID_StopEffect,
PID_GetEffectStatus,
};
/*****************************************************************************
*
* PID_New
*
*****************************************************************************/
STDMETHODIMP
PID_New(REFIID riid, LPVOID *ppvOut)
{
HRESULT hres;
CPidDrv *this;
this = LocalAlloc(LPTR, sizeof(CPidDrv));
if(this)
{
/*
* Initialize the basic object management goo.
*/
this->ed.lpVtbl = &PID_Vtbl;
this->cRef = 1;
DllAddRef();
/*
* !!IHV!! Do instance initialization here.
*
* (e.g., open the driver you are going to IOCTL to)
*
* DO NOT RESET THE DEVICE IN YOUR CONSTRUCTOR!
*
* Wait for the SendForceFeedbackCommand(SFFC_RESET)
* to reset the device. Otherwise, you may reset
* a device that another application is still using.
*/
this->hdevOvrlp = this->hdev = INVALID_HANDLE_VALUE;
/*
* Attempt to obtain the desired interface. QueryInterface
* will do an AddRef if it succeeds.
*/
hres = PID_QueryInterface(&this->ed, riid, ppvOut);
PID_Release(&this->ed);
} else
{
hres = E_OUTOFMEMORY;
}
return hres;
}