Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

4629 lines
160 KiB

/*****************************************************************************
*
* DIHidIni.c
*
* Copyright (c) 1996 - 2000 Microsoft Corporation. All Rights Reserved.
*
* Abstract:
*
* The initialization-related functions of the HID device callback.
*
* All the HID support is getting kind of bulky, so I've broken
* it out into submodules.
*
* Contents:
*
* CHid_Init
*
*****************************************************************************/
#include "dinputpr.h"
/*****************************************************************************
*
* The sqiffle for this file.
*
*****************************************************************************/
#define sqfl sqflHidIni
/*****************************************************************************
*
* 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.
*
*****************************************************************************/
/*****************************************************************************
*
* @doc INTERNAL
*
* @func UINT | CHid_LoadCalibrations |
*
* Load calibration information from the registry (or wherever).
* This is done when the device is created, and whenever we
* receive a recalibration message.
*
* @returns
*
* Returns the number of axes we calibrated. This information
* is used during device initialization to see if we need to
* worry about calibration in the future.
*
*****************************************************************************/
UINT EXTERNAL
CHid_LoadCalibrations(PCHID this)
{
UINT uiObj;
UINT uiRc = 0;
/*
* Preinitialize the HIDP_DATA indices to -1 to indicate
* that they aren't there. We must do this before we
* mess with AddDeviceData, which assumes that all the
* indices are properly set up.
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
PJOYRANGECONVERT pjrc = this->rghoc[uiObj].pjrc;
PHIDGROUPCAPS pcaps = this->rghoc[uiObj].pcaps;
if(pjrc && pcaps)
{
LPDIOBJECTDATAFORMAT podf = &this->df.rgodf[uiObj];
DIOBJECTCALIBRATION cal;
#ifdef WINNT
DIPOVCALIBRATION pov;
#endif
D(LPCTSTR ptszWhence;)
/*
* Note, we do not have to deal with mice on Win2k, yet...
*/
#ifdef WINNT
HRESULT hres;
HKEY hk;
D( ptszWhence = TEXT("unknown") );
if( podf->dwType & DIDFT_POV )
{
if( pcaps->IsPolledPOV ) {
ZeroX( pov );
hres = CType_OpenIdSubkey(this->hkInstType, podf->dwType,
KEY_QUERY_VALUE, &hk);
if(SUCCEEDED(hres))
{
hres = JoyReg_GetValue(hk, TEXT("Calibration"),
REG_BINARY, &pov,
cbX(DIPOVCALIBRATION));
RegCloseKey(hk);
}
}
} else
{
ZeroX( cal );
hres = CType_OpenIdSubkey(this->hkInstType, podf->dwType,
KEY_QUERY_VALUE, &hk);
if(SUCCEEDED(hres))
{
hres = JoyReg_GetValue(hk, TEXT("Calibration"),
REG_BINARY, &cal,
cbX(DIOBJECTCALIBRATION));
RegCloseKey(hk);
}
/*
* If there is no calibration data, then create
* some defaults based on the logical min/max.
*/
if(FAILED(hres))
{
/*
* But only if the logical min/max is sane!
*/
if(pcaps->Logical.Min < pcaps->Logical.Max)
{
cal.lMin = pcaps->Logical.Min;
/*
* HACKHACK
* The analog joystick driver cannot report the true
* range of the device, so to keep the sample driver
* pure, it reports a range of zero to the point at
* which it would consider the axis absent. This is
* good in terms of reporting healthy HID data but
* it means that any normal joystick will only return
* values in a fraction of this range. So if this
* device is an analog device default the calibration
* to the typical range.
*/
if( ( this->VendorID == MSFT_SYSTEM_VID )
&&( ( this->ProductID & 0xff00 ) == MSFT_SYSTEM_PID ) )
{
/*
* To be extra safe, compute the max from the
* reported range. The divisor is a fudge factor
* derived by what looked about right to MarcAnd.
*/
cal.lMax = pcaps->Logical.Min +
( ( pcaps->Logical.Max - pcaps->Logical.Min ) / 11 );
D(ptszWhence = TEXT("log (adj)"));
}
else
{
cal.lMax = pcaps->Logical.Max;
D(ptszWhence = TEXT("log"));
}
} else
{
D(ptszWhence = TEXT("def"));
cal.lMin = 0;
cal.lMax = 655;
}
cal.lCenter = CCal_Midpoint(cal.lMin, cal.lMax);
} else
{
D(ptszWhence = TEXT("reg"));
}
}
#else
if( GET_DIDEVICE_TYPE(this->dwDevType) == DI8DEVTYPE_MOUSE )
{
if( this->df.rgodf[uiObj].dwType & DIDFT_ABSAXIS )
{
if(pcaps->Logical.Min < pcaps->Logical.Max)
{
cal.lMin = pcaps->Logical.Min;
cal.lMax = pcaps->Logical.Max;
D(ptszWhence = TEXT("mouse log"));
}
else
{
/*
* Absolute mice traditionally report 0 - 64K
*/
cal.lMin = 0;
cal.lMax = 65535;
D(ptszWhence = TEXT("mouse def"));
}
cal.lCenter = CCal_Midpoint(cal.lMin, cal.lMax);
}
} else if ( this->idJoy < 0 ) //See manbug 50591
{
HRESULT hres;
HKEY hk;
ZeroX( cal );
hres = CType_OpenIdSubkey(this->hkInstType, podf->dwType,
KEY_QUERY_VALUE, &hk);
if(SUCCEEDED(hres))
{
hres = JoyReg_GetValue(hk, TEXT("Calibration"),
REG_BINARY, &cal,
cbX(DIOBJECTCALIBRATION));
RegCloseKey(hk);
} else {
/*
* But only if the logical min/max is sane!
*/
if(pcaps->Logical.Min < pcaps->Logical.Max)
{
cal.lMin = pcaps->Logical.Min;
cal.lMax = pcaps->Logical.Max;
} else
{
cal.lMin = 0;
cal.lMax = 655; //best guess
}
cal.lCenter = CCal_Midpoint(cal.lMin, cal.lMax);
}
} else
{
ZeroX( cal );
/*
* Because the CPL on Win9x only updates calibration in MediaResources,
* We need read that calibration information and update for HID.
*/
CHid_UpdateCalibrationFromVjoyd(this, uiObj, &cal);
D(ptszWhence = TEXT("WinMM Reg"));
}
#endif
#ifdef WINNT
if( podf->dwType & DIDFT_POV )
{
if( pcaps->IsPolledPOV ) {
memcpy( pjrc->lMinPOV, pov.lMin, cbX(pjrc->lMinPOV) );
memcpy( pjrc->lMaxPOV, pov.lMax, cbX(pjrc->lMaxPOV) );
}
} else
#endif
{
D(SquirtSqflPtszV(sqflHidParse,
TEXT(" Calibration(%d) %s %d..%d..%d"),
CHid_ObjFromType(this, podf->dwType), ptszWhence,
cal.lMin, cal.lCenter, cal.lMax));
/*
* Saturation always defaults to 100%.
*/
pjrc->dwPmin = cal.lMin;
pjrc->dwPmax = cal.lMax;
pjrc->dwPc = cal.lCenter;
if( pjrc->dwCPointsNum == 0 ) {
//use two control points by default
pjrc->dwCPointsNum = 2;
pjrc->cp[0].lP = pjrc->dwPmin;
pjrc->cp[0].dwLog = 0;
pjrc->cp[1].lP = pjrc->dwPmax;
pjrc->cp[1].dwLog = RANGEDIVISIONS;
} else {
pjrc->cp[0].lP = pjrc->dwPmin;
pjrc->cp[pjrc->dwCPointsNum-1].lP = pjrc->dwPmax;
}
CCal_RecalcRange(pjrc);
uiRc++;
}
}
}
return uiRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func void | CHid_SortCaps |
*
* Sort the capabilities by Data Index. This is important
* so that the items are assigned numbers in the same order
* by both DirectInput and HID.
*
* Note that we exploit the not-exactly-a-coincidence
* that a <t HIDP_VALUE_CAPS> and a
* <t HIDP_BUTTON_CAPS> are identical wherever they overlap.
*
* @parm PV | rgv |
*
* Array of either <t HIDP_VALUE_CAPS> or <t HIDP_BUTTON_CAPS>
* structures.
*
* @parm UINT | cv |
*
* Number of structures that need to be sorted.
*
*****************************************************************************/
void INTERNAL
CHid_SortCaps(PV rgv, UINT cv)
{
/*
* For concreteness, we use HIDP_VALUE_CAPS.
*/
PHIDP_VALUE_CAPS rgvcaps = rgv;
UINT ivcaps;
/*
* There are several non-coincidences which we exploit.
*
* HIDP_VALUE_CAPS and HIDP_BUTTON_CAPS are the same size.
*
* HIDP_VALUE_CAPS.Range.DataIndexMin,
* HIDP_VALUE_CAPS.NotRange.DataIndex,
* HIDP_BUTTON_CAPS.Range.DataIndexMin, and
* HIDP_BUTTON_CAPS.NotRange.DataIndex are all at the same offset.
*/
CAssertF(cbX(HIDP_VALUE_CAPS) == cbX(HIDP_BUTTON_CAPS));
/*
* For some reason, the compiler doesn't think that these
* expressions are constant so I can't use CAssertF.
*/
AssertF(FIELD_OFFSET(HIDP_VALUE_CAPS, NotRange.DataIndex) ==
FIELD_OFFSET(HIDP_VALUE_CAPS, Range.DataIndexMin));
AssertF(FIELD_OFFSET(HIDP_VALUE_CAPS, Range.DataIndexMin) ==
FIELD_OFFSET(HIDP_BUTTON_CAPS, Range.DataIndexMin));
AssertF(FIELD_OFFSET(HIDP_BUTTON_CAPS, Range.DataIndexMin) ==
FIELD_OFFSET(HIDP_BUTTON_CAPS, NotRange.DataIndex));
#ifdef REALLY_ANNOYING
/*
* Dump the Before list.
*/
for(ivcaps = 0; ivcaps < cv; ivcaps++)
{
SquirtSqflPtszV(sqflHidParse,
TEXT("HidP_SortCaps:%2d = %04x"),
ivcaps, rgvcaps[ivcaps].Range.DataIndexMin);
}
#endif
/*
* Since there are typically not very many caps, we will use
* a simple insertion sort.
*
* Note if caps entries have the same data index they are
* aliases. Make sure the primary alias will be the first
* in the sorted list.
*/
for(ivcaps = 1; ivcaps < cv; ivcaps++)
{
int ivcapsT;
HIDP_VALUE_CAPS vcaps = rgvcaps[ivcaps];
ivcapsT = ivcaps;
while( ( --ivcapsT >= 0 )
&&( ( rgvcaps[ivcapsT].Range.DataIndexMin >
vcaps.Range.DataIndexMin )
||( ( rgvcaps[ivcapsT].Range.DataIndexMin ==
vcaps.Range.DataIndexMin )
&&( rgvcaps[ivcapsT].IsAlias ) ) ) )
{
rgvcaps[ivcapsT+1] = rgvcaps[ivcapsT];
}
rgvcaps[ivcapsT+1] = vcaps;
}
#ifdef REALLY_ANNOYING
/*
* Dump the After list.
*/
for(ivcaps = 0; ivcaps < cv; ivcaps++)
{
SquirtSqflPtszV(sqflHidParse,
TEXT("HidP_SortCaps:%2d = %04x"),
ivcaps, rgvcaps[ivcaps].Range.DataIndexMin);
}
#endif
/*
* Assert that everything is weakly monotonically sorted.
*
* If two items are equal, then it means that HID messed up
* or the values are aliases.
* We don't complain about it here; we will notice later.
*/
for(ivcaps = 1; ivcaps < cv; ivcaps++)
{
AssertF(rgvcaps[ivcaps-1].Range.DataIndexMin <=
rgvcaps[ivcaps ].Range.DataIndexMin);
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func DWORD | CHid_FindAspect |
*
* Try to determine the aspect flags for this value.
*
* @parm PHIDP_VALUE_CAPS | pvcaps |
*
* Pointer to HID value caps to search through
* structures.
*
* @returns
*
* Flags set for the aspect if found
*
* @comm
* Currently (08-Dec-98) most devices and drivers do not
* declare units but since drivers must use the generic
* position usages in order to be recognized assume that
* these imply that position data is being returned.
*
*****************************************************************************/
DWORD CHID_FindAspect
(
PHIDP_VALUE_CAPS pvcaps
)
{
DWORD dwAspect = 0;
if( pvcaps->Units )
{
#define HID_UNIT_SYSTEM_MASK 0x0000000fL
#define HID_UNIT_LENGTH_MASK 0x000000f0L
#define HID_UNIT_MASS_MASK 0x00000f00L
#define HID_UNIT_TIME_MASK 0x0000f000L
/*
* If available, use the units to derive the DI aspect
* flags for input objects.
*/
if( pvcaps->Units & ~( HID_UNIT_SYSTEM_MASK
| HID_UNIT_LENGTH_MASK
| HID_UNIT_MASS_MASK
| HID_UNIT_TIME_MASK ) )
{
SquirtSqflPtszV(sqflTrace | sqflHidParse,
TEXT("Unit 0x%08x contains basic units that cannot be translated to aspects"),
pvcaps->Units );
}
else
{
/*
* The system of measurement should be one of the
* four defined systems and the length must be one
* dimensional.
*/
if( ( ( pvcaps->Units & ( HID_UNIT_SYSTEM_MASK | HID_UNIT_LENGTH_MASK ) ) >= 0x11 )
&&( ( pvcaps->Units & ( HID_UNIT_SYSTEM_MASK | HID_UNIT_LENGTH_MASK ) ) <= 0x14 ) )
{
switch( pvcaps->Units & ( HID_UNIT_TIME_MASK | HID_UNIT_MASS_MASK ) )
{
case 0x0000:
dwAspect = DIDOI_ASPECTPOSITION;
break;
case 0xf000:
dwAspect = DIDOI_ASPECTVELOCITY;
break;
case 0xe000:
dwAspect = DIDOI_ASPECTACCEL;
break;
case 0xe100:
dwAspect = DIDOI_ASPECTFORCE;
break;
default:
if( 0x0004 == ( pvcaps->Units & ( HID_UNIT_TIME_MASK | HID_UNIT_MASS_MASK | HID_UNIT_SYSTEM_MASK ) ) )
{
SquirtSqflPtszV(sqflTrace | sqflHidParse,
TEXT("Unit \"degrees\" will not be mapped to a DI aspect (probably a POV)") );
}
else
{
SquirtSqflPtszV(sqflTrace | sqflHidParse,
TEXT("Unit 0x%04x represents a mass/time unit that cannot be translated to aspects"),
pvcaps->Units );
}
}
}
else
{
SquirtSqflPtszV(sqflTrace | sqflHidParse,
TEXT("Unit 0x%04x contains represents a length/system unit that cannot be translated to aspects"),
pvcaps->Units );
}
}
#ifdef DEBUG
if( dwAspect )
{
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("Unit 0x%04x translated to aspect 0x%04x"),
pvcaps->Units, dwAspect );
}
#endif
#undef HID_UNIT_SYSTEM_MASK
#undef HID_UNIT_LENGTH_MASK
#undef HID_UNIT_MASS_MASK
#undef HID_UNIT_TIME_MASK
}
else
{
PHIDUSAGEMAP phum;
phum = UsageToUsageMap( DIMAKEUSAGEDWORD( pvcaps->UsagePage,
pvcaps->Range.UsageMin ) );
if( phum && phum->bPosAxis <= 6 )
{
phum = UsageToUsageMap( DIMAKEUSAGEDWORD( pvcaps->UsagePage,
pvcaps->Range.UsageMax ) );
if( phum && phum->bPosAxis <= 6 )
{
dwAspect = DIDOI_ASPECTPOSITION;
}
}
else
{
SquirtSqflPtszV(sqflTrace | sqflHidParse,
TEXT("No aspect found for 0x%04x page usage 0x%04x-0x%04x"),
pvcaps->UsagePage, pvcaps->Range.UsageMin, pvcaps->Range.UsageMax );
}
#ifdef DEBUG
if( dwAspect )
{
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("Usage (page:min-max) 0x%04x:0x%04x-0x%04x translated to aspect 0x%04x"),
pvcaps->UsagePage, pvcaps->Range.UsageMin, pvcaps->Range.UsageMax, dwAspect );
}
#endif
}
return dwAspect;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitAxisClass |
*
* Initialize one class (input, feature, output) of axes.
*
* @parm PHIDGROUPCAPS | rgcaps |
*
* Array of <t HIDGROUPCAPS> structures to receive the caps
* of the axes in the class.
*
* @parm USHORT | ccaps |
*
* Number of <t HIDGROUPCAPS> structures we expect to find.
*
* @parm HIDP_REPORT_TYPE | type |
*
* One of the values
* <c HidP_Input>,
* <c HidP_Feature> or
* <c HidP_Output>.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_InitAxisClass(PCHID this, PHIDGROUPCAPS rgcaps, USHORT ccaps,
HIDP_REPORT_TYPE type)
{
USHORT cvcaps;
NTSTATUS stat;
HRESULT hres;
DWORD ivcaps;
LONG lSignedMask;
PHIDP_VALUE_CAPS rgvcaps;
AssertF(rgcaps >= this->rgcaps);
AssertF(rgcaps + ccaps <= &this->rgcaps[this->ccaps]);
/*
* Annoying quirk:
*
* HID doesn't like it when you pass 0 to HidP_GetValueCaps,
* so we need to special-case the "no axes" scenario.
*/
if(ccaps == 0)
{
hres = S_OK;
goto done;
}
hres = AllocCbPpv(cbCxX(ccaps, HIDP_VALUE_CAPS), &rgvcaps);
if(FAILED(hres))
{
goto done;
}
cvcaps = ccaps;
stat = HidP_GetValueCaps(type, rgvcaps, &cvcaps, this->ppd);
if(FAILED(stat))
{
RPF("HidP_GetValueCaps failed - can't use device");
hres = E_FAIL;
goto freedone;
}
if(cvcaps != ccaps)
{
RPF("HidP_GetValueCaps inconsistent with HidP_GetCaps - "
"can't use device");
hres = E_FAIL;
goto freedone;
}
CHid_SortCaps(rgvcaps, cvcaps);
for(ivcaps = 0; ivcaps < cvcaps; ivcaps++)
{
PHIDP_VALUE_CAPS pvcaps = &rgvcaps[ivcaps];
PHIDGROUPCAPS pcaps = &rgcaps[ivcaps];
BOOL fPOV;
UINT uiObj;
UINT duiObj;
DWORD dwAspect;
/*
* ISSUE-2001/03/06-MarcAnd Ignoring report count
* We ignore the report count which may be bad, need
* to test device with values declared in a range to see what HID
* really gives us.
* At the descriptor level, values can be delared in a range with
* usages and the last usage is repeated for any excess values.
*/
if(pvcaps->IsRange)
{
if(pvcaps->Range.DataIndexMax - pvcaps->Range.DataIndexMin !=
pvcaps->Range.UsageMax - pvcaps->Range.UsageMin)
{
RPF("HidP_GetValueCaps corrupted VALUE_CAPS - "
"can't use device");
hres = E_FAIL;
goto freedone;
}
} else
{
pvcaps->Range.UsageMax = pvcaps->Range.UsageMin;
}
if( ( type == HidP_Input ) && !pvcaps->IsAlias )
{
/*
* The values are sorted by data index with the primary alias
* before any other. So find out the aspect of the axis each
* time we get a new primary and use it for any alias that follow.
*/
dwAspect = CHID_FindAspect( pvcaps );
}
pcaps->wReportId = pvcaps->ReportID;
this->wMaxReportId[type] = max(pcaps->wReportId, this->wMaxReportId[type]);
pcaps->UsagePage = pvcaps->UsagePage;
pcaps->UsageMin = pvcaps->Range.UsageMin;
pcaps->DataIndexMin = pvcaps->Range.DataIndexMin;
pcaps->cObj = pvcaps->Range.UsageMax -
pvcaps->Range.UsageMin + 1;
/*
* The mask consists of the top bit of the BitSize and
* all bits above it. Examples:
*
* BitSize 8 32
* BitSize - 1 7 31
* 1 << (BitSize - 1) 0x00000080 0x80000000
* (1 << (BitSize - 1)) - 1 0x0000007F 0x7FFFFFFF
* ~((1 << (BitSize - 1)) - 1) 0xFFFFFF80 0x80000000
*
*/
pcaps->BitSize = pvcaps->BitSize;
pcaps->lMask = ~((1 << (pcaps->BitSize - 1)) - 1);
lSignedMask = max( 1, ( 1 << pcaps->BitSize) -1 );
pcaps->LinkCollection = pvcaps->LinkCollection;
pcaps->Units = pvcaps->Units;
pcaps->Exponent = LOWORD(pvcaps->UnitsExp);
pcaps->Logical.Min = pvcaps->LogicalMin;
pcaps->Logical.Max = pvcaps->LogicalMax;
pcaps->Physical.Min = pvcaps->PhysicalMin;
pcaps->Physical.Max = pvcaps->PhysicalMax;
if(pcaps->Logical.Min >= pcaps->Logical.Max)
{
RPF("HidP_GetValueCaps Logical Min >= Logical Max - ");
if( pcaps->Physical.Min < pcaps->Physical.Max )
{
pcaps->Logical = pcaps->Physical;
} else
{
pcaps->Logical.Min = pcaps->lMask;
pcaps->Logical.Max = ~pcaps->lMask;
}
SquirtSqflPtszV(sqflHidParse | sqflVerbose,
TEXT("HidP_GetValueCaps:")
TEXT("Logical Min(was:%d now:%d)")
TEXT("Logical Max(was:%d now:%d)"),
pvcaps->LogicalMin, pcaps->Logical.Min,
pvcaps->LogicalMax, pcaps->Logical.Max);
}
/*
* the range for LogicalMin / LogicalMax had better fall
* within the range of values the device can possibly
* report.
*
* The lMask value happens also to be the smallest possible
* negative value, and the bitwise negation of it happens
* to be the largest possible positive value. The wonders
* of two-s complement arithmetic.
*/
/*
* Extra case is fix for 268519
*/
if(pcaps->Physical.Min > pcaps->Physical.Max)
{
RPF("HidP_GetValueCaps Physical Min/Max(%d/%d) is bad setting all to zero to %d"
"device may have bad firmware", pcaps->Physical.Min, pcaps->Physical.Max, lSignedMask);
pcaps->Logical.Min = 0;
pcaps->Physical.Min = 0;
pcaps->Logical.Max = lSignedMask;
pcaps->Physical.Max = lSignedMask;
pcaps->lMask = lSignedMask;
pcaps->IsSigned = FALSE;
}
else
{
if(pcaps->Physical.Min == pcaps->Physical.Max)
{
pcaps->Physical = pcaps->Logical;
}
if(pcaps->Logical.Min >= pcaps->lMask && // Logical Min / Max are signed
pcaps->Logical.Max <= ~pcaps->lMask)
{
pcaps->IsSigned = TRUE;
} else if(pcaps->Logical.Min >= 0 &&
pcaps->Logical.Max <= lSignedMask )
{ // Logical Min / Max are unsigned
pcaps->lMask = lSignedMask;
pcaps->IsSigned = FALSE;
} else if (pcaps->UsagePage >= HID_USAGE_PAGE_VENDOR )
{
// Let this one pass, hopefully the broken descriptors are for
// usages that are vendor specific and will not effect too many folks
RPF("HidP_GetValueCaps Logical Min/Max(%d/%d) don't fit in BitSize(%d) - "
"device may have bad firmware", pcaps->Logical.Min, pcaps->Logical.Max, pcaps->BitSize);
pcaps->Logical.Min = pcaps->lMask;
pcaps->Logical.Max = ~pcaps->lMask;
pcaps->IsSigned = TRUE;
}else
{
RPF("HidP_GetValueCaps UsagePage(0x%x)Usage(0x%x) Logical Min/Max(%d/%d) don't fit in BitSize(%d) - "
"can't use device", pcaps->UsagePage, pcaps->UsageMin, pcaps->Logical.Min, pcaps->Logical.Max, pcaps->BitSize);
hres = E_FAIL;
goto freedone;
}
}
AssertF(pcaps->Physical.Min < pcaps->Physical.Max);
pcaps->StringMin = pvcaps->Range.StringMin;
pcaps->StringMax = pvcaps->IsStringRange ?
pvcaps->Range.StringMax :
pvcaps->Range.StringMin;
pcaps->DesignatorMin = pvcaps->Range.DesignatorMin;
pcaps->DesignatorMax = pvcaps->IsDesignatorRange ?
pvcaps->Range.DesignatorMax :
pvcaps->Range.DesignatorMin;
pcaps->IsAbsolute = pvcaps->IsAbsolute;
pcaps->IsValue = TRUE;
pcaps->IsAlias = pvcaps->IsAlias;
pcaps->type = type;
/*
* HID reports axes and POVs as the same thing, and the two
* POV usages we recognize are in different pages, so you
* will never get multiple POVs coming through in a single
* value-caps.
*
* ISSUE-2001/03/06-MarcAnd POVs within caps are treated as axes
* There is, however, the problem of a POV buried inside
* a larger value-cap that describes axes. Tough. Those
* POVs are in trouble.
*/
#define HID_USAGE_GAME_POV ((USAGE) 0x20)
fPOV = pcaps->cObj == 1
&& ( ( pcaps->UsagePage == HID_USAGE_PAGE_GENERIC
&&pcaps->UsageMin == HID_USAGE_GENERIC_HATSWITCH )
||( pcaps->UsagePage == HID_USAGE_PAGE_GAME
&&pcaps->UsageMin == HID_USAGE_GAME_POV ) );
if(fPOV)
{
LONG lUnits;
/*
* POVs are assumed to start at north and increase
* clockwise through the logical maximum.
*/
lUnits = pcaps->Logical.Max - pcaps->Logical.Min + 1;
if(lUnits)
{
pcaps->usGranularity = (USHORT)(36000U / lUnits);
}
} else
{
if( ( pcaps->UsagePage == HID_USAGE_PAGE_GENERIC &&
pcaps->UsageMin <= HID_USAGE_GENERIC_HATSWITCH &&
pvcaps->Range.UsageMax >= HID_USAGE_GENERIC_HATSWITCH )
||( pcaps->UsagePage == HID_USAGE_PAGE_GAME &&
pcaps->UsageMin <= HID_USAGE_GAME_POV &&
pvcaps->Range.UsageMax >= HID_USAGE_GAME_POV ) )
{
SquirtSqflPtszV(sqflHidParse | sqflError,
TEXT("HidP_GetValueCaps - multi-usage cap ")
TEXT("includes hatswitch or POV - will be treated")
TEXT("as axis"));
}
}
#undef HID_USAGE_GAME_POV
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Objs = %d"),
type, ivcaps, pcaps->cObj);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Index %d ..."),
type, ivcaps,
pcaps->DataIndexMin);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Logical = %d..%d"),
type, ivcaps,
pcaps->Logical.Min, pcaps->Logical.Max);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Physical = %d..%d"),
type, ivcaps,
pcaps->Physical.Min, pcaps->Physical.Max);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Units = 0x%08x"),
type, ivcaps,
pcaps->Units);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: IsAbs = %d"),
type, ivcaps,
pcaps->IsAbsolute);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: BitSize = %d"),
type, ivcaps,
pcaps->BitSize);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: IsAlias = %d"),
type, ivcaps,
pcaps->IsAlias);
if(pcaps->LinkCollection)
{
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: ")
TEXT("LinkCollection %d"),
type, ivcaps,
pcaps->LinkCollection);
}
/*
* Come up with a decent Null value if possible.
* The traditional Null value is the arithmetically
* smallest value which lies outside the
* LogicalMin/LogicalMax range. Conveniently, the
* pcaps->lMask is the most negative value that is
* in range.
*/
AssertF(pcaps->Null == 0);
if(pvcaps->HasNull)
{
#ifdef WINNT
pcaps->IsPolledPOV = FALSE;
#endif
if(pcaps->lMask < pcaps->Logical.Min)
{
pcaps->Null = pcaps->lMask;
} else if(!(pcaps->lMask & (pcaps->Logical.Max + 1)))
{
pcaps->Null = pcaps->Logical.Max + 1;
} else if( ! pcaps->IsSigned )
{
pcaps->Null = 0x0;
} else
{
SquirtSqflPtszV(sqflTrace,
TEXT("VALUE_CAPS claims Null but no room!"));
}
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Null = %d"),
type, ivcaps,
pcaps->Null);
} else {
#ifdef WINNT
if( fPOV &&
(this->VendorID == MSFT_SYSTEM_VID ) &&
( (this->ProductID & 0xff00) == MSFT_SYSTEM_PID) )
{
LONG lUnits;
lUnits = pcaps->Logical.Max - pcaps->Logical.Min + 1;
if(lUnits)
{
pcaps->usGranularity = (USHORT)9000;
}
pcaps->IsPolledPOV = TRUE;
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetValueCaps(%d)[%d]: Null = %d (Polled POV)."),
type, ivcaps,
pcaps->Null);
}
#endif
}
uiObj = this->rgdwBase[type] + pcaps->DataIndexMin;
for(duiObj = 0; duiObj < pcaps->cObj; duiObj++)
{
DWORD dwObjType;
LPDIOBJECTDATAFORMAT podf;
/*
* If HID messed up and gave us something out of range,
* then give up on this value caps (since the rest are
* also out of range) and move on to the next one.
*/
if(uiObj + duiObj >= this->df.dwNumObjs)
{
RPF("HidP_GetValueCaps inconsistent with NumberDataIndices - "
"skipping object (sorry)");
break;
}
AssertF(uiObj + duiObj < this->df.dwNumObjs);
if(this->rghoc[uiObj + duiObj].pcaps)
{
RPF("HidP_GetValueCaps inconsistent - "
"can't use device");
hres = E_FAIL;
goto freedone;
}
this->rghoc[uiObj + duiObj].pcaps = pcaps;
podf = &this->df.rgodf[uiObj + duiObj];
/*
* HACKHACK! Wheels are identified by
* UsageToUsageMap as GUID_Slider, but we
* want them to be GUID_ZAxis if we are a mouse.
*
* We also set the granularity here.
*/
if(GET_DIDEVICE_TYPE(this->dwDevType) == DI8DEVTYPE_MOUSE &&
pcaps->UsageMin + duiObj == HID_USAGE_GENERIC_WHEEL &&
pcaps->UsagePage == HID_USAGE_PAGE_GENERIC)
{
podf->pguid = &GUID_ZAxis;
pcaps->usGranularity = (USHORT)g_lWheelGranularity;
} else if( type == HidP_Input )
{
PHIDUSAGEMAP phum;
phum = UsageToUsageMap( DIMAKEUSAGEDWORD( pcaps->UsagePage,
pcaps->UsageMin + duiObj ) );
if(phum)
{
podf->pguid = phum->pguid;
} else
{
podf->pguid = &GUID_Unknown;
}
} else
{
podf->pguid = &GUID_Unknown;
}
/*
* Set a default instance. This will be overwritten later
* if this object is of a type we fully understand.
*/
dwObjType = DIDFT_MAKEINSTANCE(uiObj + duiObj);
if( pcaps->IsAlias )
{
dwObjType |= DIDFT_ALIAS;
}
if(pcaps->UsagePage >= HID_USAGE_PAGE_VENDOR )
{
dwObjType |= DIDFT_VENDORDEFINED;
}
else if(podf->pguid == &GUID_POV)
{
/* Note, this must be an input to have been mapped */
dwObjType |= DIDFT_POV;
if( !pcaps->IsAlias )
{
this->dwPOVs++;
}
}
else if( type == HidP_Input )
{
/*
* In order to reduce the likelyhood of an app picking up an
* input value that is not a user controlled axis, only mark
* values as axes if they are inputs on a usage page that
* contains such usages.
* ISSUE-2000/11/07-MarcAnd ideally we should be looking at
* not only the usage of the object but at the collections
* that contain it.
*/
switch( pcaps->UsagePage )
{
case HID_USAGE_PAGE_BUTTON:
/*
* The plan was that an absolute input axis on the button
* page would be an analog button. Unfortunately it
* could be a mis-declared button array selector (482186).
* Safest thing is to ignore the thing completely.
*/
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("Ignoring value on button page usage ID 0x%04X"),
pcaps->UsageMin + duiObj );
break;
case HID_USAGE_PAGE_GENERIC:
if( pcaps->UsageMin + duiObj == HID_USAGE_GENERIC_COUNTED_BUFFER )
{
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("Leaving counted buffer as unclassified object") );
break;
}
if( pcaps->UsageMin + duiObj == HID_USAGE_GENERIC_BYTE_COUNT )
{
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("Leaving byte count as unclassified object") );
break;
}
case HID_USAGE_PAGE_SIMULATION:
case HID_USAGE_PAGE_VR:
case HID_USAGE_PAGE_SPORT:
case HID_USAGE_PAGE_GAME:
case HID_USAGE_PAGE_KEYBOARD:
case HID_USAGE_PAGE_CONSUMER:
case HID_USAGE_PAGE_DIGITIZER:
if( pcaps->IsAbsolute)
{
dwObjType |= DIDFT_ABSAXIS;
}
else
{
dwObjType |= DIDFT_RELAXIS;
}
break;
default:
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("Assuming value 0x%04X:0x%04X is not a user control"),
pcaps->UsagePage,pcaps->UsageMin + duiObj );
}
if( ( dwObjType & DIDFT_AXIS ) && !pcaps->IsAlias )
{
this->dwAxes++;
}
}
/*
* Objects must have an offset to be accessed
*/
podf->dwOfs = this->df.dwDataSize;
if( !pcaps->IsAlias)
{
this->df.dwDataSize += cbX(DWORD);
}
if(HidP_IsOutputLike(type))
{
/*
* Input and feature allow data; output does not.
*/
if(type == HidP_Output)
{
dwObjType |= ( DIDFT_NODATA | DIDFT_OUTPUT );
}
else
{
dwObjType |= DIDFT_OUTPUT;
}
}
podf->dwType = dwObjType;
if(type != HidP_Input )
{
podf->dwFlags = DIDOI_POLLED | DIDOI_NOTINPUT;
}
else if(this->IsPolledInput )
{
podf->dwFlags = dwAspect | DIDOI_POLLED;
} else
{
podf->dwFlags = dwAspect;
}
/*
* ISSUE-2001/03/06-MarcAnd DIDOI FF attributes
*/
if( this->fPIDdevice // FF device
&& ! IsEqualGUID(podf->pguid, &GUID_Unknown ) ) // We map the axis
{
NTSTATUS ntStat;
USHORT cAButton=0x0;
ntStat = HidP_GetSpecificButtonCaps
(
HidP_Output, // ReportType
pcaps->UsagePage, // UsagePage
0x0, // Link Collection
(USAGE)(pcaps->UsageMin + duiObj), // Usage
NULL, // ValueCaps
&cAButton, // ValueCapsLength
this->ppd // PreparsedData
);
if( SUCCEEDED(ntStat)
|| (ntStat == HIDP_STATUS_BUFFER_TOO_SMALL) )// In case someone has more than one
{
podf->dwFlags |= DIDOI_FFACTUATOR;
podf->dwType |= DIDFT_MAKEATTR(DIDOI_FFACTUATOR);
}
}
/*
* Note that we do not calibrate relative axes,
* since there's really nothing to calibrate.
*
* Note also that we calibrate only inputs.
* We don't want to do de-calibration on outputs.
* (And since features are input+output, we don't
* do it on features either.)
*
* We merely set up the calibration here; the
* reading of the calibration values is done
* by CHid_LoadCalibrations.
*
*/
if(type == HidP_Input)
{
PJOYRANGECONVERT pjrc = this->pjrcNext++;
this->rghoc[uiObj + duiObj].pjrc = pjrc;
/*
* Saturation always defaults to 100%.
*/
pjrc->dwSat = RANGEDIVISIONS;
AssertF(pjrc->dwDz == 0);
if( dwObjType & DIDFT_ABSAXIS )
{
pjrc->lMin = 0;
pjrc->lMax = 65535;
pjrc->lC = 65535/2;
}
#ifdef WINNT
else if( (dwObjType & DIDFT_POV) && pcaps->IsPolledPOV )
{
pjrc->fPolledPOV = TRUE;
}
#endif
}
}
D(pcaps->dwSignature = HIDGROUPCAPS_SIGNATURE);
}
hres = S_OK;
freedone:;
FreePv(rgvcaps);
done:;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitAxes |
*
* Identify and initialize the axes: input, feature and output.
*
* HID calls them "values" because they might not really
* be axes in the joystick sense.
*
* The input axes come first, then the feature axes,
* then the output axes.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_InitAxes(PCHID this)
{
HRESULT hres;
DWORD ccaps;
/*
* Do the input axes...
*/
hres = CHid_InitAxisClass(this, &this->rgcaps[0],
this->caps.NumberInputValueCaps,
HidP_Input);
if(FAILED(hres))
{
goto done;
}
ccaps = this->caps.NumberInputValueCaps;
/*
* Do the feature axes...
*/
hres = CHid_InitAxisClass(this, &this->rgcaps[ccaps],
this->caps.NumberFeatureValueCaps,
HidP_Feature);
if(FAILED(hres))
{
goto done;
}
ccaps += this->caps.NumberFeatureValueCaps;
/*
* Do the output axes...
*/
hres = CHid_InitAxisClass(this, &this->rgcaps[ccaps],
this->caps.NumberOutputValueCaps,
HidP_Output);
if(FAILED(hres))
{
goto done;
}
done:;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitButtonClass |
*
* Initialize one class (input, feature, output) of buttons.
*
* @parm PHIDGROUPCAPS | rgcaps |
*
* Array of <t HIDGROUPCAPS> structures to receive the caps
* of the buttons in the class.
*
* @parm USHORT | ccaps |
*
* Number of <t HIDGROUPCAPS> structures we expect to find.
*
* @parm HIDP_REPORT_TYPE | type |
*
* One of the values
* <c HidP_Input>,
* <c HidP_Feature> or
* <c HidP_Output>.
*
*****************************************************************************/
HRESULT INTERNAL CHid_InitButtonClass
(
PCHID this,
PHIDGROUPCAPS rgcaps,
PBYTE rgbReportIDs,
USHORT ccaps,
HIDP_REPORT_TYPE type
)
{
USHORT cbcaps;
NTSTATUS stat;
HRESULT hres;
DWORD ibcaps;
PHIDP_BUTTON_CAPS rgbcaps;
AssertF(rgcaps >= this->rgcaps);
AssertF(rgcaps + ccaps <= &this->rgcaps[this->ccaps]);
/*
* Annoying quirk:
*
* HID doesn't like it when you pass 0 to HidP_GetButtonCaps,
* so we need to special-case the "no buttons" scenario.
*/
if(ccaps == 0)
{
hres = S_OK;
goto done;
}
hres = AllocCbPpv(cbCxX(ccaps, HIDP_BUTTON_CAPS), &rgbcaps);
if(FAILED(hres))
{
goto done;
}
cbcaps = ccaps;
stat = HidP_GetButtonCaps(type, rgbcaps, &cbcaps, this->ppd);
if(FAILED(stat))
{
RPF("HidP_GetButtonCaps failed - can't use device");
hres = E_FAIL;
goto freedone;
}
/* HidP_GetCaps has the annoying habit of treating everything that is
* single bit as a button.
* This causes some problems. For example the ALPS gamepad declares its
* POVs as single bit values, (not buttons though).
* Hence we need to be prepared for the buttons being less than advertised
*/
if(cbcaps != ccaps)
{
RPF("HidP_GetButtonCaps(%d) (%d) inconsistent "
"with HidP_GetCaps (%d) - "
"can't use device", type, cbcaps, ccaps);
hres = E_FAIL;
goto freedone;
}
CHid_SortCaps(rgbcaps, cbcaps);
for(ibcaps = 0; ibcaps < cbcaps; ibcaps++)
{
PHIDP_BUTTON_CAPS pbcaps = &rgbcaps[ibcaps];
PHIDGROUPCAPS pcaps = &rgcaps[ibcaps];
UINT uiObj;
UINT duiObj;
if(pbcaps->IsRange)
{
if(pbcaps->Range.DataIndexMax - pbcaps->Range.DataIndexMin !=
pbcaps->Range.UsageMax - pbcaps->Range.UsageMin)
{
RPF("HidP_GetButtonCaps corrupted BUTTON_CAPS - "
"can't use device");
hres = E_FAIL;
goto freedone;
}
} else
{
pbcaps->Range.UsageMax = pbcaps->Range.UsageMin;
}
pcaps->wReportId = pbcaps->ReportID;
this->wMaxReportId[type] = max(pcaps->wReportId, this->wMaxReportId[type]);
pcaps->UsagePage = pbcaps->UsagePage;
pcaps->UsageMin = pbcaps->Range.UsageMin;
pcaps->DataIndexMin = pbcaps->Range.DataIndexMin;
pcaps->cObj = pbcaps->Range.UsageMax -
pbcaps->Range.UsageMin + 1;
/*
* Buttons are (from the HID definition) items with
* a bit size of 1.
*/
pcaps->BitSize = 1;
pcaps->lMask = ~((1 << (pcaps->BitSize - 1)) - 1);
/*
* Not applicable for buttons:
*
* LogicalMin/Max, PhysicalMin/Max, Units.
*/
pcaps->LinkCollection = pbcaps->LinkCollection;
pcaps->StringMin = pbcaps->Range.StringMin;
pcaps->StringMax = pbcaps->IsStringRange ?
pbcaps->Range.StringMax :
pbcaps->Range.StringMin;
pcaps->DesignatorMin = pbcaps->Range.DesignatorMin;
pcaps->DesignatorMax = pbcaps->IsDesignatorRange ?
pbcaps->Range.DesignatorMax :
pbcaps->Range.DesignatorMin;
/*
* ISSUE-2001/03/06-MarcAnd What does IsAbsolute mean for a button?
*/
pcaps->IsAbsolute = pbcaps->IsAbsolute;
AssertF(!pcaps->IsValue);
pcaps->type = type;
pcaps->IsAlias = pbcaps->IsAlias;
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetButtonCaps(%d)[%d]: ")
TEXT("Objs=%2d ")
TEXT("idx=%2d... ")
TEXT("coll=%d")
TEXT("IsAlias=%d"),
type, ibcaps,
pcaps->cObj,
pcaps->DataIndexMin,
pcaps->LinkCollection,
pcaps->IsAlias);
uiObj = this->rgdwBase[type] + pcaps->DataIndexMin;
for(duiObj = 0; duiObj < pcaps->cObj; duiObj++)
{
DWORD dwObjType;
LPDIOBJECTDATAFORMAT podf;
/*
* If HID messed up and gave us something out of range,
* then give up on this value caps (since the rest are
* also out of range) and move on to the next one.
*/
if(uiObj + duiObj >= this->df.dwNumObjs)
{
RPF("HidP_GetButtonCaps inconsistent with NumberDataIndices - "
"skipping object (sorry)");
break;
}
AssertF(uiObj + duiObj < this->df.dwNumObjs);
if(this->rghoc[uiObj + duiObj].pcaps)
{
RPF("HidP_GetButtonCaps inconsistent - "
"can't use device");
hres = E_FAIL;
goto freedone;
}
/*
* No fatal errors have been detected so store the object details
*/
AssertF( rgbReportIDs[uiObj + duiObj] == 0 );
rgbReportIDs[uiObj + duiObj] = pbcaps->ReportID;
this->rghoc[uiObj + duiObj].pcaps = pcaps;
podf = &this->df.rgodf[uiObj + duiObj];
/*
* Set a default instance. This will be overwritten later
* if this object is of a type we fully understand.
*/
dwObjType = DIDFT_MAKEINSTANCE(uiObj + duiObj);
if(pcaps->UsagePage >= HID_USAGE_PAGE_VENDOR )
{
/*
* ISSUE-2001/03/06-MarcAnd vendor defined objects
* An aliased vendor defined usage may have a standard (not
* vendor defined) alias but the whole object will still be
* marked as vendor defined
*/
if( pcaps->IsAlias )
{
dwObjType |= DIDFT_ALIAS;
}
dwObjType |= DIDFT_VENDORDEFINED;
podf->pguid = &GUID_Unknown;
}
else
{
if( pcaps->IsAlias )
{
dwObjType |= DIDFT_ALIAS;
}
/*
* In order to reduce the likelyhood of an app picking up a
* bit that is not a user controlled button, only mark bits
* as buttons if they are inputs on a usage page that
* contains such usages.
* ISSUE-2000/11/07-MarcAnd ideally we should be looking at
* not only the usage of the object but at the collections
* that contain it.
*/
if( type == HidP_Input )
{
switch( pcaps->UsagePage )
{
case HID_USAGE_PAGE_KEYBOARD:
podf->pguid = &GUID_Key;
break;
case HID_USAGE_PAGE_BUTTON:
if( ( duiObj == 0 )
&& ( pcaps->UsageMin == 0 ) )
{
/*
* Special case button zero means no buttons
* are pressed
*/
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("Ignoring \"No button\" button") );
goto IgnoreButton;
}
case HID_USAGE_PAGE_GENERIC:
case HID_USAGE_PAGE_SIMULATION:
case HID_USAGE_PAGE_VR:
case HID_USAGE_PAGE_SPORT:
case HID_USAGE_PAGE_GAME:
case HID_USAGE_PAGE_CONSUMER:
case HID_USAGE_PAGE_DIGITIZER:
podf->pguid = &GUID_Button;
break;
default:
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("Assuming button 0x%04X:0x%04X is not a user control"),
pcaps->UsagePage,pcaps->UsageMin + duiObj );
goto IgnoreButton;
}
dwObjType |= DIDFT_PSHBUTTON;
if( !pcaps->IsAlias )
{
this->dwButtons++;
}
}
else
{
SquirtSqflPtszV(sqfl | sqflTrace,
TEXT("Assuming non-input bit 0x%04X:0x%04X is not a user control"),
pcaps->UsagePage,pcaps->UsageMin + duiObj );
IgnoreButton:;
podf->pguid = &GUID_Unknown;
}
}
/*
* Objects must have an offset to be accessed
*/
podf->dwOfs = this->df.dwDataSize;
if( !pcaps->IsAlias)
{
this->df.dwDataSize += cbX(BYTE);
}
if(HidP_IsOutputLike(type))
{
/*
* Input and feature allow data; output does not.
*/
if(type == HidP_Output)
{
dwObjType |= ( DIDFT_NODATA | DIDFT_OUTPUT );
}
else
{
dwObjType |= DIDFT_OUTPUT;
}
}
podf->dwType = dwObjType;
if(type != HidP_Input )
{
podf->dwFlags = DIDOI_POLLED | DIDOI_NOTINPUT;
} else if( this->IsPolledInput )
{
podf->dwFlags = DIDOI_POLLED;
} else
{
podf->dwFlags = 0;
}
/*
* ISSUE-2001/03/06-MarcAnd DIDOI FF attributes if not defined in registry
*/
if( this->fPIDdevice
&& ( dwObjType & DIDFT_PSHBUTTON ) )
{
podf->dwFlags |= DIDOI_FFEFFECTTRIGGER;
podf->dwType |= DIDFT_MAKEATTR(DIDOI_FFEFFECTTRIGGER);
}
}
D(pcaps->dwSignature = HIDGROUPCAPS_SIGNATURE);
}
hres = S_OK;
freedone:;
FreePv(rgbcaps);
done:;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitButtons |
*
* Identify and initialize the buttons: input, feature, and output.
*
* The input buttons come first, then the feature buttons,
* then the output buttons.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_InitButtons(PCHID this)
{
HRESULT hres;
DWORD ccaps;
PBYTE rgbReportIDs;
UINT cbReportIDs;
UINT uMaxReportId;
this->ibButtonData = this->df.dwDataSize;
/*
* Skip over the value caps to get to the buttons ...
*/
ccaps = this->caps.NumberInputValueCaps +
this->caps.NumberFeatureValueCaps +
this->caps.NumberOutputValueCaps;
/*
* Allocate a temporary buffer to store the report ID of each button.
* We use the sum of all data indices and collections so that the
* internal object index of each button can be used as the index into
* the buffer. We leave value and collection elements alone so they
* are just a small waste of space.
*/
cbReportIDs = this->caps.NumberInputDataIndices +
this->caps.NumberFeatureDataIndices +
this->caps.NumberOutputDataIndices +
this->caps.NumberLinkCollectionNodes;
hres = AllocCbPpv( cbReportIDs, &rgbReportIDs );
if(FAILED(hres))
{
goto done;
}
/*
* Do the input buttons...
*/
hres = CHid_InitButtonClass(this,
&this->rgcaps[ccaps],
rgbReportIDs,
this->caps.NumberInputButtonCaps,
HidP_Input);
if(FAILED(hres))
{
goto done;
}
ccaps += this->caps.NumberInputButtonCaps;
/*
* Do the feature buttons...
*/
hres = CHid_InitButtonClass(this,
&this->rgcaps[ccaps],
rgbReportIDs,
this->caps.NumberFeatureButtonCaps,
HidP_Feature);
if(FAILED(hres))
{
goto done;
}
ccaps += this->caps.NumberFeatureButtonCaps;
/*
* Do the output buttons...
*/
hres = CHid_InitButtonClass(this,
&this->rgcaps[ccaps],
rgbReportIDs,
this->caps.NumberOutputButtonCaps,
HidP_Output);
if(FAILED(hres))
{
goto done;
}
this->cbButtonData = this->df.dwDataSize - this->ibButtonData;
/*
* If this device only has one report ID, it must be ID zero
*/
uMaxReportId = (UINT) max( this->wMaxReportId[HidP_Input],
max( this->wMaxReportId[HidP_Feature],
this->wMaxReportId[HidP_Output] ) );
/*
* If there's only one report or there are no buttons there's
* no need to set up the arrays of data masks for each report.
*/
if( uMaxReportId == 0 )
{
AssertF( this->rgpbButtonMasks == NULL );
}
else if( this->cbButtonData == 0 )
{
AssertF( this->rgpbButtonMasks == NULL );
}
else
{
/*
* Allocate enough space for a mask array and a pointer to the array
* for each report.
*/
hres = AllocCbPpv( uMaxReportId * ( this->cbButtonData + cbX( PV ) ),
&this->rgpbButtonMasks );
if( SUCCEEDED( hres ) )
{
UINT uReportId;
UINT uDataIdx;
UINT uBtnIdx;
PBYTE pbCurrentMask;
/*
* The masks start after the last pointer to masks
*/
pbCurrentMask = (PBYTE)&this->rgpbButtonMasks[uMaxReportId];
memset( pbCurrentMask, 0xFF, this->cbButtonData * uMaxReportId );
/*
* Search through our temp buffer once for each report
*/
for( uReportId=0; uReportId<uMaxReportId; uReportId++ )
{
for( uBtnIdx=uDataIdx=0; uDataIdx<cbReportIDs; uDataIdx++ )
{
/*
* Report IDs are one based but we use a zero based array
* so adjust when testing for a matching ID
*/
if( rgbReportIDs[uDataIdx] == uReportId+1 )
{
/*
* Set the pointer for this report to the current
* mask array. If a report contains more than one
* button the same value will be set repeatedly.
*/
this->rgpbButtonMasks[uReportId] = pbCurrentMask;
/*
* The final result is an AND mask so clear all bits
* so this button will be cleared when this report
* is being processed
*/
pbCurrentMask[uBtnIdx] = 0;
}
/*
* Just in case there are gaps in the HID report we
* use our own counter of button index which is only
* incremented when we find a button to keep in line
* with the contiguous block of buttons that we use.
*/
if( rgbReportIDs[uDataIdx] != 0 )
{
uBtnIdx++;
AssertF( uBtnIdx <= this->cbButtonData );
}
}
/*
* There should always be exactly as many buttons in
* all the reports combined as we found when counting
* how many buttons there were of each type.
*/
AssertF( uBtnIdx == this->cbButtonData );
/*
* If any buttons were found in this report, use next mask
*/
if( this->rgpbButtonMasks[uReportId] == pbCurrentMask )
{
pbCurrentMask += this->cbButtonData;
}
}
/*
* At least one report had to have a button in it.
*/
AssertF( pbCurrentMask != (PBYTE)&this->rgpbButtonMasks[uMaxReportId] );
/*
* Try to reduce the allocation to what we actually used
* In the worst case we only land up using excess memory
*/
ReallocCbPpv( (UINT)((PBYTE)pbCurrentMask - (PBYTE)this->rgpbButtonMasks),
&this->rgpbButtonMasks );
}
}
done:;
FreePpv( &rgbReportIDs );
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | InitCollections |
*
* Identify and initialize the HID link collections.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_InitCollections(PCHID this)
{
HRESULT hres;
NTSTATUS stat;
DWORD icoll, ccoll, ccaps;
PHIDP_LINK_COLLECTION_NODE rgcoll;
ccoll = this->caps.NumberLinkCollectionNodes;
/*
* Annoying quirk:
*
* HID doesn't like it when you pass 0 to HidP_GetLinkCollectionNodes,
* so we need to special-case the "no collections" scenario.
*/
if(ccoll == 0)
{
hres = S_OK;
goto done;
}
hres = AllocCbPpv(cbCxX(ccoll, HIDP_LINK_COLLECTION_NODE), &rgcoll);
if(FAILED(hres))
{
goto done;
}
/*
* Get the collections...
*/
stat = HidP_GetLinkCollectionNodes(rgcoll, &ccoll, this->ppd);
if(FAILED(stat))
{
RPF("HidP_GetLinkCollectionNodes failed - can't use device");
hres = E_FAIL;
goto freedone;
}
if(ccoll != this->caps.NumberLinkCollectionNodes)
{
RPF("HidP_GetLinkCollectionNodes inconsistent with HidP_GetCaps - "
"can't use device");
hres = E_FAIL;
goto freedone;
}
ccaps = this->caps.NumberInputValueCaps +
this->caps.NumberFeatureValueCaps +
this->caps.NumberOutputValueCaps +
this->caps.NumberInputButtonCaps +
this->caps.NumberFeatureButtonCaps +
this->caps.NumberOutputButtonCaps;
AssertF(ccaps + ccoll == this->ccaps);
for(icoll = 0; icoll < ccoll; icoll++)
{
PHIDP_LINK_COLLECTION_NODE pcoll = &rgcoll[icoll];
PHIDGROUPCAPS pcaps = &this->rgcaps[ccaps + icoll];
UINT uiObj;
LPDIOBJECTDATAFORMAT podf;
pcaps->UsagePage = pcoll->LinkUsagePage;
pcaps->UsageMin = pcoll->LinkUsage;
pcaps->cObj = 1;
pcaps->IsAlias = pcoll->IsAlias;
/*
* Not applicable for collections:
*
* StringMin/Max,
* DesignatorMin/Max,
* BitSize, LogicalMin/Max, PhysicalMin/Max, Units.
* IsAbsolute
*/
pcaps->LinkCollection = pcoll->Parent;
pcaps->type = HidP_Coll;
/*
* We cook up DataIndexMin to correspond to this item.
*/
pcaps->DataIndexMin = (USHORT)icoll;
uiObj = this->rgdwBase[HidP_Coll] + pcaps->DataIndexMin;
/*
* We generated these indices on our own, so they
* can't possible be wrong.
*/
AssertF(uiObj < this->df.dwNumObjs);
this->rghoc[uiObj].pcaps = pcaps;
podf = &this->df.rgodf[uiObj];
/*
* ISSUE-2001/03/06-MarcAnd collections have GUID_Unknown
* Collections are more or less hidden and therefore unusable.
*/
podf->pguid = &GUID_Unknown;
/*
* Set a default instance. This will be overwritten later
* if this object is of a type we fully understand.
*/
podf->dwType = DIDFT_MAKEINSTANCE(uiObj) | DIDFT_COLLECTION | DIDFT_NODATA;
if(pcaps->UsagePage >= HID_USAGE_PAGE_VENDOR )
{
podf->dwType |= DIDFT_VENDORDEFINED;
}
podf->dwFlags = 0;
/*
* CHid_ObjFromType relies on dwCollections not being split between
* aliased and unaliased.
*/
this->dwCollections++;
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetLinkCollectionNodes(%d)[%d]: ")
TEXT("Objs=%2d ")
TEXT("idx=%2d... ")
TEXT("coll=%d")
TEXT("IsAlias=%d"),
pcaps->type, icoll,
pcaps->cObj,
pcaps->DataIndexMin,
pcaps->LinkCollection,
pcaps->IsAlias);
D(pcaps->dwSignature = HIDGROUPCAPS_SIGNATURE);
}
hres = S_OK;
freedone:;
FreePv(rgcoll);
done:;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | AllocObjectMemory |
*
* Allocate all the memory that will be used to store object
* information.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_AllocObjectMemory(PCHID this)
{
DWORD cb;
DWORD cvcaps, cbcaps, ccoll, cjrc;
HRESULT hres;
/*
* Some trace squirties because HID is tricky.
*/
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberInputDataIndices = %d"),
this->caps.NumberInputDataIndices);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberOutputDataIndices = %d"),
this->caps.NumberOutputDataIndices);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberFeatureDataIndices = %d"),
this->caps.NumberFeatureDataIndices);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberLinkCollectionNodes = %d"),
this->caps.NumberLinkCollectionNodes);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberInputValueCaps = %d"),
this->caps.NumberInputValueCaps);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberOutputValueCaps = %d"),
this->caps.NumberOutputValueCaps);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberFeatureValueCaps = %d"),
this->caps.NumberFeatureValueCaps);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberInputButtonCaps = %d"),
this->caps.NumberInputButtonCaps);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberOutputButtonCaps = %d"),
this->caps.NumberOutputButtonCaps);
SquirtSqflPtszV(sqflVerbose | sqflHidParse,
TEXT("HidP_GetCaps: NumberFeatureButtonCaps = %d"),
this->caps.NumberFeatureButtonCaps);
/*
* Allocate the memory into which we place
* the DIOBJECTDATAFORMATs we build.
*/
this->df.dwNumObjs = this->caps.NumberInputDataIndices +
this->caps.NumberOutputDataIndices +
this->caps.NumberFeatureDataIndices +
this->caps.NumberLinkCollectionNodes;
if(this->df.dwNumObjs >= DIDFT_GETINSTANCE(DIDFT_ANYINSTANCE))
{
("Too many objects in HID device (%d) - can't use device",
this->df.dwNumObjs);
hres = E_FAIL;
goto done;
}
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_AllocObjectMemory: dwNumObjs = %d"),
this->df.dwNumObjs);
/*
* ISSUE-2001/10/17-MarcAnd Axes declared in ranges cause AVs
* We only allocate as many JOYRANGECONVERT elements as there are value
* caps in HID but multiple axes may be declared in a range which will
* be reported in a single value caps.
*/
cjrc = this->caps.NumberInputValueCaps;
cvcaps = this->caps.NumberInputValueCaps +
this->caps.NumberFeatureValueCaps +
this->caps.NumberOutputValueCaps;
cbcaps = this->caps.NumberInputButtonCaps +
this->caps.NumberFeatureButtonCaps +
this->caps.NumberOutputButtonCaps;
ccoll = this->caps.NumberLinkCollectionNodes;
this->ccaps = cvcaps + cbcaps + ccoll;
/*
* Allocating the memory is done in four phases.
*
* 1. Tally up how much memory we need,
* 2. Allocate that memory,
* 3. Dole out the memory we allocated,
* 4. Check that we didn't mess up.
*
* Since this is extremely error-prone (I've messed it up at least
* once), the work is hidden inside macros.
*
* The macro THINGS expands to a series of THING()s, each of which
* specifies a field name and the size it should be. Each time you
* want to iterate over the fields, use the THINGS macro.
*/
#define THINGS() \
THING(df.rgodf, cbCxX(this->df.dwNumObjs, DIOBJECTDATAFORMAT)); \
THING(rghoc, cbCxX(this->df.dwNumObjs, HIDOBJCAPS)); \
THING(rgcaps, cbCxX(this->ccaps, HIDGROUPCAPS)); \
THING(pjrcNext, cbCxX(cjrc, JOYRANGECONVERT)); \
/*
* Make a pass through the fields adding up the memory requirements.
*/
#define THING(f, cbF) cb += cbF
cb = 0;
THINGS();
#undef THING
hres = ReallocCbPpv(cb, &this->df.rgodf);
if(SUCCEEDED(hres))
{
PV pv;
/*
* Make a pass through the fields carving up the memory block
* and handing out pieces of it.
*/
#define THING(f, cbF) this->f = pv; pv = pvAddPvCb(pv, cbF)
pv = this->df.rgodf;
THINGS();
#undef THING
/*
* There should be no byte left over.
*/
AssertF(pvAddPvCb(this->df.rgodf, cb) == pv);
}
#undef THINGS
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_AllocObjectMemory: pv = %08x, cb = 0x%08x"),
this->df.rgodf, cb);
done:;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | EnumKeyboardMunge |
*
* Enumerate the objects in the list, indicating whether
* each object is "keyboardlike" or "otherlike".
*
* @parm LPBYTE | pb |
*
* Pointer to translation table that converts HID usages
* into keyboard scan codes.
*
* @parm KBDMUNGECALLBACK | Munge |
*
* Callback function that handles each object as we find it.
*
* @parm PV | pvRef |
*
* Reference data for callback.
*
* @cb void CALLBACK | KbdMungeCallback |
*
* Called once for each object on a keyboard HID device.
*
* @parm PCHID | this |
*
* The device itself.
*
* @parm UINT | uiObj |
*
* The object being enumerated.
*
* @parm UINT | dik |
*
* DirectInput scan code for the object, or a value greater than
* or equal to <c DIKBD_CKEYS> if it's a fake instance number
* concocted for a non-AT key.
*
*****************************************************************************/
typedef void (CALLBACK *KBDMUNGECALLBACK)(PCHID this, UINT uiObj, UINT dik);
void INTERNAL
CHid_EnumKeyboardMunge(PCHID this, LPBYTE pb, KBDMUNGECALLBACK Munge)
{
UINT uiObj;
BYTE rgbSeen[DIKBD_CKEYS];
UINT uiCollections;
/*
* In principle we could walk the this->rgcaps array, but
* that would open the risk that the this->rgcaps array
* and this->df.rgodf array are out of sync for some
* bizarre reason. Do it the slow way just to be safe.
*
* Furthermore, only the first item with a particular
* keyboard usage gets mapped into the DirectInput table.
* So if a keyboard has two ESCAPE keys, only the first
* one shows up in the DirectInput table; the second one
* shows up as "just another key".
*/
this->uiInstanceMax = DIKBD_CKEYS;
ZeroX(rgbSeen);
for(uiCollections = uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
PHIDGROUPCAPS pcaps;
UINT dik;
UINT duiObj;
UINT DataIndex;
UINT Usage;
pcaps = this->rghoc[uiObj].pcaps;
if( pcaps->type == HidP_Coll )
{
dik = uiCollections++;
}
else
{
AssertF( HidP_IsValidReportType(pcaps->type) );
DataIndex = uiObj - this->rgdwBase[pcaps->type];
duiObj = DataIndex - pcaps->DataIndexMin;
AssertF(duiObj < pcaps->cObj);
Usage = pcaps->UsageMin + duiObj;
if(pcaps->UsagePage == HID_USAGE_PAGE_KEYBOARD)
{
if(Usage < DIKBD_CKEYS && pb[Usage] && !rgbSeen[pb[Usage]])
{
rgbSeen[pb[Usage]] = 1;
dik = pb[Usage];
} else
{
dik = this->uiInstanceMax++;
}
} else
{
dik = this->uiInstanceMax++;
}
}
Munge(this, uiObj, dik);
}
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | TallyKeyboardObjs |
*
* Callback function used during preliminary tallying to
* tot up how many of the objects can be treated as
* AT-compatible keyboard gizmos and how many are HID-specific.
*
* @parm UINT | uiObj |
*
* The object being enumerated.
*
* @parm UINT | dik |
*
* DirectInput scan code for the object, or a value greater than
* or equal to <c DIKBD_CKEYS> if it's a fake instance number
* concocted for a non-AT key.
*
*****************************************************************************/
void INTERNAL
CHid_TallyKeyboardObjs(PCHID this, UINT uiObj, UINT dik)
{
this;
uiObj;
dik;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method void | CHid | ReassignKeyboardObjs |
*
* Callback function used to shuffle instance numbers around
* to make them AT-compatible when possible.
*
* @parm UINT | uiObj |
*
* The object being enumerated.
*
* @parm UINT | dik |
*
* DirectInput scan code for the object, or a value greater than
* or equal to <c DIKBD_CKEYS> if it's a fake instance number
* concocted for a non-AT key or an index number for a collection.
*
*****************************************************************************/
void INTERNAL
CHid_ReassignKeyboardObjs(PCHID this, UINT uiObj, UINT dik)
{
if( this->df.rgodf[uiObj].dwType & DIDFT_COLLECTION )
{
this->rgicoll[dik] = uiObj;
}
else
{
this->rgiobj[dik] = uiObj;
}
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CHid_ReassignKeyboardObjs: ")
TEXT("uiObj = %03x, dwType = %08x, dik=%04x"),
uiObj, this->df.rgodf[uiObj].dwType,
dik);
this->df.rgodf[uiObj].dwType =
(this->df.rgodf[uiObj].dwType & ~DIDFT_INSTANCEMASK) |
DIDFT_MAKEINSTANCE(dik);
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | MungeKeyboard |
*
* We just created a keyboard device.
*
* Unfortunately, DirectInput has some annoying requirements
* for keyboard devices, so here is where we swizzle the instance
* numbers around to keep DirectInput happy.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_MungeKeyboard(PCHID this)
{
HRESULT hres;
LPBYTE pb;
AssertF(this->rgiobj == 0);
pb = pvFindResource(g_hinst, IDDATA_HIDMAP, RT_RCDATA);
if(pb)
{
/*
* Count up the number of non-keyboard things on this device.
* They will get instance numbers starting at DIKBD_CKEYS.
*/
CHid_EnumKeyboardMunge(this, pb, CHid_TallyKeyboardObjs);
/*
* Now that we know how many nonstandard keyboard thingies
* we have, allocate room for the translation table
* and work all the instance values around to keep
* legacy apps happy.
*/
hres = ReallocCbPpv(cbCdw(this->uiInstanceMax + this->dwCollections), &this->rgiobj);
if(SUCCEEDED(hres))
{
memset(this->rgiobj, 0xFF, cbCxX(this->uiInstanceMax + this->dwCollections, INT));
/*
* In case a keyboard comes along with non-button inputs,
* set up the other types of pointers to the same buffer.
*/
this->rgipov = this->rgiaxis = this->rgiobj;
/*
* Put collections at the end, there should be at least one
*/
AssertF( this->dwCollections );
this->rgicoll = &this->rgiobj[this->uiInstanceMax];
CHid_EnumKeyboardMunge(this, pb, CHid_ReassignKeyboardObjs);
hres = S_OK;
}
/*
* Prefix warns that the resource is leaked (mb:34650) but there is
* no API to release a raw resource (FreeResource is a stub) so there
* is nothing we can do.
*/
} else
{
hres = E_FAIL;
}
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | MungeNotKeyboard |
*
* We just created a device that is not a keyboard.
*
* Since we need the instance for each object to be relative only to
* objects of that type, we need to replace the device relative
* values generated into type relative ones. In addition, to
* maintain compatability with pre-HID object instances we need to
* use axis instance numbers that would be generated for a WinMM
* mapped axis.
* Aliased objects must be given the same instance number as the
* primary alias so that the only difference between them is their
* usage. This prevents a data format being generated using multiple
* aliases of the same object.
*
* ISSUE-2001/03/13-MarcAnd HID object munging is incomplete
* 1. Multiple values on a single axis (force and position)
* 2. Multiple instances of an axis type (two throttles)
* 3. Does not distinguish output only values (actuators) from axes
* 4. Keyboards with anything other than buttons should have this
* done but won't
*
*****************************************************************************/
/*
* Internal function to convert all the attributes of one object to another
*
* If an axis needs to be reinterpreted here, both the axis semantic and the
* object GUID need to be changed so that the axis will be used consistently
* in both semantic mapper and data format games.
*
* Note, multiple instances of an axis type are not handled well.
*/
void INTERNAL ReinterpretObject
(
PCHID this,
PDWORD pdwHints,
int cAxes,
PDWORD pdwTypeFlags,
DWORD dwSrcHint,
DWORD dwDstHint,
BYTE bSemFlags,
PCGUID pgNew
)
{
if( ( *pdwTypeFlags & ( dwSrcHint | dwDstHint ) ) == dwSrcHint )
{
int idx;
for( idx = 0; idx < cAxes + 6; idx++ )
{
if( pdwHints[idx] == dwSrcHint )
{
*pdwTypeFlags ^= ( dwSrcHint | dwDstHint );
pdwHints[idx] = dwDstHint;
this->rgbaxissemflags[idx] = bSemFlags;
this->df.rgodf[this->rgiaxis[idx]].pguid = pgNew;
break;
}
}
AssertF( idx < cAxes + 6 );
}
}
HRESULT INTERNAL
CHid_MungeNotKeyboard(PCHID this)
{
HRESULT hres;
unsigned int uiObj;
int iButton;
int iCollection = 0;
int iPOV = 0;
int iAxis = 0;
//to indicate the index of the last "actual" button
int iLastButtonIndex = 0;
//to indicate the number of "non-actual" buttons
int iOtherButton = 0;
PINT piRemap = 0;
BOOL fCarController = FALSE;
DWORD dwTypeFlags = 0;
int cAxes;
D( iButton = 0; )
AssertF(this->rgiobj == 0);
/*
* First count the distinct types so we know how to segment rgiobj
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
SquirtSqflPtszV(sqflHidParse | sqflVerbose,
TEXT("CHid_MungeNotKeyboard: uiObj: %d starts as dwType 0x%08x"),
uiObj, this->df.rgodf[uiObj].dwType );
if( !this->rghoc[uiObj].pcaps )
{
continue;
}
if( this->rghoc[uiObj].pcaps->IsAlias )
{
/*
* If there's a steering wheel anywhere, we need to know
*/
if( ( DIDFT_AXIS == ( this->df.rgodf[uiObj].dwType & ( DIDFT_AXIS | DIDFT_NODATA ) ) )
&& ( this->rghoc[uiObj].pcaps->UsagePage == HID_USAGE_PAGE_SIMULATION )
&& ( HID_USAGE_SIMULATION_STEERING ==
( this->rghoc[uiObj].pcaps->UsageMin + uiObj
- this->rgdwBase[this->rghoc[uiObj].pcaps->type]
- this->rghoc[uiObj].pcaps->DataIndexMin ) ) )
{
fCarController = TRUE;
}
continue;
}
/*
* Don't bother taking pointer to dwType in this simple
* loop as it should be enregistered anyway.
*/
if( this->df.rgodf[uiObj].dwType & DIDFT_COLLECTION )
{
iCollection++;
}
else if( this->df.rgodf[uiObj].dwType & DIDFT_NODATA )
{
/*
* Don't count objects that report no data
*/
continue;
}
else if( this->df.rgodf[uiObj].dwType & DIDFT_BUTTON )
{
//HID reports everything that has size 1 bit as a button.
//We need to know the index of the highest "actual" button -- the one that has USAGE_PAGE_BUTTON
//And then we also need to know how many "non-actual" buttons there are.
//The total amount of memory we need to allocate for buttons is the sum of those 2.
if (this->rghoc[uiObj].pcaps->UsagePage == HID_USAGE_PAGE_BUTTON)
{
int iUsage = ( this->rghoc[uiObj].pcaps->UsageMin
+ uiObj
- this->rgdwBase[this->rghoc[uiObj].pcaps->type]
- this->rghoc[uiObj].pcaps->DataIndexMin );
if (iUsage > iLastButtonIndex)
{
iLastButtonIndex = iUsage;
}
}
else
{
iOtherButton++;
}
D( iButton++; )
}
else if( this->df.rgodf[uiObj].dwType & DIDFT_AXIS )
{
iAxis++;
}
else if( this->df.rgodf[uiObj].dwType & DIDFT_POV )
{
iPOV++;
}
}
/*
* Assert that we counted everything OK while we have a button count
*/
AssertF( (int)this->df.dwNumObjs >= ( iPOV + iButton + iAxis + iCollection ) );
/*
* The total amount of memory we need to allocate for buttons is the sum of
* the highest "actual" button index and the number of "non-actual" buttons.
*/
iButton = iOtherButton + iLastButtonIndex;
AssertF(iButton >= 0);
/*
* You can go to a whole lot of trouble finding out exactly what gaps
* are left by the WinMM mapping but in the end, it's only 24 bytes of
* data and it takes way more to work out how to save them.
* So just allocate 6 extra in case all the axes are non-WinMM.
* Note, there is weirdnes involved in WinMM axes as a real WinMM device
* always have the U (Ry) and V (Rx) axes mapped to S0 and S1 however HID
* devices have more flexible mappings. Since the main reason to try to
* keep WinMM and HID axes the same is for FF, only X and Y are critical.
* Keep the button count in cAxes so we can reference it when we need to
* without worrying about whether iAxis is the current max or the real
* max. Should do this for the other counts as well but that can wait
* until this function gets its long overdue restructuring.
*/
cAxes = iAxis;
this->uiInstanceMax = iCollection + iButton + cAxes + iPOV + 6;
/*
* Note, piRemap received a pointer to a buffer user for workspace.
* The single allocation is subdivided as follows:
* a buffer for an array of ints for matched objects,
* an array of USAGES for axes and
* and array of DWORDs for hint flags.
*/
if( SUCCEEDED( hres = ReallocCbPpv(cbCdw(this->uiInstanceMax), &this->rgiobj) )
&& SUCCEEDED( hres = AllocCbPpv( ( cbCdw( 2 * (cAxes + 6) ) + cbCxX(this->uiInstanceMax, INT) ), &piRemap ) )
&& SUCCEEDED( hres = AllocCbPpv( cAxes + 6, &this->rgbaxissemflags ) ) )
{
/*
* NOTE - this is not the order they are stored in the registry - but the macro used for
* generating them internally has been reversed somewhere along the track.
*/
typedef union _USAGES
{
struct
{
USAGE Usage;
USAGE UsagePage;
};
DWORD dwUsages;
} USAGES, *PUSAGES;
PUSAGES pUsageMatches;
PDWORD pdwHints;
int iNops = 0;
int iUsages = 0;
int iAxisIdx;
HKEY hkAxis;
DWORD dwTestType = 0;
int iTypeAxes = 0;
BOOL bAllMatch=TRUE;
ZeroMemory( this->rgbaxissemflags, cAxes + 6 );
memset(piRemap, 0xFF, cbCxX(this->uiInstanceMax, INT));
memset(this->rgiobj, 0xFF, cbCxX(this->uiInstanceMax, INT));
/*
* CHid_ObjFromType relies on the order of these for range checking
*/
this->rgipov = &this->rgiobj[iButton];
this->rgiaxis = &this->rgipov[iPOV];
this->rgicoll = &this->rgipov[cAxes+6];
/*
* In order to allow IHVs to describe their devices with usages
* that we don't have in our table but still work for legacy apps, we
* allow axes to be selected using the DIOBJECTATTRIBUTES in the
* registry.
*/
pUsageMatches = (PUSAGES)&piRemap[this->uiInstanceMax];
pdwHints = &pUsageMatches[cAxes + 6].dwUsages;
/*
* Assert that we can treat the combined usage page / usage
* WORDs using a union of a DWORD and two WORDs.
CAssertF( ( FIELD_OFFSET( DIOBJECTATTRIBUTES, wUsage )
- FIELD_OFFSET( DIOBJECTATTRIBUTES, wUsagePage ) )
== ( FIELD_OFFSET( USAGES, Usage )
- FIELD_OFFSET( USAGES, UsagePage ) ) );
*/
/*
* Fetch IHV matches for all reasonable axes.
*/
for( iAxisIdx = 0; iAxisIdx < cAxes + 5; iAxisIdx++ )
{
if( SUCCEEDED( CType_OpenIdSubkey( this->hkType,
DIDFT_AXIS | DIDFT_MAKEINSTANCE( iAxisIdx ),
KEY_QUERY_VALUE, &hkAxis ) ) )
{
DIOBJECTATTRIBUTES attr;
if( ( SUCCEEDED( JoyReg_GetValue( hkAxis, TEXT("Attributes"),
REG_BINARY, &attr, cbX(attr) ) ) )
&& ( *(PDWORD)&attr.wUsagePage ) )
{
pUsageMatches[iAxisIdx].UsagePage = attr.wUsagePage;
pUsageMatches[iAxisIdx].Usage = attr.wUsage;
iUsages++;
/*
* Check it really exists on the device
* New behaviour is to ignore ALL registry mappings
* and dfault to DX7 implementation if a bogus
* control is found
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
if( ( this->df.rgodf[uiObj].dwType & DIDFT_AXIS )
&& ( this->rghoc[uiObj].pcaps )
&& ( this->rghoc[uiObj].pcaps->type == HidP_Input ) )
{
USAGES Usages;
UINT uidObj;
Usages.UsagePage = this->rghoc[uiObj].pcaps->UsagePage;
uidObj = uiObj - ( this->rgdwBase[HidP_Input] + this->rghoc[uiObj].pcaps->DataIndexMin );
Usages.Usage = this->rghoc[uiObj].pcaps->UsageMin + uidObj;
if( Usages.dwUsages == pUsageMatches[iAxisIdx].dwUsages )
{
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CHid_MungeNotKeyboard: Object %d matches 0x%08x"),
uiObj, pUsageMatches[iAxisIdx].dwUsages );
break;
}
}
}
if (uiObj >= this->df.dwNumObjs)
{
//Error in registry settings - usage/usagepage pair not present on device
//reset
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CHid_MungeNotKeyboard: No matches for 0x%08x - abandoning IHV map"),
pUsageMatches[iAxisIdx].dwUsages );
iUsages=0;
memset(piRemap, 0xFF, cbCxX(this->uiInstanceMax, INT));
bAllMatch=FALSE;
break;
}
}
else
{
iNops++;
}
RegCloseKey( hkAxis );
if( iUsages + iNops == cAxes )
{
/*
* We've opened as many keys as we have axes
* continue the loop in debug as sanity check.
*/
#ifdef XDEBUG
int iDbgIdx;
for( iDbgIdx = iAxisIdx + 1; iDbgIdx < cAxes + 5; iDbgIdx++ )
{
if( SUCCEEDED( CType_OpenIdSubkey( this->hkType,
DIDFT_AXIS | DIDFT_MAKEINSTANCE( iDbgIdx ),
KEY_QUERY_VALUE, &hkAxis ) ) )
{
RPF( "More axis keys than axes on device %04x:%04x!",
this->VendorID, this->ProductID );
}
}
#endif
break;
}
}
}
/*
* Go through all input axes, including aliases, trying to find a match
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs && bAllMatch; uiObj++)
{
UINT uiObjPrimary;
int iUsageIdx;
if( ( this->df.rgodf[uiObj].dwType & DIDFT_AXIS )
&& ( this->rghoc[uiObj].pcaps )
&& ( this->rghoc[uiObj].pcaps->type == HidP_Input ) )
{
USAGES Usages;
UINT uidObj;
if( !this->rghoc[uiObj].pcaps->IsAlias )
{
uiObjPrimary = uiObj;
}
Usages.UsagePage = this->rghoc[uiObj].pcaps->UsagePage;
uidObj = uiObj - ( this->rgdwBase[HidP_Input] + this->rghoc[uiObj].pcaps->DataIndexMin );
Usages.Usage = this->rghoc[uiObj].pcaps->UsageMin + uidObj;
/*
* Use incremental loop for better JoyHID consistency
*/
for( iUsageIdx = 0; iUsageIdx <= iAxisIdx; iUsageIdx++ )
{
if( Usages.dwUsages == pUsageMatches[iUsageIdx].dwUsages )
{
PHIDUSAGEMAP phum;
LPDIOBJECTDATAFORMAT podf;
/*
* Remember this one for later and
* discount it from further matches.
*/
piRemap[uiObjPrimary] = iUsageIdx;
pUsageMatches[iUsageIdx].dwUsages = 0;
/*
* Try to fix up the object GUID
* Use a usage page usage match if possible
* otherwise pretend this is a generic axis
* of the appropriate type or settle for
* unknown.
*/
podf = &this->df.rgodf[uiObj + uidObj];
if( iUsageIdx < 6 )
{
phum = UsageToUsageMap( DIMAKEUSAGEDWORD(
HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_X + iUsageIdx ) );
AssertF( phum );
podf->pguid = phum->pguid;
this->rgbaxissemflags[iUsageIdx] = phum->bSemFlag;
pdwHints[iUsageIdx] = phum->dwSemHint;
dwTypeFlags |= phum->dwSemHint;
}
else if (iUsageIdx < 8)
{
podf->pguid = &GUID_Slider;
this->rgbaxissemflags[iUsageIdx] = DISEM_FLAGS_GET( DISEM_FLAGS_S );
}
else
{
podf->pguid = &GUID_Unknown;
this->rgbaxissemflags[iUsageIdx] = DISEM_FLAGS_GET( DISEM_FLAGS_S );
}
/*
* Mark the primary axis associated with
* the matched axis as already set up.
*/
this->rgiaxis[iUsageIdx] = uiObjPrimary;
this->df.rgodf[uiObjPrimary].dwType &= ~DIDFT_INSTANCEMASK;
this->df.rgodf[uiObjPrimary].dwType |= DIDFT_MAKEINSTANCE(iUsageIdx);
break;
}
}
}
else
{
/* Not an axis, with caps and input */
}
}
iCollection = 0;
iButton = 0;
iPOV = 0;
iAxis = 6;
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
PHIDUSAGEMAP phum;
PDWORD pdwType;
pdwType = &this->df.rgodf[uiObj].dwType;
if( !this->rghoc[uiObj].pcaps || this->rghoc[uiObj].pcaps->IsAlias
||!( *pdwType & ( DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV | DIDFT_COLLECTION ) ))
{
continue;
}
if( *pdwType & DIDFT_COLLECTION )
{
*pdwType = (*pdwType & ~DIDFT_INSTANCEMASK) | DIDFT_MAKEINSTANCE(iCollection);
this->rgicoll[iCollection++] = uiObj;
continue;
}
else if( *pdwType & DIDFT_NODATA )
{
/*
* Leave other no data objects alone
*/
continue;
}
if( *pdwType & DIDFT_BUTTON )
{
*pdwType &= ~DIDFT_INSTANCEMASK;
//The buttons need to be sorted (manbug 30320) --
//the ones that have USAGE_PAGE_BUTTON come first, sorted by button number,
//w/ gaps for missing numbers if necessary;
//then add on those that do not have USAGE_PAGE_BUTTON
//(since HID reports anything w/ size 1 bit is a button), unsorted.
if (this->rghoc[uiObj].pcaps->UsagePage == HID_USAGE_PAGE_BUTTON)
{
//put in the correct position, leaving gaps if needed.
//Usage - 1 will give us the position, since HID usages are 1-based,
//but DInput button indeces are 0-based.
int iPosition = ( this->rghoc[uiObj].pcaps->UsageMin
+ uiObj
- this->rgdwBase[this->rghoc[uiObj].pcaps->type]
- this->rghoc[uiObj].pcaps->DataIndexMin ) - 1;
AssertF(iPosition >= 0);
*pdwType |= DIDFT_MAKEINSTANCE(iPosition);
this->rgiobj[iPosition] = uiObj;
}
else
{
//iLastIndex indicates where to put the non-USAGE_PAGE_BUTTON things
//in the order that they come in the report
*pdwType |= DIDFT_MAKEINSTANCE(iLastButtonIndex);
this->rgiobj[iLastButtonIndex++] = uiObj;
}
//increment the count of how many real things (excluing gaps) we've got
iButton++;
}
else if( *pdwType & DIDFT_AXIS )
{
if( piRemap[uiObj] == -1 )
{
WORD wUsage;
UINT uiObjPrimary = uiObj;
*pdwType &= ~DIDFT_INSTANCEMASK;
/*
* Check this axis and it's aliases for a match
*/
while( TRUE )
{
wUsage = (WORD) ( this->rghoc[uiObj].pcaps->UsageMin
+ uiObj
- this->rgdwBase[this->rghoc[uiObj].pcaps->type]
- this->rghoc[uiObj].pcaps->DataIndexMin );
phum = UsageToUsageMap( DIMAKEUSAGEDWORD(
this->rghoc[uiObj].pcaps->UsagePage, wUsage ) );
/*
* Slightly odd loop structure to avoid incrementing
* uiObj on the last iteration. This makes sure we
* don't miss anything in the outer loop and that
* uiObj relates to the alias we matched when testing
* for default semantic mappings.
*/
if( phum || (uiObj+1 == this->df.dwNumObjs)
|| !this->rghoc[uiObj+1].pcaps->IsAlias )
{
break;
}
uiObj++;
}
if( phum )
{
/*
* Since the axis is recognixed up the count
*/
iTypeAxes++;
/*
* Find it's position keeping WinMM in mind.
*/
if( ( phum->bPosAxis < 6 )
&&( this->rgiaxis[phum->bPosAxis] == -1 ) )
{
this->rgiaxis[phum->bPosAxis] = uiObjPrimary ;
*pdwType |= DIDFT_MAKEINSTANCE(phum->bPosAxis);
}
else if( ( phum->bPosAxis == 6 )
&&( this->rgiaxis[2] == -1 ) )
{
this->rgiaxis[2] = uiObjPrimary;
*pdwType |= DIDFT_MAKEINSTANCE(2);
}
else
{
*pdwType |= DIDFT_MAKEINSTANCE(iAxis);
this->rgiaxis[iAxis++] = uiObjPrimary ;
}
/*
* While we know which usage is most recognizable, add in
* flags to help us refine the device type and save off
* flags for default semantic mapping of this axis.
*/
this->rgbaxissemflags[DIDFT_GETINSTANCE(*pdwType)] = phum->bSemFlag;
pdwHints[DIDFT_GETINSTANCE(*pdwType)] = phum->dwSemHint;
dwTypeFlags |= phum->dwSemHint;
}
else
{
/*
* Unfortunately, the current HID parser does not
* implement more unusual HID caps like "preferred
* state" so just assume any other axes are sliders.
*/
if( this->rghoc[uiObj].pcaps->UsagePage == HID_USAGE_PAGE_SIMULATION )
{
if( wUsage == HID_USAGE_SIMULATION_CLUTCH )
{
dwTypeFlags |= DISEM_HINT_CLUTCH;
pdwHints[iAxis] = DISEM_HINT_CLUTCH;
this->rgbaxissemflags[iAxis] = DISEM_FLAGS_GET( DISEM_FLAGS_C );
iTypeAxes++;
}
else
{
if( wUsage == HID_USAGE_SIMULATION_SHIFTER )
{
dwTypeFlags |= DISEM_HINT_SHIFTER;
pdwHints[iAxis] = DISEM_HINT_CLUTCH;
iTypeAxes++;
}
else
{
pdwHints[iAxis] = DISEM_HINT_SLIDER;
}
this->rgbaxissemflags[iAxis] = DISEM_FLAGS_GET( DISEM_FLAGS_S );
}
}
*pdwType |= DIDFT_MAKEINSTANCE(iAxis);
this->rgiaxis[iAxis++] = uiObjPrimary ;
}
}
}
else
{
/*
* We already checked that it's one of the above or a POV
* so it must be a POV.
*/
AssertF( *pdwType & DIDFT_POV );
*pdwType &= ~DIDFT_INSTANCEMASK;
*pdwType |= DIDFT_MAKEINSTANCE(iPOV);
this->rgipov[iPOV++] = uiObj;
dwTypeFlags |= DISEM_HINT_POV;
}
}
/*
* Now that we know about all the controls we base our type
* decisions on, setup/validate the type and subtype.
*/
AssertF( GET_DIDEVICE_TYPE( this->dwDevType ) != DI8DEVTYPE_KEYBOARD );
if( GET_DIDEVICE_TYPE( this->dwDevType ) == DI8DEVTYPE_MOUSE )
{
if( dwTypeFlags & DISEM_HINT_ABSOLUTE )
{
this->dwDevType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_MOUSE,
DI8DEVTYPEMOUSE_ABSOLUTE)
| DIDEVTYPE_HID;
}
}
else
{
DWORD dwFlags2;
JOYREGHWSETTINGS hws;
/*
* Get the old registry flags for initial hints in case this
* device has more generic usages than it needed to have and
* these have registry overrides.
* Only look for flags that are less generic to avoid a case
* where an older DInput (or JoyHID) labelled this device
* inadequately.
* If the call fails the buffer is zeroed
*/
JoyReg_GetValue(this->hkType, REGSTR_VAL_JOYOEMDATA,
REG_BINARY, &hws, cbX(hws));
if( this->hkProp )
{
JoyReg_GetValue( this->hkProp, REGSTR_VAL_FLAGS2, REG_BINARY,
&dwFlags2, cbX(dwFlags2) );
}
CAssertF( ( DISEM_HINT_THROTTLE >> 6 ) == JOY_HWS_HASZ );
CAssertF( ( DISEM_HINT_POV >> 6 ) == JOY_HWS_HASPOV );
dwTestType = GetValidDI8DevType( dwFlags2, iButton, dwTypeFlags >> 14 );
if( dwTestType )
{
/*
* If a valid override exists just use it
*/
this->dwDevType = dwTestType | DIDEVTYPE_HID;
}
else
{
#ifdef XDEBUG
/*
* Fetch the value again in debug so we can report failures.
*/
if( this->hkProp )
{
DWORD dwDbgFlags2;
JoyReg_GetValue( this->hkProp, REGSTR_VAL_FLAGS2, REG_BINARY,
&dwDbgFlags2, cbX(dwDbgFlags2) );
if( GET_DIDEVICE_TYPEANDSUBTYPE( dwDbgFlags2 ) )
{
RPF( "Ignoring invalid type/subtype Flags2 value 0x%08x for HID", dwDbgFlags2 );
}
}
#endif
/*
* This one is straight forward
*/
if( hws.dwFlags & JOY_HWS_ISHEADTRACKER )
{
dwTestType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_HEADTRACKER );
goto MNK_CheckType;
}
if( hws.dwFlags & JOY_HWS_ISYOKE )
{
dwTestType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_FLIGHT,
DI8DEVTYPEFLIGHT_YOKE);
goto MNK_CheckType;
}
/*
* Other registry flags only relate to the type so the
* subtype will still need to be found.
*/
if( hws.dwFlags & JOY_HWS_ISGAMEPAD )
{
dwTestType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_GAMEPAD, DI8DEVTYPEGAMEPAD_STANDARD);
goto MNK_AdjustType;
}
}
/*
* If we have somehow recognized this as a car controller, do any
* processing necessary to munge the axes.
* Note we have to go through this path, even if the device has a
* registry override type and subtype so that the right axes get
* used for a device which has been changed into a car controller
*/
if( ( hws.dwFlags & JOY_HWS_ISCARCTRL )
|| fCarController
|| ( GET_DIDEVICE_TYPE( this->dwDevType ) == DI8DEVTYPE_DRIVING ) )
{
dwTestType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_DRIVING, 0);
if( DISEM_HINT_X == ( dwTypeFlags & ( DISEM_HINT_X | DISEM_HINT_STEERING ) ) )
{
/*
* If the device has no wheel but has an X axis
* use that instead. The semantic flags are the
* same so just switch the hints.
*/
dwTypeFlags ^= ( DISEM_HINT_X | DISEM_HINT_STEERING );
}
if( ( dwTypeFlags & DISEM_HINT_STEERING ) == 0 )
{
/*
* If there's still no steering wheel, make the type
* device so it can be processed into a supplemental
*/
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_DEVICE, 0 );
}
/*
* In terms of HID usages, the common forms of pedals are:
*
* 1) Accellerator and Brake
* 2) Split Y axis, below center accel, above brake
* 3) Z accel, Y brake
* 4) Y accel, Rz brake
* 5) Z accel, Rz brake
*
* The first form is ideal so all that is needed is to
* make sure that any further processing does not disturb
* those axes whilst allowing for only one being exact.
* The second form is assumed for a car controller with
* only a Y (in addition to a possible X).
* The other two forms are distinguished either by an
* explicite registry flag or by the exact match of Y and
* Rz with none of Z, accel or brake for the latter form.
* Since a real Z axis would be an oddity on a steering
* wheel, assume that the IHVs who have taken the Y, Rz
* path are not going to add a Z.
*/
/*
* If there is an override to Y split pedals and a Y is
* present then set type and subtype and bypass the rest.
*/
switch( dwFlags2 & JOYTYPE_INFOMASK )
{
case JOYTYPE_INFODEFAULT:
break;
case JOYTYPE_INFOYYPEDALS:
if( dwTypeFlags & DISEM_HINT_Y )
{
if( dwTypeFlags & DISEM_HINT_STEERING )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_DRIVING, DI8DEVTYPEDRIVING_COMBINEDPEDALS );
}
else
{
AssertF( GET_DIDEVICE_TYPEANDSUBTYPE( dwTestType ) == MAKE_DIDEVICE_TYPE( DI8DEVTYPE_DEVICE, 0 ) );
}
goto MNK_CheckType;
}
RPF( "JOYTYPE_INFOYYPEDALS set but device has no Y" );
break;
case JOYTYPE_INFOZYPEDALS:
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Y, DISEM_HINT_BRAKE,
DISEM_FLAGS_GET(DIAXIS_ANY_B_1), &GUID_RzAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Z, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_THROTTLE, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
break;
case JOYTYPE_INFOYRPEDALS:
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_RZ, DISEM_HINT_BRAKE,
DISEM_FLAGS_GET(DIAXIS_ANY_B_1), &GUID_RzAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Y, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
break;
case JOYTYPE_INFOZRPEDALS:
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_RZ, DISEM_HINT_BRAKE,
DISEM_FLAGS_GET(DIAXIS_ANY_B_1), &GUID_RzAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Z, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_THROTTLE, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
break;
default:
RPF( "Ignoring invalid JOYTYPE_INFO* Flags in 0x%08x", dwFlags2 & JOYTYPE_INFOMASK );
}
/*
* In the absence of an override, first see if there's
* anything worth reinterpreting.
*/
if( 0 == ( dwTypeFlags &
( DISEM_HINT_Z | DISEM_HINT_ACCELERATOR | DISEM_HINT_THROTTLE
| DISEM_HINT_RZ | DISEM_HINT_BRAKE | DISEM_HINT_ACCELERATOR ) ) )
{
if( ( dwTypeFlags & ( DISEM_HINT_STEERING | DISEM_HINT_Y ) )
== ( DISEM_HINT_STEERING | DISEM_HINT_Y ) )
{
/*
* Combined pedal device
*/
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_DRIVING, DI8DEVTYPEDRIVING_COMBINEDPEDALS );
}
else
{
AssertF( GET_DIDEVICE_TYPEANDSUBTYPE( dwTestType ) == MAKE_DIDEVICE_TYPE( DI8DEVTYPE_DEVICE, 0 ) );
}
goto MNK_CheckType;
}
else if( ( DISEM_HINT_Y | DISEM_HINT_RZ ) ==
( dwTypeFlags
& ( DISEM_HINT_Y | DISEM_HINT_Z | DISEM_HINT_RZ | DISEM_HINT_BRAKE | DISEM_HINT_ACCELERATOR ) ) )
{
/*
* Although the axis GUIDs accel == Y and brake == RZ are
* already correct, use the common function to change the
* semantic flags or I'll forget to change the axis flags.
*/
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Y, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_RZ, DISEM_HINT_BRAKE,
DISEM_FLAGS_GET(DIAXIS_ANY_B_1), &GUID_RzAxis );
}
else
{
/*
* Both of the other split pedal types have a Z-like accelerator
*/
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Z, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_THROTTLE, DISEM_HINT_ACCELERATOR,
DISEM_FLAGS_GET(DIAXIS_ANY_A_1), &GUID_YAxis );
/*
* Look for a brake on RZ before Y as a device with RZ
* is very likely to report a Y as well.
*/
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_RZ, DISEM_HINT_BRAKE,
DISEM_FLAGS_GET(DIAXIS_ANY_B_1), &GUID_RzAxis );
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Y, DISEM_HINT_BRAKE,
DISEM_FLAGS_GET(DIAXIS_ANY_B_1), &GUID_RzAxis );
}
/*
* If somehow we have found an accellerator, make sure we
* don't expose any Y axes as well or the accellerator
* may get bumped from the default Y position.
*/
if( dwTypeFlags & DISEM_HINT_ACCELERATOR )
{
int idx;
dwTypeFlags &= ~DISEM_HINT_Y;
for( idx = 0; idx < cAxes + 6; idx++ )
{
if( pdwHints[idx] == DISEM_HINT_Y )
{
pdwHints[idx] = DISEM_HINT_SLIDER;
dwTypeFlags |= DISEM_HINT_SLIDER;
this->rgbaxissemflags[idx] = DISEM_FLAGS_GET(DIAXIS_ANY_S_1);
this->df.rgodf[this->rgiaxis[idx]].pguid = &GUID_Slider;
}
}
}
}
else
{
/*
* Just take the default calculated from the HID caps (or the override)
*/
dwTestType = this->dwDevType;
#ifdef XDEBUG
if( GET_DIDEVICE_TYPEANDSUBTYPE( dwTestType ) != GET_DIDEVICE_TYPEANDSUBTYPE( dwFlags2 ) )
{
switch( GET_DIDEVICE_TYPE( dwTestType ) )
{
case DI8DEVTYPE_DEVICE:
AssertF( GET_DIDEVICE_SUBTYPE( dwTestType ) == 0 );
break;
case DI8DEVTYPE_JOYSTICK:
AssertF( GET_DIDEVICE_SUBTYPE( dwTestType ) == DI8DEVTYPEJOYSTICK_STANDARD );
break;
case DI8DEVTYPE_GAMEPAD:
AssertF( GET_DIDEVICE_SUBTYPE( dwTestType ) == DI8DEVTYPEGAMEPAD_STANDARD );
break;
default:
RPF( "Invalid type %02x", GET_DIDEVICE_TYPE( dwTestType ) );
AssertF( !"Invalid type!" );
}
}
#endif
/*
* Check for Z axis behavior overrides
* Since the default behavior is to always use a Z as a Z,
* only the override to slider is needed here.
*/
if( dwFlags2 & JOYTYPE_INFOZISSLIDER )
{
if( dwTypeFlags & DISEM_HINT_Z )
{
/*
* Reset the slider flag as ReinterpretObject does
* not change axes if the target this axis
*/
dwTypeFlags &= ~DISEM_HINT_SLIDER;
ReinterpretObject( this, pdwHints, cAxes, &dwTypeFlags,
DISEM_HINT_Z, DISEM_HINT_SLIDER,
DISEM_FLAGS_GET(DIAXIS_ANY_S_1), &GUID_Slider );
}
else
{
RPF( "JOYTYPE_INFOZISSLIDER set but device has no Z" );
}
}
}
/*
* If the dwFlags2 and dwTestType are the same, we are using a
* registry override so don't try to refine it.
*/
if( GET_DIDEVICE_TYPEANDSUBTYPE( dwTestType ) != GET_DIDEVICE_TYPEANDSUBTYPE( dwFlags2 ) )
{
if( dwTypeFlags & DISEM_HINT_STEERING )
{
/*
* If it has a steering wheel, it's a driving device
*/
dwTestType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_DRIVING, 0 );
}
else if( ( dwTypeFlags & DISEM_HINT_SIXDOF ) == DISEM_HINT_SIXDOF )
{
/*
* Special case six degree of freedom devices
*/
dwTestType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_1STPERSON,
DI8DEVTYPE1STPERSON_SIXDOF );
goto MNK_CheckType;
}
MNK_AdjustType:;
/*
* We should be left with only the following device types:
* joystick, gamepad, driving and device
* For the first three only subtypes need to be found.
* For DI8DEVTYPE_DEVICE some may be changed to
* DI8DEVTYPE_SUPPLEMENTAL if an appropriate subtype can be
* found.
*/
switch( GET_DIDEVICE_TYPE( dwTestType ) )
{
case DI8DEVTYPE_DEVICE:
/*
* Since this is not a joystick or gamepad, only use it
* if it has the vehicle simulation controls we support.
*/
if( ( dwTypeFlags & ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE | DISEM_HINT_CLUTCH ) )
== ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE | DISEM_HINT_CLUTCH ) )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_THREEPEDALS );
}
else if( ( dwTypeFlags & ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE ) )
== ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE ) )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_DUALPEDALS );
}
else if( dwTypeFlags & DISEM_HINT_THROTTLE )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_THROTTLE );
}
else if( dwTypeFlags & DISEM_HINT_SHIFTER )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_SHIFTER );
}
else if( dwTypeFlags & DISEM_HINT_RUDDER )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_RUDDERPEDALS );
}
else
{
/*
* Totally unknown so leave it as device
* Allowing other devices to be treated as game
* controllers can cause HID controls on devices
* such as speakers to be included.
*/
}
break;
case DI8DEVTYPE_JOYSTICK:
case DI8DEVTYPE_GAMEPAD:
if( ( dwTypeFlags & ( DISEM_HINT_X | DISEM_HINT_Y ) ) != ( DISEM_HINT_X | DISEM_HINT_Y ) )
{
dwTestType = MAKE_DIDEVICE_TYPE( DI8DEVTYPE_SUPPLEMENTAL,
DI8DEVTYPESUPPLEMENTAL_UNKNOWN );
}
break;
case DI8DEVTYPE_DRIVING:
if( ( dwTypeFlags & ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE | DISEM_HINT_CLUTCH ) )
== ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE | DISEM_HINT_CLUTCH ) )
{
dwTestType |= MAKE_DIDEVICE_TYPE( 0, DI8DEVTYPEDRIVING_THREEPEDALS );
}
else if( ( dwTypeFlags & ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE ) )
== ( DISEM_HINT_ACCELERATOR | DISEM_HINT_BRAKE ) )
{
dwTestType |= MAKE_DIDEVICE_TYPE( 0, DI8DEVTYPEDRIVING_DUALPEDALS );
}
else if( dwTypeFlags & DISEM_HINT_Y )
{
dwTestType |= MAKE_DIDEVICE_TYPE( 0, DI8DEVTYPEDRIVING_COMBINEDPEDALS );
}
else
{
dwTestType |= MAKE_DIDEVICE_TYPE( 0, DI8DEVTYPEDRIVING_LIMITED );
}
break;
default:
AssertF( !"Unexpected device type" );
}
/*
* Use the common function to make this a limited type if the
* number of buttons or flags dictate it.
* Since the type and subtype are known to be valid, the return
* value should never be a failure (zero).
*/
MNK_CheckType:;
this->dwDevType = DIDEVTYPE_HID
| GetValidDI8DevType( dwTestType, iButton, hws.dwFlags );
AssertF( this->dwDevType & ~DIDEVTYPE_HID );
}
}
/*
* Finally, mark all secondary aliases with the primary alias
* instance and sqfl all the translations.
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
int iPrimary;
D( iPrimary = -1; )
if( this->rghoc[uiObj].pcaps )
{
if( !this->rghoc[uiObj].pcaps->IsAlias )
{
iPrimary = this->df.rgodf[uiObj].dwType;
}
else
{
D( AssertF( iPrimary != -1 ) );
/*
* Prefix notices that iPrimary would be uninitialized if
* we find the alias before the primary but that should
* never happen as the object are sorted by definition.
*/
if( DIDFT_GETTYPE(iPrimary) != DIDFT_GETTYPE(this->df.rgodf[uiObj].dwType) )
{
SquirtSqflPtszV(sqflHidParse | sqflError,
TEXT("CHid_MungeNotKeyboard: uiObj: %d dwType 0x%08x ")
TEXT("does not match primary 0x%08x"),
uiObj, this->df.rgodf[uiObj].dwType, iPrimary );
}
/*
* There are very few attributes of an alias that are not
* overridden by the primary.
*/
this->df.rgodf[uiObj].dwType = ( iPrimary & ~DIDFT_ALIASATTRMASK )
| ( this->df.rgodf[uiObj].dwType & DIDFT_ALIASATTRMASK );
}
SquirtSqflPtszV(sqflHidParse | sqflVerbose,
TEXT("CHid_MungeNotKeyboard: uiObj: %d set to dwType 0x%08x"),
uiObj, this->df.rgodf[uiObj].dwType );
}
else
{
SquirtSqflPtszV(sqflHidParse | sqflVerbose,
TEXT("CHid_MungeNotKeyboard: uiObj: %d has pcaps == NULL"),
uiObj );
}
}
}
else
{
FreePpv( &this->rgbaxissemflags );
}
FreePpv( &piRemap );
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitObjects |
*
* Identify and initialize the objects supported by the device.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_InitObjects(PCHID this)
{
HRESULT hres;
UINT uiObj;
UINT iType;
/*
* Build the base array table to convert HID item indexes
* into DirectInput ID instance numbers.
*/
AssertF(this->rgdwBase[HidP_Input] == 0);
this->rgdwBase[HidP_Feature] = this->caps.NumberInputDataIndices;
this->rgdwBase[HidP_Output ] = this->rgdwBase[HidP_Feature] +
this->caps.NumberFeatureDataIndices;
this->rgdwBase[HidP_Coll ] = this->rgdwBase[HidP_Output ] +
this->caps.NumberOutputDataIndices;
/*
* Determine if this device supports PID
*/
this->fPIDdevice = FALSE;
if( this->caps.NumberOutputValueCaps != 0x0
&& this->caps.NumberOutputButtonCaps != 0x0 )
{
NTSTATUS ntStat;
USHORT cAButton=0x0;
ntStat = HidP_GetSpecificButtonCaps
(
HidP_Output, // ReportType
HID_USAGE_PAGE_PID, // UsagePage
0x0, // Link Collection
0x0, // Usage
NULL, // ValueCaps
&cAButton, // ValueCapsLength
this->ppd // PreparsedData
);
if( ntStat == HIDP_STATUS_BUFFER_TOO_SMALL
&& cAButton > 0x2 // Is this enough for PID device ??
)
{
this->fPIDdevice = TRUE;
}
}
/*
* Note that we must do axes first because that keeps
* everything aligned.
*
* Warning, diem.c assumes axes come first.
*/
hres = CHid_InitAxes(this);
if(FAILED(hres))
{
goto done;
}
hres = CHid_InitButtons(this);
if(FAILED(hres))
{
goto done;
}
hres = CHid_InitCollections(this);
if(FAILED(hres))
{
goto done;
}
/*
* Round the data size up to the nearest DWORD.
*/
this->df.dwDataSize = (this->df.dwDataSize + 3) & ~3;
/*
* Allocate memory for report ID enable flags
*
* ISSUE-2001/05/12-MarcAnd Memory allocations should be merged
* These memory blocks could be merged. For most devices the total memory
* needed is only one or two bytes so there may be better ways.
*/
for( iType = HidP_Input; iType < HidP_Max; iType++ )
{
this->wMaxReportId[iType] += 1;
hres = AllocCbPpv(this->wMaxReportId[iType], &this->pEnableReportId[iType]);
if( FAILED(hres) )
{
goto done;
}
}
AssertF(this->rgiobj == 0);
/*
* Munge the data before trying to look up supplemental
* information in the registry.
*/
if(GET_DIDEVICE_TYPE(this->dwDevType) == DI8DEVTYPE_KEYBOARD)
{
CHid_MungeKeyboard(this);
}
else
{
CHid_MungeNotKeyboard(this);
}
/*
* Collect attributes for each object and add them to the
* device type code. This allows the registry to enable
* things like force feedback.
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
CType_RegGetTypeInfo(this->hkType, &this->df.rgodf[uiObj], this->fPIDdevice);
if( ( GET_DIDEVICE_TYPE(this->dwDevType) == DI8DEVTYPE_DRIVING )
&& ( this->df.rgodf[uiObj].dwFlags & DIDOI_FFACTUATOR )
&& ( this->df.rgodf[uiObj].pguid != &GUID_XAxis ) )
{
/*
* IHVs set FF attributes on non-FF axes for wheels because
* first generation FF apps were only written to support joysticks.
* Since we now munge the various configurations of pedal axes to
* report all split pedals in the same way, the fake Y axis can
* land up on different axes, usually Slider0. Rather than have
* people code to these different fake axes, strip out actuator
* status from any driving axis except the wheel.
*/
this->df.rgodf[uiObj].dwFlags &= ~DIDOI_FFACTUATOR;
this->df.rgodf[uiObj].dwType &= ~DIDFT_FFACTUATOR;
}
}
#ifdef DEBUG
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
PHIDGROUPCAPS pcaps;
UINT uiObjReal =
CHid_ObjFromType(this, this->df.rgodf[uiObj].dwType);
pcaps = this->rghoc[uiObjReal].pcaps;
if(pcaps)
{
AssertF(pcaps->dwSignature == HIDGROUPCAPS_SIGNATURE);
}
if( uiObjReal != uiObj )
{
RPF( "uiObj = 0x%08x, type = 0x%08x, exposed Obj = 0x%08x",
uiObj, this->df.rgodf[uiObj].dwType, uiObjReal );
}
AssertF(CHid_ObjFromType(this, this->df.rgodf[uiObj].dwType)
== uiObj);
/*
* Anything that is DIDFT_AXIS must be a HID axis. However,
* the converse is not true for the case of analog buttons.
*/
AssertF(fLimpFF(this->df.rgodf[uiObj].dwType & DIDFT_AXIS,
pcaps->IsValue));
SquirtSqflPtszV(sqfl | sqflVerbose,
TEXT("CHid_InitObj: uiObj = %02x, dwType = %08x"),
uiObj, this->df.rgodf[uiObj].dwType);
}
#endif
hres = S_OK;
done:;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitParse |
*
* Identify and initialize the data structures needed for
* parsing reports.
*
*****************************************************************************/
HRESULT INTERNAL
CHid_InitParse(PCHID this)
{
DWORD cb;
HRESULT hres;
/*
* Obtain the maximum number of HIDP_DATA structures
* that will be returned at one go.
*/
this->hriIn .cdataMax = HidP_MaxDataListLength(HidP_Input , this->ppd);
this->hriOut.cdataMax = HidP_MaxDataListLength(HidP_Output, this->ppd);
this->hriFea.cdataMax = HidP_MaxDataListLength(HidP_Feature, this->ppd);
/*
* More annoyances.
*/
this->hriIn .cbReport = this->caps. InputReportByteLength;
this->hriOut.cbReport = this->caps. OutputReportByteLength;
this->hriFea.cbReport = this->caps.FeatureReportByteLength;
/*
* Some trace squirties because HID is tricky.
*/
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_InitParse: MaxDataListLength(Input) = %d"),
this->hriIn.cdataMax);
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_InitParse: MaxDataListLength(Output) = %d"),
this->hriOut.cdataMax);
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_InitParse: MaxDataListLength(Feature)= %d"),
this->hriFea.cdataMax);
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_InitParse: InputReportByteLength = %d"),
this->caps.InputReportByteLength);
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_InitParse: OutputReportByteLength = %d"),
this->caps.OutputReportByteLength);
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_InitParse: FeatureReportByteLength = %d"),
this->caps.FeatureReportByteLength);
/*
* Now allocate all the report-related memory.
*/
this->cbPhys = this->df.dwDataSize;
/*
* Allocating the memory is done in four phases.
*
* 1. Tally up how much memory we need,
* 2. Allocate that memory,
* 3. Dole out the memory we allocated,
* 4. Check that we didn't mess up.
*
* Since this is extremely error-prone (I've messed it up at least
* once), the work is hidden inside macros.
*
* The macro THINGS expands to a series of THING()s, each of which
* specifies a field name and the size it should be. Each time you
* want to iterate over the fields, use the THINGS macro.
*
* (Yes, this is the same comment block as when we did this before.)
*
* Note, the arrays of HIDP_DATA structures must be correctly
* aligned in some architechtures.
*/
CAssertF( FIELD_OFFSET(CHID, hriIn.rgdata) == FIELD_OFFSET(CHID, pvGroup2) );
#define THINGS() \
THING(hriIn.rgdata, cbCxX(this->hriIn.cdataMax, HIDP_DATA)); \
THING(hriOut.rgdata, cbCxX(this->hriOut.cdataMax, HIDP_DATA)); \
THING(hriFea.rgdata, cbCxX(this->hriFea.cdataMax, HIDP_DATA)); \
THING(hriIn.pvReport, this->hriIn.cbReport); \
THING(hriOut.pvReport, this->hriOut.cbReport); \
THING(hriFea.pvReport, this->hriFea.cbReport); \
THING(pvPhys, this->cbPhys); \
THING(pvStage, this->cbPhys); \
/*
* Make a pass through the fields adding up the memory requirements.
*/
#define THING(f, cbF) cb += cbF
cb = 0;
THINGS();
#undef THING
hres = ReallocCbPpv(cb, &this->pvGroup2);
if(SUCCEEDED(hres))
{
PV pv;
/*
* Assert that the allocation is aligned
*/
AssertF( !( ((UINT_PTR)this->pvGroup2) & ( MAX_NATURAL_ALIGNMENT - 1 ) ) );
/*
* Make a pass through the fields carving up the memory block
* and handing out pieces of it.
*/
#define THING(f, cbF) this->f = pv; pv = pvAddPvCb(pv, cbF)
pv = this->pvGroup2;
THINGS();
#undef THING
/*
* There should be no byte left over.
*/
AssertF(pvAddPvCb(this->pvGroup2, cb) == pv);
}
#undef THINGS
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitParseData |
*
* Post-init pass to set up all the data used by parsing.
*
*****************************************************************************/
HRESULT EXTERNAL
CHid_InitParseData(PCHID this)
{
HRESULT hres;
UINT uiObj;
/*
* Preinitialize the HIDP_DATA indices to -1 to indicate
* that they aren't there. We must do this before we
* mess with AddDeviceData, which assumes that all the
* indices are properly set up.
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
this->rghoc[uiObj].idata = -1;
}
/*
* Now do some initialization of each object.
*/
for(uiObj = 0; uiObj < this->df.dwNumObjs; uiObj++)
{
PHIDGROUPCAPS pcaps = this->rghoc[uiObj].pcaps;
LPDIOBJECTDATAFORMAT podf = &this->df.rgodf[uiObj];
/*
* ISSUE-2001/03/13-MarcAnd Should we panic if this assertion fails?
*/
AssertF(pcaps);
if(pcaps)
{
/*
* For input-like objects, we need to initialize the
* physical state fields to sane defaults so apps
* don't get confused if they issue a read before the first
* report arrives.
*
* Buttons start out not pressed, which means we don't need
* to do anything since it's already zero-initialized.
*
* Relative axes start out not moving, which means we don't need
* to do anything since it's already zero-initialized.
*
* Absolute axes start out centered.
*
* POVs start out neutral.
*
*/
/* No calibration for features as they are input / output */
if(pcaps->type == HidP_Input )
{
LONG UNALIGNED * pl;
pl = pvAddPvCb(this->pvPhys, podf->dwOfs);
if(podf->dwType & DIDFT_ABSAXIS )
{
if( this->rghoc[uiObj].pjrc->fRaw )
*pl = (this->rghoc[uiObj].pcaps->Logical.Min + this->rghoc[uiObj].pcaps->Logical.Max) / 2;
else
*pl = this->rghoc[uiObj].pjrc->lC;
} else if(podf->dwType & DIDFT_POV)
{
*pl = JOY_POVCENTERED;
}
}
/*
* ISSUE-2001/03/13-MarcAnd output objects uninitialized
* For output-like objects, we would have liked to have set the
* value to Null if possible to keep things vaguely sane.
* Unfortunately code like:
* if(HidP_IsOutputLike(pcaps->type))
* {
* CHid_AddDeviceData(this, uiObj,pcaps->Null);
* }
* Does not work!
*/
}
}
hres = S_OK;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | InitAttributes |
*
* Pull out the <t HIDD_ATTRIBUTES> and squirrel away the
* information we like. Doing this up front is important
* in case the device gets unplugged later and we lose the
* ability to talk to it.
*
* @parm PHIDD_ATTRIBUTES | pattr |
*
* <t HIDD_ATTRIBUTES> containing attributes of device.
*
*****************************************************************************/
HRESULT EXTERNAL
CHid_InitAttributes(PCHID this, PHIDD_ATTRIBUTES pattr)
{
HRESULT hres;
TCHAR tszType[20];
#ifndef UNICODE
WCHAR wszType[20];
#endif
int ctch;
// Input report is disabled until we read flags2 from registry.
this->fEnableInputReport = FALSE;
this->fFlags2Checked = FALSE;
this->ProductID = pattr->ProductID;
this->VendorID = pattr->VendorID;
AssertF(this->hkType == 0);
if( ( this->VendorID == MSFT_SYSTEM_VID )
&&( ( this->ProductID >= MSFT_SYSTEM_PID + JOY_HW_PREDEFMIN )
&&( this->ProductID < MSFT_SYSTEM_PID + JOY_HW_PREDEFMAX ) ) )
{
/*
* Predefined types don't have keys
*/
}
else
{
/*
* The type key for HID devices is "VID_xxxx&PID_yyyy",
* mirroring the format used by plug and play.
*/
ctch = wsprintf(tszType, VID_PID_TEMPLATE,
this->VendorID, this->ProductID);
AssertF(ctch < cA(tszType));
#ifdef UNICODE
hres = JoyReg_OpenTypeKey(tszType, MAXIMUM_ALLOWED, REG_OPTION_NON_VOLATILE, &this->hkType);
JoyReg_OpenPropKey(tszType, MAXIMUM_ALLOWED, REG_OPTION_NON_VOLATILE, &this->hkProp);
/*
* If we fail to open the prop key - we will continue to function with loss in functionality
* Specifically no device images, etc
*/
#else
TToU(wszType, cA(wszType), tszType);
hres = JoyReg_OpenTypeKey(wszType, MAXIMUM_ALLOWED, REG_OPTION_NON_VOLATILE, &this->hkType);
JoyReg_OpenPropKey(wszType, MAXIMUM_ALLOWED, REG_OPTION_NON_VOLATILE, &this->hkProp);
/*
* If we fail to open the prop key - we will continue to function with loss in functionality
* Specifically no device images, etc
*/
#endif
/*
* It is not a problem if we can't open the type key.
* The device will run suboptimally, but it will still run.
*/
AssertF(fLeqvFF(SUCCEEDED(hres), this->hkType));
}
hres = S_OK;
return hres;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func BOOL | CHid_DoPathAndIdMatch |
*
* Given a device name, obtain the corresponding path
* ("device interface") associated with it, and check
* that it's the right string.
*
* @parm LPCTSTR | ptszId |
*
* The device name.
*
* @parm LPCTSTR | ptszPath |
*
* The path we should get back.
*
*****************************************************************************/
BOOL INTERNAL
CHid_DoPathAndIdMatch(LPCTSTR ptszId, LPCTSTR ptszPath)
{
GUID guidHid;
HDEVINFO hdev;
BOOL fRc;
HidD_GetHidGuid(&guidHid);
hdev = SetupDiGetClassDevs(&guidHid, ptszId, 0,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if(hdev != INVALID_HANDLE_VALUE)
{
SP_DEVICE_INTERFACE_DATA did;
PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd;
/*
* SetupDI requires that the caller initialize cbSize.
*/
did.cbSize = cbX(did);
if(SetupDiEnumDeviceInterfaces(hdev, 0, &guidHid, 0, &did))
{
pdidd = NULL;
if(DIHid_GetDevicePath(hdev, &did, &pdidd, NULL))
{
fRc = ( lstrcmpi(pdidd->DevicePath, ptszPath) == 0x0 );
if( fRc == FALSE )
{
SquirtSqflPtszV(sqflHidParse | sqflError,
TEXT("pdidd->DevicePath = %s")
TEXT("ptszPath = %s "),
pdidd->DevicePath, ptszPath
);
}
FreePv(pdidd);
} else // GetDevicePath FAILED
{
fRc = FALSE;
SquirtSqflPtszV(sqflHidParse,
TEXT("GetDevicePath FAILED"));
}
} else // SetupDiEnumDeviceInterface FAILED
{
fRc = FALSE;
SquirtSqflPtszV(sqflHidParse,
TEXT("SetupDiEnumDeviceInterface FAILED"));
}
SetupDiDestroyDeviceInfoList(hdev);
} else // SetupDiGetClassDevs FAILED
{
fRc = FALSE;
SquirtSqflPtszV(sqflHidParse,
TEXT("SetupDiGetClassDevs FAILED"));
}
return fRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | IsPolledDevice |
*
* Returns true if this device has to be polled for input data
* False if this device supports event driven input
*
* @parm HANDLE | hdev |
*
* File Handle to a HID device
*
*****************************************************************************/
BOOL EXTERNAL CHid_IsPolledDevice( HANDLE hdev )
{
/*
* To determine if a device is polled, we send it an IOCTL to set its
* poll frequency. If the device responds with a, huh!
* (STATUS_INVALID_DEVICE_REQUEST) then we know the device is not polled.
* On Win2k we use the poll interval value zero which is a special value
* that signals HID that we want to do opportunistic polls rather than
* polls on a background timer. In this case, as long as polls are not
* faster than the predefined minimum (currently 5ms) the poll will be
* completed either with recent data or the result of an immediate poll.
* On Win98 Gold opportunistic polls are not implemented so we always
* use HIDs background polling, with an interval set to keep the device
* responsive without swamping the system. To make sure we use a read
* thread, rather than a blocking read, we have to treat this devices as
* interrupt driven.
* HID makes this change the polling interval specific to our handle so
* that other apps reading from this device will not be damaged.
*/
BOOL frc;
ULONG uPollingFreq;
DWORD cbRc;
#ifdef WINNT
BOOL fRet;
uPollingFreq = 0;
#else
uPollingFreq = 40;
#endif
frc = DeviceIoControl (hdev,
IOCTL_HID_SET_POLL_FREQUENCY_MSEC,
&uPollingFreq, cbX(uPollingFreq),
&uPollingFreq, cbX(uPollingFreq),
&cbRc, NULL);
#ifdef WINNT
if( frc )
{
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_IsPolledDevice: Opportunistic polling set") );
fRet = TRUE;
} else
{
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_IsPolledDevice: NOT POLLED, LastError = 0x%x"),
GetLastError());
fRet = FALSE;
}
return fRet;
#else
return FALSE;
#endif
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @func HANDLE | CHid_OpenDevicePath |
*
* Given a device name, open the device via its
* device interface.
*
* @parm LPCTSTR | ptszId |
*
* @parm DWORD | dwAttributes |
*
* Create File attributes
*
* The device name.
*
*****************************************************************************/
HANDLE EXTERNAL
CHid_OpenDevicePath(PCHID this, DWORD dwAttributes)
{
HANDLE hDev;
hDev = CreateFile(this->ptszPath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0, /* no SECURITY_ATTRIBUTES */
OPEN_EXISTING,
dwAttributes, /* attributes */
0); /* template */
if( hDev == INVALID_HANDLE_VALUE )
{
SquirtSqflPtszV(sqflHidParse | sqflBenign,
TEXT("Failed to open HID %s, le=%d"), this->ptszPath, GetLastError() );
}
this->IsPolledInput = CHid_IsPolledDevice(hDev);
return hDev;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | GetHdevInfo |
*
* Get information about the device that is kept in the
* HANDLE itself. We create the handle, get the goo, and
* then close the handle.
*
* The preparsed data is stashed into the <e CHid.ppd>
* field of the <t CHid> structure.
*
* @parm PHIDD_ATTRIBUTES | pattr |
*
* Receives the <t HIDD_ATTRIBUTES> of the device.
*
*****************************************************************************/
BOOL INTERNAL
CHid_GetHdevInfo(PCHID this, PHIDD_ATTRIBUTES pattr)
{
HANDLE hdev;
BOOL fRc = FALSE;
hdev = CHid_OpenDevicePath(this, FILE_FLAG_OVERLAPPED);
if(hdev != INVALID_HANDLE_VALUE)
{
pattr->Size = cbX(*pattr);
if( HidD_GetAttributes(hdev, pattr) )
{
if( HidD_GetPreparsedData(hdev, &this->ppd) )
{
fRc = TRUE;
}
else
{
RPF( "HidD_GetPreparsedData failed, le=%d", GetLastError() );
}
}
else
{
RPF( "HidD_GetAttributes failed, le=%d", GetLastError() );
}
CloseHandle(hdev);
}
return fRc;
}
/*****************************************************************************
*
* @doc INTERNAL
*
* @method HRESULT | CHid | Init |
*
* Initialize the object.
*
*****************************************************************************/
HRESULT EXTERNAL
CHid_Init(PCHID this, REFGUID rguid)
{
HRESULT hres = E_FAIL;
PHIDDEVICEINFO phdi;
EnterProc(CHid_Init, (_ "p", this));
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_Init: Starting %08x"), rguid->Data1);
this->df.dwSize = cbX(DIDATAFORMAT);
this->df.dwObjSize = cbX(DIOBJECTDATAFORMAT);
AssertF(this->df.dwDataSize == 0);
AssertF(this->df.rgodf == 0);
AssertF(this->df.dwFlags == 0);
AssertF(this->df.dwNumObjs == 0);
AssertF(this->dwAxes == 0);
AssertF(this->dwButtons == 0);
AssertF(this->dwCollections == 0);
this->idJoy = -1; /* Unknown associated VJOYD device */
this->hdev = INVALID_HANDLE_VALUE;
this->hdevEm = INVALID_HANDLE_VALUE;
this->diHacks.nMaxDeviceNameLength = MAX_PATH;
DllEnterCrit();
phdi = phdiFindHIDInstanceGUID(rguid);
if(phdi)
{
this->dwDevType = phdi->osd.dwDevType;
this->idJoy = phdi->idJoy;
/*
* Dup the registry key so we can hang onto it after
* the original has been closed. If the RegOpenKeyEx
* fails, the value of this->hkInstType will stay zero
* so we won't run with garbage.
*/
AssertF(this->hkInstType == 0);
hres = hresMumbleKeyEx(phdi->hk,
TEXT("Type"),
DI_KEY_ALL_ACCESS,
REG_OPTION_NON_VOLATILE,
&this->hkInstType);
/*
* Dup the paths and stuff.
*/
hres = hresDupPtszPptsz(phdi->pdidd->DevicePath, &this->ptszPath);
if(SUCCEEDED(hres))
{
hres = hresDupPtszPptsz(phdi->ptszId, &this->ptszId);
}
}
/*
* Get out of the critical section as quickly as possible.
* Note phdi is invalid once we leave the critical section however
* we can safely use is as a flag that the GUID was found.
*/
DllLeaveCrit();
if(phdi)
{
if(SUCCEEDED(hres))
{
HIDD_ATTRIBUTES attr;
if( !CHid_GetHdevInfo(this, &attr) )
{
SquirtSqflPtszV(sqflHidParse | sqflError,
TEXT("%hs: CHid_GetHdevInfo failed"), s_szProc );
hres = E_FAIL;
}
else if( FAILED(HidP_GetCaps(this->ppd, &this->caps) ) )
{
SquirtSqflPtszV(sqflHidParse | sqflError,
TEXT("%hs: HidP_GetCaps failed, le=%d"), s_szProc, GetLastError() );
hres = E_FAIL;
}
else if( !CHid_DoPathAndIdMatch(this->ptszId, this->ptszPath) )
{
SquirtSqflPtszV(sqflHidParse | sqflError,
TEXT("%hs: Path and HW ID do not match"), s_szProc );
hres = E_FAIL;
}
else
{
if(SUCCEEDED(hres = CHid_InitAttributes(this, &attr)) &&
SUCCEEDED(hres = CHid_AllocObjectMemory(this)) &&
SUCCEEDED(hres = CHid_InitObjects(this)) &&
SUCCEEDED(hres = CHid_InitParse(this)) &&
SUCCEEDED(hres = CHid_InitParseData(this)))
{
VXDDEVICEFORMAT devf;
UINT uiCal;
/*
* Load calibration information, and if there were
* no calibratable items, then wipe out this->pjrcNext
* to indicate that there is no need to watch for
* recalibration messages.
*/
uiCal = CHid_LoadCalibrations(this);
if(uiCal == 0)
{
this->pjrcNext = NULL;
}
/*
* Remember to do this after we have
* created the data format.
*/
devf.cbData = this->df.dwDataSize;
devf.cObj = this->df.dwNumObjs;
devf.rgodf = this->df.rgodf;
/*
* Note, dwExtra is 64 bits on 64 bit platforms
* should update the name one day.
*/
devf.dwExtra = (UINT_PTR)this;
devf.dwEmulation = 0;
hres = Hel_HID_CreateInstance(&devf, &this->pvi);
/* Polled input devices may not be attached */
if(this->IsPolledInput)
{
HANDLE hdev;
PHIDDEVICEINFO phdi;
PBUSDEVICEINFO pbdi;
hdev = CHid_OpenDevicePath(this, 0x0);
if( hdev != INVALID_HANDLE_VALUE )
{
int i;
BOOL frc;
DWORD cbRead;
BOOL bPresent = FALSE;
for( i=0x0; i < FAILED_POLL_THRESHOLD; i++ )
{
frc = ReadFile(hdev, this->hriIn.pvReport,
this->hriIn.cbReport, &cbRead, 0x0 );
if( frc != 0x0 &&
cbRead == this->hriIn.cbReport )
{
bPresent = TRUE;
break;
} else
{
Sleep(10);
SquirtSqflPtszV(sqflHidParse | sqflVerbose,
TEXT("CHid_Init: ReadFailed, LastError = 0x%x"),
GetLastError());
}
}
CloseHandle(hdev);
if( bPresent == FALSE )
{
this->pvi->fl |= VIFL_UNPLUGGED;
SquirtSqflPtszV(sqflHidParse | sqflBenign,
TEXT("%hs: ReadFailed, setting to unplugged"), s_szProc );
}
DllEnterCrit();
phdi = phdiFindHIDDeviceInterface(this->ptszPath);
AssertF(phdi != NULL);
pbdi = pbdiFromphdi(phdi);
DllLeaveCrit();
if( pbdi != NULL )
{
if( pbdi->fDeleteIfNotConnected == TRUE )
{
if( bPresent == FALSE )
{
lstrcpy( g_tszIdLastRemoved, pbdi->ptszId );
g_tmLastRemoved = GetTickCount();
DIBusDevice_Remove(pbdi);
}
pbdi->fDeleteIfNotConnected = FALSE;
}
}
} else
{
// Could not Open the device
this->pvi->fl |= VIFL_UNPLUGGED;
}
}
}
}
}
} else
{
// Squirt: device mysteriously gone
hres = DIERR_DEVICENOTREG;
}
SquirtSqflPtszV(sqflHidParse,
TEXT("CHid_Init: Ending %08x"), rguid->Data1);
ExitOleProc();
return hres;
}