Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3986 lines
124 KiB

/*****************************************************************************
*
* DIHid.c
*
* Copyright (c) 1996 Microsoft Corporation. All Rights Reserved.
*
* Abstract:
*
* The HID device callback.
*
* Contents:
*
* CHid_New
*
*****************************************************************************/
#include "dinputpr.h"
/*****************************************************************************
*
* The sqiffle for this file.
*
*****************************************************************************/
#define sqfl sqflHidDev
#ifdef HID_SUPPORT
/*****************************************************************************
*
* Declare the interfaces we will be providing.
*
*****************************************************************************/
Primary_Interface(CHid, IDirectInputDeviceCallback);
Interface_Template_Begin(CHid)
Primary_Interface_Template(CHid, IDirectInputDeviceCallback)
Interface_Template_End(CHid)
/*****************************************************************************
*
* Forward declarations
*
* These are out of laziness, not out of necessity.
*
*****************************************************************************/
LRESULT CALLBACK
CHid_SubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp,
UINT_PTR uid, ULONG_PTR dwRef);
STDMETHODIMP_(DWORD) CHid_GetUsage(PDICB pdcb, int iobj);
/*****************************************************************************
*
* Hid devices are totally arbitrary, so there is nothing static we
* can cook up to describe them. We generate all the information on
* the fly.
*
*****************************************************************************/
/*****************************************************************************
*
* Auxiliary helper definitions for CHid.
*
*****************************************************************************/
#define ThisClass CHid
#define ThisInterface IDirectInputDeviceCallback
#define riidExpected &IID_IDirectInputDeviceCallback
/*****************************************************************************
*
* CHid::QueryInterface (from IUnknown)
* CHid::AddRef (from IUnknown)
* CHid::Release (from IUnknown)
*
*****************************************************************************/
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | QueryInterface |
*
* Gives a client access to other interfaces on an object.
*
* @cwrap LPDIRECTINPUT | lpDirectInput
*
* @parm IN REFIID | riid |
*
* The requested interface's IID.
*
* @parm OUT LPVOID * | ppvObj |
*
* Receives a pointer to the obtained interface.
*
* @returns
*
* Returns a COM error code.
*
* @xref OLE documentation for <mf IUnknown::QueryInterface>.
*
*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | AddRef |
*
* Increments the reference count for the interface.
*
* @cwrap LPDIRECTINPUT | lpDirectInput
*
* @returns
*
* Returns the object reference count.
*
* @xref OLE documentation for <mf IUnknown::AddRef>.
*
*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | Release |
*
* Decrements the reference count for the interface.
* If the reference count on the object falls to zero,
* the object is freed from memory.
*
* @cwrap LPDIRECTINPUT | lpDirectInput
*
* @returns
*
* Returns the object reference count.
*
* @xref OLE documentation for <mf IUnknown::Release>.
*
*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | QIHelper |
*
* We don't have any dynamic interfaces and simply forward
* to <f Common_QIHelper>.
*
*
* @parm IN REFIID | riid |
*
* The requested interface's IID.
*
* @parm OUT LPVOID * | ppvObj |
*
* Receives a pointer to the obtained interface.
*
*****************************************************************************/
#ifdef DEBUG
Default_QueryInterface(CHid)
Default_AddRef(CHid)
Default_Release(CHid)
#else
#define CHid_QueryInterface Common_QueryInterface
#define CHid_AddRef Common_AddRef
#define CHid_Release Common_Release
#endif
#define CHid_QIHelper Common_QIHelper
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | RemoveSubclass |
*
* Remove our subclass hook on the window.
*
*****************************************************************************/
void INTERNAL
CHid_RemoveSubclass(PCHID this)
{
/*
* !! All the comments in CJoy_RemoveSubclass apply here !!
*/
if(this->hwnd)
{
HWND hwnd = this->hwnd;
this->hwnd = 0;
if(!RemoveWindowSubclass(hwnd, CHid_SubclassProc, 0))
{
/*
* The RemoveWindowSubclass can fail if the window
* was destroyed behind our back.
*/
// AssertF(!IsWindow(hwnd));
}
Sleep(0); /* Let the worker thread drain */
Common_Unhold(this);
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | Unacquire |
*
* Tell the device driver to stop data acquisition.
*
* It is the caller's responsibility to call this only
* when the device has been acquired.
*
* Warning! We require that the device critical section be
* held so we don't race against our worker thread.
*
* @returns
*
* Returns a COM error code. The following error codes are
* intended to be illustrative and not necessarily comprehensive.
*
* <c DI_OK> = <c S_OK>: The operation completed successfully.
*
* <c S_FALSE>: The operation was begun and should be completed
* by the caller by communicating with the <t VXDINSTANCE>.
*
*****************************************************************************/
STDMETHODIMP
CHid_Unacquire(PDICB pdcb)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::HID::Unacquire,
(_ "p", pdcb));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF(this->pvi);
AssertF(this->pvi->pdd);
AssertF(CDIDev_InCrit(this->pvi->pdd));
hres = S_FALSE; /* Please finish for me */
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func void | CHid_Finalize |
*
* Releases the resources of the device after all references
* (both strong and weak) are gone.
*
* @parm PV | pvObj |
*
* Object being released. Note that it may not have been
* completely initialized, so everything should be done
* carefully.
*
*****************************************************************************/
void INTERNAL
CHid_Finalize(PV pvObj)
{
UINT iType;
PCHID this = pvObj;
if(this->hkInstType)
{
RegCloseKey(this->hkInstType);
}
if(this->hkType)
{
RegCloseKey(this->hkType);
}
AssertF(this->hdev == INVALID_HANDLE_VALUE);
AssertF(this->hdevEm == INVALID_HANDLE_VALUE);
if(this->ppd)
{
HidD_FreePreparsedData(this->ppd);
}
/*
*
* Free group 2 memory:
*
* hriIn.rgdata Input data
* hriOut.rgdata Output data
* hriFea.rgdata Feature data (both in and out)
*
* hriIn.pvReport Raw input report
* hriOut.pvReport Raw output report
* hriFea.pvReport Raw feature report
*
* pvPhys Used by ED
* pvStage
*/
FreePpv(&this->pvGroup2);
/*
* Freeing df.rgodf also frees rgpvCaps, rgvcaps, rgbcaps, rgcoll.
*/
FreePpv(&this->df.rgodf);
FreePpv(&this->rgiobj);
FreePpv(&this->ptszPath);
FreePpv(&this->ptszId);
FreePpv(&this->rgpbButtonMasks);
for(iType = 0x0; iType < HidP_Max; iType++)
{
FreePpv(&this->pEnableReportId[iType]);
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | AppFinalize |
*
* The client <t VXDINSTANCE> contains a weak pointer back
* to us so that that it can party on the data format we
* collected.
*
* @parm PV | pvObj |
*
* Object being released from the application's perspective.
*
*****************************************************************************/
void INTERNAL
CHid_AppFinalize(PV pvObj)
{
PCHID this = pvObj;
if(this->pvi)
{
HRESULT hres;
CHid_RemoveSubclass(this);
hres = Hel_DestroyInstance(this->pvi);
AssertF(SUCCEEDED(hres));
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func LRESULT | CHid_SubclassProc |
*
* Window subclass procedure which watches for
* joystick configuration change notifications.
*
* Even if we are not a joystick, we still listen to
* this, in case somebody recalibrated a remote control
* or some other wacky thing like that.
*
* However, if our device has no calibratable controls,
* then there's no point in watching for recalibration
* notifications.
*
* @parm HWND | hwnd |
*
* The victim window.
*
* @parm UINT | wm |
*
* Window message.
*
* @parm WPARAM | wp |
*
* Message-specific data.
*
* @parm LPARAM | lp |
*
* Message-specific data.
*
* @parm UINT | uid |
*
* Callback identification number, always zero.
*
* @parm DWORD | dwRef |
*
* Reference data, a pointer to our joystick device callback.
*
*****************************************************************************/
LRESULT CALLBACK
CHid_SubclassProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp,
UINT_PTR uid, ULONG_PTR dwRef)
{
#ifdef XDEBUG
static CHAR s_szProc[] = "";
#endif
PCHID this = (PCHID)dwRef;
AssertF(uid == 0);
/*
* Wacky subtlety going on here to avoid race conditions.
* See the mondo comment block in CJoy_RemoveSubclass [sic]
* for details.
*
* We can get faked out if the memory associated with the
* CHid is still physically allocated, the vtbl is magically
* still there and the hwnd field somehow matches our hwnd.
*/
if(SUCCEEDED(hresPv(this)) && this->hwnd == hwnd)
{
switch(wm)
{
case WM_POWERBROADCAST :
// 7/18/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
SquirtSqflPtszV( sqfl | sqflError,
TEXT("WM_POWERBROADCAST(0x%x) for 0x%p"), wp, this);
if(wp == PBT_APMSUSPEND )
{
CEm_ForceDeviceUnacquire(pemFromPvi(this->pvi)->ped, 0x0 );
}
else if(wp == PBT_APMRESUMESUSPEND )
{
CEm_ForceDeviceUnacquire(pemFromPvi(this->pvi)->ped, 0x0 );
DIBus_BuildList(TRUE);
}
break;
default:
if( wm == g_wmJoyChanged )
{
/*
* Once we receive this notification message, we need to rebuild
* our list, because sometimes the user has just changed the device's ID.
* See manbug: 35445
*/
DIHid_BuildHidList(TRUE);
Common_Hold(this);
CHid_LoadCalibrations(this);
Common_Unhold(this);
}
// 7/18/2000(a-JiTay): IA64: Use %p format specifier for 32/64-bit pointers.
SquirtSqflPtszV( sqfl | sqflVerbose,
TEXT("wp(0x%x) wm(0x%x) for 0x%p"), wm, wp, this);
break;
}
}
return DefSubclassProc(hwnd, wm, wp, lp);
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | GetPhysicalState |
*
* Read the physical device state into <p pmstOut>.
*
* Note that it doesn't matter if this is not atomic.
* If a device report arrives while we are reading it,
* we will get a mix of old and new data. No big deal.
*
* @parm PCHID | this |
*
* The object in question.
*
* @parm PV | pvOut |
*
* Where to put the device state.
*
* @returns
* None.
*
*****************************************************************************/
void INLINE
CHid_GetPhysicalState(PCHID this, PV pvOut)
{
AssertF(this->pvPhys);
AssertF(this->cbPhys);
CopyMemory(pvOut, this->pvPhys, this->cbPhys);
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | Acquire |
*
* Tell the device driver to begin data acquisition.
* We create a handle to the device so we can talk to it again.
* We must create each time so we can survive in the
* "unplug/replug" case. When a device is unplugged,
* its <t HANDLE> becomes permanently invalid and must be
* re-opened for it to work again.
*
* Warning! We require that the device critical section be
* held so we don't race against our worker thread.
*
* @returns
*
* Returns a COM error code. The following error codes are
* intended to be illustrative and not necessarily comprehensive.
*
* <c DI_OK> = <c S_OK>: The operation completed successfully.
*
* <c S_FALSE>: The operation was begun and should be completed
* by the caller by communicating with the <t VXDINSTANCE>.
*
*****************************************************************************/
STDMETHODIMP
CHid_Acquire(PDICB pdcb)
{
HRESULT hres;
HANDLE h;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::HID::Acquire,
(_ "p", pdcb));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF(this->pvi);
AssertF(this->pvi->pdd);
AssertF(CDIDev_InCrit(this->pvi->pdd));
AssertF(this->hdev == INVALID_HANDLE_VALUE);
/*
* We must check connectivity by opening the device, because NT
* leaves the device in the info list even though it has
* been unplugged.
*/
h = CHid_OpenDevicePath(this, FILE_FLAG_OVERLAPPED);
if(h != INVALID_HANDLE_VALUE)
{
NTSTATUS stat;
DIJOYTYPEINFO dijti;
DWORD dwFlags2 = 0;
WCHAR wszType[cbszVIDPID];
HKEY hkProp;
/*
* Obtain Flags2 to find out if input report is disabled for this device,
* if we haven't done so.
*/
if (!this->fFlags2Checked)
{
/* Check the type key or get predefined name */
ZeroX(dijti);
dijti.dwSize = cbX(dijti);
if( ( this->VendorID == MSFT_SYSTEM_VID )
&&( ( this->ProductID >= MSFT_SYSTEM_PID + JOY_HW_PREDEFMIN )
&&( this->ProductID < MSFT_SYSTEM_PID + JOY_HW_PREDEFMAX ) ) )
{
wszType[0] = L'#';
wszType[1] = L'0' + (WCHAR)(this->ProductID-MSFT_SYSTEM_PID);
wszType[2] = L'\0';
}
else
{
#ifndef WINNT
static WCHAR wszDefHIDName[] = L"HID Game Controller";
#endif
#ifndef UNICODE
TCHAR tszType[cbszVIDPID];
wsprintf(tszType, VID_PID_TEMPLATE, this->VendorID, this->ProductID);
TToU( wszType, cA(wszType), tszType );
#else
wsprintf(wszType, VID_PID_TEMPLATE, this->VendorID, this->ProductID);
#endif
}
/* Got key name. Now open the key. */
hres = JoyReg_OpenPropKey( wszType, KEY_QUERY_VALUE, REG_OPTION_NON_VOLATILE, &hkProp );
if (SUCCEEDED(hres))
{
JoyReg_GetValue( hkProp, REGSTR_VAL_FLAGS2, REG_BINARY,
&dwFlags2, cbX(dwFlags2) );
this->fEnableInputReport = ( (dwFlags2 & JOYTYPE_ENABLEINPUTREPORT) != 0 );
RegCloseKey(hkProp);
}
this->fFlags2Checked = TRUE;
}
if ( this->fEnableInputReport )
{
BYTE id;
for (id = 0; id < this->wMaxReportId[HidP_Input]; ++id)
if (this->pEnableReportId[HidP_Input][id])
{
BOOL bRet;
*(BYTE*)this->hriIn.pvReport = id;
bRet = HidD_GetInputReport(h, this->hriIn.pvReport, this->hriIn.cbReport);
if (bRet)
{
stat = CHid_ParseData(this, HidP_Input, &this->hriIn);
if (SUCCEEDED(stat))
{
this->pvi->fl |= VIFL_INITIALIZE; /* Set the flag so the event can be buffered.
since VIFL_ACQUIRED isn't set yet. */
CEm_AddState(&this->ed, this->pvStage, GetTickCount());
this->pvi->fl &= ~VIFL_INITIALIZE; /* Clear the flag when done. */
}
} else
{
DWORD dwError = GetLastError();
// ERROR_SEM_TIMEOUT means the device has timed out.
if (dwError == ERROR_SEM_TIMEOUT)
{
/*
* Timed out. The device does not support input report. We need to record
* the fact in registry so that GetInputReport() does not ever get called
* again for this device, since each failed call takes five seconds to
* complete.
*/
this->fEnableInputReport = FALSE;
dwFlags2 &= ~JOYTYPE_ENABLEINPUTREPORT;
hres = JoyReg_OpenPropKey(wszType, MAXIMUM_ALLOWED, REG_OPTION_NON_VOLATILE, &hkProp);
if (SUCCEEDED(hres))
{
hres = JoyReg_SetValue( hkProp, REGSTR_VAL_FLAGS2,
REG_BINARY, (PV)&dwFlags2, cbX( dwFlags2 ) );
RegCloseKey(hkProp);
}
break;
}
RPF("CHid_InitParse: Unable to read HID input report LastError(0x%x)", GetLastError() );
}
}
}
CloseHandle(h);
/* Please finish for me */
hres = S_FALSE;
} else
{
hres = DIERR_UNPLUGGED;
}
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetInstance |
*
* Obtains the DirectInput instance handle.
*
* @parm OUT PPV | ppvi |
*
* Receives the instance handle.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetInstance(PDICB pdcb, PPV ppvi)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::GetInstance, (_ "p", pdcb));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF(this->pvi);
*ppvi = (PV)this->pvi;
hres = S_OK;
ExitOleProcPpvR(ppvi);
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetDataFormat |
*
* Obtains the device's preferred data format.
*
* @parm OUT LPDIDEVICEFORMAT * | ppdf |
*
* <t LPDIDEVICEFORMAT> to receive pointer to device format.
*
* @returns
*
* Returns a COM error code. The following error codes are
* intended to be illustrative and not necessarily comprehensive.
*
* <c DI_OK> = <c S_OK>: The operation completed successfully.
*
* <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: The
* <p lpmdr> parameter is not a valid pointer.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetDataFormat(PDICB pdcb, LPDIDATAFORMAT *ppdf)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::GetDataFormat,
(_ "p", pdcb));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
*ppdf = &this->df;
hres = S_OK;
ExitOleProcPpvR(ppdf);
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT | DIHid_GetRegistryProperty |
*
* @parm LPTSTR | ptszId |
*
* Device Instance ID.
*
* @parm DWORD | dwProperty |
*
* The property being queried.
*
* @parm LPDIPROPHEADER | diph |
*
* Property data to be set.
*
*****************************************************************************/
HRESULT INTERNAL
DIHid_GetParentRegistryProperty(LPTSTR ptszId, DWORD dwProperty, LPDIPROPHEADER pdiph, BOOL bGrandParent)
{
HDEVINFO hdev;
LPDIPROPSTRING pstr = (PV)pdiph;
TCHAR tsz[MAX_PATH];
HRESULT hres;
ZeroX(tsz);
hdev = SetupDiCreateDeviceInfoList(NULL, NULL);
if(hdev != INVALID_HANDLE_VALUE)
{
SP_DEVINFO_DATA dinf;
/*
* For the instance name, use the friendly name if possible.
* Else, use the device description.
*/
dinf.cbSize = cbX(SP_DEVINFO_DATA);
if(SetupDiOpenDeviceInfo(hdev, ptszId, NULL, 0, &dinf))
{
DEVINST DevInst;
CONFIGRET cr;
if( ( cr = CM_Get_Parent(&DevInst, dinf.DevInst, 0x0)) == CR_SUCCESS )
{
ULONG ulLength;
CAssertF( SPDRP_DEVICEDESC +1 == CM_DRP_DEVICEDESC );
CAssertF( SPDRP_FRIENDLYNAME +1 == CM_DRP_FRIENDLYNAME );
if(bGrandParent)
{
cr = CM_Get_Parent(&DevInst, DevInst, 0x0);
if( cr != CR_SUCCESS )
{
// No GrandParent ??
}
}
ulLength = MAX_PATH * cbX(TCHAR);
if( cr == CR_SUCCESS &&
( cr = CM_Get_DevNode_Registry_Property(
DevInst,
dwProperty+1,
NULL,
tsz,
&ulLength,
0x0 ) ) == CR_SUCCESS )
{
// Success
hres = S_OK;
#ifdef UNICODE
lstrcpyW(pstr->wsz, tsz);
#else
TToU(pstr->wsz, MAX_PATH, tsz);
#endif
} else
{
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CM_Get_DevNode_Registry_Property FAILED") );
hres = E_FAIL;
}
} else
{
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CM_Get_Parent FAILED") );
hres = E_FAIL;
}
}
SetupDiDestroyDeviceInfoList(hdev);
} else
{
hres = E_FAIL;
}
return hres;
}
HRESULT EXTERNAL
DIHid_GetRegistryProperty(LPTSTR ptszId, DWORD dwProperty, LPDIPROPHEADER pdiph)
{
HDEVINFO hdev;
LPDIPROPSTRING pstr = (PV)pdiph;
TCHAR tsz[MAX_PATH];
HRESULT hres;
ZeroX(tsz);
hdev = SetupDiCreateDeviceInfoList(NULL, NULL);
if(hdev != INVALID_HANDLE_VALUE)
{
SP_DEVINFO_DATA dinf;
/*
* For the instance name, use the friendly name if possible.
* Else, use the device description.
*/
dinf.cbSize = cbX(SP_DEVINFO_DATA);
if(SetupDiOpenDeviceInfo(hdev, ptszId, NULL, 0, &dinf))
{
if(SetupDiGetDeviceRegistryProperty(hdev, &dinf, dwProperty, NULL,
(LPBYTE)tsz, MAX_PATH, NULL) )
{
hres = S_OK;
#ifdef UNICODE
lstrcpyW(pstr->wsz, tsz);
#else
TToU(pstr->wsz, MAX_PATH, tsz);
#endif
} else
{
hres = E_FAIL;
}
} else
{
hres = E_FAIL;
}
SetupDiDestroyDeviceInfoList(hdev);
} else
{
hres = E_FAIL;
}
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | GetGuidAndPath |
*
* Get a Hid device's class GUID (namely, the HID guid)
* and device interface (path).
*
* @parm PCHID | this |
*
* The Hid object.
*
* @parm LPDIPROPHEADER | pdiph |
*
* Structure to receive property value.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_GetGuidAndPath(PCHID this, LPDIPROPHEADER pdiph)
{
HRESULT hres;
LPDIPROPGUIDANDPATH pgp = (PV)pdiph;
pgp->guidClass = GUID_HIDClass;
TToU(pgp->wszPath, cA(pgp->wszPath), this->ptszPath);
hres = S_OK;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func BOOL | fHasSpecificHardwareMatch |
*
* Find out from SetupAPI whether the device was matched with a
* specific hardware ID match or generic match.
* A specific match should have caused a device description to be
* installed which is likely to be at least as good as what HID could
* get from a product string in firmware. (a. because it's easier to
* update an INF after release than firmware; b. because HID can only
* get us an English string.) Generic matches on the other hand are,
* by definition, all the same so cannot be used to tell two devices
* apart.
*
* @parm LPTSTR ptszId
*
* Device Instance ID.
*
* @returns
* <c TRUE> if the device was installed using a specific match.
* <c FALSE> if it was not or if installation info was unobtainable.
*
* @comm
* This is used on Win2k for game controllers and Win9x for mice and
* keyboards. Win2k we can't read HID mice and keyboards and on
* Win9x VJoyD should always create device names before DInput.dll.
* On Win9x this is less of a big deal for game controllers because
* IHVs are accoustomed to adding their display name to
* MediaProperties.
*
*****************************************************************************/
BOOL fHasSpecificHardwareMatch( LPTSTR ptszId )
{
HDEVINFO hInfo;
BOOL fRc = FALSE;
EnterProcI(fHasSpecificHardwareMatch,(_ "s", ptszId));
hInfo = SetupDiCreateDeviceInfoList(NULL, NULL);
if( hInfo != INVALID_HANDLE_VALUE )
{
SP_DEVINFO_DATA dinf;
dinf.cbSize = cbX(SP_DEVINFO_DATA);
if( SetupDiOpenDeviceInfo(hInfo, ptszId, NULL, 0, &dinf) )
{
CONFIGRET cr;
DEVINST DevInst;
cr = CM_Get_Parent( &DevInst, dinf.DevInst, 0x0 );
if( cr == CR_SUCCESS )
{
TCHAR tszDevInst[MAX_PATH];
cr = CM_Get_Device_ID( DevInst, (DEVINSTID)tszDevInst, MAX_PATH, 0 );
if( cr == CR_SUCCESS )
{
if( SetupDiOpenDeviceInfo(hInfo, tszDevInst, NULL, 0, &dinf) )
{
HKEY hkDrv;
hkDrv = SetupDiOpenDevRegKey( hInfo, &dinf, DICS_FLAG_GLOBAL, 0,
DIREG_DRV, MAXIMUM_ALLOWED );
if( hkDrv != INVALID_HANDLE_VALUE )
{
PTCHAR tszHardwareID = NULL;
PTCHAR tszMatchingID = NULL;
ULONG ulLength = 0;
cr = CM_Get_DevNode_Registry_Property(DevInst,
CM_DRP_HARDWAREID,
NULL,
NULL,
&ulLength,
0x0 );
/*
* Win2k returns CR_BUFFER_SMALL but
* Win9x returns CR_SUCCESS so allow both.
*/
if( ( ( cr == CR_BUFFER_SMALL ) || ( cr == CR_SUCCESS ) )
&& ulLength )
{
#ifndef WINNT
/*
* Need to allocate extra for terminator on Win9x
*/
ulLength++;
#endif
if( SUCCEEDED( AllocCbPpv( ulLength + ( MAX_PATH * cbX(tszMatchingID[0]) ), &tszMatchingID ) ) )
{
cr = CM_Get_DevNode_Registry_Property(DevInst,
CM_DRP_HARDWAREID,
NULL,
(PBYTE)&tszMatchingID[MAX_PATH],
&ulLength,
0x0 );
if( cr == CR_SUCCESS )
{
tszHardwareID = &tszMatchingID[MAX_PATH];
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("CR error %d getting HW ID"), cr );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("No memory requesting %d bytes for HW ID"), ulLength );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("Unexpected CR error %d getting HW ID size"), cr );
}
if( tszHardwareID )
{
ulLength = MAX_PATH * cbX(tszMatchingID[0]);
cr = RegQueryValueEx( hkDrv, REGSTR_VAL_MATCHINGDEVID, 0, 0, (PBYTE)tszMatchingID, &ulLength );
if( CR_SUCCESS == cr )
{
while( ulLength = lstrlen( tszHardwareID ) )
{
if( !lstrcmpi( tszHardwareID, tszMatchingID ) )
{
fRc = TRUE;
break;
}
tszHardwareID += ulLength + 1;
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("No matching ID!, cr = %d"), cr );
}
}
if( tszMatchingID )
{
FreePv( tszMatchingID );
}
RegCloseKey( hkDrv );
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("SetupDiOpenDevRegKey failed, le = %d"), GetLastError() );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("SetupDiOpenDeviceInfo failed for %S (parent), le = %d"),
tszDevInst, GetLastError() );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("CM_Get_Device_ID FAILED %d"), cr );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("CM_Get_Parent FAILED %d"), cr );
}
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("SetupDiOpenDeviceInfo failed for %S (child), le = %d"),
ptszId, GetLastError() );
}
SetupDiDestroyDeviceInfoList(hInfo);
}
else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("SetupDiCreateDeviceInfoList failed, le = %d"), GetLastError() );
}
ExitProc();
return fRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func BOOL | fGetProductStringFromDevice |
*
* Try getting the product name from HID.
* If the device has one of these, this is what is displayed
* when the device is initially recognized. Unfortunately
* this name does not land up in the friendly name registry
* entry so in case this gets fixed we go directly to HID.
*
* @parm PCHID | this |
*
* The Hid object.
*
* @parm PWCHAR | wszBuffer |
*
* Where to put the product string if found.
*
* @parm ULONG | ulBufferLen |
*
* How big the string buffer is in bytes
*
* @returns
* <c TRUE> if a string has been placed in the buffer
* <c FALSE> if no string was retrieved
*
*****************************************************************************/
BOOL fGetProductStringFromDevice
(
PCHID this,
PWCHAR wszBuffer,
ULONG ulBufferLen
)
{
BOOL fRc;
/*
* If we already have a handle open (device is acquired), use
* it, otherwise open one just for now.
*/
if( this->hdev != INVALID_HANDLE_VALUE )
{
fRc = HidD_GetProductString( this->hdev, wszBuffer, ulBufferLen );
}
else
{
HANDLE hdev;
hdev = CHid_OpenDevicePath(this, FILE_FLAG_OVERLAPPED);
if(hdev != INVALID_HANDLE_VALUE)
{
wszBuffer[0] = 0;
fRc = HidD_GetProductString( hdev, wszBuffer, ulBufferLen );
fRc = (fRc)?(wszBuffer[0] != 0):FALSE;
CloseHandle(hdev);
}
else
{
fRc = FALSE;
}
}
return fRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetProperty |
*
* Get a Hid device property.
*
* @parm PCHID | this |
*
* The Hid object.
*
* @parm IN LPCDIPROPINFO | ppropi |
*
* Information describing the property being retrieved.
*
* @parm LPDIPROPHEADER | pdiph |
*
* Structure to receive property value.
*
* @returns
*
* <c E_NOTIMPL> nothing happened. The caller will do
* the default thing in response to <c E_NOTIMPL>.
*
*****************************************************************************/
#ifdef WINNT
TCHAR g_wszDefaultHIDName[80];
UINT g_uLenDefaultHIDSize;
#endif
STDMETHODIMP
CHid_GetProperty(PDICB pdcb, LPCDIPROPINFO ppropi, LPDIPROPHEADER pdiph)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::GetProperty,
(_ "pxxp", pdcb, ppropi->pguid, ppropi->iobj, pdiph));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
if(ppropi->iobj < this->df.dwNumObjs)
{ /* Object property */
AssertF(ppropi->dwDevType == this->df.rgodf[ppropi->iobj].dwType);
switch((DWORD)(UINT_PTR)(ppropi->pguid))
{
case (DWORD)(UINT_PTR)(DIPROP_ENABLEREPORTID):
{
LPDIPROPDWORD ppropdw = CONTAINING_RECORD(pdiph, DIPROPDWORD, diph);
PHIDGROUPCAPS pcaps = this->rghoc[ppropi->iobj].pcaps;
AssertF(fLimpFF(pcaps,
pcaps->dwSignature == HIDGROUPCAPS_SIGNATURE));
ppropdw->dwData = 0x0;
AssertF(pcaps->wReportId < this->wMaxReportId[pcaps->type]);
AssertF(this->pEnableReportId[pcaps->type]);
(UCHAR)ppropdw->dwData = *(this->pEnableReportId[pcaps->type] + pcaps->wReportId);
hres = S_OK;
}
break;
case (DWORD)(UINT_PTR)(DIPROP_PHYSICALRANGE):
{
LPDIPROPRANGE pdiprg = CONTAINING_RECORD(pdiph, DIPROPRANGE, diph);
PHIDGROUPCAPS pcaps = this->rghoc[ppropi->iobj].pcaps;
pdiprg->lMin = pcaps->Physical.Min;
pdiprg->lMax = pcaps->Physical.Max;
hres = S_OK;
break;
}
break;
case (DWORD)(UINT_PTR)(DIPROP_LOGICALRANGE):
{
LPDIPROPRANGE pdiprg = CONTAINING_RECORD(pdiph, DIPROPRANGE, diph);
PHIDGROUPCAPS pcaps = this->rghoc[ppropi->iobj].pcaps;
pdiprg->lMin = pcaps->Logical.Min;
pdiprg->lMax = pcaps->Logical.Max;
hres = S_OK;
break;
}
break;
default:
if(ppropi->dwDevType & DIDFT_POV)
{
PHIDGROUPCAPS pcaps = this->rghoc[ppropi->iobj].pcaps;
AssertF(fLimpFF(pcaps,
pcaps->dwSignature == HIDGROUPCAPS_SIGNATURE));
#ifdef WINNT
if( pcaps && pcaps->IsPolledPOV && ppropi->pguid == DIPROP_CALIBRATIONMODE ) {
PJOYRANGECONVERT pjrc = this->rghoc[ppropi->iobj].pjrc;
if(pjrc)
{
hres = CCal_GetProperty(pjrc, ppropi->pguid, pdiph, this->dwVersion);
} else
{
hres = E_NOTIMPL;
}
} else
#endif
if(pcaps && ppropi->pguid == DIPROP_GRANULARITY)
{
LPDIPROPDWORD pdipdw = (PV)pdiph;
pdipdw->dwData = pcaps->usGranularity;
hres = S_OK;
} else
{
hres = E_NOTIMPL;
}
} else if(ppropi->dwDevType & DIDFT_RELAXIS)
{
/*
* All relative axes have a full range by default,
* so we don't need to do anything.
*/
hres = E_NOTIMPL;
} else if(ppropi->dwDevType & DIDFT_ABSAXIS)
{
PJOYRANGECONVERT pjrc = this->rghoc[ppropi->iobj].pjrc;
/*
* Theoretically, every absolute axis will have
* calibration info. But test just in case something
* impossible happens.
*/
if(pjrc)
{
hres = CCal_GetProperty(pjrc, ppropi->pguid, pdiph, this->dwVersion);
} else
{
hres = E_NOTIMPL;
}
} else
{
SquirtSqflPtszV(sqflHidDev | sqflError,
TEXT("CHid_GetProperty(iobj=%08x): E_NOTIMPL on guid: %08x"),
ppropi->iobj, ppropi->pguid);
hres = E_NOTIMPL;
}
}
} else if(ppropi->iobj == 0xFFFFFFFF)
{ /* Device property */
switch((DWORD)(UINT_PTR)ppropi->pguid)
{
case (DWORD)(UINT_PTR)DIPROP_GUIDANDPATH:
hres = CHid_GetGuidAndPath(this, pdiph);
break;
case (DWORD)(UINT_PTR)DIPROP_INSTANCENAME:
{
/*
* DX8 CHANGE !
*
* Friendly names cause all manner of problems with devices that
* use auto detection so only allow non-predefined analog devices
* to use them.
*/
if( ( this->VendorID == MSFT_SYSTEM_VID )
&& ( this->ProductID >= MSFT_SYSTEM_PID + JOY_HW_PREDEFMAX )
&& ( ( this->ProductID & 0xff00 ) == MSFT_SYSTEM_PID ) )
{
AssertF(this->hkType);
if( this->hkType )
{
LPDIPROPSTRING pstr = (PV)pdiph;
hres = JoyReg_GetValue(this->hkType,
REGSTR_VAL_JOYOEMNAME, REG_SZ,
pstr->wsz,
cbX(pstr->wsz));
if( SUCCEEDED(hres ) )
{
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT( "Got instance name %s"), pstr->wsz );
#if (DIRECTINPUT_VERSION > 0x061A)
if( ( this->diHacks.nMaxDeviceNameLength < MAX_PATH )
&& ( this->diHacks.nMaxDeviceNameLength < lstrlenW(pstr->wsz) ) )
{
pstr->wsz[this->diHacks.nMaxDeviceNameLength] = L'\0';
}
#endif
hres = S_OK;
break;
}
}
}
/*
* Fall through to catch the product name
*/
}
/*
* DX8 CHANGE !
*
* In Win2k, this is the way devices get named. The original DX7
* used SetupAPI to get a friendly name (which only ever seems to be
* written by DInput) and if that failed, device description.
* Unfortunately Setup gives all devices matched with a generic match
* the same "USB Human Input Device" name, which is useless to game
* players. Devices listed specifically in input.inf have much
* better names but all new devices are hosed.
* See bug 32586 for more links.
*/
case (DWORD)(UINT_PTR)DIPROP_PRODUCTNAME:
{
LPDIPROPSTRING pdipstr = (PV)pdiph;
/*
* For now, don't deal with mice and keyboard names on NT
*/
#ifdef WINNT
AssertF( ( GET_DIDEVICE_TYPE( this->dwDevType ) != DIDEVTYPE_KEYBOARD )
&& ( GET_DIDEVICE_TYPE( this->dwDevType ) != DIDEVTYPE_MOUSE ) );
#endif
if( GET_DIDEVICE_TYPE( this->dwDevType ) < DIDEVTYPE_JOYSTICK )
{
if( fHasSpecificHardwareMatch( this->ptszId )
&& SUCCEEDED( hres = DIHid_GetParentRegistryProperty(this->ptszId, SPDRP_DEVICEDESC, pdiph, 0x0 ) ) )
{
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT("Got sys dev description %S"), pdipstr->wsz );
}
else if( fGetProductStringFromDevice( this, pdipstr->wsz, cbX( pdipstr->wsz ) ) )
{
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT( "Got sys dev name from device %S"), pdipstr->wsz );
hres = S_OK;
}
else
{
if( SUCCEEDED( hres = DIHid_GetRegistryProperty(this->ptszId, SPDRP_DEVICEDESC, pdiph ) ) )
{
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT( "Got sys dev name from devnode registry %S"), pdipstr->wsz );
}
else
{
UINT uDefName;
switch( GET_DIDEVICE_TYPE( this->dwDevType ) )
{
case DIDEVTYPE_MOUSE:
uDefName = IDS_STDMOUSE;
break;
case DIDEVTYPE_KEYBOARD:
uDefName = IDS_STDKEYBOARD;
break;
default:
uDefName = IDS_DEVICE_NAME;
break;
}
if( LoadStringW(g_hinst, uDefName, pdipstr->wsz, cA( pdipstr->wsz ) ) )
{
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT( "Loaded default sys dev name %S"), pdipstr->wsz );
hres = S_OK;
}
else
{
/*
* Give up, this machine is toast if we can't
* even load a string from our own resources.
*/
SquirtSqflPtszV(sqflHidDev | sqflError,
TEXT("CHid_GetProperty(guid:%08x) failed to get name"),
ppropi->pguid);
hres = E_FAIL;
}
}
}
}
else
{
/*
* For game controllers, first look in MediaProperties.
* This is the most likely place to find a localized string
* free from corruption by the setup process.
* This should only fail before the type key is created when
* it first used so other paths are rare.
*/
DIJOYTYPEINFO dijti;
WCHAR wszType[cbszVIDPID];
/* Check the type key or get predefined name */
ZeroX(dijti);
dijti.dwSize = cbX(dijti);
if( ( this->VendorID == MSFT_SYSTEM_VID )
&&( ( this->ProductID >= MSFT_SYSTEM_PID + JOY_HW_PREDEFMIN )
&&( this->ProductID < MSFT_SYSTEM_PID + JOY_HW_PREDEFMAX ) ) )
{
wszType[0] = L'#';
wszType[1] = L'0' + (WCHAR)(this->ProductID-MSFT_SYSTEM_PID);
wszType[2] = L'\0';
hres = JoyReg_GetPredefTypeInfo( wszType, &dijti, DITC_DISPLAYNAME);
AssertF( SUCCEEDED( hres ) );
AssertF( dijti.wszDisplayName[0] != L'\0' );
lstrcpyW(pdipstr->wsz, dijti.wszDisplayName);
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT( "Got name as predefined %s"), pdipstr->wsz );
}
else
{
#ifndef WINNT
static WCHAR wszDefHIDName[] = L"HID Game Controller";
#endif
BOOL fOverwriteDeviceName = FALSE;
#ifndef UNICODE
TCHAR tszType[cbszVIDPID];
wsprintf(tszType, VID_PID_TEMPLATE, this->VendorID, this->ProductID);
TToU( wszType, cA(wszType), tszType );
#else
wsprintf(wszType, VID_PID_TEMPLATE, this->VendorID, this->ProductID);
#endif
#ifdef WINNT
#define INPUT_INF_FILENAME L"\\INF\\INPUT.INF"
if( g_wszDefaultHIDName[0] == L'\0' )
{
WCHAR wszInputINF[MAX_PATH];
UINT uLen;
uLen = GetWindowsDirectoryW( wszInputINF, cA( wszInputINF ) );
/*
* If the path is too long, don't set the filename
* so the the default string gets used when the
* GetPrivateProfileString fails.
*/
if( uLen < cA( wszInputINF ) - cA( INPUT_INF_FILENAME ) )
{
memcpy( (PBYTE)&wszInputINF[uLen], (PBYTE)INPUT_INF_FILENAME, cbX( INPUT_INF_FILENAME ) );
}
/*
* Remember the length, if the string was too long to
* fit in the buffer there will be plenty to make a
* reasonable comparison.
*/
g_uLenDefaultHIDSize = 2 * GetPrivateProfileStringW(
L"strings", L"HID.DeviceDesc", L"USB Human Interface Device",
g_wszDefaultHIDName, cA( g_wszDefaultHIDName ) - 1, wszInputINF );
}
#undef INPUT_INF_FILENAME
#endif //#ifdef WINNT
if( SUCCEEDED(hres = JoyReg_GetTypeInfo(wszType, &dijti, DITC_DISPLAYNAME))
&& (dijti.wszDisplayName[0] != L'\0')
#ifdef WINNT
&& ( (g_uLenDefaultHIDSize == 0)
|| memcmp(dijti.wszDisplayName, g_wszDefaultHIDName, g_uLenDefaultHIDSize) )// not equal
#else
&& memcmp(dijti.wszDisplayName, wszDefHIDName, cbX(wszDefHIDName)-2) //not equal
#endif
)
{
lstrcpyW(pdipstr->wsz, dijti.wszDisplayName);
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT("Got name from type info %s"), pdipstr->wsz );
}
#ifdef WINNT
else if( fHasSpecificHardwareMatch( this->ptszId )
&& SUCCEEDED( hres = DIHid_GetParentRegistryProperty(this->ptszId, SPDRP_DEVICEDESC, pdiph, 0x0 ) ) )
{
fOverwriteDeviceName = TRUE;
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT("Got specific description %s"), pdipstr->wsz );
}
#endif
else
{
if( fGetProductStringFromDevice( this, pdipstr->wsz, cbX( pdipstr->wsz ) ) )
{
fOverwriteDeviceName = TRUE;
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT("Got description %s from device"), pdipstr->wsz );
}
else
{
/*
* Just make up a name from the caps
*/
CType_MakeGameCtrlName( pdipstr->wsz,
this->dwDevType, this->dwAxes, this->dwButtons, this->dwPOVs );
fOverwriteDeviceName = TRUE;
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT("Made up name %s"), pdipstr->wsz );
}
hres = S_OK;
}
if( fOverwriteDeviceName ) {
/*
* If we have a better name, overwrite the old one with this better one.
* See manbug 46438.
*/
AssertF(this->hkType);
AssertF(pdipstr->wsz[0]);
hres = JoyReg_SetValue(this->hkType,
REGSTR_VAL_JOYOEMNAME, REG_SZ,
pdipstr->wsz,
cbX(pdipstr->wsz));
if( FAILED(hres) ){
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT("Unable to overwrite generic device name with %s"), pdipstr->wsz );
// This failure (unlikely) doesn't matter.
hres = S_OK;
}
}
}
}
#if (DIRECTINPUT_VERSION > 0x061A)
if( SUCCEEDED(hres)
&& ( this->diHacks.nMaxDeviceNameLength < MAX_PATH )
&& ( this->diHacks.nMaxDeviceNameLength < lstrlenW(pdipstr->wsz) ) )
{
pdipstr->wsz[this->diHacks.nMaxDeviceNameLength] = L'\0';
}
#endif
break;
}
case (DWORD)(UINT_PTR)DIPROP_JOYSTICKID:
if( fHasAllBitsFlFl( this->dwDevType, DIDEVTYPE_JOYSTICK | DIDEVTYPE_HID ) )
{
LPDIPROPDWORD pdipdw = (PV)pdiph;
pdipdw->dwData = this->idJoy;
hres = S_OK;
} else
{
hres = E_NOTIMPL;
}
break;
case (DWORD)(UINT_PTR)DIPROP_GETPORTDISPLAYNAME:
if( fWinnt )
{
/* For HID devices Port Display Name is the grand parent name */
hres = DIHid_GetParentRegistryProperty(this->ptszId, SPDRP_FRIENDLYNAME, pdiph, TRUE);
if( FAILED(hres) )
{
/* Maybe we can use the Product Name */
hres = DIHid_GetParentRegistryProperty(this->ptszId, SPDRP_DEVICEDESC, pdiph, TRUE);
if( SUCCEEDED(hres) )
{
/* We only sort of succeeded */
hres = S_FALSE;
}
}
#if (DIRECTINPUT_VERSION > 0x061A)
if( SUCCEEDED(hres) )
{
LPDIPROPSTRING pdipstr = (PV)pdiph;
if( this->diHacks.nMaxDeviceNameLength < lstrlenW(pdipstr->wsz) ) {
pdipstr->wsz[this->diHacks.nMaxDeviceNameLength] = L'\0';
}
}
#endif
} else
{
// Not sure how this works on Win9x
hres = E_NOTIMPL;
}
break;
case (DWORD)(UINT_PTR)(DIPROP_ENABLEREPORTID):
hres = E_NOTIMPL;
break;
default:
SquirtSqflPtszV(sqflHid | sqflBenign ,
TEXT("CHid_GetProperty(iobj=0xFFFFFFFF): E_NOTIMPL on guid: %08x"),
ppropi->pguid);
hres = E_NOTIMPL;
break;
}
} else
{
SquirtSqflPtszV(sqflHidDev | sqflError,
TEXT("CHid_GetProperty(iobj=%08x): E_NOTIMPL on guid: %08x"),
ppropi->iobj, ppropi->pguid);
hres = E_NOTIMPL;
}
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func LONG | CHid_CoordinateTransform |
*
* Convert numbers from logical to physical or vice versa.
*
* If either the To or From values look suspicious, then
* ignore them and leave the values alone.
*
* @parm PLMINMAX | Dst |
*
* Destination min/max information.
*
* @parm PLMINMAX | Src |
*
* Source min/max information.
*
* @parm LONG | lVal |
*
* Source value to be converted.
*
* @returns
*
* The destination value after conversion.
*
*****************************************************************************/
LONG EXTERNAL
CHid_CoordinateTransform(PLMINMAX Dst, PLMINMAX Src, LONG lVal)
{
/*
* Note that the sanity check is symmetric in Src and Dst.
* This is important, so that we never get into a weird
* case where we can convert one way but can't convert back.
*/
if(Dst->Min < Dst->Max && Src->Min < Src->Max)
{
/*
* We need to perform a straight linear interpolation.
* The math comes out like this:
*
* x - x0 y - y0
* ------- = -------
* x1 - x0 y1 - y0
*
* If you now do a "solve for y", you get
*
*
* y1 - y0
* y = (x - x0) ------- + y0
* x1 - x0
*
* where "x" is Src, "y" is Dst, 0 is Min, and 1 is Max.
*
*
*/
lVal = MulDiv(lVal - Src->Min, Dst->Max - Dst->Min,
Src->Max - Src->Min) + Dst->Min;
}
return lVal;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method int | CHid | IsMatchingJoyDevice |
*
* Does the cached joystick ID match us?
*
* @parm OUT PVXDINITPARMS | pvip |
*
* On success, contains parameter values.
*
* @returns
*
* Nonzero on success.
*
*****************************************************************************/
BOOL INTERNAL
CHid_IsMatchingJoyDevice(PCHID this, PVXDINITPARMS pvip)
{
CHAR sz[MAX_PATH];
LPSTR pszPath;
BOOL fRc;
pszPath = JoyReg_JoyIdToDeviceInterface_95(this->idJoy, pvip, sz);
if(pszPath)
{
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("CHid_IsMatchingJoyDevice: %d -> %s"),
this->idJoy, pszPath);
#ifdef UNICODE
{
CHAR szpath[MAX_PATH];
UToA( szpath, cA(szpath), (LPWSTR)this->ptszPath);
fRc = ( lstrcmpiA(pszPath, szpath) == 0x0 );
}
#else
fRc = ( lstrcmpiA(pszPath, (PCHAR)this->ptszPath) == 0x0 );
#endif
} else
{
fRc = FALSE;
}
return fRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | FindJoyDevice |
*
* Look for the VJOYD device that matches us, if any.
*
* On return, the <e CHID.idJoy> field contains the
* matching joystick number, or -1 if not found.
*
* @parm OUT PVXDINITPARMS | pvip |
*
* On success, contains parameter values.
*
*****************************************************************************/
void INTERNAL
CHid_FindJoyDevice(PCHID this, PVXDINITPARMS pvip)
{
/*
* If we have a cached value, and it still works, then
* our job is done.
*/
if(this->idJoy >= 0 &&
CHid_IsMatchingJoyDevice(this, pvip))
{
} else
{
/*
* Need to keep looking. (Or start looking.)
*
* A countdown loop is nicer, but for efficiency, we count
* upwards, since the joystick we want tends to be near the
* beginning.
*/
for(this->idJoy = 0; this->idJoy < cJoyMax; this->idJoy++)
{
if(CHid_IsMatchingJoyDevice(this, pvip))
{
goto done;
}
}
this->idJoy = -1;
}
done:;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method int | CHid | MapAxis |
*
* Find VJOYD axis from HID axis, if one.
*
* @parm PVXDINITPARMS | pvip |
*
* Parameter values that let us known which axes VJOYD
* has mapped to which HID Axes.
*
* @parm UINT | iobj |
*
* Object index of the object whose axis value changed.
*
* @returns
*
* The VJOYD axis number that changed (0 to 5), or -1
* if there is no matching axis. There will be no matching
* axis if, for example, the device has something that is
* not expressible via VJOYD (e.g., a temperature sensor).
*
*****************************************************************************/
int INTERNAL
CHid_MapAxis(PCHID this, PVXDINITPARMS pvip, UINT iobj)
{
int iAxis;
DWORD dwUsage;
AssertF(this->dcb.lpVtbl->GetUsage == CHid_GetUsage);
dwUsage = CHid_GetUsage(&this->dcb, (int)iobj);
if(dwUsage)
{
/*
* A countdown loop lets us fall out with the correct failure
* code (namely, -1).
*/
iAxis = cJoyPosAxisMax;
while(--iAxis >= 0)
{
if(pvip->Usages[iAxis] == dwUsage)
{
break;
}
}
} else
{
/*
* Eek! No usage information for the axis. Then it certainly
* isn't a VJOYD axis.
*/
iAxis = -1;
}
return iAxis;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | UpdateVjoydCalibration |
*
* Somebody changed the calibration on a single axis. If we
* are shadowing a joystick, then look for the VJOYD alias of
* our device and update its registry settings, too.
*
*
* @parm UINT | iobj |
*
* Object index of the object whose calibration changed.
*
*****************************************************************************/
void EXTERNAL
CHid_UpdateVjoydCalibration(PCHID this, UINT iobj)
{
HRESULT hres;
int iAxis;
VXDINITPARMS vip;
DIJOYCONFIG cfg;
PHIDGROUPCAPS pcaps;
PJOYRANGECONVERT pjrc;
AssertF(iobj < this->df.dwNumObjs);
/*
* Proceed if...
*
* - We can find the VJOYD device we correspond to.
* - We can find the axis that got updated.
* - The indicated axis has capability information.
* - The indicated axis has calibration information.
* - We can read the old calibration information.
*/
CHid_FindJoyDevice(this, &vip);
if(this->idJoy >= 0 &&
(iAxis = CHid_MapAxis(this, &vip, iobj)) >= 0 &&
(pcaps = this->rghoc[iobj].pcaps) != NULL &&
(pjrc = this->rghoc[iobj].pjrc) != NULL &&
SUCCEEDED(hres = JoyReg_GetConfig(this->idJoy, NULL, &cfg,
DIJC_REGHWCONFIGTYPE)))
{
PLMINMAX Dst = &pcaps->Physical;
PLMINMAX Src = &pcaps->Logical;
AssertF(iAxis < cJoyPosAxisMax);
#define JoyPosValue(phwc, f, i) \
*(LPDWORD)pvAddPvCb(&(phwc)->hwv.jrvHardware.f, \
ibJoyPosAxisFromPosAxis(i))
/*
* We use logical coordinates, but VJOYD wants physical
* coordinates, so do the conversion while we copy the
* values.
*/
#define ConvertValue(f1, f2) \
JoyPosValue(&cfg.hwc, f1, iAxis) = \
CHid_CoordinateTransform(Dst, Src, pjrc->f2) \
ConvertValue(jpMin , dwPmin);
ConvertValue(jpMax , dwPmax);
ConvertValue(jpCenter, dwPc );
#undef ConvertValue
#undef JoyPosValue
/*
* Notice that we do *not* pass the DIJC_UPDATEALIAS flag
* because WE ARE THE ALIAS! If we had passed the flag,
* then JoyReg would create us and attempt to update our
* calibration which we don't want it to do because the
* whole thing was our idea in the first place.
*/
hres = JoyReg_SetConfig(this->idJoy, &cfg.hwc, &cfg,
DIJC_REGHWCONFIGTYPE);
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | UpdateCalibrationFromVjoyd |
*
* This function is only for Win9x. Joy.cpl uses winmm (through vjoyd)
* to calibrate the device, and save calibration information directly into
* registry without notifying HID. ANother issue is: vjoyd only use unsigned
* data (physical data), while HID also use signed data. When we read
* calibration information from VJOYD, we need do conversion.
*
* @parm UINT | iobj |
*
* Object index of the object whose calibration changed.
*
*****************************************************************************/
void EXTERNAL
CHid_UpdateCalibrationFromVjoyd(PCHID this, UINT iobj, LPDIOBJECTCALIBRATION pCal)
{
HRESULT hres;
int iAxis;
VXDINITPARMS vip;
DIJOYCONFIG cfg;
PHIDGROUPCAPS pcaps;
PJOYRANGECONVERT pjrc;
AssertF(iobj < this->df.dwNumObjs);
/*
* Proceed if...
*
* - We can find the VJOYD device we correspond to.
* - We can find the axis that got updated.
* - The indicated axis has capability information.
* - The indicated axis has calibration information.
* - We can read the calibration information.
*/
CHid_FindJoyDevice(this, &vip);
if(this->idJoy >= 0 &&
(iAxis = CHid_MapAxis(this, &vip, iobj)) >= 0 &&
(pcaps = this->rghoc[iobj].pcaps) != NULL &&
(pjrc = this->rghoc[iobj].pjrc) != NULL &&
SUCCEEDED(hres = JoyReg_GetConfig(this->idJoy, NULL, &cfg,
DIJC_REGHWCONFIGTYPE)))
{
PLMINMAX Src = &pcaps->Physical;
PLMINMAX Dst = &pcaps->Logical;
AssertF(iAxis < cJoyPosAxisMax);
#define JoyPosValue(phwc, f, i) \
*(LPDWORD)pvAddPvCb(&(phwc)->hwv.jrvHardware.f, \
ibJoyPosAxisFromPosAxis(i))
/*
* We use logical coordinates, but VJOYD wants physical
* coordinates, so do the conversion while we copy the
* values.
*/
#define ConvertValue(f1, f2) \
pCal->f2 = CHid_CoordinateTransform(Dst, Src, \
JoyPosValue(&cfg.hwc, f1, iAxis) )
ConvertValue(jpMin , lMin);
ConvertValue(jpMax , lMax);
ConvertValue(jpCenter, lCenter);
#undef ConvertValue
#undef JoyPosValue
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func HRESULT | DIHid_SetRegistryProperty |
*
* Wrapper around <f SetupDiSetDeviceRegistryProperty>
* that handles character set issues.
*
* @parm LPTSTR ptszId
*
* Device Instance ID.
*
* @parm DWORD | dwProperty |
*
* The property being queried.
*
* @parm LPCDIPROPHEADER | diph |
*
* Property data to be set.
*
*****************************************************************************/
HRESULT INTERNAL
DIHid_SetParentRegistryProperty(LPTSTR ptszId, DWORD dwProperty, LPCDIPROPHEADER pdiph, BOOL bGrandParent)
{
HDEVINFO hdev;
TCHAR tsz[MAX_PATH];
LPDIPROPSTRING pstr = (PV)pdiph;
HRESULT hres = E_FAIL;
hdev = SetupDiCreateDeviceInfoList(NULL, NULL);
if(hdev != INVALID_HANDLE_VALUE)
{
SP_DEVINFO_DATA dinf;
ZeroX(tsz);
#ifdef UNICODE
lstrcpyW(tsz, pstr->wsz);
#else
UToA(tsz, cA(tsz), pstr->wsz);
#endif
dinf.cbSize = cbX(SP_DEVINFO_DATA);
if(SetupDiOpenDeviceInfo(hdev, ptszId, NULL, 0, &dinf))
{
CONFIGRET cr;
DEVINST DevInst;
if( (cr = CM_Get_Parent(&DevInst, dinf.DevInst, 0x0) ) == CR_SUCCESS )
{
CAssertF( SPDRP_DEVICEDESC +1 == CM_DRP_DEVICEDESC );
CAssertF( SPDRP_FRIENDLYNAME +1 == CM_DRP_FRIENDLYNAME );
if(bGrandParent)
{
cr = CM_Get_Parent(&DevInst, DevInst, 0x0);
if( cr != CR_SUCCESS )
{
// No GrandParent ??
}
}
if( ( cr = CM_Set_DevNode_Registry_Property(
DevInst,
dwProperty+1,
(LPBYTE)tsz,
MAX_PATH *cbX(TCHAR),
0x0 ) ) == CR_SUCCESS )
{
hres = S_OK;
} else
{
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CM_Get_DevNode_Registry_Property FAILED") );
}
} else
{
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CM_Get_Parent FAILED") );
}
} else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("SetupDiOpenDeviceInfo FAILED, le = %d"), GetLastError() );
}
SetupDiDestroyDeviceInfoList(hdev);
} else
{
SquirtSqflPtszV(sqfl | sqflError,
TEXT("SetupDiCreateDeviceInfoList FAILED, le = %d"), GetLastError() );
}
return hres;
}
HRESULT INTERNAL
DIHid_SetRegistryProperty(LPTSTR ptszId, DWORD dwProperty, LPCDIPROPHEADER pdiph)
{
HDEVINFO hdev;
TCHAR tsz[MAX_PATH];
LPDIPROPSTRING pstr = (PV)pdiph;
HRESULT hres;
hdev = SetupDiCreateDeviceInfoList(NULL, NULL);
if(hdev != INVALID_HANDLE_VALUE)
{
SP_DEVINFO_DATA dinf;
ZeroX(tsz);
#ifdef UNICODE
lstrcpyW(tsz, pstr->wsz);
#else
UToA(tsz, cA(tsz), pstr->wsz);
#endif
dinf.cbSize = cbX(SP_DEVINFO_DATA);
if(SetupDiOpenDeviceInfo(hdev, ptszId, NULL, 0, &dinf))
{
if(SetupDiSetDeviceRegistryProperty(hdev, &dinf, dwProperty,
(LPBYTE)tsz, MAX_PATH*cbX(TCHAR)) )
{
hres = S_OK;
} else
{
hres = E_FAIL;
}
} else
{
hres = E_FAIL;
}
SetupDiDestroyDeviceInfoList(hdev);
} else
{
hres = E_FAIL;
}
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | SetProperty |
*
* Set a hid device property.
*
* @parm PCHID | this |
*
* The hid object.
*
* @parm IN LPCDIPROPINFO | ppropi |
*
* Information describing the property being set.
*
* @parm LPCDIPROPHEADER | pdiph |
*
* Structure containing property value.
*
* @returns
*
* <c E_NOTIMPL> nothing happened. The caller will do
* the default thing in response to <c E_NOTIMPL>.
*
*****************************************************************************/
STDMETHODIMP
CHid_SetProperty(PDICB pdcb, LPCDIPROPINFO ppropi, LPCDIPROPHEADER pdiph)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::SetProperty,
(_ "pxxp", pdcb, ppropi->pguid, ppropi->iobj, pdiph));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
if(ppropi->iobj < this->df.dwNumObjs)
{
/*
* Object Property
*/
PHIDGROUPCAPS pcaps;
AssertF(ppropi->dwDevType == this->df.rgodf[ppropi->iobj].dwType);
AssertF(ppropi->iobj == CHid_ObjFromType(this, ppropi->dwDevType));
if( pcaps = this->rghoc[ppropi->iobj].pcaps )
{
switch((DWORD)(UINT_PTR)ppropi->pguid)
{
case (DWORD)(UINT_PTR)(DIPROP_ENABLEREPORTID):
{
LPDIPROPDWORD ppropdw = CONTAINING_RECORD(pdiph, DIPROPDWORD, diph);
AssertF(pcaps->wReportId < this->wMaxReportId[pcaps->type]);
AssertF(this->pEnableReportId[pcaps->type]);
hres = S_OK;
if( ppropdw->dwData == 0x1 )
{
*(this->pEnableReportId[pcaps->type] + pcaps->wReportId) = 0x1;
pcaps->fReportDisabled = FALSE;
} else
{
*(this->pEnableReportId[pcaps->type] + pcaps->wReportId) = 0x0;
pcaps->fReportDisabled = TRUE;
}
}
break;
default:
{ /* Object property */
PJOYRANGECONVERT pjrc;
AssertF(ppropi->dwDevType == this->df.rgodf[ppropi->iobj].dwType);
AssertF(ppropi->iobj == CHid_ObjFromType(this, ppropi->dwDevType));
if((pjrc = this->rghoc[ppropi->iobj].pjrc) &&
(pcaps = this->rghoc[ppropi->iobj].pcaps))
{
if( ppropi->dwDevType == DIDFT_POV )
{
#ifdef WINNT
/*
* Only allow POV calibration for the private
* DX5 version used by GCDEF. This stops WinMM
* and Nascar 4 from getting unexpected raw
* data for POVs when polling for raw axes.
*/
if( ( this->dwVersion == 0x5B2 )
&& ( pcaps->IsPolledPOV ) )
{
hres = CCal_SetProperty(pjrc, ppropi, pdiph, this->hkInstType, this->dwVersion);
if( SUCCEEDED(hres) ) {
CHid_LoadCalibrations(this);
/*
* If this doesn't succeed, no big deal. So, we needn't check hres.
*/
hres = CHid_InitParseData( this );
}
} else
#endif
{
hres = E_NOTIMPL;
}
} else if (ppropi->dwDevType & DIDFT_RELAXIS)
{
/*
* All relative axes have a full range by default,
* so we don't need to do anything.
*/
hres = E_NOTIMPL;
} else if(ppropi->dwDevType & DIDFT_ABSAXIS)
{
/*
* Specific calibrations arrive in VJOYD coordinates.
* We need to convert them to DirectInput (logical)
* coordinates if so.
*/
DIPROPCAL cal;
if(ppropi->pguid == DIPROP_SPECIFICCALIBRATION)
{
PLMINMAX Dst = &pcaps->Logical;
PLMINMAX Src = &pcaps->Physical;
LPDIPROPCAL pcal = CONTAINING_RECORD(pdiph, DIPROPCAL, diph);
cal.lMin = CHid_CoordinateTransform(Dst, Src, pcal->lMin);
cal.lCenter = CHid_CoordinateTransform(Dst, Src, pcal->lCenter);
cal.lMax = CHid_CoordinateTransform(Dst, Src, pcal->lMax);
pdiph = &cal.diph;
}
hres = CCal_SetProperty(pjrc, ppropi, pdiph, this->hkInstType, this->dwVersion);
/*
* If we successfully changed the calibration of a joystick
* device, then see if it's a VJOYD device.
*/
if(SUCCEEDED(hres) &&
ppropi->pguid == DIPROP_CALIBRATION &&
GET_DIDEVICE_TYPE(this->dwDevType) == DIDEVTYPE_JOYSTICK)
{
CHid_UpdateVjoydCalibration(this, ppropi->iobj);
}
/*
* We've been call by an app so there's no point in calling
* Common_Hold/Unhold around this.
*/
CHid_LoadCalibrations(this);
if( SUCCEEDED(hres) )
{
/*
* If this doesn't succeed, no big deal. So, we needn't check hres.
*/
hres = CHid_InitParseData( this );
}
} else {
hres = E_NOTIMPL;
}
} else
{
hres = E_NOTIMPL;
}
}
}
} else
{
SquirtSqflPtszV(sqflHidDev | sqflError,
TEXT("CHid_SetProperty FAILED due to missing caps for type 0x%08x, obj %d"),
ppropi->dwDevType, ppropi->iobj );
hres = E_NOTIMPL;
}
} else if(ppropi->iobj == 0xFFFFFFFF)
{ /* Device property */
switch((DWORD)(UINT_PTR)ppropi->pguid)
{
case (DWORD)(UINT_PTR)DIPROP_GUIDANDPATH:
SquirtSqflPtszV(sqflHidDev | sqflError,
TEXT("CHid_SetProperty(iobj=%08x): PROP_GUIDANDPATH is read only.") );
hres = E_NOTIMPL;
break;
case (DWORD)(UINT_PTR)DIPROP_INSTANCENAME:
/*
* DX8 CHANGE !
*
* Friendly names cause all manner of problems with devices that
* use auto detection so only allow non-predefined analog devices
* to use them.
*/
if( ( this->VendorID == MSFT_SYSTEM_VID )
&& ( this->ProductID >= MSFT_SYSTEM_PID + JOY_HW_PREDEFMAX )
&& ( ( this->ProductID & 0xff00 ) == MSFT_SYSTEM_PID ) )
{
AssertF(this->hkType);
if( this->hkType )
{
LPDIPROPSTRING pstr = (PV)pdiph;
hres = JoyReg_SetValue(this->hkType,
REGSTR_VAL_JOYOEMNAME, REG_SZ,
pstr->wsz,
cbX(pstr->wsz));
if( SUCCEEDED(hres ) )
{
SquirtSqflPtszV(sqflHid | sqflVerbose,
TEXT( "Set instance name %s"), pstr->wsz );
hres = S_OK;
} else {
hres = E_FAIL;
}
} else {
hres = E_FAIL;
}
}
else
{
/*
* GenJ returns E_NOTIMPL for this property so do the same
*/
hres = E_NOTIMPL;
}
break;
case (DWORD)(UINT_PTR)DIPROP_PRODUCTNAME:
if(fWinnt)
{
hres = DIHid_SetParentRegistryProperty(this->ptszId, SPDRP_DEVICEDESC, pdiph, 0x0 );
} else
{
hres = DIHid_SetRegistryProperty(this->ptszId, SPDRP_DEVICEDESC, pdiph);
}
break;
case (DWORD)(UINT_PTR)(DIPROP_ENABLEREPORTID):
{
LPDIPROPDWORD ppropdw = CONTAINING_RECORD(pdiph, DIPROPDWORD, diph);
UINT iType;
if( ppropdw->dwData == 0x0 )
{
for( iType = 0x0; iType < HidP_Max; iType++)
{
ZeroBuf(this->pEnableReportId[iType], this->wMaxReportId[iType]);
}
} else
{
for( iType = 0x0; iType < HidP_Max; iType++)
{
memset(this->pEnableReportId[iType], 0x1, this->wMaxReportId[iType]);
}
}
hres = S_OK;
}
break;
case (DWORD)(UINT_PTR)DIPROP_RANGE:
case (DWORD)(UINT_PTR)DIPROP_DEADZONE:
case (DWORD)(UINT_PTR)DIPROP_SATURATION:
case (DWORD)(UINT_PTR)DIPROP_CALIBRATIONMODE:
case (DWORD)(UINT_PTR)DIPROP_CALIBRATION:
{
/*
* Post DX7 Gold fix
* For axis properties, iterate through all objects on the
* device, setting the property on each absolute axis.
*/
/*
* ISSUE-2001/03/29-timgill DX7 compat fix should be fixed for ME
* For minimum delta, go through a whole callback set
* property for each axis. For Millennium this should
* be fixed to use a common subroutine.
*/
DIPROPCAL axisprop;
DIPROPINFO axispropinfo;
INT iObj;
HRESULT hresAxis;
axispropinfo.pguid = ppropi->pguid;
/*
* The largest property data we handle here is for the
* DIPROP_CALIBRATION.
*/
AssertF( pdiph->dwSize <= cbX( axisprop ) );
/*
* Copy whatever we have and modify it for each axis
*/
memcpy( &axisprop, pdiph, pdiph->dwSize );
axisprop.diph.dwHow = DIPH_BYID;
/*
* Make sure we only report real failures.
*/
hres = S_OK;
for( iObj = this->df.dwNumObjs; iObj >= 0; iObj-- )
{
if( ( ( this->df.rgodf[iObj].dwType
& ( DIDFT_ALIAS | DIDFT_VENDORDEFINED | DIDFT_OUTPUT | DIDFT_ABSAXIS ) )
== DIDFT_ABSAXIS )
#ifdef WINNT
|| ( ( this->df.rgodf[iObj].dwType
& ( DIDFT_ALIAS | DIDFT_VENDORDEFINED | DIDFT_OUTPUT | DIDFT_POV ) )
== DIDFT_POV )
#endif
)
{
axisprop.diph.dwObj = axispropinfo.dwDevType = this->df.rgodf[iObj].dwType;
axispropinfo.iobj = (UINT)iObj;
hresAxis = CHid_SetProperty(pdcb, (LPCDIPROPINFO)&axispropinfo, &axisprop.diph );
if( FAILED( hresAxis ) && ( hresAxis != E_NOTIMPL ) )
{
hres = hresAxis;
break;
}
}
}
}
break;
default:
SquirtSqflPtszV(sqflHidDev| sqflBenign,
TEXT("CHid_SetProperty(iobj=%08x): E_NOTIMPL on guid: %08x"),
ppropi->iobj, ppropi->pguid);
hres = E_NOTIMPL;
break;
}
} else
{
SquirtSqflPtszV(sqflHidDev | sqflError,
TEXT("CHid_SetProperty(iobj=%08x): E_NOTIMPL on guid: %08x"),
ppropi->iobj, ppropi->pguid);
hres = E_NOTIMPL;
}
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | GetCapabilities |
*
* Get Hid device capabilities.
*
* @parm LPDIDEVCAPS | pdc |
*
* Device capabilities structure to receive result.
*
* @returns
* <c S_OK> on success.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetCapabilities(PDICB pdcb, LPDIDEVCAPS pdc)
{
HRESULT hres;
PCHID this;
HANDLE h;
EnterProcI(IDirectInputDeviceCallback::Hid::GetCapabilities,
(_ "pp", pdcb, pdc));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
/*
* We must check connectivity by opening the device, because NT
* leaves the device in the info list even though it has
* been unplugged.
*/
h = CHid_OpenDevicePath(this, FILE_FLAG_OVERLAPPED);
if(h != INVALID_HANDLE_VALUE)
{
CloseHandle(h);
if( !fWinnt )
{
VXDINITPARMS vip;
CHid_FindJoyDevice(this, &vip);
if( TRUE == CHid_IsMatchingJoyDevice(this, &vip) )
{
#ifdef DEBUG //always use HID path
TCHAR szJoyProp[] = REGSTR_PATH_PRIVATEPROPERTIES TEXT("\\Joystick");
HKEY hkJoyProp;
TCHAR szUseHid[] = TEXT("UseHidPath");
DWORD dwUseHid;
hres = hresMumbleKeyEx(HKEY_LOCAL_MACHINE,
szJoyProp,
DI_KEY_ALL_ACCESS,
REG_OPTION_NON_VOLATILE,
&hkJoyProp);
if( SUCCEEDED(hres) )
{
DWORD cb = sizeof(dwUseHid);
LONG lRc;
lRc = RegQueryValueEx(hkJoyProp, szUseHid, 0, 0, (LPBYTE)&dwUseHid, &cb);
if( lRc != ERROR_SUCCESS )
{
DWORD dwDefault = 1;
dwUseHid = dwDefault;
lRc = RegSetValueEx(hkJoyProp, szUseHid, 0, REG_DWORD, (LPBYTE)&dwDefault, cb);
}
RegCloseKey(hkJoyProp);
}
if( !dwUseHid )
{
pdc->dwFlags |= DIDC_ALIAS ;
}
#endif
}
}
#if !defined(WINNT) && DIRECTINPUT_VERSION > 0x050A
if( ( this->dwVersion < 0x0700 ) && ( this->dwVersion != 0x05B2 ) )
{
/*
* Post DX7 Gold Fix
* Keep this an alias for older apps.
*/
pdc->dwFlags |= DIDC_ALIAS;
}
else if( this->hkType )
{
DWORD dwFlags1;
if( SUCCEEDED( JoyReg_GetValue( this->hkType,
REGSTR_VAL_FLAGS1, REG_BINARY,
&dwFlags1,
cbX(dwFlags1) ) ) )
{
if( dwFlags1 & JOYTYPE_NOHIDDIRECT )
{
pdc->dwFlags |= DIDC_ALIAS;
}
}
}
#endif
if( this->pvi->fl & VIFL_UNPLUGGED )
{
pdc->dwFlags &= ~DIDC_ATTACHED;
} else
{
pdc->dwFlags |= DIDC_ATTACHED;
}
} else
{
pdc->dwFlags &= ~DIDC_ATTACHED;
}
if( this->IsPolledInput )
{
pdc->dwFlags |= DIDC_POLLEDDEVICE;
}
pdc->dwDevType = this->dwDevType;
pdc->dwAxes = this->dwAxes;
pdc->dwButtons = this->dwButtons;
pdc->dwPOVs = this->dwPOVs;
hres = S_OK;
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetDeviceState |
*
* Obtains the state of the Hid device.
*
* It is the caller's responsibility to have validated all the
* parameters and ensure that the device has been acquired.
*
* @parm OUT LPVOID | lpvData |
*
* Hid data in the preferred data format.
*
* @returns
*
* Returns a COM error code. The following error codes are
* intended to be illustrative and not necessarily comprehensive.
*
* <c DI_OK> = <c S_OK>: The operation completed successfully.
*
* <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>: The
* <p lpmdr> parameter is not a valid pointer.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetDeviceState(PDICB pdcb, LPVOID pvData)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::GetDeviceState,
(_ "pp", pdcb, pvData));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF(this->pvi);
AssertF(this->pvPhys);
AssertF(this->cbPhys);
if(this->pvi->fl & VIFL_ACQUIRED)
{
CHid_GetPhysicalState(this, pvData);
hres = S_OK;
} else
{
hres = DIERR_INPUTLOST;
}
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetObjectInfo |
*
* Obtain the friendly name and FF/HID information
* of an object.
*
* @parm IN LPCDIPROPINFO | ppropi |
*
* Information describing the object being accessed.
*
* @parm IN OUT LPDIDEVICEOBJECTINSTANCEW | pdidioiW |
*
* Structure to receive information. All fields have been
* filled in up to the <e DIDEVICEOBJECTINSTANCE.tszObjName>.
*
* @returns
*
* Returns a COM error code.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetObjectInfo(PDICB pdcb, LPCDIPROPINFO ppropi,
LPDIDEVICEOBJECTINSTANCEW pdidoiW)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::GetObjectInfo,
(_ "pxp", pdcb, ppropi->iobj, pdidoiW));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF((int)ppropi->iobj >= 0);
if(ppropi->iobj < this->df.dwNumObjs)
{
UINT uiInstance = ppropi->iobj;
PHIDGROUPCAPS pcaps;
AssertF(ppropi->dwDevType == this->df.rgodf[uiInstance].dwType);
AssertF(uiInstance == CHid_ObjFromType(this, ppropi->dwDevType));
pcaps = this->rghoc[uiInstance].pcaps;
/*
* pcaps might be NULL if HID messed up and left gaps
* in the index lists.
*/
if(pcaps)
{
UINT ids, duiInstance;
AssertF(pcaps->dwSignature == HIDGROUPCAPS_SIGNATURE);
/*
* See if there's anything in the registry that will help.
*/
CType_RegGetObjectInfo(this->hkType, ppropi->dwDevType, pdidoiW);
if(ppropi->dwDevType & DIDFT_COLLECTION)
{
ids = IDS_COLLECTIONTEMPLATE;
duiInstance = 0;
} else
{
if(ppropi->dwDevType & DIDFT_BUTTON)
{
ids = IDS_BUTTONTEMPLATE;
} else if(ppropi->dwDevType & DIDFT_AXIS)
{
ids = IDS_AXISTEMPLATE;
} else if(ppropi->dwDevType & DIDFT_POV)
{
ids = IDS_POVTEMPLATE;
} else
{
ids = IDS_UNKNOWNTEMPLATE;
}
/*
* Now convert the uiInstance to a duiInstance,
* giving the index of this object into the group.
*/
AssertF(HidP_IsValidReportType(pcaps->type));
duiInstance = uiInstance -
(this->rgdwBase[pcaps->type] +
pcaps->DataIndexMin);
}
/*
* Okay, now we have all the info we need to proceed.
*/
/*
* If there was no overriding name in the registry, then
* try to get a custom name from the usage page/usage.
* If even that fails, then use the generic name.
* Note, generic names will contain zero based numbers
* which can look wrong if some objects have names and
* others take defaults.
*/
if(pdidoiW->tszName[0])
{
} else
if(GetHIDString(pcaps->UsageMin + duiInstance,
pcaps->UsagePage,
pdidoiW->tszName, cA(pdidoiW->tszName)))
{
if(ppropi->dwDevType & DIDFT_COLLECTION)
{
InsertCollectionNumber(DIDFT_GETINSTANCE( ppropi->dwDevType ),
pdidoiW->tszName);
}
} else
{
GetNthString(pdidoiW->tszName, ids,
DIDFT_GETINSTANCE( ppropi->dwDevType ));
}
if(pdidoiW->dwSize >= cbX(DIDEVICEOBJECTINSTANCE_DX5W))
{
pdidoiW->wCollectionNumber = pcaps->LinkCollection;
pdidoiW->wDesignatorIndex = pcaps->DesignatorMin + duiInstance;
if(pdidoiW->wDesignatorIndex > pcaps->DesignatorMax)
{
pdidoiW->wDesignatorIndex = pcaps->DesignatorMax;
}
/*
* Much as you may try, you cannot override the usage
* page and usage. Doing so would mess up the GUID
* selection code that happens in DIHIDINI.C.
*
* If you change your mind and allow overridden usage
* pages and usages, then you'll also have to change
* CHid_GetUsage.
*
* At this point, the registry overrides have already
* been read so defeat the override here.
*/
pdidoiW->wUsagePage = pcaps->UsagePage;
pdidoiW->wUsage = pcaps->UsageMin + duiInstance;
pdidoiW->dwDimension = pcaps->Units;
pdidoiW->wExponent = pcaps->Exponent;
pdidoiW->wReportId = pcaps->wReportId;
}
hres = S_OK;
} else
{
hres = E_INVALIDARG;
}
} else
{
hres = E_INVALIDARG;
}
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method DWORD | CHid | GetUsage |
*
* Given an object index, return the usage and usage page,
* packed into a single <t DWORD>.
*
* @parm int | iobj |
*
* The object index to convert.
*
* @returns
*
* Returns a <c DIMAKEUSAGEDWORD> of the resulting usage and
* usage page, or zero on error.
*
*****************************************************************************/
STDMETHODIMP_(DWORD)
CHid_GetUsage(PDICB pdcb, int iobj)
{
PCHID this;
PHIDGROUPCAPS pcaps;
DWORD dwRc;
EnterProcI(IDirectInputDeviceCallback::Hid::GetUsage,
(_ "pu", pdcb, iobj));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF(iobj >= 0);
AssertF((UINT)iobj < this->df.dwNumObjs);
pcaps = this->rghoc[iobj].pcaps;
/*
* pcaps might be NULL if HID messed up and left gaps
* in the index lists.
*/
if(pcaps)
{
UINT duiInstance;
AssertF(pcaps->dwSignature == HIDGROUPCAPS_SIGNATURE);
if(this->df.rgodf[iobj].dwType & DIDFT_COLLECTION)
{
duiInstance = 0;
} else
{
/*
* Now convert the iobj to a duiInstance,
* giving the index of this object into the group.
*/
AssertF(HidP_IsValidReportType(pcaps->type));
duiInstance = iobj -
(this->rgdwBase[pcaps->type] +
pcaps->DataIndexMin);
}
/*
* CHid_GetObjectInfo also assumes that there is no way
* to override the usage page and usage values in the
* registry.
*/
dwRc = DIMAKEUSAGEDWORD(pcaps->UsagePage,
pcaps->UsageMin + duiInstance);
} else
{
dwRc = 0;
}
ExitProcX(dwRc);
return dwRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | MapUsage |
*
*
* Given a usage and usage page (munged into a single
* <t DWORD>), find a device object that matches it.
*
* @parm DWORD | dwUsage |
*
* The usage page and usage combined into a single <t DWORD>
* with the <f DIMAKEUSAGEDWORD> macro.
*
* @parm PINT | piOut |
*
* Receives the object index of the found object, if successful.
*
* @returns
*
* Returns a COM error code.
*
* <c S_OK> if an object was found.
*
* <c DIERR_NOTFOUND> if no matching object was found.
*
*****************************************************************************/
STDMETHODIMP
CHid_MapUsage(PDICB pdcb, DWORD dwUsage, PINT piOut)
{
HRESULT hres;
PCHID this;
UINT icaps;
UINT uiObj;
UINT duiObj;
EnterProcI(IDirectInputDeviceCallback::Hid::MapUsage,
(_ "px", pdcb, dwUsage));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
for(icaps = 0; icaps < this->ccaps; icaps++)
{
PHIDGROUPCAPS pcaps = &this->rgcaps[icaps];
LPDIOBJECTDATAFORMAT podf;
/*
* Shall we support mapping HidP_Output usage?
* If we should, it is easy to add it later.
*/
uiObj = this->rgdwBase[HidP_Input] + pcaps->DataIndexMin;
for(duiObj = 0; duiObj < pcaps->cObj; duiObj++)
{
podf = &this->df.rgodf[uiObj + duiObj];
if( dwUsage == GuidToUsage(podf->pguid) )
{
*piOut = uiObj+duiObj;
AssertF(*piOut < (INT)this->df.dwNumObjs);
hres = S_OK;
goto done;
}
}
}
hres = DIERR_NOTFOUND;
done:;
ExitBenignOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | SetCooperativeLevel |
*
* Notify the device of the cooperative level.
*
* @parm IN HWND | hwnd |
*
* The window handle.
*
* @parm IN DWORD | dwFlags |
*
* The cooperativity level.
*
* @returns
*
* Returns a COM error code.
*
*****************************************************************************/
STDMETHODIMP
CHid_SetCooperativeLevel(PDICB pdcb, HWND hwnd, DWORD dwFlags)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::SetCooperativityLevel,
(_ "pxx", pdcb, hwnd, dwFlags));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
/*
* We won't subclass Motocross Madness. See NT bug 262280.
* Use the app hacks for MCM and any app like it.
*/
#if (DIRECTINPUT_VERSION > 0x061A)
if( !this->diHacks.fNoSubClass )
#endif
{
AssertF(this->pvi);
/*
* First get out of the old window.
*/
CHid_RemoveSubclass(this);
/*
* If a new window is passed, then subclass it so we can
* watch for joystick configuration change messages.
*
* If we can't, don't worry. All it means that we won't
* be able to catch when the user recalibrates a device,
* which isn't very often.
*/
if(hwnd)
{
if(SetWindowSubclass(hwnd, CHid_SubclassProc, 0x0, (ULONG_PTR)this))
{
this->hwnd = hwnd;
Common_Hold(this);
}
} else
{
RPF("SetCooperativeLevel: You really shouldn't pass hwnd = 0; "
"device calibration may be dodgy");
}
}
hres = S_OK;
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | RunControlPanel |
*
* Run the Hid control panel.
*
* @parm IN HWND | hwndOwner |
*
* The owner window.
*
* @parm DWORD | dwFlags |
*
* Flags.
*
*****************************************************************************/
STDMETHODIMP
CHid_RunControlPanel(PDICB pdcb, HWND hwnd, DWORD dwFlags)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::RunControlPanel,
(_ "pxx", pdcb, hwnd, dwFlags));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
/*
* How to invoke HID cpl?
*
* hres = (fWinnt) ? hresRunControlPanel(TEXT("srcmgr.cpl,@2")) :
* hresRunControlPanel(TEXT("sysdm.cpl,@0,1"));
*
* Currently, we just launch joy.cpl. If more HID devices show up
* which don't belong to game control panel, we may change it to
* proper cpl.
*/
hres = hresRunControlPanel(TEXT("joy.cpl"));
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetFFConfigKey |
*
* Open and return the registry key that contains
* force feedback configuration information.
*
* @parm DWORD | sam |
*
* Security access mask.
*
* @parm PHKEY | phk |
*
* Receives the registry key.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetFFConfigKey(PDICB pdcb, DWORD sam, PHKEY phk)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::HID::GetFFConfigKey,
(_ "px", pdcb, sam));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
hres = JoyReg_OpenFFKey(this->hkType, sam, phk);
AssertF(fLeqvFF(SUCCEEDED(hres), *phk));
if(FAILED(hres) && this->fPIDdevice )
{
*phk = NULL;
hres = S_FALSE;
}
ExitBenignOleProcPpvR(phk);
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetDeviceInfo |
*
* Obtain general information about the device.
*
* @parm OUT LPDIDEVICEINSTANCEW | pdiW |
*
* <t DEVICEINSTANCE> to be filled in. The
* <e DEVICEINSTANCE.dwSize> and <e DEVICEINSTANCE.guidInstance>
* have already been filled in.
*
* Secret convenience: <e DEVICEINSTANCE.guidProduct> is equal
* to <e DEVICEINSTANCE.guidInstance>.
*
*****************************************************************************/
STDMETHODIMP
CHid_GetDeviceInfo(PDICB pdcb, LPDIDEVICEINSTANCEW pdiW)
{
HRESULT hres;
PCHID this;
DIPROPINFO propi;
DIPROPSTRING dips;
EnterProcI(IDirectInputDeviceCallback::Hid::GetDeviceInfo,
(_ "pp", pdcb, pdiW));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
AssertF(IsValidSizeDIDEVICEINSTANCEW(pdiW->dwSize));
DICreateStaticGuid(&pdiW->guidProduct, this->ProductID, this->VendorID);
pdiW->dwDevType = this->dwDevType;
if(pdiW->dwSize >= cbX(DIDEVICEINSTANCE_DX5W))
{
pdiW->wUsagePage = this->caps.UsagePage;
pdiW->wUsage = this->caps.Usage;
}
propi.dwDevType = DIPH_DEVICE;
propi.iobj = 0xFFFFFFFF;
propi.pguid = DIPROP_PRODUCTNAME;
if(SUCCEEDED(hres = pdcb->lpVtbl->GetProperty(pdcb, &propi, &dips.diph)) )
{
lstrcpyW(pdiW->tszProductName, dips.wsz);
}
propi.pguid = DIPROP_INSTANCENAME;
if( FAILED(pdcb->lpVtbl->GetProperty(pdcb, &propi, &dips.diph)))
{
// Use Product Name
}
lstrcpyW(pdiW->tszInstanceName, dips.wsz);
#ifdef IDirectInputDevice2Vtbl
if(pdiW->dwSize >= cbX(DIDEVICEINSTANCE_DX5W))
{
HKEY hkFF;
HRESULT hresFF;
/*
* If there is a force feedback driver, then fetch the driver CLSID
* as the FF GUID.
*/
hresFF = CHid_GetFFConfigKey(pdcb, KEY_QUERY_VALUE, &hkFF);
if(SUCCEEDED(hresFF))
{
LONG lRc;
TCHAR tszClsid[ctchGuid];
lRc = RegQueryString(hkFF, TEXT("CLSID"), tszClsid, cA(tszClsid));
if(lRc == ERROR_SUCCESS &&
ParseGUID(&pdiW->guidFFDriver, tszClsid))
{
} else
{
ZeroX(pdiW->guidFFDriver);
}
RegCloseKey(hkFF);
}
}
#endif
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | CreateEffect |
*
*
* Create an <i IDirectInputEffectDriver> interface.
*
* @parm LPDIRECTINPUTEFFECTSHEPHERD * | ppes |
*
* Receives the shepherd for the effect driver.
*
*****************************************************************************/
STDMETHODIMP
CHid_CreateEffect(PDICB pdcb, LPDIRECTINPUTEFFECTSHEPHERD *ppes)
{
HRESULT hres;
PCHID this;
HKEY hk;
EnterProcI(IDirectInputDeviceCallback::HID::CreateEffect, (_ "p", pdcb));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
hres = CHid_GetFFConfigKey(pdcb, KEY_QUERY_VALUE, &hk);
if(SUCCEEDED(hres))
{
DIHIDFFINITINFO init;
PHIDDEVICEINFO phdi;
hres = CEShep_New(hk, 0, &IID_IDirectInputEffectShepherd, ppes);
if(SUCCEEDED(hres))
{
#ifndef UNICODE
WCHAR wszPath[MAX_PATH];
#endif
init.dwSize = cbX(init);
#ifdef UNICODE
init.pwszDeviceInterface = this->ptszPath;
#else
init.pwszDeviceInterface = wszPath;
TToU(wszPath, cA(wszPath), this->ptszPath);
#endif
DllEnterCrit();
phdi = phdiFindHIDDeviceInterface(this->ptszPath);
if( phdi )
{
init.GuidInstance = phdi->guid;
} else
{
ZeroX(init.GuidInstance);
}
DllLeaveCrit();
hres = (*ppes)->lpVtbl->DeviceID((*ppes), this->idJoy, TRUE, &init);
if(SUCCEEDED(hres))
{
} else
{
Invoke_Release(ppes);
}
}
if (hk != NULL)
{
RegCloseKey(hk);
}
} else
{
hres = E_NOTIMPL;
*ppes = 0;
}
ExitOleProcPpvR(ppes);
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | SendOutputReport |
*
* Actually send the report as an output report.
*
* @parm PHIDREPORTINFO | phri |
*
* The report being sent.
*
* @returns
*
* Returns a COM error code.
*
*****************************************************************************/
void CALLBACK
CHid_DummyCompletion(DWORD dwError, DWORD cbRead, LPOVERLAPPED po)
{
}
STDMETHODIMP
CHid_SendOutputReport(PCHID this, PHIDREPORTINFO phri)
{
HRESULT hres;
OVERLAPPED o;
AssertF(phri == &this->hriOut);
ZeroX(o);
/*
* Annoying API: Since this->hdev was opened
* as FILE_FLAG_OVERLAPPED, *all* I/O must be overlapped.
* So we simulate a synchronous I/O by issuing an
* overlapped I/O and waiting for the completion.
*/
if(WriteFileEx(this->hdev, phri->pvReport,
phri->cbReport, &o, CHid_DummyCompletion))
{
do
{
SleepEx(INFINITE, TRUE);
} while(!HasOverlappedIoCompleted(&o));
if(phri->cbReport == o.InternalHigh)
{
hres = S_OK;
} else
{
RPF("SendDeviceData: Wrong HID output report size?");
hres = E_FAIL; /* Aigh! HID lied to me! */
}
} else
{
hres = hresLe(GetLastError());
CEm_ForceDeviceUnacquire(pemFromPvi(this->pvi)->ped, 0x0);
}
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | SendFeatureReport |
*
* Actually send the report as an feature report.
*
* @parm PHIDREPORTINFO | phri |
*
* The report being sent.
*
* @returns
*
* Returns a COM error code.
*
*****************************************************************************/
STDMETHODIMP
CHid_SendFeatureReport(PCHID this, PHIDREPORTINFO phri)
{
HRESULT hres;
AssertF(phri == &this->hriFea);
if(HidD_SetFeature(this->hdev, phri->pvReport, phri->cbReport))
{
hres = S_OK;
} else
{
RPF("SendDeviceData: Unable to set HID feature");
hres = hresLe(GetLastError());
}
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | SendDeviceData |
*
* Spew some data to the device.
*
* @parm IN LPCDIDEVICEOBJECTDATA | rgdod |
*
* Array of <t DIDEVICEOBJECTDATA> structures.
*
* @parm INOUT LPDWORD | pdwInOut |
*
* On entry, number of items to send;
* on exit, number of items actually sent.
*
* @parm DWORD | fl |
*
* Flags.
*
* @returns
*
* Returns a COM error code. The following error codes are
* intended to be illustrative and not necessarily comprehensive.
*
* <c DI_OK> = <c S_OK>: The operation completed successfully.
*
* <c DIERR_REPORTFULL>: Too many items are set in the report.
* (More than can be sent to the device)
*
*****************************************************************************/
STDMETHODIMP
CHid_SendDeviceData(PDICB pdcb, LPCDIDEVICEOBJECTDATA rgdod,
LPDWORD pdwInOut, DWORD fl)
{
HRESULT hres;
PCHID this;
DWORD dwIn, dw;
EnterProcI(IDirectInputDeviceCallback::Hid::SendDeviceData,
(_ "pux", pdcb, *pdwInOut, fl));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
dwIn = *pdwInOut;
*pdwInOut = 0;
if(fl & DISDD_CONTINUE)
{
} else
{
CHid_ResetDeviceData(this, &this->hriOut, HidP_Output);
CHid_ResetDeviceData(this, &this->hriFea, HidP_Feature);
}
for(dw = 0; dw < dwIn; dw++)
{
DWORD dwType = rgdod[dw].dwOfs;
UINT uiObj = CHid_ObjFromType(this, dwType);
if(uiObj < this->df.dwNumObjs &&
DIDFT_FINDMATCH(this->df.rgodf[uiObj].dwType, dwType))
{
hres = CHid_AddDeviceData(this, uiObj, rgdod[dw].dwData);
if(FAILED(hres))
{
*pdwInOut = dw;
goto done;
}
} else
{
hres = E_INVALIDARG;
goto done;
}
}
/*
* All the items made it into the buffer.
*/
*pdwInOut = dw;
/*
* Now send it all out.
*/
if(SUCCEEDED(hres = CHid_SendHIDReport(this, &this->hriOut, HidP_Output,
CHid_SendOutputReport)) &&
SUCCEEDED(hres = CHid_SendHIDReport(this, &this->hriFea, HidP_Feature,
CHid_SendFeatureReport)))
{
}
done:;
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | Poll |
*
* Read the features to see what's there.
*
* @returns
*
* <c S_OK> if we pinged okay.
*
*****************************************************************************/
STDMETHODIMP
CHid_Poll(PDICB pdcb)
{
// Prefix: 45082
HRESULT hres = S_FALSE;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::Poll, (_ "p", pdcb));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
//ISSUE-2001/03/29-timgill NT5 Beta1 compat fix
if( this->IsPolledInput )
{
hres = DIERR_UNPLUGGED;
if(ReadFileEx(this->hdev, this->hriIn.pvReport,
this->hriIn.cbReport, &this->o, CHid_DummyCompletion))
{
do
{
SleepEx( INFINITE, TRUE);
} while(!HasOverlappedIoCompleted(&this->o));
if(this->hriIn.cbReport == this->o.InternalHigh)
{
NTSTATUS stat;
//CEm_HID_PrepareState(this);
CopyMemory(this->pvStage, this->pvPhys, this->cbPhys);
stat = CHid_ParseData(this, HidP_Input, &this->hriIn);
if(SUCCEEDED(stat))
{
CEm_AddState(&this->ed, this->pvStage, GetTickCount());
this->pvi->fl &= ~VIFL_UNPLUGGED;
hres = S_OK;
} else
{
hres = stat;
}
}
}
if( FAILED(hres) )
{
hres = DIERR_UNPLUGGED;
this->pvi->fl |= VIFL_UNPLUGGED;
if( !this->diHacks.fNoPollUnacquire )
{
CEm_ForceDeviceUnacquire(pemFromPvi(this->pvi)->ped, 0x0);
}
}
}
if( this->hriFea.cbReport )
{
UINT uReport;
/*
* We should never get here unless there really are any
* features that need to be polled.
*/
AssertF(this->hriFea.cbReport);
AssertF(this->hriFea.pvReport);
/*
* Read the new features and parse/process them.
*
* Notice that we read the features into the same buffer
* that we log them into. That's okay; the "live" parts
* of the two buffers never actually overlap.
*/
for( uReport = 0x0; uReport < this->wMaxReportId[HidP_Feature]; uReport++ )
{
if( *(this->pEnableReportId[HidP_Feature] + uReport ) == TRUE )
{
*((UCHAR*)(this->hriFea.pvReport)) = (UCHAR)uReport;
/*
* Wipe out all the old goo because we're taking over.
*/
CHid_ResetDeviceData(this, &this->hriFea, HidP_Feature);
if(HidD_GetFeature(this->hdev, this->hriFea.pvReport,
this->hriFea.cbReport))
{
NTSTATUS stat;
stat = CHid_ParseData(this, HidP_Feature, &this->hriFea);
AssertF(SUCCEEDED(stat));
if(SUCCEEDED(stat))
{
CEm_AddState(&this->ed, this->pvStage, GetTickCount());
}
hres = stat;
} else
{
RPF("CHid_Poll: Unable to read HID features (ReportID%d) LastError(0x%x)", uReport, GetLastError() );
hres = hresLe(GetLastError());
}
}
}
}
if( this->dwVersion < 0x05B2 )
{
/*
* In Win9x, we need hard code it to be S_OK, otherwise, some games:
* such as Carmegeddon 2, will fails.
* The NT and onwards CPL requires poll to return true status
*/
hres = S_OK;
}
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* CHid_New (constructor)
*
* Fail the create if we can't open the device.
*
*****************************************************************************/
STDMETHODIMP
CHid_New(PUNK punkOuter, REFGUID rguid, RIID riid, PPV ppvObj)
{
HRESULT hres;
EnterProcI(IDirectInputDeviceCallback::Hid::<constructor>,
(_ "Gp", riid, ppvObj));
hres = Common_NewRiid(CHid, punkOuter, riid, ppvObj);
if(SUCCEEDED(hres))
{
/* Must use _thisPv in case of aggregation */
PCHID this = _thisPv(*ppvObj);
if(SUCCEEDED(hres = CHid_Init(this, rguid)))
{
} else
{
Invoke_Release(ppvObj);
}
}
ExitOleProcPpvR(ppvObj);
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | SetDIData |
*
* Set DirectInput version and apphack data from CDIDev *.
*
* @parm DWORD | dwVer |
*
* DirectInput version
*
* @parm LPVOID | lpdihacks |
*
* AppHack data
*
* @returns
*
* <c E_NOTIMPL> because we don't support usages.
*
*****************************************************************************/
STDMETHODIMP
CHid_SetDIData(PDICB pdcb, DWORD dwVer, LPVOID lpdihacks)
{
HRESULT hres;
PCHID this;
EnterProcI(IDirectInputDeviceCallback::Hid::SetDIData,
(_ "pup", pdcb, dwVer, lpdihacks));
/*
* This is an internal interface, so we can skimp on validation.
*/
this = _thisPvNm(pdcb, dcb);
this->dwVersion = dwVer;
((LPDIAPPHACKS)lpdihacks)->dwDevType = this->dwDevType;
CopyMemory(&this->diHacks, (LPDIAPPHACKS)lpdihacks, sizeof(this->diHacks));
hres = S_OK;
ExitOleProcR();
return hres;
}
/*****************************************************************************
*
* The long-awaited vtbls and templates
*
*****************************************************************************/
#pragma BEGIN_CONST_DATA
#define CHid_Signature 0x20444948 /* "HID " */
Primary_Interface_Begin(CHid, IDirectInputDeviceCallback)
CHid_GetInstance,
CDefDcb_GetVersions,
CHid_GetDataFormat,
CHid_GetObjectInfo,
CHid_GetCapabilities,
CHid_Acquire,
CHid_Unacquire,
CHid_GetDeviceState,
CHid_GetDeviceInfo,
CHid_GetProperty,
CHid_SetProperty,
CDefDcb_SetEventNotification,
#ifdef WINNT
CHid_SetCooperativeLevel,
#else
CDefDcb_SetCooperativeLevel,
#endif
CHid_RunControlPanel,
CDefDcb_CookDeviceData,
CHid_CreateEffect,
CHid_GetFFConfigKey,
CHid_SendDeviceData,
CHid_Poll,
CHid_GetUsage,
CHid_MapUsage,
CHid_SetDIData,
Primary_Interface_End(CHid, IDirectInputDeviceCallback)
#endif