Add support for multiple Joystick devices

This includes a major refactoring of the Linux and Windows implementations
Each joystick device is now its own QObject. They all setup their own
native device instances and poll the button states.
On Windows, COM initialization has been removed since it caused segfaults
while releasing the DirectInput8 instance.

ref T254
This commit is contained in:
Roland Winklmeier
2018-09-19 13:49:26 +02:00
committed by Klaus Basan
parent fa74f28bca
commit 84f1e21cf8
4 changed files with 357 additions and 361 deletions

View File

@@ -31,26 +31,71 @@ namespace
namespace BlackInput
{
CJoystickDevice::CJoystickDevice(const QString &path, QFile *fd, QObject *parent)
: QObject(parent), m_path(path), m_fd(fd)
{
m_fd->setParent(this);
char deviceName[256];
if (ioctl(m_fd->handle(), JSIOCGNAME(sizeof(deviceName)), deviceName) < 0)
{
strncpy(deviceName, "Unknown", sizeof(deviceName));
}
CLogMessage(this).info("Found joystick: %1") << deviceName;
fcntl(m_fd->handle(), F_SETFL, O_NONBLOCK);
/* Forward */
struct js_event event;
while (m_fd->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event)) {}
QSocketNotifier *notifier = new QSocketNotifier(m_fd->handle(), QSocketNotifier::Read, m_fd);
connect(notifier, &QSocketNotifier::activated, this, &CJoystickDevice::processInput);
m_name = QString(deviceName);
}
CJoystickDevice::~CJoystickDevice()
{
if (m_fd)
{
m_fd->close();
m_fd->deleteLater();
}
}
void CJoystickDevice::processInput()
{
struct js_event event;
while (m_fd->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event))
{
switch (event.type & ~JS_EVENT_INIT)
{
case JS_EVENT_BUTTON:
if (event.value) { emit buttonChanged(m_name, event.number, true); }
else { emit buttonChanged(m_name, event.number, false); }
break;
}
}
}
CJoystickLinux::CJoystickLinux(QObject *parent) :
IJoystick(parent),
m_mapper(new QSignalMapper(this)),
m_inputWatcher(new QFileSystemWatcher(this))
{
connect(m_mapper, static_cast<void (QSignalMapper::*)(QObject *)>(&QSignalMapper::mapped), this, &CJoystickLinux::ps_readInput);
m_inputWatcher->addPath(inputDevicesDir());
connect(m_inputWatcher, &QFileSystemWatcher::directoryChanged, this, &CJoystickLinux::ps_directoryChanged);
ps_directoryChanged(inputDevicesDir());
connect(m_inputWatcher, &QFileSystemWatcher::directoryChanged, this, &CJoystickLinux::reloadDevices);
reloadDevices(inputDevicesDir());
}
void CJoystickLinux::cleanupJoysticks()
{
for (auto it = m_joysticks.begin(); it != m_joysticks.end();)
for (auto it = m_joystickDevices.begin(); it != m_joystickDevices.end();)
{
if (!it.value()->exists())
// Remove all joysticks that do not exist anymore (/dev/input/js* removed).
if (!(*it)->isAttached())
{
it.value()->deleteLater();
it = m_joysticks.erase(it);
CJoystickDevice *joystickDevice = *it;
it = m_joystickDevices.erase(it);
joystickDevice->deleteLater();
}
else
{
@@ -61,39 +106,39 @@ namespace BlackInput
void CJoystickLinux::addJoystickDevice(const QString &path)
{
Q_ASSERT(!m_joysticks.contains(path));
QFile *joystick = new QFile(path, this);
if (joystick->open(QIODevice::ReadOnly))
QFile *fd = new QFile(path);
if (fd->open(QIODevice::ReadOnly))
{
char name[256];
if (ioctl(joystick->handle(), JSIOCGNAME(sizeof(name)), name) < 0)
{
strncpy(name, "Unknown", sizeof(name));
}
CLogMessage(this).info("Found joystick: %1") << name;
fcntl(joystick->handle(), F_SETFL, O_NONBLOCK);
/* Forward */
struct js_event event;
while (joystick->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event)) {}
QSocketNotifier *notifier = new QSocketNotifier(joystick->handle(), QSocketNotifier::Read, joystick);
m_mapper->setMapping(notifier, joystick);
connect(notifier, &QSocketNotifier::activated, m_mapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
notifier->setEnabled(true);
m_joysticks.insert(path, joystick);
CJoystickDevice *joystickDevice = new CJoystickDevice(path, fd, this);
connect(joystickDevice, &CJoystickDevice::buttonChanged, this, &CJoystickLinux::joystickButtonChanged);
m_joystickDevices.push_back(joystickDevice);
}
else
{
joystick->deleteLater();
fd->close();
fd->deleteLater();
}
}
void CJoystickLinux::ps_directoryChanged(QString path)
void CJoystickLinux::joystickButtonChanged(const QString &name, int index, bool isPressed)
{
BlackMisc::Input::CHotkeyCombination oldCombination(m_buttonCombination);
if (isPressed)
{
m_buttonCombination.addJoystickButton({name, index});
}
else
{
m_buttonCombination.removeJoystickButton({name, index});
}
if (oldCombination != m_buttonCombination)
{
emit buttonCombinationChanged(m_buttonCombination);
}
}
void CJoystickLinux::reloadDevices(QString path)
{
cleanupJoysticks();
@@ -101,41 +146,15 @@ namespace BlackInput
for (const auto &entry : dir.entryInfoList())
{
QString f = entry.absoluteFilePath();
if (!m_joysticks.contains(f))
auto it = std::find_if(m_joystickDevices.begin(), m_joystickDevices.end(), [path] (const CJoystickDevice *device)
{
return device->getPath() == path;
});
if (it == m_joystickDevices.end())
{
addJoystickDevice(f);
}
}
}
void CJoystickLinux::ps_readInput(QObject *object)
{
QFile *joystick = qobject_cast<QFile *>(object);
Q_ASSERT(joystick);
struct js_event event;
while (joystick->read(reinterpret_cast<char *>(&event), sizeof(event)) == sizeof(event))
{
BlackMisc::Input::CHotkeyCombination oldCombination(m_buttonCombination);
switch (event.type & ~JS_EVENT_INIT)
{
case JS_EVENT_BUTTON:
if (event.value)
{
m_buttonCombination.addJoystickButton(event.number);
}
else
{
m_buttonCombination.removeJoystickButton(event.number);
}
if (oldCombination != m_buttonCombination)
{
emit buttonCombinationChanged(m_buttonCombination);
}
break;
}
}
}
} // ns

View File

@@ -24,6 +24,38 @@ class QSignalMapper;
namespace BlackInput
{
//! Linux Joystick device
class CJoystickDevice : public QObject
{
Q_OBJECT
public:
//! Constructor
CJoystickDevice(const QString &path, QFile *fd, QObject *parent);
~CJoystickDevice();
//! Get device name
QString getName() const { return m_name; }
//! Get device path, e.g. /dev/input/js0
QString getPath() const { return m_path; }
//! Is joystick device still attached?
bool isAttached() const { return m_fd->exists(); }
signals:
//! Joystick button changed
void buttonChanged(const QString &name, int index, bool isPressed);
private:
//! Slot for reading the device handle
void processInput();
QString m_name; //!< Device name
QString m_path; //!< Device path, e.g. /dev/input/js0
QFile *m_fd = nullptr; //!< Linux file descriptor
};
//! Linux implemenation of IJoystick
//! \sa https://www.kernel.org/doc/Documentation/input/joystick-api.txt
class CJoystickLinux : public IJoystick
@@ -42,7 +74,6 @@ namespace BlackInput
virtual ~CJoystickLinux() = default;
private:
friend class IJoystick;
//! Removes all joysticks that are no longer present.
@@ -51,23 +82,18 @@ namespace BlackInput
//! Adds new joystick input for reading
void addJoystickDevice(const QString &path);
void joystickButtonChanged(const QString &name, int index, bool isPressed);
//! Constructor
CJoystickLinux(QObject *parent = nullptr);
private slots:
//! Slot for handling directory changes
//! \param path Watched directory path.
void ps_directoryChanged(QString path);
//! Slot for reading the device handle
//! \param object QFile that has data to be read.
void ps_readInput(QObject *object);
void reloadDevices(QString path);
private:
BlackMisc::Input::CHotkeyCombination m_buttonCombination;
QSignalMapper *m_mapper = nullptr; //!< Maps device handles
QMap<QString, QFile *> m_joysticks; //!< All read joysticks, file path <-> file instance pairs
QVector<CJoystickDevice *> m_joystickDevices; //!< All joystick devices
QFileSystemWatcher *m_inputWatcher = nullptr;
};

View File

@@ -20,168 +20,43 @@ using namespace BlackMisc::Input;
namespace BlackInput
{
const TCHAR *CJoystickWindows::m_helperWindowClassName = TEXT("HelperWindow");
const TCHAR *CJoystickWindows::m_helperWindowName = TEXT("JoystickCatcherWindow");
ATOM CJoystickWindows::m_helperWindowClass = 0;
HWND CJoystickWindows::m_helperWindow = nullptr;
CJoystickWindows::CJoystickWindows(QObject *parent) : IJoystick(parent)
CJoystickDevice::CJoystickDevice(DirectInput8Ptr directInputPtr, const DIDEVICEINSTANCE *pdidInstance, QObject *parent)
: QObject(parent),
m_directInput(directInputPtr)
{
// Initialize COM
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
this->initDirectInput();
this->enumJoystickDevices();
this->filterJoystickDevices();
if (!m_availableJoystickDevices.isEmpty()) { this->createJoystickDevice(); }
m_deviceName = QString::fromWCharArray(pdidInstance->tszInstanceName);
m_productName = QString::fromWCharArray(pdidInstance->tszProductName);
m_guidDevice = pdidInstance->guidInstance;
m_guidProduct = pdidInstance->guidProduct;
}
CJoystickWindows::~CJoystickWindows()
bool CJoystickDevice::init(HWND helperWindow)
{
if (m_directInputDevice)
{
// currently disabled as it causes crashi
// m_directInputDevice->Release();
// m_directInputDevice = nullptr;
}
if (m_directInput)
{
//! \todo hack without input device this crashes
if (m_directInputDevice) { m_directInput->Release(); }
m_directInput = nullptr;
}
CoUninitialize();
}
void CJoystickWindows::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event);
this->pollDeviceState();
}
HRESULT CJoystickWindows::initDirectInput()
{
HRESULT hr = CoCreateInstance(CLSID_DirectInput8, nullptr, CLSCTX_INPROC_SERVER, IID_IDirectInput8, (LPVOID *)&m_directInput);
if (FAILED(hr))
{
CLogMessage(this).error("Cannot create instance %1") << GetLastError();
return hr;
}
HINSTANCE instance = GetModuleHandle(nullptr);
if (instance == nullptr)
{
CLogMessage(this).error("GetModuleHandle() failed with error code: %1") << GetLastError();
return E_FAIL;
}
hr = m_directInput->Initialize(instance, DIRECTINPUT_VERSION);
if (FAILED(hr))
{
CLogMessage(this).error("Direct input init failed");
return hr;
}
return hr;
}
HRESULT CJoystickWindows::enumJoystickDevices()
{
if (!m_directInput)
{
CLogMessage(this).warning("No direct input");
return E_FAIL;
}
HRESULT hr;
if (FAILED(hr = m_directInput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumJoysticksCallback, this, DIEDFL_ATTACHEDONLY)))
{
CLogMessage(this).error("Error reading joystick devices");
return hr;
}
if (m_availableJoystickDevices.isEmpty())
{
CLogMessage(this).info("No joystick device found");
}
return hr;
}
void CJoystickWindows::filterJoystickDevices()
{
IDirectInputDevice8 *directInputDevice = nullptr;
DIDEVCAPS deviceCaps;
deviceCaps.dwSize = sizeof(DIDEVCAPS);
HRESULT hr = S_OK;
for (auto i = m_availableJoystickDevices.begin(); i != m_availableJoystickDevices.end();)
{
// Create device
hr = m_directInput->CreateDevice(i->guidDevice, &directInputDevice, nullptr);
if (FAILED(hr))
{
i = m_availableJoystickDevices.erase(i);
continue;
}
hr = directInputDevice->GetCapabilities(&deviceCaps);
if (FAILED(hr))
{
i = m_availableJoystickDevices.erase(i);
continue;
}
// Filter devices with 0 buttons
if (deviceCaps.dwButtons == 0)
{
i = m_availableJoystickDevices.erase(i);
continue;
}
if (directInputDevice)
{
directInputDevice->Release();
directInputDevice = nullptr;
}
++i;
}
}
HRESULT CJoystickWindows::createJoystickDevice()
{
HRESULT hr = S_OK;
// Check if device list is empty first
if (m_availableJoystickDevices.isEmpty()) { return E_FAIL; }
// FIXME: Take the first device with number of buttons > 0
// For the future, the user should be able to choose which device
// he wants to use.
const CJoystickDeviceData &deviceData = m_availableJoystickDevices.constFirst();
// Create device
Q_ASSERT_X(m_directInput, Q_FUNC_INFO, "We should not get here without direct input");
if (FAILED(hr = m_directInput->CreateDevice(deviceData.guidDevice, &m_directInputDevice, nullptr)))
{
CLogMessage(this).warning("IDirectInput8::CreateDevice failed: ") << hr;
return hr;
IDirectInputDevice8 *diDevice = nullptr;
if (FAILED(hr = m_directInput->CreateDevice(m_guidDevice, &diDevice, nullptr)))
{
CLogMessage(this).warning("IDirectInput8::CreateDevice failed: ") << hr;
return false;
}
m_directInputDevice.reset(diDevice);
}
this->createHelperWindow();
// Set cooperative level
if (FAILED(hr = m_directInputDevice->SetCooperativeLevel(m_helperWindow, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND)))
if(!helperWindow) { return false; }
if (FAILED(hr = m_directInputDevice->SetCooperativeLevel(helperWindow, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND)))
{
CLogMessage(this).warning("IDirectInputDevice8::SetCooperativeLevel failed: ") << hr;
return hr;
return false;
}
// Set data format to c_dfDIJoystick2
if (FAILED(hr = m_directInputDevice->SetDataFormat(&c_dfDIJoystick2)))
{
CLogMessage(this).warning("IDirectInputDevice8::SetDataFormat failed: ") << hr;
return hr;
return false;
}
DIDEVCAPS deviceCaps;
@@ -190,106 +65,34 @@ namespace BlackInput
if (FAILED(hr = m_directInputDevice->GetCapabilities(&deviceCaps)))
{
CLogMessage(this).warning("IDirectInputDevice8::GetCapabilities failed: ") << hr;
return hr;
return false;
}
m_joystickDeviceInputs.clear();
// Filter devices with 0 buttons
if (deviceCaps.dwButtons == 0) { return false; }
if (FAILED(hr = m_directInputDevice->EnumObjects(enumObjectsCallback, this, DIDFT_BUTTON)))
{
CLogMessage(this).warning("IDirectInputDevice8::EnumObjects failed: ") << hr;
return hr;
return false;
}
CLogMessage(this).info("Created joystick device '%1' with %2 buttons") << deviceData.deviceName << deviceCaps.dwButtons;
CLogMessage(this).info("Created joystick device '%1' with %2 buttons") << m_deviceName << deviceCaps.dwButtons;
this->startTimer(50);
return hr;
return true;
}
int CJoystickWindows::createHelperWindow()
void CJoystickDevice::timerEvent(QTimerEvent *event)
{
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASS wce;
// Make sure window isn't created twice
if (m_helperWindow != nullptr)
{
return 0;
}
// Create the class
ZeroMemory(&wce, sizeof(WNDCLASS));
wce.lpfnWndProc = DefWindowProc;
wce.lpszClassName = (LPCWSTR) m_helperWindowClassName;
wce.hInstance = hInstance;
/* Register the class. */
m_helperWindowClass = RegisterClass(&wce);
if (m_helperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
{
return -1;
}
/* Create the window. */
m_helperWindow = CreateWindowEx(0, m_helperWindowClassName,
m_helperWindowName,
WS_OVERLAPPED, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, HWND_MESSAGE, nullptr,
hInstance, nullptr);
if (m_helperWindow == nullptr)
{
UnregisterClass(m_helperWindowClassName, hInstance);
return -1;
}
return 0;
Q_UNUSED(event);
pollDeviceState();
}
void CJoystickWindows::updateAndSendButtonStatus(qint32 buttonIndex, bool isPressed)
{
BlackMisc::Input::CHotkeyCombination oldCombination(m_buttonCombination);
if (isPressed) { m_buttonCombination.addJoystickButton(buttonIndex); }
else { m_buttonCombination.removeJoystickButton(buttonIndex); }
if (oldCombination != m_buttonCombination)
{
emit buttonCombinationChanged(m_buttonCombination);
}
}
void CJoystickWindows::addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance)
{
CJoystickDeviceData deviceData;
deviceData.deviceName = QString::fromWCharArray(pdidInstance->tszInstanceName);
deviceData.productName = QString::fromWCharArray(pdidInstance->tszProductName);
deviceData.guidDevice = pdidInstance->guidInstance;
deviceData.guidProduct = pdidInstance->guidProduct;
if (!m_availableJoystickDevices.contains(deviceData)) m_availableJoystickDevices.push_back(deviceData);
}
void CJoystickWindows::addJoystickDeviceInput(const DIDEVICEOBJECTINSTANCE *dev)
{
CJoystickDeviceInput deviceInput;
deviceInput.m_number = m_joystickDeviceInputs.size();
deviceInput.m_offset = DIJOFS_BUTTON(deviceInput.m_number);
deviceInput.m_name = QString::fromWCharArray(dev->tszName);
m_joystickDeviceInputs.append(deviceInput);
}
HRESULT CJoystickWindows::pollDeviceState()
HRESULT CJoystickDevice::pollDeviceState()
{
DIJOYSTATE2 state;
HRESULT hr = S_OK;
if (!m_directInputDevice)
{
CLogMessage(this).warning("No input device");
return S_FALSE;
}
if (FAILED(hr = m_directInputDevice->Poll()))
{
if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
@@ -319,12 +122,149 @@ namespace BlackInput
for (CJoystickDeviceInput input : m_joystickDeviceInputs)
{
const qint32 buttonIndex = input.m_offset - DIJOFS_BUTTON0;
updateAndSendButtonStatus(buttonIndex, state.rgbButtons[buttonIndex] & 0x80);
bool isPressed = state.rgbButtons[buttonIndex] & 0x80;
if (isPressed) { emit buttonChanged(m_deviceName, buttonIndex, true); }
else { emit buttonChanged(m_deviceName, buttonIndex, false); }
}
return hr;
}
BOOL CALLBACK CJoystickDevice::enumObjectsCallback(const DIDEVICEOBJECTINSTANCE *dev, LPVOID pvRef)
{
CJoystickDevice *joystickDevice = static_cast<CJoystickDevice *>(pvRef);
// Make sure we only got GUID_Button types
if (dev->guidType != GUID_Button) return DIENUM_CONTINUE;
CJoystickDeviceInput deviceInput;
deviceInput.m_number = joystickDevice->m_joystickDeviceInputs.size();
deviceInput.m_offset = DIJOFS_BUTTON(deviceInput.m_number);
deviceInput.m_name = QString::fromWCharArray(dev->tszName);
joystickDevice->m_joystickDeviceInputs.append(deviceInput);
CLogMessage(static_cast<CJoystickWindows *>(nullptr)).debug() << "Found joystick button" << QString::fromWCharArray(dev->tszName);
return DIENUM_CONTINUE;
}
CJoystickWindows::CJoystickWindows(QObject *parent) : IJoystick(parent)
{
this->createHelperWindow();
this->initDirectInput();
this->enumJoystickDevices();
}
CJoystickWindows::~CJoystickWindows()
{
m_joystickDevices.clear();
m_directInput.reset();
}
void ReleaseDirectInput(IDirectInput8 *obj)
{
if (obj) { obj->Release(); }
};
HRESULT CJoystickWindows::initDirectInput()
{
IDirectInput8 *directInput = nullptr;
HRESULT hr = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, reinterpret_cast<LPVOID *>(&directInput), nullptr);
m_directInput = DirectInput8Ptr(directInput, ReleaseDirectInput);
return hr;
}
HRESULT CJoystickWindows::enumJoystickDevices()
{
if (!m_directInput)
{
CLogMessage(this).warning("No direct input");
return E_FAIL;
}
HRESULT hr;
if (FAILED(hr = m_directInput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumJoysticksCallback, this, DIEDFL_ATTACHEDONLY)))
{
CLogMessage(this).error("Error reading joystick devices");
return hr;
}
if (m_joystickDevices.empty())
{
CLogMessage(this).info("No joystick device found");
}
return hr;
}
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;
wce.lpszClassName = (LPCWSTR) helperWindowClassName;
wce.hInstance = hInstance;
/* Register the class. */
helperWindowClass = RegisterClass(&wce);
if (helperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
{
return -1;
}
/* Create the window. */
helperWindow = CreateWindowEx(0, helperWindowClassName,
helperWindowName,
WS_OVERLAPPED, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, HWND_MESSAGE, nullptr,
hInstance, nullptr);
if (helperWindow == nullptr)
{
UnregisterClass(helperWindowClassName, hInstance);
return -1;
}
return 0;
}
void CJoystickWindows::addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance)
{
CJoystickDevice *device = new CJoystickDevice(m_directInput, pdidInstance, this);
bool success = device->init(helperWindow);
if (success)
{
connect(device, &CJoystickDevice::buttonChanged, this, &CJoystickWindows::joystickButtonChanged);
m_joystickDevices.push_back(device);
}
else
{
device->deleteLater();
}
}
void CJoystickWindows::joystickButtonChanged(const QString &name, int index, bool isPressed)
{
BlackMisc::Input::CHotkeyCombination oldCombination(m_buttonCombination);
if (isPressed) { m_buttonCombination.addJoystickButton({name, index}); }
else { m_buttonCombination.removeJoystickButton({name, index}); }
if (oldCombination != m_buttonCombination)
{
emit buttonCombinationChanged(m_buttonCombination);
}
}
BOOL CALLBACK CJoystickWindows::enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext)
{
CJoystickWindows *obj = static_cast<CJoystickWindows *>(pContext);
@@ -337,25 +277,12 @@ namespace BlackInput
return true;
}
BOOL CALLBACK CJoystickWindows::enumObjectsCallback(const DIDEVICEOBJECTINSTANCE *dev, LPVOID pvRef)
bool operator == (const CJoystickDevice &lhs, const CJoystickDevice &rhs)
{
CJoystickWindows *joystick = static_cast<CJoystickWindows *>(pvRef);
// Make sure we only got GUID_Button types
if (dev->guidType != GUID_Button) return DIENUM_CONTINUE;
joystick->addJoystickDeviceInput(dev);
CLogMessage(static_cast<CJoystickWindows *>(nullptr)).debug() << "Found joystick button" << QString::fromWCharArray(dev->tszName);
return DIENUM_CONTINUE;
}
bool operator == (CJoystickDeviceData const &lhs, CJoystickDeviceData const &rhs)
{
return lhs.guidDevice == rhs.guidDevice &&
lhs.guidProduct == rhs.guidProduct &&
lhs.deviceName == rhs.deviceName &&
lhs.productName == rhs.productName;
return lhs.m_guidDevice == rhs.m_guidDevice &&
lhs.m_guidProduct == rhs.m_guidProduct &&
lhs.m_deviceName == rhs.m_deviceName &&
lhs.m_productName == rhs.m_productName;
}
bool operator == (CJoystickDeviceInput const &lhs, CJoystickDeviceInput const &rhs)

View File

@@ -17,6 +17,7 @@
#include "blackmisc/input/joystickbutton.h"
#include "blackmisc/collection.h"
#include <QVector>
#include <memory>
#ifndef NOMINMAX
#define NOMINMAX
@@ -26,14 +27,8 @@
namespace BlackInput
{
//! Joystick device data
struct CJoystickDeviceData
{
GUID guidDevice; //!< Device GUID
GUID guidProduct; //!< Product GUID
QString deviceName; //!< Device name
QString productName; //!< Product name
};
//! Shared IDirectInput8 ptr
using DirectInput8Ptr = std::shared_ptr<IDirectInput8>;
//! Joystick device input/button
struct CJoystickDeviceInput
@@ -43,8 +38,60 @@ namespace BlackInput
QString m_name; //!< Input name
};
//! Joystick device
class CJoystickDevice : public QObject
{
Q_OBJECT
public:
//! Constructor
CJoystickDevice(DirectInput8Ptr directInputPtr, const DIDEVICEINSTANCE *pdidInstance, QObject *parent = nullptr);
//! Initialize DirectInput device
bool init(HWND helperWindow);
signals:
//! Joystick button changed
void buttonChanged(const QString &name, int index, bool isPressed);
protected:
//! Timer based updates
virtual void timerEvent(QTimerEvent *event) override;
private:
friend bool operator == (const CJoystickDevice &lhs, const CJoystickDevice &rhs);
struct DirectInputDevice8Deleter
{
void operator()(IDirectInputDevice8 *obj)
{
if (obj)
{
obj->Unacquire();
obj->Release();
}
}
};
using DirectInputDevice8Ptr = std::unique_ptr<IDirectInputDevice8, DirectInputDevice8Deleter>;
//! Poll the device buttons
HRESULT pollDeviceState();
//! Joystick button enumeration callback
static BOOL CALLBACK enumObjectsCallback(const DIDEVICEOBJECTINSTANCE *dev, LPVOID pvRef);
GUID m_guidDevice; //!< Device GUID
GUID m_guidProduct; //!< Product GUID
QString m_deviceName; //!< Device name
QString m_productName; //!< Product name
DirectInput8Ptr m_directInput;
DirectInputDevice8Ptr m_directInputDevice;
QVector<CJoystickDeviceInput> m_joystickDeviceInputs;
};
//! Equal operator
bool operator == (CJoystickDeviceData const &lhs, CJoystickDeviceData const &rhs);
bool operator == (CJoystickDevice const &lhs, CJoystickDevice const &rhs);
//! Windows implemenation of IJoystick with DirectInput
class BLACKINPUT_EXPORT CJoystickWindows : public IJoystick
@@ -61,10 +108,6 @@ namespace BlackInput
//! \brief Destructor
virtual ~CJoystickWindows() override;
protected:
//! Timer based updates
virtual void timerEvent(QTimerEvent *event) override;
private:
friend class IJoystick;
@@ -77,45 +120,26 @@ namespace BlackInput
//! Enumerate all attached joystick devices
HRESULT enumJoystickDevices();
void filterJoystickDevices();
//! Create a joystick device
HRESULT createJoystickDevice();
//! Poll the device buttons
HRESULT pollDeviceState();
//! Creates a hidden DI helper window
int createHelperWindow();
//! Update and signal button status to InputManager
void updateAndSendButtonStatus(qint32 buttonIndex, bool isPressed);
//! Add new joystick device
void addJoystickDevice(const DIDEVICEINSTANCE *pdidInstance);
//! Add new joystick input/button
void addJoystickDeviceInput(const DIDEVICEOBJECTINSTANCE *dev);
void joystickButtonChanged(const QString &name, int index, bool isPressed);
//! Joystick enumeration callback
static BOOL CALLBACK enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext);
//! Joystick button enumeration callback
static BOOL CALLBACK enumObjectsCallback(const DIDEVICEOBJECTINSTANCE *dev, LPVOID pvRef);
// todo RW: Try to use QScopedPointer. So far I could not find out how to use it with IDirectInput8::CreateDevice
// remark KB: if created with CoCreateInstance we do not "own" the object and cannot free the memory, and must use release
IDirectInput8 *m_directInput = nullptr; //!< DirectInput object
IDirectInputDevice8 *m_directInputDevice = nullptr; //!< DirectInput device
QVector<CJoystickDeviceData> m_availableJoystickDevices; //!< List of found and available joystick devices
QVector<CJoystickDeviceInput> m_joystickDeviceInputs; //!< List of available device buttons
DirectInput8Ptr m_directInput; //!< DirectInput object
QVector<CJoystickDevice *> m_joystickDevices; //!< Joystick devices
BlackMisc::Input::CHotkeyCombination m_buttonCombination;
static const WCHAR *m_helperWindowClassName; //!< Helper window class name
static const WCHAR *m_helperWindowName; //!< Helper window name
static ATOM m_helperWindowClass;
static HWND m_helperWindow; //!< Helper window handle
const TCHAR *helperWindowClassName = TEXT("HelperWindow");
const TCHAR *helperWindowName = TEXT("JoystickCatcherWindow");
ATOM helperWindowClass = 0;
HWND helperWindow = nullptr;
};
} // ns