Add support for buffered DirectInput data to reduce polling load when it is supported. Only attempt setting a DirectInput device's axis mode if the device reports having axes and its axis mode is not already set to absolute. If the axis mode still can't be set to absolute, blacklist the device so that any future attempts will abort early. This should help when dealing with broken device drivers that report being game controllers and not fully supporting game controller functionality.

This commit is contained in:
binary1248 2020-01-04 02:51:18 +01:00 committed by Lukas Dürrenberger
parent 95c98093e9
commit 3557c46ae6
2 changed files with 349 additions and 82 deletions

View File

@ -80,6 +80,17 @@ namespace
typedef std::vector<JoystickRecord> JoystickList;
JoystickList joystickList;
struct JoystickBlacklistEntry
{
unsigned int vendorId;
unsigned int productId;
};
typedef std::vector<JoystickBlacklistEntry> JoystickBlacklist;
JoystickBlacklist joystickBlacklist;
const DWORD directInputEventBufferSize = 32;
}
@ -330,7 +341,16 @@ Joystick::Identification JoystickImpl::getIdentification() const
JoystickState JoystickImpl::update()
{
if (directInput)
return updateDInput();
{
if (m_buffered)
{
return updateDInputBuffered();
}
else
{
return updateDInputPolled();
}
}
JoystickState state;
@ -391,7 +411,7 @@ void JoystickImpl::initializeDInput()
// Try to acquire a DirectInput 8.x interface
HRESULT result = directInput8Create(GetModuleHandleW(NULL), 0x0800, guids::IID_IDirectInput8W, reinterpret_cast<void**>(&directInput), NULL);
if (result)
if (FAILED(result))
{
// De-initialize everything
directInput = NULL;
@ -460,7 +480,7 @@ void JoystickImpl::updateConnectionsDInput()
++i;
}
if (result)
if (FAILED(result))
{
err() << "Failed to enumerate DirectInput devices: " << result << std::endl;
@ -499,6 +519,8 @@ bool JoystickImpl::openDInput(unsigned int index)
std::memset(&m_deviceCaps, 0, sizeof(DIDEVCAPS));
m_deviceCaps.dwSize = sizeof(DIDEVCAPS);
m_state = JoystickState();
m_buffered = false;
// Search for a joystick with the given index in the connected list
for (std::vector<JoystickRecord>::iterator i = joystickList.begin(); i != joystickList.end(); ++i)
@ -508,13 +530,54 @@ bool JoystickImpl::openDInput(unsigned int index)
// Create device
HRESULT result = directInput->CreateDevice(i->guid, &m_device, NULL);
if (result)
if (FAILED(result))
{
err() << "Failed to create DirectInput device: " << result << std::endl;
return false;
}
// Get vendor and product id of the device
DIPROPDWORD property;
std::memset(&property, 0, sizeof(property));
property.diph.dwSize = sizeof(property);
property.diph.dwHeaderSize = sizeof(property.diph);
property.diph.dwHow = DIPH_DEVICE;
if (SUCCEEDED(m_device->GetProperty(DIPROP_VIDPID, &property.diph)))
{
m_identification.productId = HIWORD(property.dwData);
m_identification.vendorId = LOWORD(property.dwData);
// Check if device is already blacklisted
if (m_identification.productId && m_identification.vendorId)
{
for (JoystickBlacklist::const_iterator iter = joystickBlacklist.begin(); iter != joystickBlacklist.end(); ++iter)
{
if ((m_identification.productId == iter->productId) &&
(m_identification.vendorId == iter->vendorId))
{
// Device is blacklisted
m_device->Release();
m_device = NULL;
return false;
}
}
}
}
// Get friendly product name of the device
DIPROPSTRING stringProperty;
std::memset(&stringProperty, 0, sizeof(stringProperty));
stringProperty.diph.dwSize = sizeof(stringProperty);
stringProperty.diph.dwHeaderSize = sizeof(stringProperty.diph);
stringProperty.diph.dwHow = DIPH_DEVICE;
stringProperty.diph.dwObj = 0;
if (SUCCEEDED(m_device->GetProperty(DIPROP_PRODUCTNAME, &stringProperty.diph)))
m_identification.name = stringProperty.wsz;
static bool formatInitialized = false;
static DIDATAFORMAT format;
@ -524,59 +587,80 @@ bool JoystickImpl::openDInput(unsigned int index)
const DWORD povType = DIDFT_POV | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE;
const DWORD buttonType = DIDFT_BUTTON | DIDFT_OPTIONAL | DIDFT_ANYINSTANCE;
static DIOBJECTDATAFORMAT data[8 + 4 + sf::Joystick::ButtonCount];
static DIOBJECTDATAFORMAT data[8 * 4 + 4 + sf::Joystick::ButtonCount];
data[0].pguid = &guids::GUID_XAxis;
data[0].dwOfs = DIJOFS_X;
for (int i = 0; i < 4; ++i)
{
data[8 * i + 0].pguid = &guids::GUID_XAxis;
data[8 * i + 1].pguid = &guids::GUID_YAxis;
data[8 * i + 2].pguid = &guids::GUID_ZAxis;
data[8 * i + 3].pguid = &guids::GUID_RxAxis;
data[8 * i + 4].pguid = &guids::GUID_RyAxis;
data[8 * i + 5].pguid = &guids::GUID_RzAxis;
data[8 * i + 6].pguid = &guids::GUID_Slider;
data[8 * i + 7].pguid = &guids::GUID_Slider;
}
data[1].pguid = &guids::GUID_YAxis;
data[1].dwOfs = DIJOFS_Y;
data[ 0].dwOfs = DIJOFS_X;
data[ 1].dwOfs = DIJOFS_Y;
data[ 2].dwOfs = DIJOFS_Z;
data[ 3].dwOfs = DIJOFS_RX;
data[ 4].dwOfs = DIJOFS_RY;
data[ 5].dwOfs = DIJOFS_RZ;
data[ 6].dwOfs = DIJOFS_SLIDER(0);
data[ 7].dwOfs = DIJOFS_SLIDER(1);
data[ 8].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVX);
data[ 9].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVY);
data[10].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVZ);
data[11].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRx);
data[12].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRy);
data[13].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lVRz);
data[14].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglVSlider[0]);
data[15].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglVSlider[1]);
data[16].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAX);
data[17].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAY);
data[18].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lAZ);
data[19].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARx);
data[20].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARy);
data[21].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lARz);
data[22].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglASlider[0]);
data[23].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglASlider[1]);
data[24].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFX);
data[25].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFY);
data[26].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFZ);
data[27].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRx);
data[28].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRy);
data[29].dwOfs = FIELD_OFFSET(DIJOYSTATE2, lFRz);
data[30].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglFSlider[0]);
data[31].dwOfs = FIELD_OFFSET(DIJOYSTATE2, rglFSlider[1]);
data[2].pguid = &guids::GUID_ZAxis;
data[2].dwOfs = DIJOFS_Z;
data[3].pguid = &guids::GUID_RxAxis;
data[3].dwOfs = DIJOFS_RX;
data[4].pguid = &guids::GUID_RyAxis;
data[4].dwOfs = DIJOFS_RY;
data[5].pguid = &guids::GUID_RzAxis;
data[5].dwOfs = DIJOFS_RZ;
data[6].pguid = &guids::GUID_Slider;
data[6].dwOfs = DIJOFS_SLIDER(0);
data[7].pguid = &guids::GUID_Slider;
data[7].dwOfs = DIJOFS_SLIDER(1);
for (int i = 0; i < 8; ++i)
for (int i = 0; i < 8 * 4; ++i)
{
data[i].dwType = axisType;
data[i].dwFlags = DIDOI_ASPECTPOSITION;
data[i].dwFlags = 0;
}
for (int i = 0; i < 4; ++i)
{
data[8 + i].pguid = &guids::GUID_POV;
data[8 + i].dwOfs = static_cast<DWORD>(DIJOFS_POV(i));
data[8 + i].dwType = povType;
data[8 + i].dwFlags = 0;
data[8 * 4 + i].pguid = &guids::GUID_POV;
data[8 * 4 + i].dwOfs = static_cast<DWORD>(DIJOFS_POV(i));
data[8 * 4 + i].dwType = povType;
data[8 * 4 + i].dwFlags = 0;
}
for (int i = 0; i < sf::Joystick::ButtonCount; ++i)
{
data[8 + 4 + i].pguid = NULL;
data[8 + 4 + i].dwOfs = static_cast<DWORD>(DIJOFS_BUTTON(i));
data[8 + 4 + i].dwType = buttonType;
data[8 + 4 + i].dwFlags = 0;
data[8 * 4 + 4 + i].pguid = NULL;
data[8 * 4 + 4 + i].dwOfs = static_cast<DWORD>(DIJOFS_BUTTON(i));
data[8 * 4 + 4 + i].dwType = buttonType;
data[8 * 4 + 4 + i].dwFlags = 0;
}
format.dwSize = sizeof(DIDATAFORMAT);
format.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
format.dwFlags = DIDFT_ABSAXIS;
format.dwDataSize = sizeof(DIJOYSTATE);
format.dwNumObjs = 8 + 4 + sf::Joystick::ButtonCount;
format.dwDataSize = sizeof(DIJOYSTATE2);
format.dwNumObjs = 8 * 4 + 4 + sf::Joystick::ButtonCount;
format.rgodf = data;
formatInitialized = true;
@ -585,7 +669,7 @@ bool JoystickImpl::openDInput(unsigned int index)
// Set device data format
result = m_device->SetDataFormat(&format);
if (result)
if (FAILED(result))
{
err() << "Failed to set DirectInput device data format: " << result << std::endl;
@ -598,7 +682,7 @@ bool JoystickImpl::openDInput(unsigned int index)
// Get device capabilities
result = m_device->GetCapabilities(&m_deviceCaps);
if (result)
if (FAILED(result))
{
err() << "Failed to get DirectInput device capabilities: " << result << std::endl;
@ -608,30 +692,10 @@ bool JoystickImpl::openDInput(unsigned int index)
return false;
}
// Set axis mode to absolute
DIPROPDWORD property;
std::memset(&property, 0, sizeof(property));
property.diph.dwSize = sizeof(property);
property.diph.dwHeaderSize = sizeof(property.diph);
property.diph.dwHow = DIPH_DEVICE;
property.dwData = DIPROPAXISMODE_ABS;
result = m_device->SetProperty(DIPROP_AXISMODE, &property.diph);
if (result)
{
err() << "Failed to set DirectInput device axis mode: " << result << std::endl;
m_device->Release();
m_device = NULL;
return false;
}
// Enumerate device objects (axes/povs/buttons)
result = m_device->EnumObjects(&JoystickImpl::deviceObjectEnumerationCallback, this, DIDFT_AXIS | DIDFT_BUTTON | DIDFT_POV);
if (result)
if (FAILED(result))
{
err() << "Failed to enumerate DirectInput device objects: " << result << std::endl;
@ -641,29 +705,116 @@ bool JoystickImpl::openDInput(unsigned int index)
return false;
}
// Get friendly product name of the device
DIPROPSTRING stringProperty;
std::memset(&stringProperty, 0, sizeof(stringProperty));
stringProperty.diph.dwSize = sizeof(stringProperty);
stringProperty.diph.dwHeaderSize = sizeof(stringProperty.diph);
stringProperty.diph.dwHow = DIPH_DEVICE;
stringProperty.diph.dwObj = 0;
if (!m_device->GetProperty(DIPROP_PRODUCTNAME, &stringProperty.diph))
// Set device's axis mode to absolute if the device reports having at least one axis
for (int i = 0; i < Joystick::AxisCount; ++i)
{
m_identification.name = stringProperty.wsz;
if (m_axes[i] != -1)
{
std::memset(&property, 0, sizeof(property));
property.diph.dwSize = sizeof(property);
property.diph.dwHeaderSize = sizeof(property.diph);
property.diph.dwHow = DIPH_DEVICE;
property.diph.dwObj = 0;
result = m_device->GetProperty(DIPROP_AXISMODE, &property.diph);
if (FAILED(result))
{
err() << "Failed to get DirectInput device axis mode for device \""
<< m_identification.name.toAnsiString() << "\": " << result << std::endl;
m_device->Release();
m_device = NULL;
return false;
}
// If the axis mode is already set to absolute we don't need to set it again ourselves
if (property.dwData == DIPROPAXISMODE_ABS)
break;
std::memset(&property, 0, sizeof(property));
property.diph.dwSize = sizeof(property);
property.diph.dwHeaderSize = sizeof(property.diph);
property.diph.dwHow = DIPH_DEVICE;
property.dwData = DIPROPAXISMODE_ABS;
m_device->SetProperty(DIPROP_AXISMODE, &property.diph);
// Check if the axis mode has been set to absolute
std::memset(&property, 0, sizeof(property));
property.diph.dwSize = sizeof(property);
property.diph.dwHeaderSize = sizeof(property.diph);
property.diph.dwHow = DIPH_DEVICE;
property.diph.dwObj = 0;
result = m_device->GetProperty(DIPROP_AXISMODE, &property.diph);
if (FAILED(result))
{
err() << "Failed to verify DirectInput device axis mode for device \""
<< m_identification.name.toAnsiString() << "\": " << result << std::endl;
m_device->Release();
m_device = NULL;
return false;
}
// If the axis mode hasn't been set to absolute fail here and blacklist the device
if (property.dwData != DIPROPAXISMODE_ABS)
{
if (m_identification.vendorId && m_identification.productId)
{
JoystickBlacklistEntry entry;
entry.vendorId = m_identification.vendorId;
entry.productId = m_identification.productId;
joystickBlacklist.push_back(entry);
// Pre-C++11 shrink_to_fit()
JoystickBlacklist(joystickBlacklist.begin(), joystickBlacklist.end()).swap(joystickBlacklist);
}
m_device->Release();
m_device = NULL;
return false;
}
break;
}
}
// Get vendor and produce id of the device
// Try to enable buffering by setting the buffer size
std::memset(&property, 0, sizeof(property));
property.diph.dwSize = sizeof(property);
property.diph.dwHeaderSize = sizeof(property.diph);
property.diph.dwHow = DIPH_DEVICE;
property.dwData = directInputEventBufferSize;
if (!m_device->GetProperty(DIPROP_VIDPID, &property.diph))
result = m_device->SetProperty(DIPROP_BUFFERSIZE, &property.diph);
if (result == DI_OK)
{
m_identification.productId = HIWORD(property.dwData);
m_identification.vendorId = LOWORD(property.dwData);
// Buffering supported
m_buffered = true;
}
else if (result == DI_POLLEDDEVICE)
{
// Only polling supported
m_buffered = false;
}
else
{
err() << "Failed to set DirectInput device buffer size for device \""
<< m_identification.name.toAnsiString() << "\": " << result << std::endl;
m_device->Release();
m_device = NULL;
return false;
}
return true;
@ -709,7 +860,113 @@ JoystickCaps JoystickImpl::getCapabilitiesDInput() const
////////////////////////////////////////////////////////////
JoystickState JoystickImpl::updateDInput()
JoystickState JoystickImpl::updateDInputBuffered()
{
// If we don't make it to the end of this function, mark the device as disconnected
m_state.connected = false;
if (!m_device)
return m_state;
DIDEVICEOBJECTDATA events[directInputEventBufferSize];
DWORD eventCount = directInputEventBufferSize;
// Try to get the device data
HRESULT result = m_device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), events, &eventCount, 0);
// If we have not acquired or have lost the device, attempt to (re-)acquire it and get the device data again
if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST))
{
m_device->Acquire();
result = m_device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), events, &eventCount, 0);
}
// If we still can't get the device data, assume it has been disconnected
if ((result == DIERR_NOTACQUIRED) || (result == DIERR_INPUTLOST))
{
m_device->Release();
m_device = NULL;
return m_state;
}
if (FAILED(result))
{
err() << "Failed to get DirectInput device data: " << result << std::endl;
return m_state;
}
// Iterate through all buffered events
for (DWORD i = 0; i < eventCount; ++i)
{
bool eventHandled = false;
// Get the current state of each axis
for (int j = 0; j < Joystick::AxisCount; ++j)
{
if (m_axes[j] == events[i].dwOfs)
{
if (j == Joystick::PovX)
{
unsigned short value = LOWORD(events[i].dwData);
if (value != 0xFFFF)
{
float angle = (static_cast<float>(value)) * 3.141592654f / DI_DEGREES / 180.f;
m_state.axes[j] = std::sin(angle) * 100.f;
}
else
{
m_state.axes[j] = 0;
}
}
else if (j == Joystick::PovY)
{
unsigned short value = LOWORD(events[i].dwData);
if (value != 0xFFFF)
{
float angle = (static_cast<float>(value)) * 3.141592654f / DI_DEGREES / 180.f;
m_state.axes[j] = std::cos(angle) * 100.f;
}
else
{
m_state.axes[j] = 0.f;
}
}
else
{
m_state.axes[j] = (static_cast<float>(static_cast<short>(events[i].dwData)) + 0.5f) * 100.f / 32767.5f;
}
eventHandled = true;
break;
}
}
if (eventHandled)
continue;
// Get the current state of each button
for (int j = 0; j < Joystick::ButtonCount; ++j)
{
if (m_buttons[j] == events[i].dwOfs)
m_state.buttons[j] = (events[i].dwData != 0);
}
}
m_state.connected = true;
return m_state;
}
////////////////////////////////////////////////////////////
JoystickState JoystickImpl::updateDInputPolled()
{
JoystickState state;
@ -718,7 +975,7 @@ JoystickState JoystickImpl::updateDInput()
// Poll the device
m_device->Poll();
DIJOYSTATE joystate;
DIJOYSTATE2 joystate;
// Try to get the device state
HRESULT result = m_device->GetDeviceState(sizeof(joystate), &joystate);
@ -740,7 +997,7 @@ JoystickState JoystickImpl::updateDInput()
return state;
}
if (result)
if (FAILED(result))
{
err() << "Failed to get DirectInput device state: " << result << std::endl;
@ -878,7 +1135,7 @@ BOOL CALLBACK JoystickImpl::deviceObjectEnumerationCallback(const DIDEVICEOBJECT
HRESULT result = joystick.m_device->SetProperty(DIPROP_RANGE, &propertyRange.diph);
if (result)
if (result != DI_OK)
err() << "Failed to set DirectInput device axis property range: " << result << std::endl;
return DIENUM_CONTINUE;

View File

@ -186,12 +186,20 @@ public:
JoystickCaps getCapabilitiesDInput() const;
////////////////////////////////////////////////////////////
/// \brief Update the joystick and get its new state (DInput)
/// \brief Update the joystick and get its new state (DInput, Buffered)
///
/// \return Joystick state
///
////////////////////////////////////////////////////////////
JoystickState updateDInput();
JoystickState updateDInputBuffered();
////////////////////////////////////////////////////////////
/// \brief Update the joystick and get its new state (DInput, Polled)
///
/// \return Joystick state
///
////////////////////////////////////////////////////////////
JoystickState updateDInputPolled();
private:
@ -227,6 +235,8 @@ private:
int m_axes[Joystick::AxisCount]; //!< Offsets to the bytes containing the axes states, -1 if not available
int m_buttons[Joystick::ButtonCount]; //!< Offsets to the bytes containing the button states, -1 if not available
Joystick::Identification m_identification; //!< Joystick identification
JoystickState m_state; //!< Buffered joystick state
bool m_buffered; //!< true if the device uses buffering, false if the device uses polling
};
} // namespace priv