diff options
Diffstat (limited to 'dinput')
-rw-r--r-- | dinput/dinput.cpp | 124 | ||||
-rw-r--r-- | dinput/dinput.hpp | 66 | ||||
-rw-r--r-- | dinput/keybinding-worker.cpp | 320 | ||||
-rw-r--r-- | dinput/keybinding-worker.hpp | 35 | ||||
-rw-r--r-- | dinput/lang/zh_CN.ts | 2 | ||||
-rw-r--r-- | dinput/win32-joystick.cpp | 215 | ||||
-rw-r--r-- | dinput/win32-joystick.hpp | 74 |
7 files changed, 452 insertions, 384 deletions
diff --git a/dinput/dinput.cpp b/dinput/dinput.cpp index 226d3277..b9713b8a 100644 --- a/dinput/dinput.cpp +++ b/dinput/dinput.cpp @@ -1,88 +1,98 @@ +#undef NDEBUG + #include "dinput.hpp" -#include <QDebug> +#include "compat/macros.h" -std::atomic<int> dinput_handle::refcnt; -std::atomic_flag dinput_handle::init_lock = ATOMIC_FLAG_INIT; +#include <cassert> +#include <cstdlib> +#include <dinput.h> -LPDIRECTINPUT8& dinput_handle::init_di() -{ - CoInitialize(nullptr); +#include <QDebug> - static LPDIRECTINPUT8 di_ = nullptr; - if (di_ == nullptr) - { - if (!SUCCEEDED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&di_, NULL))) - { - di_ = nullptr; - } - } - return di_; -} +diptr di_t::handle; +QMutex di_t::lock; -dinput_handle::di_t dinput_handle::make_di() +diptr di_t::init_di() { - while (init_lock.test_and_set()) { /* busy loop */ } + QMutexLocker l(&lock); - LPDIRECTINPUT8& ret = init_di(); + CoInitialize(nullptr); - init_lock.clear(); + if (!handle) + handle = init_di_(); - return di_t(ret); + return handle; } -void dinput_handle::di_t::free_di() +diptr di_t::operator->() const { - if (handle && *handle) - { - (*handle)->Release(); - *handle = nullptr; - } - handle = nullptr; + return init_di(); } -void dinput_handle::di_t::ref_di() +di_t::operator bool() const { - //const int refcnt_ = refcnt.fetch_add(1) + 1; - (void) refcnt.fetch_add(1); + return !!init_di(); } -dinput_handle::di_t& dinput_handle::di_t::operator=(const di_t& new_di) +di_t::operator diptr() const { - if (handle) - unref_di(); + return init_di(); +} - handle = new_di.handle; +diptr di_t::init_di_() +{ + diptr di = nullptr; + HRESULT hr = DirectInput8Create(GetModuleHandle(nullptr), + DIRECTINPUT_VERSION, + IID_IDirectInput8, + (void**)&di, + nullptr); + if (!SUCCEEDED(hr)) + { + qDebug() << "can't make dinput:" << (void*)(LONG_PTR)hr; + qDebug() << "crashing!"; + std::abort(); + } - if (handle) - ref_di(); + //qDebug() << "dinput: initialized"; - return *this; + return di; } -void dinput_handle::di_t::unref_di() +di_t::di_t() = default; + +bool di_t::poll_device(IDirectInputDevice8A* dev) { - const int refcnt_ = refcnt.fetch_sub(1) - 1; + HRESULT hr; + assert(handle); - if (refcnt_ == 0) + switch (dev->Poll()) { - while (init_lock.test_and_set()) { /* busy loop */ } - - qDebug() << "exit: di handle"; - free_di(); - - init_lock.clear(); + case DI_OK: + case DI_NOEFFECT: + return true; + default: + break; } -} -dinput_handle::di_t::di_t(LPDIRECTINPUT8& handle) : handle(&handle) -{ - ref_di(); -} + switch (hr = dev->Acquire()) + { + default: + break; + case DI_OK: + case S_FALSE: + switch (hr = dev->Poll()) + { + case DI_OK: + case DI_NOEFFECT: + return true; + default: + break; + } + break; + } -dinput_handle::di_t::di_t() : handle(nullptr) {} + eval_once(qDebug() << "dinput: device poll failed:" << (void*)hr); -dinput_handle::di_t::~di_t() -{ - if (handle) - unref_di(); + return false; } diff --git a/dinput/dinput.hpp b/dinput/dinput.hpp index fc73a90a..09c9a30b 100644 --- a/dinput/dinput.hpp +++ b/dinput/dinput.hpp @@ -8,44 +8,48 @@ #pragma once +#include <QMutex> + #include "export.hpp" #undef DIRECTINPUT_VERSION #define DIRECTINPUT_VERSION 0x800 -#include <dinput.h> -#include <atomic> -class OTR_DINPUT_EXPORT dinput_handle final +struct IDirectInputDevice8A; +typedef struct IDirectInputDevice8A IDirectInputDevice8A; +struct IDirectInput8A; +typedef struct IDirectInput8A IDirectInput8A; +struct _GUID; +typedef struct _GUID GUID; +struct _DIDATAFORMAT; +typedef struct _DIDATAFORMAT DIDATAFORMAT; +typedef int BOOL; +struct DIDEVICEINSTANCEA; +typedef struct DIDEVICEINSTANCEA DIDEVICEINSTANCEA; +struct DIDEVICEOBJECTINSTANCEA; +typedef struct DIDEVICEOBJECTINSTANCEA DIDEVICEOBJECTINSTANCEA; + +// XXX TODO -sh 20190209 +// keybinding_worker and joystick context are badly named +// add namespaces and rename, including inner joystick device struct + +using diptr = IDirectInput8A*; + +class OTR_DINPUT_EXPORT di_t final { + static diptr handle; + static QMutex lock; + static diptr init_di_(); + static diptr init_di(); + public: - class di_t; + di_t(); + di_t(const di_t&) : di_t() {} + di_t& operator=(const di_t&) = default; -private: - static std::atomic<int> refcnt; - static std::atomic_flag init_lock; + diptr operator->() const; + operator bool() const; + operator diptr() const; - static LPDIRECTINPUT8& init_di(); -public: - class di_t final - { - friend class dinput_handle; - - LPDIRECTINPUT8* handle; - - di_t(LPDIRECTINPUT8& handle); - void free_di(); - void unref_di(); - void ref_di(); - - public: - LPDIRECTINPUT8 operator->() { return *handle; } - operator LPDIRECTINPUT8() { return *handle; } - LPDIRECTINPUT8 di() { return *handle; } - di_t& operator=(const di_t& new_di); - di_t(); - ~di_t(); - }; - - static di_t make_di(); - dinput_handle() = delete; + static bool poll_device(IDirectInputDevice8A* dev); }; diff --git a/dinput/keybinding-worker.cpp b/dinput/keybinding-worker.cpp index 30d21534..7cd8d663 100644 --- a/dinput/keybinding-worker.cpp +++ b/dinput/keybinding-worker.cpp @@ -9,21 +9,29 @@ #ifdef _WIN32 #include "keybinding-worker.hpp" -#include "compat/meta.hpp" +#include "compat/macros.h" +#include "compat/thread-name.hpp" #include <QDebug> #include <QMutexLocker> -#include <windows.h> +#include <dinput.h> -Key::Key() {} +static void destroy(IDirectInputDevice8A*& dev) +{ + if (dev) + dev->Release(); + dev = nullptr; +} + +Key::Key() = default; bool Key::should_process() { if (!enabled || (keycode == 0 && guid == "")) return false; - bool ret = prog1(!held || timer.elapsed_ms() > 100, - timer.start()); + bool ret = !held || timer.elapsed_ms() > 100; + timer.start(); return ret; } @@ -33,68 +41,82 @@ KeybindingWorker::~KeybindingWorker() requestInterruption(); wait(); - if (dinkeyboard) { - dinkeyboard->Unacquire(); - dinkeyboard->Release(); - } + + destroy(dinkeyboard); + destroy(dinmouse); } -bool KeybindingWorker::init() +bool KeybindingWorker::init_(IDirectInputDevice8A*& dev, const char* name, const GUID& guid, const DIDATAFORMAT& fmt) { + if (dev) + return true; + if (!din) { qDebug() << "can't create dinput handle"; - return false; + goto fail; } - if (din->CreateDevice(GUID_SysKeyboard, &dinkeyboard, NULL) != DI_OK) { - qDebug() << "setup CreateDevice function failed!" << GetLastError(); - return false; + if (auto hr = din->CreateDevice(guid, &dev, nullptr); hr != DI_OK) + { + qDebug() << "dinput: create" << name << "failed" << (void*)hr; + goto fail; } - if (dinkeyboard->SetDataFormat(&c_dfDIKeyboard) != DI_OK) { - qDebug() << "setup SetDataFormat function failed!" << GetLastError(); - dinkeyboard->Release(); - dinkeyboard = 0; - return false; + if (auto hr = dev->SetDataFormat(&fmt); hr != DI_OK) + { + qDebug() << "dinput:" << name << "SetDataFormat" << (void*)hr; + goto fail; } - if (dinkeyboard->SetCooperativeLevel((HWND) fake_main_window.winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND) != DI_OK) { - dinkeyboard->Release(); - dinkeyboard = 0; - qDebug() << "setup SetCooperativeLevel function failed!" << GetLastError(); - return false; + if (auto hr = dev->SetCooperativeLevel((HWND) fake_main_window.winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND); + hr != DI_OK) + { + qDebug() << "dinput:" << name << "SetCooperativeLevel" << (void*)hr; + goto fail; } + return true; +fail: + destroy(dev); + return false; +} + +bool KeybindingWorker::init() +{ + bool ret = init_(dinkeyboard, "keyboard", GUID_SysKeyboard, c_dfDIKeyboard) && + init_(dinmouse, "mouse", GUID_SysMouse, c_dfDIMouse2); + + if (!ret) + goto fail; + { DIPROPDWORD dipdw; - dipdw.dwData = 128; + dipdw.dwData = num_keyboard_states; dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); dipdw.diph.dwHow = DIPH_DEVICE; dipdw.diph.dwObj = 0; dipdw.diph.dwSize = sizeof(dipdw); - if ( dinkeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK) + + if (auto hr = dinkeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph); hr != DI_OK) { - qDebug() << "setup keyboard buffer mode failed!"; - dinkeyboard->Release(); - dinkeyboard = 0; - return false; + qDebug() << "dinput: keyboard DIPROP_BUFFERSIZE" << (void*)hr; + goto fail; } } - if (dinkeyboard->Acquire() != DI_OK) - { - dinkeyboard->Release(); - dinkeyboard = 0; - qDebug() << "setup dinkeyboard Acquire failed!" << GetLastError(); - return false; - } - return true; + +fail: + destroy(dinkeyboard); + destroy(dinmouse); + return false; } -KeybindingWorker::KeybindingWorker() : dinkeyboard(nullptr), din(dinput_handle::make_di()) +KeybindingWorker::KeybindingWorker() { + fake_main_window.setAttribute(Qt::WA_NativeWindow); + if (init()) start(QThread::HighPriority); } @@ -107,102 +129,156 @@ KeybindingWorker& KeybindingWorker::make() void KeybindingWorker::run() { + portable::set_curthread_name("keybinding worker"); + while (!isInterruptionRequested()) { { QMutexLocker l(&mtx); - if (receivers.size()) + if (!receivers.empty()) { - /* There are some problems reported on various forums - * with regard to key-up events. But that's what I dug up: - * - * https://www.gamedev.net/forums/topic/633011-keyboard-getdevicedata-buffered-never-releases-keys/ - * - * "Over in the xna forums (http://xboxforums.create.msdn.com/forums/p/108722/642144.aspx#642144) - * we discovered this behavior is caused by calling Unacquire in your event processing loop. - * Funnily enough only the keyboard seems to be affected." - * - * Key-up events work on my end. - */ - - { - DWORD sz = num_keyboard_states; - const HRESULT hr = dinkeyboard->GetDeviceData(sizeof(*keyboard_states), keyboard_states, &sz, 0); - - if (hr != DI_OK) - { - qDebug() << "Tracker::run GetDeviceData function failed!" << hr; - Sleep(25); - continue; - } - else - { - for (unsigned k = 0; k < sz; k++) - { - const unsigned idx = keyboard_states[k].dwOfs & 0xff; // defensive programming - const bool held = !!(keyboard_states[k].dwData & 0x80); - - switch (idx) - { - case DIK_LCONTROL: - case DIK_LSHIFT: - case DIK_LALT: - case DIK_RCONTROL: - case DIK_RSHIFT: - case DIK_RALT: - case DIK_LWIN: - case DIK_RWIN: - break; - default: - { - Key k; - k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT]; - k.alt = keystate[DIK_LALT] | keystate[DIK_RALT]; - k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL]; - k.keycode = idx; - k.held = held; - - for (auto& r : receivers) - (*r)(k); - break; - } - } - keystate[idx] = held; - } - } - } - - { - using joy_fn = std::function<void(const QString& guid, int idx, bool held)>; - - joy_fn f = [&](const QString& guid, int idx, bool held) { - Key k; - k.keycode = idx; - k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT]; - k.alt = keystate[DIK_LALT] | keystate[DIK_RALT]; - k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL]; - k.guid = guid; - k.held = held; - - for (auto& r : receivers) - (*r)(k); - }; - - joy_ctx.poll(f); - } + bool ok = true; + + ok &= run_keyboard_nolock(); + ok &= run_mouse_nolock(); + ok &= run_joystick_nolock(); + + if (!ok) + Sleep(500); } } - Sleep(100); + Sleep(25); } } -KeybindingWorker::fun* KeybindingWorker::_add_receiver(fun& receiver) +bool KeybindingWorker::run_mouse_nolock() +{ + DIMOUSESTATE2 state; + + if (!di_t::poll_device(dinmouse)) + eval_once(qDebug() << "dinput: mouse poll failed"); + + if (auto hr = dinmouse->GetDeviceState(sizeof(state), &state); hr != DI_OK) + { + eval_once(qDebug() << "dinput: mouse GetDeviceState failed" << (void*) hr << GetLastError); + return false; + } + + Key k; + k.guid = QStringLiteral("mouse"); + + for (int i = first_mouse_button; i < num_mouse_buttons; i++) + { + const bool new_state = state.rgbButtons[i] & 0x80; + k.held = new_state; + k.keycode = i; + bool& old_state = mouse_state[i - first_mouse_button]; + if (old_state != new_state) + { + for (auto& r : receivers) + (*r)(k); + } + old_state = new_state; + } + return true; +} + +bool KeybindingWorker::run_keyboard_nolock() +{ + /* There are some problems reported on various forums + * with regard to key-up events. But that's what I dug up: + * + * https://www.gamedev.net/forums/topic/633011-keyboard-getdevicedata-buffered-never-releases-keys/ + * + * "Over in the xna forums (http://xboxforums.create.msdn.com/forums/p/108722/642144.aspx#642144) + * we discovered this behavior is caused by calling Unacquire in your event processing loop. + * Funnily enough only the keyboard seems to be affected." + * + * Key-up events work on my end. + */ + + if (!di_t::poll_device(dinkeyboard)) + eval_once(qDebug() << "dinput: keyboard poll failed"); + + DIDEVICEOBJECTDATA keyboard_states[num_keyboard_states]; + + DWORD sz = num_keyboard_states; + HRESULT hr = dinkeyboard->GetDeviceData(sizeof(*keyboard_states), keyboard_states, &sz, 0); + + if (FAILED(hr)) + { + eval_once(qDebug() << "dinput: keyboard GetDeviceData failed" << (void*)hr); + return false; + } + + for (unsigned k = 0; k < sz; k++) + { + const int idx = keyboard_states[k].dwOfs & 0xff; // defensive programming + const bool held = !!(keyboard_states[k].dwData & 0x80); + + if (held == keystate[idx]) + continue; + keystate[idx] = held; + + switch (idx) + { + case DIK_LCONTROL: + case DIK_LSHIFT: + case DIK_LALT: + case DIK_RCONTROL: + case DIK_RSHIFT: + case DIK_RALT: + case DIK_LWIN: + case DIK_RWIN: + break; + default: + { + Key k; + k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT]; + k.alt = keystate[DIK_LALT] | keystate[DIK_RALT]; + k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL]; + k.keycode = idx; + k.held = held; + + for (auto& r : receivers) + (*r)(k); + } + break; + } + } + + return true; +} + +bool KeybindingWorker::run_joystick_nolock() +{ + using joy_fn = std::function<void(const QString& guid, int idx, bool held)>; + + joy_fn f = [&](const QString& guid, int idx, bool held) { + Key k; + k.keycode = idx; + k.shift = keystate[DIK_LSHIFT] | keystate[DIK_RSHIFT]; + k.alt = keystate[DIK_LALT] | keystate[DIK_RALT]; + k.ctrl = keystate[DIK_LCONTROL] | keystate[DIK_RCONTROL]; + k.guid = guid; + k.held = held; + + for (auto& r : receivers) + (*r)(k); + }; + + joy_ctx.poll(f); + + return true; +} + +KeybindingWorker::fun* KeybindingWorker::add_receiver(fun& receiver) { QMutexLocker l(&mtx); receivers.push_back(std::make_unique<fun>(receiver)); - fun* f = receivers[receivers.size() - 1].get(); + fun* f = &*receivers[receivers.size() - 1]; //qDebug() << "add receiver" << (long) f; joy_ctx.refresh(); return f; @@ -218,7 +294,7 @@ void KeybindingWorker::remove_receiver(KeybindingWorker::fun* pos) for (int i = s(receivers.size()) - 1; i >= 0; i--) { using u = unsigned; - if (receivers[u(i)].get() == pos) + if (&*receivers[u(i)] == pos) { ok = true; //qDebug() << "remove receiver" << (long) pos; @@ -228,7 +304,7 @@ void KeybindingWorker::remove_receiver(KeybindingWorker::fun* pos) } if (!ok) { - qDebug() << "bad remove receiver" << (long) pos; + qDebug() << "bad remove receiver" << (void*) pos; } } diff --git a/dinput/keybinding-worker.hpp b/dinput/keybinding-worker.hpp index 407b0107..1c22ca17 100644 --- a/dinput/keybinding-worker.hpp +++ b/dinput/keybinding-worker.hpp @@ -24,13 +24,13 @@ struct OTR_DINPUT_EXPORT Key { QString guid; + Timer timer; int keycode = 0; bool shift = false; bool ctrl = false; bool alt = false; bool held = true; bool enabled = true; - Timer timer; public: Key(); @@ -41,45 +41,52 @@ struct OTR_DINPUT_EXPORT KeybindingWorker : private QThread { using fun = std::function<void(const Key&)>; + KeybindingWorker(const KeybindingWorker&) = delete; + KeybindingWorker& operator=(KeybindingWorker&) = delete; + private: - LPDIRECTINPUTDEVICE8 dinkeyboard; + static constexpr int num_keyboard_states = 64; + static constexpr int num_mouse_buttons = 8; + static constexpr int first_mouse_button = 3; + + IDirectInputDevice8A* dinkeyboard = nullptr, *dinmouse = nullptr; win32_joy_ctx joy_ctx; std::vector<std::unique_ptr<fun>> receivers; QMutex mtx; QMainWindow fake_main_window; - dinput_handle::di_t din; + di_t din; bool keystate[256] {}; - bool old_keystate[256] {}; + bool mouse_state[num_mouse_buttons - first_mouse_button] = {}; void run() override; + bool run_keyboard_nolock(); + bool run_joystick_nolock(); + bool run_mouse_nolock(); + bool init(); + bool init_(IDirectInputDevice8A*& dev, const char* name, const GUID& guid, const DIDATAFORMAT& fmt); KeybindingWorker(); static KeybindingWorker& make(); - fun* _add_receiver(fun &receiver); + fun* add_receiver(fun& receiver); void remove_receiver(fun* pos); - ~KeybindingWorker(); - - static constexpr int num_keyboard_states = 128; - DIDEVICEOBJECTDATA keyboard_states[num_keyboard_states]; - - KeybindingWorker(const KeybindingWorker&) = delete; - KeybindingWorker& operator=(KeybindingWorker&) = delete; + ~KeybindingWorker() override; public: class Token { fun* pos; + public: Token(const Token&) = delete; Token& operator=(Token&) = delete; - public: + ~Token() { make().remove_receiver(pos); } Token(fun receiver) { - pos = make()._add_receiver(receiver); + pos = make().add_receiver(receiver); } }; }; diff --git a/dinput/lang/zh_CN.ts b/dinput/lang/zh_CN.ts index 6401616d..e5ca8aa9 100644 --- a/dinput/lang/zh_CN.ts +++ b/dinput/lang/zh_CN.ts @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE TS> -<TS version="2.1"> +<TS version="2.1" language="zh_CN"> </TS> diff --git a/dinput/win32-joystick.cpp b/dinput/win32-joystick.cpp index 9127b6a8..cb7a3837 100644 --- a/dinput/win32-joystick.cpp +++ b/dinput/win32-joystick.cpp @@ -1,40 +1,39 @@ #ifdef _WIN32 -#undef NDEBUG #include "win32-joystick.hpp" -#include "compat/sleep.hpp" -#include <cassert> -#include <cstring> +#include "compat/macros.h" + +#include <cstddef> #include <algorithm> #include <cmath> -#include <objbase.h> +#include <iterator> +#include <QWidget> #include <QDebug> -// XXX how many axis update events can we reasonably get in a short time frame? -enum { num_buffers = 256 }; -DIDEVICEOBJECTDATA win32_joy_ctx::joy::keystate_buffers[num_buffers]; +#include <dinput.h> +#include <objbase.h> + +namespace win32_joy_impl { QMutex win32_joy_ctx::enum_state::mtx; win32_joy_ctx::enum_state win32_joy_ctx::enumerator; -void win32_joy_ctx::poll(fn f) +void win32_joy_ctx::poll(fn const& f) { //refresh(false); - QMutexLocker l(&enumerator.mtx); + QMutexLocker l(&enum_state::mtx); auto& joys = enumerator.get_joys(); for (auto& j : joys) - { j.second->poll(f); - } } bool win32_joy_ctx::poll_axis(const QString &guid, int* axes) { - QMutexLocker l(&enumerator.mtx); + QMutexLocker l(&enum_state::mtx); for (int k = 0; k < 10; k++) { @@ -48,35 +47,15 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes) return false; auto& j = iter->second; - auto& joy_handle = j->joy_handle; - bool ok = false; - HRESULT hr; - - if (!FAILED(hr = joy_handle->Poll())) - { - ok = true; - } - - if (!ok && FAILED(hr = joy_handle->Acquire())) - { - //qDebug() << "joy acquire failed" << hr; - } + DIJOYSTATE2 js; - if (!ok) - { - portable::sleep(25); - (void) joy_handle->Unacquire(); + if (!di_t::poll_device(joy_handle)) continue; - } - DIJOYSTATE2 js; - std::memset(&js, 0, sizeof(js)); - - if (FAILED(hr = joy_handle->GetDeviceState(sizeof(js), &js))) + if (FAILED(joy_handle->GetDeviceState(sizeof(js), &js))) { - //qDebug() << "joy get state failed" << guid << hr; - portable::sleep(50); + //qDebug() << "joy get state failed" << guid; continue; } @@ -92,7 +71,7 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes) js.rglSlider[1] }; - for (int i = 0; i < 8; i++) + for (unsigned i = 0; i < std::size(values); i++) axes[i] = values[i]; return true; @@ -104,7 +83,7 @@ bool win32_joy_ctx::poll_axis(const QString &guid, int* axes) std::vector<win32_joy_ctx::joy_info> win32_joy_ctx::get_joy_info() { std::vector<joy_info> ret; - QMutexLocker l(&enumerator.mtx); + QMutexLocker l(&enum_state::mtx); auto& joys = enumerator.get_joys(); ret.reserve(joys.size()); @@ -123,17 +102,18 @@ win32_joy_ctx::win32_joy_ctx() void win32_joy_ctx::refresh() { - QMutexLocker l(&enumerator.mtx); + QMutexLocker l(&enum_state::mtx); enumerator.refresh(); } QString win32_joy_ctx::guid_to_string(const GUID& guid) { - char buf[40] = {0}; - wchar_t szGuidW[40] = {0}; + char buf[40] = {}; + wchar_t szGuidW[40] = {}; - StringFromGUID2(guid, szGuidW, 40); - WideCharToMultiByte(0, 0, szGuidW, -1, buf, 40, NULL, NULL); + StringFromGUID2(guid, szGuidW, sizeof(buf)); + WideCharToMultiByte(0, 0, szGuidW, -1, buf, sizeof(buf), nullptr, nullptr); + buf[sizeof(buf)-1] = 0; return QString(buf); } @@ -150,27 +130,24 @@ void win32_joy_ctx::joy::release() } } -bool win32_joy_ctx::joy::poll(fn f) +bool win32_joy_ctx::joy::poll(fn const& f) { HRESULT hr; - bool ok = false; - - (void) joy_handle->Acquire(); - if (!FAILED(hr = joy_handle->Poll())) - ok = true; - - if (!ok) + if (!di_t::poll_device(joy_handle)) { - //qDebug() << "joy acquire failed" << guid << hr; - (void) joy_handle->Unacquire(); + eval_once(qDebug() << "joy poll failed" << guid << (void*)hr); + //(void)joy_handle->Unacquire(); + //Sleep(0); return false; } + DIDEVICEOBJECTDATA keystate_buffers[num_buffers]; + DWORD sz = num_buffers; if (FAILED(hr = joy_handle->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), keystate_buffers, &sz, 0))) { - //qDebug() << "joy get state failed" << guid << hr; + eval_once(qDebug() << "joy GetDeviceData failed" << guid << (void*)hr); return false; } @@ -181,74 +158,65 @@ bool win32_joy_ctx::joy::poll(fn f) bool is_pov = false; int i = -1; +#define POV_HAT_OFFSET(k) \ + (offsetof(DIJOYSTATE2, rgdwPOV) + (k) * sizeof(DWORD)) +#define BUTTON_OFFSET(k) \ + (offsetof(DIJOYSTATE2, rgbButtons) + (k) * sizeof(BYTE)) + switch (event.dwOfs) { - case DIJOFS_POV(0): i = 0, is_pov = true; break; - case DIJOFS_POV(2): i = 1, is_pov = true; break; - case DIJOFS_POV(3): i = 2, is_pov = true; break; - case DIJOFS_POV(4): i = 3, is_pov = true; break; + case POV_HAT_OFFSET(0): i = 0; is_pov = true; break; + case POV_HAT_OFFSET(1): i = 1; is_pov = true; break; + case POV_HAT_OFFSET(2): i = 2; is_pov = true; break; + case POV_HAT_OFFSET(3): i = 3; is_pov = true; break; default: - if (event.dwOfs >= DIJOFS_BUTTON0 && event.dwOfs <= DIJOFS_BUTTON(127)) + if (event.dwOfs >= BUTTON_OFFSET(0) && event.dwOfs <= BUTTON_OFFSET(max_buttons - 1)) { - unsigned tmp = event.dwOfs; - tmp -= DIJOFS_BUTTON0; - tmp /= DIJOFS_BUTTON1 - DIJOFS_BUTTON0; - tmp &= 127; - i = tmp; + i = int(event.dwOfs - BUTTON_OFFSET(0)); + i /= sizeof(DIJOYSTATE2().rgbButtons[0]); + i %= max_buttons; // defensive programming } break; } if (is_pov) { - //qDebug() << "DBG: pov" << i << event.dwData; - - using std::round; + unsigned pos = event.dwData / value_per_pov_hat_direction; - unsigned char pos; - unsigned pos_ = event.dwData; - if ((pos_ & 0xffff) == 0xffff) - pos = 0; - else if (pos_ == ~0u) - pos = 0; - else - { - using uc = unsigned char; - pos = uc(((pos_ / 9000u) % 4u) + 1u); - } + i = max_buttons + i * pov_hat_directions; - const bool state[] = + for (unsigned j = 0; j < pov_hat_directions; j++) { - pos == 1, - pos == 2, - pos == 3, - pos == 4 - }; - - i = 128u + i * 4u; - - for (unsigned j = 0; j < 4; j++) - { - //pressed[i] = state[j]; - f(guid, i, state[j]); + const unsigned idx = i + j; + const bool new_value = pos == j; + if (last_state[idx] != new_value) + { +#ifdef WIN32_JOY_DEBUG + qDebug() << "DBG: pov" << idx << (pos == j); +#endif + last_state[idx] = new_value; + f(guid, idx, new_value); + } } } - else if (i != -1) + else if ((unsigned)i < max_buttons) { - const bool state = !!(event.dwData & 0x80); - //qDebug() << "DBG: btn" << i << state; - //pressed[i] = state; - f(guid, i, state); + const bool new_value = !!(event.dwData & 0x80); + if (last_state[i] != new_value) + { +#ifdef WIN32_JOY_DEBUG + qDebug() << "DBG: btn" << i << new_value; +#endif + last_state[i] = new_value; + f(guid, i, new_value); + } } - } return true; } -win32_joy_ctx::enum_state::enum_state() : di(dinput_handle::make_di()) -{ -} +win32_joy_ctx::enum_state::enum_state() = default; win32_joy_ctx::enum_state::~enum_state() { @@ -260,10 +228,13 @@ win32_joy_ctx::enum_state::~enum_state() void win32_joy_ctx::enum_state::refresh() { all.clear(); +#ifdef __SANITIZE_ADDRESS__ + return; +#endif if (!di) { - qDebug() << "can't create dinput"; + qDebug() << "dinput: can't create dinput"; return; } @@ -274,20 +245,16 @@ void win32_joy_ctx::enum_state::refresh() this, DIEDFL_ATTACHEDONLY))) { - qDebug() << "failed enum joysticks" << hr; + eval_once(qDebug() << "dinput: failed enum joysticks" << (void*)hr); return; } for (auto it = joys.begin(); it != joys.end(); ) { if (std::find_if(all.cbegin(), all.cend(), [&](const QString& guid2) { return it->second->guid == guid2; }) == all.end()) - { it = joys.erase(it); - } else - { ++it; - } } } @@ -308,20 +275,20 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumJoysticksCallback(const DIDEVICEINS { HRESULT hr; - LPDIRECTINPUTDEVICE8 h; + IDirectInputDevice8A* h; if (FAILED(hr = state.di->CreateDevice(pdidInstance->guidInstance, &h, nullptr))) { - qDebug() << "createdevice" << guid << hr; + qDebug() << "dinput: failed joystick CreateDevice" << guid << (void*)hr; goto end; } - if (FAILED(h->SetDataFormat(&c_dfDIJoystick2))) + if (FAILED(hr = h->SetDataFormat(&c_dfDIJoystick2))) { - qDebug() << "format"; + qDebug() << "dinput: failed joystick SetDataFormat" << (void*)hr; h->Release(); goto end; } - // not a static member - need main() to run for some time first + // not a library-load-time member - need main() to run for some time first static const QWidget fake_window; if (FAILED(h->SetCooperativeLevel(reinterpret_cast<HWND>(fake_window.winId()), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND))) @@ -332,16 +299,16 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumJoysticksCallback(const DIDEVICEINS } { - DIPROPDWORD dipdw; - dipdw.dwData = 128; + DIPROPDWORD dipdw = {}; dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); + dipdw.diph.dwSize = sizeof(dipdw); dipdw.diph.dwHow = DIPH_DEVICE; dipdw.diph.dwObj = 0; - dipdw.diph.dwSize = sizeof(dipdw); + dipdw.dwData = num_buffers; if (h->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph) != DI_OK) { - qDebug() << "setup joystick buffer mode failed!"; + qDebug() << "dinput: joystick DIPROP_BUFFERSIZE"; h->Release(); goto end; } @@ -349,7 +316,7 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumJoysticksCallback(const DIDEVICEINS if (FAILED(hr = h->EnumObjects(EnumObjectsCallback, h, DIDFT_ALL))) { - qDebug() << "enum-objects"; + qDebug() << "dinput: joystick EnumObjects"; h->Release(); goto end; } @@ -364,10 +331,9 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumObjectsCallback(const DIDEVICEOBJEC { if (pdidoi->dwType & DIDFT_AXIS) { - DIPROPRANGE diprg; - std::memset(&diprg, 0, sizeof(diprg)); - diprg.diph.dwSize = sizeof( DIPROPRANGE ); - diprg.diph.dwHeaderSize = sizeof( DIPROPHEADER ); + DIPROPRANGE diprg = {}; + diprg.diph.dwSize = sizeof(DIPROPRANGE); + diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); diprg.diph.dwHow = DIPH_BYID; diprg.diph.dwObj = pdidoi->dwType; diprg.lMax = joy_axis_size; @@ -375,9 +341,9 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumObjectsCallback(const DIDEVICEOBJEC HRESULT hr; - if (FAILED(hr = reinterpret_cast<LPDIRECTINPUTDEVICE8>(ctx)->SetProperty(DIPROP_RANGE, &diprg.diph))) + if (FAILED(hr = reinterpret_cast<IDirectInputDevice8A*>(ctx)->SetProperty(DIPROP_RANGE, &diprg.diph))) { - qDebug() << "DIPROP_RANGE" << hr; + qDebug() << "dinput: failed joystick DIPROP_RANGE" << (void*)hr; return DIENUM_STOP; } } @@ -385,7 +351,7 @@ BOOL CALLBACK win32_joy_ctx::enum_state::EnumObjectsCallback(const DIDEVICEOBJEC return DIENUM_CONTINUE; } -win32_joy_ctx::joy::joy(LPDIRECTINPUTDEVICE8 handle, const QString &guid, const QString &name) +win32_joy_ctx::joy::joy(IDirectInputDevice8A* handle, const QString& guid, const QString &name) : joy_handle(handle), guid(guid), name(name) { //qDebug() << "make joy" << guid << name << joy_handle; @@ -397,4 +363,5 @@ win32_joy_ctx::joy::~joy() release(); } +} // ns win32_joy_impl #endif diff --git a/dinput/win32-joystick.hpp b/dinput/win32-joystick.hpp index 42ecf57f..8e5344d6 100644 --- a/dinput/win32-joystick.hpp +++ b/dinput/win32-joystick.hpp @@ -7,65 +7,67 @@ */ #pragma once -#ifdef _WIN32 - #include "dinput.hpp" #include "compat/timer.hpp" #include "export.hpp" -#include <cstring> +#include "compat/qhash.hpp" + #include <memory> #include <vector> #include <functional> -#include <algorithm> #include <unordered_map> +#include <iterator> + #include <QString> -#include <QDebug> #include <QMutex> -#include <QMutexLocker> -#include <QWidget> -namespace std { -template<> -struct hash<QString> -{ - inline std::size_t operator()(const QString& value) const - { - return qHash(value); - } -}; -} +namespace win32_joy_impl { + +static constexpr unsigned max_buttons = 128; +static constexpr unsigned max_pov_hats = 4; +static constexpr unsigned pov_hat_directions = 8; + +// cf. https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v=vs.85) +// see also remarks on the page +// no need to check for pos == unsigned(-1) || pos == 0xffff, +// this logic doesn't require that +static constexpr unsigned value_per_pov_hat_direction = 36000 / pov_hat_directions; +static constexpr unsigned max_buttons_and_pov_hats = max_buttons + max_pov_hats * pov_hat_directions; -struct OTR_DINPUT_EXPORT win32_joy_ctx +//static_assert(pov_hat_directions == 4 || pov_hat_directions == 8); + +// XXX how many axis update events can we reasonably get in a short time frame? +static constexpr unsigned num_buffers = 16; + +//#define WIN32_JOY_DEBUG + +struct OTR_DINPUT_EXPORT win32_joy_ctx final { using fn = std::function<void(const QString& guid, int btn, bool held)>; - struct joy + struct joy final { - LPDIRECTINPUTDEVICE8 joy_handle; + IDirectInputDevice8A* joy_handle; QString guid, name; - enum { num_pressed_keys = 128 + 4 * 4 }; - //bool pressed[num_pressed_keys] {}; - Timer first_timer; - - static DIDEVICEOBJECTDATA keystate_buffers[256]; + bool last_state[max_buttons_and_pov_hats] {}; - joy(LPDIRECTINPUTDEVICE8 handle, const QString& guid, const QString& name); + joy(IDirectInputDevice8A* handle, const QString& guid, const QString& name); ~joy(); void release(); - bool poll(fn f); + bool poll(fn const& f); }; using joys_t = std::unordered_map<QString, std::shared_ptr<joy>>; - static constexpr inline int joy_axis_size = 65536; + static constexpr int joy_axis_size = 65536; struct joy_info { QString name, guid; }; - void poll(fn f); + void poll(fn const& f); bool poll_axis(const QString& guid, int* axes); std::vector<joy_info> get_joy_info(); @@ -75,8 +77,6 @@ struct OTR_DINPUT_EXPORT win32_joy_ctx win32_joy_ctx(); void refresh(); - using di_t = dinput_handle::di_t; - private: static QString guid_to_string(const GUID& guid); @@ -84,10 +84,10 @@ private: { std::vector<QString> all; joys_t joys; - dinput_handle::di_t di; + di_t di; - static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext); - static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* ctx); + static BOOL __stdcall EnumJoysticksCallback(const DIDEVICEINSTANCEA* pdidInstance, void* pContext); + static BOOL __stdcall EnumObjectsCallback(const DIDEVICEOBJECTINSTANCEA* pdidoi, void* ctx); public: static QMutex mtx; @@ -96,9 +96,13 @@ private: ~enum_state(); void refresh(); const joys_t& get_joys() const; + + enum_state(enum_state const&) = delete; }; static enum_state enumerator; }; -#endif +} // ns win32_joy_impl + +using win32_joy_ctx = win32_joy_impl::win32_joy_ctx; |