summaryrefslogtreecommitdiffhomepage
path: root/dinput
diff options
context:
space:
mode:
Diffstat (limited to 'dinput')
-rw-r--r--dinput/dinput.cpp124
-rw-r--r--dinput/dinput.hpp66
-rw-r--r--dinput/keybinding-worker.cpp320
-rw-r--r--dinput/keybinding-worker.hpp35
-rw-r--r--dinput/lang/zh_CN.ts2
-rw-r--r--dinput/win32-joystick.cpp215
-rw-r--r--dinput/win32-joystick.hpp74
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;