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 "joystickwindows.h"
#include "blackmisc/logmessage.h" #include "blackmisc/logmessage.h"
#include "comdef.h" #include "comdef.h"
#include "Dbt.h"
// Qt5 defines UNICODE, hence we can expect an wchar_t strings. // 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 // If it fails to compile, because of char/wchar_t errors, you are most likely
@@ -101,40 +102,24 @@ namespace BlackInput
HRESULT CJoystickDevice::pollDeviceState() HRESULT CJoystickDevice::pollDeviceState()
{ {
m_directInputDevice->Poll();
DIJOYSTATE2 state; DIJOYSTATE2 state;
HRESULT hr = S_OK; HRESULT hr = m_directInputDevice->GetDeviceState(sizeof(DIJOYSTATE2), &state);
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
if (FAILED(hr = m_directInputDevice->Poll()))
{ {
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) m_directInputDevice->Acquire();
{ m_directInputDevice->Poll();
m_directInputDevice->Acquire(); hr = m_directInputDevice->GetDeviceState(sizeof(DIJOYSTATE2), &state);
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;
}
}
} }
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)) for (const CJoystickDeviceInput &input : as_const(m_joystickDeviceInputs))
{ {
@@ -199,6 +184,7 @@ namespace BlackInput
{ {
this->initDirectInput(); this->initDirectInput();
this->enumJoystickDevices(); this->enumJoystickDevices();
this->requestDeviceNotification();
} }
} }
else else
@@ -218,6 +204,7 @@ namespace BlackInput
m_joystickDevices.clear(); m_joystickDevices.clear();
m_directInput.reset(); m_directInput.reset();
if (m_coInitializeSucceeded) { CoUninitialize(); } if (m_coInitializeSucceeded) { CoUninitialize(); }
if (hDevNotify) { UnregisterDeviceNotification(hDevNotify); }
destroyHelperWindow(); destroyHelperWindow();
} }
@@ -273,24 +260,22 @@ namespace BlackInput
int CJoystickWindows::createHelperWindow() int CJoystickWindows::createHelperWindow()
{ {
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASS wce;
// Make sure window isn't created twice // Make sure window isn't created twice
if (helperWindow != nullptr) if (helperWindow != nullptr)
{ {
return 0; return 0;
} }
// Create the class HINSTANCE hInstance = GetModuleHandle(nullptr);
ZeroMemory(&wce, sizeof(WNDCLASS)); WNDCLASSEX wce;
wce.lpfnWndProc = DefWindowProc; ZeroMemory(&wce, sizeof(wce));
wce.cbSize = sizeof(wce);
wce.lpfnWndProc = windowProc;
wce.lpszClassName = (LPCWSTR) helperWindowClassName; wce.lpszClassName = (LPCWSTR) helperWindowClassName;
wce.hInstance = hInstance; wce.hInstance = hInstance;
/* Register the class. */ /* Register the class. */
helperWindowClass = RegisterClass(&wce); if (! RegisterClassEx(&wce))
if (helperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
{ {
return -1; return -1;
} }
@@ -308,9 +293,20 @@ namespace BlackInput
return -1; return -1;
} }
SetProp(helperWindow, L"CJoystickWindows", this);
return 0; 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() void CJoystickWindows::destroyHelperWindow()
{ {
HINSTANCE hInstance = GetModuleHandle(nullptr); HINSTANCE hInstance = GetModuleHandle(nullptr);
@@ -321,7 +317,6 @@ namespace BlackInput
helperWindow = nullptr; helperWindow = nullptr;
UnregisterClass(helperWindowClassName, hInstance); UnregisterClass(helperWindowClassName, hInstance);
helperWindowClass = 0;
} }
void CJoystickWindows::addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance) void CJoystickWindows::addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance)
@@ -331,6 +326,7 @@ namespace BlackInput
if (success) if (success)
{ {
connect(device, &CJoystickDevice::buttonChanged, this, &CJoystickWindows::joystickButtonChanged); connect(device, &CJoystickDevice::buttonChanged, this, &CJoystickWindows::joystickButtonChanged);
connect(device, &CJoystickDevice::connectionLost, this, &CJoystickWindows::removeJoystickDevice);
m_joystickDevices.push_back(device); m_joystickDevices.push_back(device);
} }
else 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) void CJoystickWindows::joystickButtonChanged(const CJoystickButton &joystickButton, bool isPressed)
{ {
CHotkeyCombination oldCombination(m_buttonCombination); 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) BOOL CALLBACK CJoystickWindows::enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)
{ {
CJoystickWindows *obj = static_cast<CJoystickWindows *>(pContext); CJoystickWindows *obj = static_cast<CJoystickWindows *>(pContext);
@@ -358,9 +408,12 @@ namespace BlackInput
/* ignore XInput devices here, keep going. */ /* ignore XInput devices here, keep going. */
//if (isXInputDevice( &pdidInstance->guidProduct )) return DIENUM_CONTINUE; //if (isXInputDevice( &pdidInstance->guidProduct )) return DIENUM_CONTINUE;
obj->addJoystickDevice(pdidInstance); if (! obj->isJoystickAlreadyAdded(pdidInstance))
CLogMessage(static_cast<CJoystickWindows *>(nullptr)).debug() << "Found joystick device" << QString::fromWCharArray(pdidInstance->tszInstanceName); {
return true; 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) bool operator == (const CJoystickDevice &lhs, const CJoystickDevice &rhs)

View File

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