#pragma once #ifdef _WIN32 #include #include #include #include #include #ifndef DIRECTINPUT_VERSION # define DIRECTINPUT_VERSION 0x800 #endif #include #include #include "opentrack-compat/timer.hpp" #include #include struct win32_joy_ctx { using fn = std::function; void poll(fn f) { refresh(false); for (int i = joys.size() - 1; i >= 0; i--) { if (!joys[i]->poll(f)) joys.erase(joys.begin() + i); } } struct joy { LPDIRECTINPUTDEVICE8 joy_handle; QString guid; bool pressed[128]; joy(LPDIRECTINPUTDEVICE8 handle, const QString& guid) : joy_handle(handle), guid(guid) { qDebug() << "got joy" << guid; for (int i = 0; i < 128; i++) pressed[i] = false; poll([&](const QString&, int idx, bool held) -> void { pressed[idx] = held; }); } ~joy() { qDebug() << "nix joy" << guid; release(); } void release() { if (joy_handle) { (void) joy_handle->Unacquire(); joy_handle->Release(); joy_handle = nullptr; } } bool poll(fn f) { HRESULT hr; bool ok = false; for (int i = 0; i < 5; i++) { if (!FAILED(joy_handle->Poll())) { ok = true; break; } if ((hr = joy_handle->Acquire()) != DI_OK) continue; else ok = true; break; } if (!ok) { qDebug() << "joy acquire failed" << guid << hr; return false; } DIJOYSTATE2 js; memset(&js, 0, sizeof(js)); if (FAILED(hr = joy_handle->GetDeviceState(sizeof(js), &js))) { qDebug() << "joy get state failed" << guid << hr; return false; } for (int i = 0; i < 128; i++) { const bool state = !!(js.rgbButtons[i] & 0x80); if (state != pressed[i]) { f(guid, i, state); qDebug() << "btn" << guid << i << state; } pressed[i] = state; } return true; } }; static QString guid_to_string(const GUID guid) { char buf[40] = {0}; wchar_t szGuidW[40] = {0}; StringFromGUID2(guid, szGuidW, 40); WideCharToMultiByte(0, 0, szGuidW, -1, buf, 40, NULL, NULL); return QString(buf); } win32_joy_ctx() : dinput_handle(nullptr) { (void) CoInitialize(nullptr); HRESULT hr; if (FAILED(hr = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**) &dinput_handle, nullptr))) goto fail; refresh(true); return; fail: qDebug() << "dinput8 failed for shortcuts" << hr; release(); } ~win32_joy_ctx() { release(); } void release() { joys = std::vector>(); if (dinput_handle) { dinput_handle->Release(); dinput_handle = nullptr; } } void refresh(bool first) { if (!dinput_handle) return; if (!first) { if (timer_joylist.elapsed_ms() < joylist_refresh_ms) return; timer_joylist.start(); } enum_state st(dinput_handle, joys); } struct enum_state { std::vector>& joys; std::vector all; LPDIRECTINPUT8 dinput_handle; enum_state(LPDIRECTINPUT8 di, std::vector>& joys) : joys(joys), dinput_handle(di) { HRESULT hr; if(FAILED(hr = dinput_handle->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY))) { qDebug() << "failed enum joysticks" << hr; return; } for (int i = joys.size() - 1; i >= 0; i--) { const auto& guid = joys[i]->guid; if (std::find_if(all.cbegin(), all.cend(), [&](const QString& guid2) -> bool { return guid == guid2; }) == all.cend()) joys.erase(joys.begin() + i); } } static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext) { enum_state& state = *reinterpret_cast(pContext); const QString guid = guid_to_string(pdidInstance->guidInstance); #if 0 const QString name = QString(pdidInstance->tszInstanceName); // the logic here is that iff multiple joysticks of same name exist, then take guids into account at all const int cnt_names = std::count_if(state.joys.begin(), state.joys.end(), [&](const joy& j) -> bool { return j.name == name; }); // this is potentially bad since replugged sticks can change guids (?) #endif const bool exists = std::find_if(state.joys.cbegin(), state.joys.cend(), [&](const std::shared_ptr& j) -> bool { return j->guid == guid; }) != state.joys.cend(); state.all.push_back(guid); if (!exists) { HRESULT hr; LPDIRECTINPUTDEVICE8 h; if (FAILED(hr = state.dinput_handle->CreateDevice(pdidInstance->guidInstance, &h, nullptr))) { qDebug() << "create joystick breakage" << guid << hr; goto end; } if (FAILED(h->SetDataFormat(&c_dfDIJoystick2))) { qDebug() << "format"; h->Release(); goto end; } if (FAILED(h->SetCooperativeLevel((HWND) GetDesktopWindow(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND))) { qDebug() << "coop"; h->Release(); goto end; } #if 0 if (FAILED(hr = h->EnumObjects(EnumObjectsCallback, h, DIDFT_ALL))) { qDebug() << "enum-objects"; h->Release(); goto end; } #endif state.joys.push_back(std::make_shared(h, guid)); } end: return DIENUM_CONTINUE; } #if 0 static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* ctx) { if (pdidoi->dwType & DIDFT_AXIS) { DIPROPRANGE diprg; memset(&diprg, 0, sizeof(diprg)); diprg.diph.dwSize = sizeof( DIPROPRANGE ); diprg.diph.dwHeaderSize = sizeof( DIPROPHEADER ); diprg.diph.dwHow = DIPH_BYID; diprg.diph.dwObj = pdidoi->dwType; diprg.lMax = 32; diprg.lMin = -32; if (FAILED(reinterpret_cast(ctx)->SetProperty(DIPROP_RANGE, &diprg.diph))) return DIENUM_STOP; } return DIENUM_CONTINUE; } #endif }; LPDIRECTINPUT8 dinput_handle; std::vector> joys; Timer timer_joylist; enum { joylist_refresh_ms = 250 }; }; #endif