mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-02 15:15:50 +08:00
347 lines
12 KiB
C++
347 lines
12 KiB
C++
/* 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 "joystickwindows.h"
|
|
#include "blackmisc/logmessage.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
|
|
// using ascii functions of WINAPI. To fix it, introduce #ifdef UNICODE and add char
|
|
// handling in the second branch.
|
|
|
|
using namespace BlackMisc;
|
|
using namespace BlackMisc::Input;
|
|
|
|
namespace BlackInput
|
|
{
|
|
CJoystickDevice::CJoystickDevice(DirectInput8Ptr directInputPtr, const DIDEVICEINSTANCE *pdidInstance, QObject *parent)
|
|
: QObject(parent),
|
|
m_directInput(directInputPtr)
|
|
{
|
|
m_deviceName = QString::fromWCharArray(pdidInstance->tszInstanceName);
|
|
m_productName = QString::fromWCharArray(pdidInstance->tszProductName);
|
|
m_guidDevice = pdidInstance->guidInstance;
|
|
m_guidProduct = pdidInstance->guidProduct;
|
|
}
|
|
|
|
bool CJoystickDevice::init(HWND helperWindow)
|
|
{
|
|
HRESULT hr;
|
|
// Create device
|
|
{
|
|
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);
|
|
}
|
|
|
|
// Set cooperative level
|
|
if (!helperWindow) { return false; }
|
|
if (FAILED(hr = m_directInputDevice->SetCooperativeLevel(helperWindow, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND)))
|
|
{
|
|
CLogMessage(this).warning("IDirectInputDevice8::SetCooperativeLevel failed: ") << 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 false;
|
|
}
|
|
|
|
DIDEVCAPS deviceCaps;
|
|
deviceCaps.dwSize = sizeof(DIDEVCAPS);
|
|
// Get device capabilities - we are interested in the number of buttons.
|
|
if (FAILED(hr = m_directInputDevice->GetCapabilities(&deviceCaps)))
|
|
{
|
|
CLogMessage(this).warning("IDirectInputDevice8::GetCapabilities failed: ") << hr;
|
|
return false;
|
|
}
|
|
|
|
// 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 false;
|
|
}
|
|
|
|
CLogMessage(this).info("Created joystick device '%1' with %2 buttons") << m_deviceName << deviceCaps.dwButtons;
|
|
this->startTimer(50);
|
|
return true;
|
|
}
|
|
|
|
void CJoystickDevice::timerEvent(QTimerEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
pollDeviceState();
|
|
}
|
|
|
|
HRESULT CJoystickDevice::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()))
|
|
{
|
|
CLogMessage(this).warning("DirectInput error code: ") << 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)))
|
|
{
|
|
CLogMessage(this).warning("DirectInput error code: ") << hr;
|
|
return hr;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (CJoystickDeviceInput input : m_joystickDeviceInputs)
|
|
{
|
|
const qint32 buttonIndex = input.m_offset - DIJOFS_BUTTON0;
|
|
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)
|
|
{
|
|
// Initialize COM.
|
|
// https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-coinitializeex
|
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
|
|
// RPC_E_CHANGED_MODE: CoInitializeEx was already called by someone else in this thread with a different mode.
|
|
if (hr == RPC_E_CHANGED_MODE)
|
|
{
|
|
CLogMessage(this).debug("CoInitializeEx was already called with a different mode. Trying again.");
|
|
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
|
|
}
|
|
|
|
// Continue here only if CoInitializeEx was successful
|
|
// S_OK: The COM library was initialized successfully on this thread.
|
|
// S_FALSE: The COM library is already initialized on this thread. Reference count was incremented. This is not an error.
|
|
if (hr == S_OK || hr == S_FALSE)
|
|
{
|
|
m_coInitializeSucceeded = true;
|
|
this->createHelperWindow();
|
|
|
|
if (helperWindow)
|
|
{
|
|
this->initDirectInput();
|
|
this->enumJoystickDevices();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CLogMessage(this).warning("CoInitializeEx returned error code %1");
|
|
}
|
|
|
|
}
|
|
|
|
CJoystickWindows::~CJoystickWindows()
|
|
{
|
|
// All DirectInput devices need to be cleaned up before the call to CoUninitialize()
|
|
for (CJoystickDevice *joystickDevice : m_joystickDevices)
|
|
{
|
|
delete joystickDevice;
|
|
}
|
|
m_joystickDevices.clear();
|
|
m_directInput.reset();
|
|
if (m_coInitializeSucceeded) { CoUninitialize(); }
|
|
destroyHelperWindow();
|
|
}
|
|
|
|
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);
|
|
HRESULT hr = CoCreateInstance(CLSID_DirectInput8, nullptr, CLSCTX_INPROC_SERVER, IID_IDirectInput8, reinterpret_cast<LPVOID *>(&directInput));
|
|
if (FAILED(hr)) { return hr; }
|
|
m_directInput = DirectInput8Ptr(directInput, ReleaseDirectInput);
|
|
|
|
HINSTANCE instance = GetModuleHandle(nullptr);
|
|
hr = m_directInput->Initialize(instance, DIRECTINPUT_VERSION);
|
|
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::destroyHelperWindow()
|
|
{
|
|
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
|
|
|
if (helperWindow == nullptr) { return; }
|
|
|
|
DestroyWindow(helperWindow);
|
|
helperWindow = nullptr;
|
|
|
|
UnregisterClass(helperWindowClassName, hInstance);
|
|
helperWindowClass = 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
|
|
{
|
|
delete device;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
bool operator == (const CJoystickDevice &lhs, const CJoystickDevice &rhs)
|
|
{
|
|
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)
|
|
{
|
|
return lhs.m_number == rhs.m_number &&
|
|
lhs.m_offset == rhs.m_offset &&
|
|
lhs.m_name == rhs.m_name;
|
|
}
|
|
} // ns
|