Add Windows Joystick hotplug support

A hidden window will listen for device attached and removed signals from Windows. In case such a event occurs, all devices are enumerated again.
ref T587
This commit is contained in:
Roland Rossgotterer
2019-04-26 10:35:37 +02:00
committed by Klaus Basan
parent 9f3bc40071
commit e279b77ca6
2 changed files with 113 additions and 43 deletions

View File

@@ -9,6 +9,7 @@
#include "joystickwindows.h"
#include "blackmisc/logmessage.h"
#include "comdef.h"
#include "Dbt.h"
// Qt5 defines UNICODE, hence we can expect an wchar_t strings.
// If it fails to compile, because of char/wchar_t errors, you are most likely
@@ -101,40 +102,24 @@ namespace BlackInput
HRESULT CJoystickDevice::pollDeviceState()
{
m_directInputDevice->Poll();
DIJOYSTATE2 state;
HRESULT hr = S_OK;
if (FAILED(hr = m_directInputDevice->Poll()))
HRESULT hr = m_directInputDevice->GetDeviceState(sizeof(DIJOYSTATE2), &state);
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
{
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
{
m_directInputDevice->Acquire();
if (FAILED(hr = m_directInputDevice->Poll()))
{
if (m_lastHRError == hr) { return hr; } // avoid flooding with messages
m_lastHRError = hr;
CLogMessage(this).warning(u"DirectInput error code (POLL input lost/notacquired): %1 %2") << hr << hrString(hr);
return hr;
}
}
m_directInputDevice->Acquire();
m_directInputDevice->Poll();
hr = m_directInputDevice->GetDeviceState(sizeof(DIJOYSTATE2), &state);
}
if (FAILED(hr = m_directInputDevice->GetDeviceState(sizeof(DIJOYSTATE2), &state)))
{
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
{
m_directInputDevice->Acquire();
if (FAILED(hr = m_directInputDevice->GetDeviceState(sizeof(DIJOYSTATE2), &state)))
{
if (m_lastHRError == hr) { return hr; } // avoid flooding with messages
m_lastHRError = hr;
CLogMessage(this).warning(u"DirectInput error code (state input lost/notacquired): %1 %2") << hr << hrString(hr);
return hr;
}
}
}
m_lastHRError = hr;
if (FAILED(hr))
{
CLogMessage(this).warning(u"Cannot acquire and poll joystick device %1. Removing it.") << m_deviceName;
emit connectionLost(m_guidDevice);
return hr;
}
for (const CJoystickDeviceInput &input : as_const(m_joystickDeviceInputs))
{
@@ -199,6 +184,7 @@ namespace BlackInput
{
this->initDirectInput();
this->enumJoystickDevices();
this->requestDeviceNotification();
}
}
else
@@ -218,6 +204,7 @@ namespace BlackInput
m_joystickDevices.clear();
m_directInput.reset();
if (m_coInitializeSucceeded) { CoUninitialize(); }
if (hDevNotify) { UnregisterDeviceNotification(hDevNotify); }
destroyHelperWindow();
}
@@ -273,24 +260,22 @@ namespace BlackInput
int CJoystickWindows::createHelperWindow()
{
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASS wce;
// Make sure window isn't created twice
if (helperWindow != nullptr)
{
return 0;
}
// Create the class
ZeroMemory(&wce, sizeof(WNDCLASS));
wce.lpfnWndProc = DefWindowProc;
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASSEX wce;
ZeroMemory(&wce, sizeof(wce));
wce.cbSize = sizeof(wce);
wce.lpfnWndProc = windowProc;
wce.lpszClassName = (LPCWSTR) helperWindowClassName;
wce.hInstance = hInstance;
/* Register the class. */
helperWindowClass = RegisterClass(&wce);
if (helperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
if (! RegisterClassEx(&wce))
{
return -1;
}
@@ -308,9 +293,20 @@ namespace BlackInput
return -1;
}
SetProp(helperWindow, L"CJoystickWindows", this);
return 0;
}
void CJoystickWindows::requestDeviceNotification()
{
DEV_BROADCAST_DEVICEINTERFACE notificationFilter;
ZeroMemory(&notificationFilter, sizeof(notificationFilter));
notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
hDevNotify = RegisterDeviceNotification(helperWindow, &notificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
}
void CJoystickWindows::destroyHelperWindow()
{
HINSTANCE hInstance = GetModuleHandle(nullptr);
@@ -321,7 +317,6 @@ namespace BlackInput
helperWindow = nullptr;
UnregisterClass(helperWindowClassName, hInstance);
helperWindowClass = 0;
}
void CJoystickWindows::addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance)
@@ -331,6 +326,7 @@ namespace BlackInput
if (success)
{
connect(device, &CJoystickDevice::buttonChanged, this, &CJoystickWindows::joystickButtonChanged);
connect(device, &CJoystickDevice::connectionLost, this, &CJoystickWindows::removeJoystickDevice);
m_joystickDevices.push_back(device);
}
else
@@ -339,6 +335,19 @@ namespace BlackInput
}
}
bool CJoystickWindows::isJoystickAlreadyAdded(const DIDEVICEINSTANCE *pdidInstance) const
{
for (const CJoystickDevice *device : m_joystickDevices)
{
if (IsEqualGUID(device->getDeviceGuid(), pdidInstance->guidInstance))
{
return true;
}
}
return false;
}
void CJoystickWindows::joystickButtonChanged(const CJoystickButton &joystickButton, bool isPressed)
{
CHotkeyCombination oldCombination(m_buttonCombination);
@@ -351,6 +360,47 @@ namespace BlackInput
}
}
void CJoystickWindows::removeJoystickDevice(const GUID &guid)
{
for (auto it = m_joystickDevices.begin(); it != m_joystickDevices.end(); ++it)
{
CJoystickDevice *device = *it;
if (IsEqualGUID(guid, device->getDeviceGuid()))
{
device->deleteLater();
m_joystickDevices.erase(it);
break;
}
}
}
// Window callback function (handles window messages)
//
LRESULT CALLBACK CJoystickWindows::windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CJoystickWindows* joystickWindows = static_cast<CJoystickWindows*>(GetProp(hWnd, L"CJoystickWindows"));
if (joystickWindows)
{
switch (uMsg)
{
case WM_DEVICECHANGE:
{
if (wParam == DBT_DEVICEARRIVAL)
{
DEV_BROADCAST_HDR* dbh = (DEV_BROADCAST_HDR*) lParam;
if (dbh && dbh->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
joystickWindows->enumJoystickDevices();
}
}
}
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
BOOL CALLBACK CJoystickWindows::enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)
{
CJoystickWindows *obj = static_cast<CJoystickWindows *>(pContext);
@@ -358,9 +408,12 @@ namespace BlackInput
/* ignore XInput devices here, keep going. */
//if (isXInputDevice( &pdidInstance->guidProduct )) return DIENUM_CONTINUE;
obj->addJoystickDevice(pdidInstance);
CLogMessage(static_cast<CJoystickWindows *>(nullptr)).debug() << "Found joystick device" << QString::fromWCharArray(pdidInstance->tszInstanceName);
return true;
if (! obj->isJoystickAlreadyAdded(pdidInstance))
{
obj->addJoystickDevice(pdidInstance);
CLogMessage(static_cast<CJoystickWindows *>(nullptr)).debug() << "Found joystick device" << QString::fromWCharArray(pdidInstance->tszInstanceName);
}
return DIENUM_CONTINUE;
}
bool operator == (const CJoystickDevice &lhs, const CJoystickDevice &rhs)

View File

@@ -51,10 +51,16 @@ namespace BlackInput
//! Get all available device buttons
BlackMisc::Input::CJoystickButtonList getDeviceButtons() const;
//! Get device GUID
GUID getDeviceGuid() const { return m_guidDevice; }
signals:
//! Joystick button changed
void buttonChanged(const BlackMisc::Input::CJoystickButton &joystickButton, bool isPressed);
//! Connection to joystick lost. Probably unplugged.
void connectionLost(const GUID &guid);
protected:
//! Timer based updates
virtual void timerEvent(QTimerEvent *event) override;
@@ -85,7 +91,6 @@ namespace BlackInput
//! Joystick button enumeration callback
static BOOL CALLBACK enumObjectsCallback(const DIDEVICEOBJECTINSTANCE *dev, LPVOID pvRef);
HRESULT m_lastHRError = S_OK;
GUID m_guidDevice; //!< Device GUID
GUID m_guidProduct; //!< Product GUID
QString m_deviceName; //!< Device name
@@ -131,19 +136,31 @@ namespace BlackInput
//! Creates a hidden DI helper window
int createHelperWindow();
//! Request USB device notifications sent to our helper window.
//! This is required for joystick hotplug support
void requestDeviceNotification();
//! Destroys a hidden DI helper window
void destroyHelperWindow();
//! Add new joystick device
void addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance);
//! Remove joystick device
void removeJoystickDevice(const GUID &guid);
//! Is joystick instance already added?
bool isJoystickAlreadyAdded(const DIDEVICEINSTANCE *pdidInstance) const;
void joystickButtonChanged(const BlackMisc::Input::CJoystickButton &joystickButton, bool isPressed);
static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
//! Joystick enumeration callback
static BOOL CALLBACK enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext);
ATOM helperWindowClass = 0;
HWND helperWindow = nullptr;
HDEVNOTIFY hDevNotify = nullptr;
const TCHAR *helperWindowClassName = TEXT("HelperWindow");
const TCHAR *helperWindowName = TEXT("JoystickCatcherWindow");