mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-29 04:35:41 +08:00
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:
committed by
Klaus Basan
parent
9f3bc40071
commit
e279b77ca6
@@ -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(¬ificationFilter, sizeof(notificationFilter));
|
||||||
|
notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
|
||||||
|
notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||||
|
hDevNotify = RegisterDeviceNotification(helperWindow, ¬ificationFilter, 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)
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
Reference in New Issue
Block a user