mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-31 21:15:33 +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 "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(¬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()
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user