/* Copyright (c) 2014-2015, Stanislaw Halik * Permission to use, copy, modify, and/or distribute this * software for any purpose with or without fee is hereby granted, * provided that the above copyright notice and this permission * notice appear in all copies. */ #ifdef _WIN32 #include "keybinding-worker.hpp" #include "compat/macros.hpp" #include "compat/thread-name.hpp" #include #include #include 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 = !held || timer.elapsed_ms() > 100; timer.start(); return ret; } KeybindingWorker::~KeybindingWorker() { qDebug() << "exit: keybinding worker"; requestInterruption(); wait(); destroy(dinkeyboard); destroy(dinmouse); } 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"; goto fail; } if (auto hr = din->CreateDevice(guid, &dev, nullptr); hr != DI_OK) { qDebug() << "dinput: create" << name << "failed" << (void*)hr; goto fail; } if (auto hr = dev->SetDataFormat(&fmt); hr != DI_OK) { qDebug() << "dinput:" << name << "SetDataFormat" << (void*)hr; goto fail; } 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 = num_keyboard_states; dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); dipdw.diph.dwHow = DIPH_DEVICE; dipdw.diph.dwObj = 0; dipdw.diph.dwSize = sizeof(dipdw); if (auto hr = dinkeyboard->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph); hr != DI_OK) { qDebug() << "dinput: keyboard DIPROP_BUFFERSIZE" << (void*)hr; goto fail; } } return true; fail: destroy(dinkeyboard); destroy(dinmouse); return false; } KeybindingWorker::KeybindingWorker() { fake_main_window.setAttribute(Qt::WA_NativeWindow); if (init()) start(QThread::HighPriority); } KeybindingWorker& KeybindingWorker::make() { static KeybindingWorker k; return k; } void KeybindingWorker::run() { portable::set_curthread_name("keybinding worker"); while (!isInterruptionRequested()) { { QMutexLocker l(&mtx); if (!receivers.empty()) { bool ok = true; ok &= run_keyboard_nolock(); ok &= run_mouse_nolock(); ok &= run_joystick_nolock(); if (!ok) Sleep(500); } } Sleep(25); } } 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; 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(receiver)); fun* f = &*receivers[receivers.size() - 1]; //qDebug() << "add receiver" << (long) f; joy_ctx.refresh(); return f; } void KeybindingWorker::remove_receiver(KeybindingWorker::fun* pos) { QMutexLocker l(&mtx); bool ok = false; using s = int; for (int i = s(receivers.size()) - 1; i >= 0; i--) { using u = unsigned; if (&*receivers[u(i)] == pos) { ok = true; //qDebug() << "remove receiver" << (long) pos; receivers.erase(receivers.begin() + i); break; } } if (!ok) { qDebug() << "bad remove receiver" << (void*) pos; } } #endif