diff --git a/src/blackinput/win/joystick_windows.cpp b/src/blackinput/win/joystick_windows.cpp new file mode 100644 index 000000000..6998d04c7 --- /dev/null +++ b/src/blackinput/win/joystick_windows.cpp @@ -0,0 +1,328 @@ +/* Copyright (C) 2014 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "joystick_windows.h" +#include + +// 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 +// using ascii functions of WINAPI. To fix it, introduce #ifdef UNICODE and add char +// handling in the second branch. + +using namespace BlackMisc::Hardware; + +namespace BlackInput +{ + + TCHAR *CJoystickWindows::m_helperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); + TCHAR *CJoystickWindows::m_helperWindowName = TEXT("SDLHelperWindowInputMsgWindow"); + ATOM CJoystickWindows::m_helperWindowClass = 0; + HWND CJoystickWindows::m_helperWindow = nullptr; + + CJoystickWindows::CJoystickWindows(QObject *parent) : + IJoystick(parent) + { + // Initialize COM + CoInitializeEx(nullptr, COINIT_MULTITHREADED); + + initDirectInput(); + enumJoystickDevices(); + if (!m_availableJoystickDevices.isEmpty()) createJoystickDevice(); + } + + CJoystickWindows::~CJoystickWindows() + { + CoUninitialize(); + } + + void CJoystickWindows::startCapture() + { + // TODO + } + + void CJoystickWindows::triggerButton(const CJoystickButton button, bool isPressed) + { + if(!isPressed) emit buttonUp(button); + else emit buttonDown(button); + } + + void CJoystickWindows::timerEvent(QTimerEvent * /* event */) + { + pollDeviceState(); + } + + HRESULT CJoystickWindows::initDirectInput() + { + HRESULT hr; + if (FAILED(hr = CoCreateInstance(CLSID_DirectInput8, nullptr, CLSCTX_INPROC_SERVER, + IID_IDirectInput8, (LPVOID *)&m_directInput))) + { + // TODO Print an error + return hr; + } + + HINSTANCE instance = GetModuleHandle(nullptr); + if (instance == nullptr) + { + qWarning() << "GetModuleHandle() failed with error code" << GetLastError(); + return E_FAIL; + } + + if (FAILED(hr = m_directInput->Initialize(instance, DIRECTINPUT_VERSION))) + { + // TODO Print an error + return hr; + } + + return hr; + } + + HRESULT CJoystickWindows::enumJoystickDevices() + { + HRESULT hr; + if (FAILED(hr = m_directInput->EnumDevices(DI8DEVTYPE_JOYSTICK, enumJoysticksCallback, this, DIEDFL_ATTACHEDONLY))) + { + // TODO print error message + return hr; + } + + if (m_availableJoystickDevices.isEmpty()) qWarning() << "No joystick device found!"; + return hr; + } + + HRESULT CJoystickWindows::createJoystickDevice() + { + HRESULT hr = S_OK; + + DIPROPDWORD dipdw; + + // Check if device list is empty first + if (m_availableJoystickDevices.isEmpty()) return E_FAIL; + + ZeroMemory(&dipdw, sizeof(DIPROPDWORD)); + dipdw.diph.dwSize = sizeof(DIPROPDWORD); + dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); + + // FIXME: Take the first device for the time being + // For the future, the user should be able to choose which device + // he wants to use. + CJoystickDeviceData deviceData = m_availableJoystickDevices.first(); + + // Create device + if (FAILED(hr = m_directInput->CreateDevice(deviceData.guidDevice, &m_directInputDevice, nullptr))) + { + // FIXME: print error message + return hr; + } + + createHelperWindow(); + + // Set cooperative level + if (FAILED(hr = m_directInputDevice->SetCooperativeLevel(m_helperWindow, DISCL_NONEXCLUSIVE | + DISCL_BACKGROUND))) + { + // FIXME: print error message + return hr; + } + + // Set data format to c_dfDIJoystick2 + if (FAILED(hr = m_directInputDevice->SetDataFormat(&c_dfDIJoystick2))) + { + // FIXME: print error message + return hr; + } + + DIDEVCAPS deviceCaps; + deviceCaps.dwSize = sizeof(DIDEVCAPS); + // Get device capabilities - we are interested in the number of buttons. + if (FAILED(hr = m_directInputDevice->GetCapabilities(&deviceCaps))) + { + // FIXME: print error message + return hr; + } + + m_joystickDeviceInputs.clear(); + if (FAILED(hr = m_directInputDevice->EnumObjects(enumObjectsCallback, this, DIDFT_BUTTON))) + { + // FIXME: print error message + return hr; + } + + qDebug() << "No. of buttons:" << deviceCaps.dwButtons; + + startTimer(50); + return hr; + } + + int CJoystickWindows::createHelperWindow() + { + 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; + + } + + void CJoystickWindows::updateAndSendButtonStatus(qint32 buttonIndex, bool isPressed) + { + if (isPressed) + { + if (!m_pressedButtons.contains(buttonIndex)) + { + CJoystickButton joystickButton(buttonIndex); + emit buttonDown(joystickButton); + m_pressedButtons.push_back(buttonIndex); + } + } + else + { + if (m_pressedButtons.contains(buttonIndex)) + { + CJoystickButton joystickButton(buttonIndex); + emit buttonUp(joystickButton); + m_pressedButtons.remove(buttonIndex); + } + } + } + + 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() + { + DIJOYSTATE2 state; + HRESULT hr = S_OK; + + if (FAILED(hr = m_directInputDevice->Poll())) + { + if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) + { + m_directInputDevice->Acquire(); + if (FAILED(hr = m_directInputDevice->Poll())) + { + // FIXME: print error message + 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))) + { + // FIXME: print error message + return hr; + } + } + } + + for (CJoystickDeviceInput input : m_joystickDeviceInputs) + { + qint32 buttonIndex = input.m_offset - DIJOFS_BUTTON0; + updateAndSendButtonStatus(buttonIndex, state.rgbButtons[buttonIndex] & 0x80); + } + + return hr; + } + + BOOL CALLBACK CJoystickWindows::enumJoysticksCallback(const DIDEVICEINSTANCE *pdidInstance, VOID *pContext) + { + CJoystickWindows *obj = static_cast(pContext); + + /* ignore XInput devices here, keep going. */ + //if (isXInputDevice( &pdidInstance->guidProduct )) return DIENUM_CONTINUE; + + obj->addJoystickDevice(pdidInstance); + qDebug() << "Found joystick device" << QString::fromWCharArray(pdidInstance->tszInstanceName); + + return true; + } + + BOOL CALLBACK CJoystickWindows::enumObjectsCallback(const DIDEVICEOBJECTINSTANCE *dev, LPVOID pvRef) + { + CJoystickWindows *joystick = static_cast(pvRef); + + // Make sure we only got GUID_Button types + if (dev->guidType != GUID_Button) return DIENUM_CONTINUE; + + joystick->addJoystickDeviceInput(dev); + qDebug() << "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; + } + + bool operator == (CJoystickDeviceInput const &lhs, CJoystickDeviceInput const &rhs) + { + return lhs.m_number == rhs.m_number && + lhs.m_offset == rhs.m_offset && + lhs.m_name == rhs.m_name; + } + +} // namespace BlackInput diff --git a/src/blackinput/win/joystick_windows.h b/src/blackinput/win/joystick_windows.h new file mode 100644 index 000000000..653b9a784 --- /dev/null +++ b/src/blackinput/win/joystick_windows.h @@ -0,0 +1,131 @@ +/* Copyright (C) 2014 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#ifndef BLACKINPUT_JOYSTICKWINDOWS_H +#define BLACKINPUT_JOYSTICKWINDOWS_H + +//! \file + +#include "blackinput/joystick.h" +#include "blackmisc/hwjoystickbutton.h" +#include "blackmisc/collection.h" +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include + +namespace BlackInput +{ + //! Joystick device data + struct CJoystickDeviceData + { + GUID guidDevice; //!< Device GUID + GUID guidProduct; //!< Product GUID + QString deviceName; //!< Device name + QString productName; //!< Product name + }; + + //! Joystick device input/button + struct CJoystickDeviceInput + { + int m_number; //!< Input number + int m_offset; //!< Input offset + QString m_name; //!< Input name + }; + + //! Equal operator + bool operator == (CJoystickDeviceData const &lhs, CJoystickDeviceData const &rhs); + + //! Windows implemenation of IJoystick with DirectInput + class CJoystickWindows : public IJoystick + { + Q_OBJECT + + public: + + //! \brief Destructor + virtual ~CJoystickWindows(); + + //! \copydoc IJoystick::startCapture() + virtual void startCapture() override; + + //! \copydoc IJoystick::triggerButton() + virtual void triggerButton(const BlackMisc::Hardware::CJoystickButton button, bool isPressed) override; + + protected: + + friend class IJoystick; + + //! Destructor + CJoystickWindows(QObject *parent = nullptr); + + //! Copy Constructor + CJoystickWindows(CJoystickWindows const &); + + //! Assignment operator + void operator=(CJoystickWindows const &); + + //! Timer based updates + virtual void timerEvent(QTimerEvent *event); + + private: + + //! Initialize DirectInput + HRESULT initDirectInput(); + + //! Enumerate all attached joystick devices + HRESULT enumJoystickDevices(); + + //! 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); + + //! 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); + + IDirectInput8 *m_directInput = nullptr; //!< DirectInput object + IDirectInputDevice8 *m_directInputDevice = nullptr; //!< DirectInput device + QList m_availableJoystickDevices; //!< List of found and available joystick devices + + QList m_joystickDeviceInputs; //!< List of available device buttons + BlackMisc::CCollection m_pressedButtons; //!< Collection of pressed buttons + + IJoystick::Mode m_mode = ModeNominal; //!< Current working mode + + static WCHAR *m_helperWindowClassName; //!< Helper window class name + static WCHAR *m_helperWindowName; //!< Helper window name + static ATOM m_helperWindowClass; + static HWND m_helperWindow; //!< Helper window handle + + }; + +} // namespace BlackInput + +#endif // BLACKINPUT_JOYSTICK_WINDOWS_H