diff --git a/src/blackinput/linux/joysticklinux.cpp b/src/blackinput/linux/joysticklinux.cpp index cdfc15363..072bba47a 100644 --- a/src/blackinput/linux/joysticklinux.cpp +++ b/src/blackinput/linux/joysticklinux.cpp @@ -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(&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(&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(&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(&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(&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(object); - Q_ASSERT(joystick); - - - struct js_event event; - while (joystick->read(reinterpret_cast(&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 diff --git a/src/blackinput/linux/joysticklinux.h b/src/blackinput/linux/joysticklinux.h index 1b8effad4..02a72c401 100644 --- a/src/blackinput/linux/joysticklinux.h +++ b/src/blackinput/linux/joysticklinux.h @@ -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 m_joysticks; //!< All read joysticks, file path <-> file instance pairs + QVector m_joystickDevices; //!< All joystick devices QFileSystemWatcher *m_inputWatcher = nullptr; }; diff --git a/src/blackinput/win/joystickwindows.cpp b/src/blackinput/win/joystickwindows.cpp index e475219bd..1f0223ba4 100644 --- a/src/blackinput/win/joystickwindows.cpp +++ b/src/blackinput/win/joystickwindows.cpp @@ -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(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(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(&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(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(pvRef); - - // Make sure we only got GUID_Button types - if (dev->guidType != GUID_Button) return DIENUM_CONTINUE; - - joystick->addJoystickDeviceInput(dev); - CLogMessage(static_cast(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) diff --git a/src/blackinput/win/joystickwindows.h b/src/blackinput/win/joystickwindows.h index ac6e469b7..36253fb17 100644 --- a/src/blackinput/win/joystickwindows.h +++ b/src/blackinput/win/joystickwindows.h @@ -17,6 +17,7 @@ #include "blackmisc/input/joystickbutton.h" #include "blackmisc/collection.h" #include +#include #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; //! 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; + + //! 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 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 m_availableJoystickDevices; //!< List of found and available joystick devices - QVector m_joystickDeviceInputs; //!< List of available device buttons + DirectInput8Ptr m_directInput; //!< DirectInput object + QVector 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