Implemented Controller Hotkeys Added controller hotkeys Added guide button support for XInput and winkey The hotkey configurations can be found in HID -> Display controller hotkeys If the Xbox Gamebar overlay is enabled then use the Back button instead of the Guide button. - Fixed hotkey thread destruction - Fixed XINPUT_STATE by padding 4 bytes - Added hotkey vibration for user feedback - Replaced MessageBoxA with ImGuiDialog::ShowMessageBox Co-authored-by: Margen67 <Margen67@users.noreply.github.com>
235 lines
7.9 KiB
C++
235 lines
7.9 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/hid/xinput/xinput_input_driver.h"
|
|
|
|
// Must be included before xinput.h to avoid windows.h conflicts:
|
|
#include "xenia/base/platform_win.h"
|
|
|
|
#include <xinput.h> // NOLINT(build/include_order)
|
|
|
|
#include "xenia/base/clock.h"
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/hid/hid_flags.h"
|
|
namespace xe {
|
|
namespace hid {
|
|
namespace xinput {
|
|
|
|
XInputInputDriver::XInputInputDriver(xe::ui::Window* window,
|
|
size_t window_z_order)
|
|
: InputDriver(window, window_z_order),
|
|
module_(nullptr),
|
|
XInputGetCapabilities_(nullptr),
|
|
XInputGetState_(nullptr),
|
|
XInputGetStateEx_(nullptr),
|
|
XInputGetKeystroke_(nullptr),
|
|
XInputSetState_(nullptr),
|
|
XInputEnable_(nullptr) {}
|
|
|
|
XInputInputDriver::~XInputInputDriver() {
|
|
if (module_) {
|
|
FreeLibrary((HMODULE)module_);
|
|
module_ = nullptr;
|
|
XInputGetCapabilities_ = nullptr;
|
|
XInputGetState_ = nullptr;
|
|
XInputGetStateEx_ = nullptr;
|
|
XInputGetKeystroke_ = nullptr;
|
|
XInputSetState_ = nullptr;
|
|
XInputEnable_ = nullptr;
|
|
}
|
|
}
|
|
|
|
X_STATUS XInputInputDriver::Setup() {
|
|
HMODULE module = LoadLibraryW(L"xinput1_4.dll");
|
|
if (!module) {
|
|
return X_STATUS_DLL_NOT_FOUND;
|
|
}
|
|
|
|
// Support guide button with XInput using XInputGetStateEx
|
|
auto const XInputGetStateEx = (LPCSTR)100;
|
|
|
|
// Required.
|
|
auto xigc = GetProcAddress(module, "XInputGetCapabilities");
|
|
auto xigs = GetProcAddress(module, "XInputGetState");
|
|
auto xigsEx = GetProcAddress(module, XInputGetStateEx);
|
|
auto xigk = GetProcAddress(module, "XInputGetKeystroke");
|
|
auto xiss = GetProcAddress(module, "XInputSetState");
|
|
|
|
// Not required.
|
|
auto xie = GetProcAddress(module, "XInputEnable");
|
|
|
|
// Only fail when we don't have the bare essentials;
|
|
if (!xigc || !xigs || !xigk || !xiss) {
|
|
FreeLibrary(module);
|
|
return X_STATUS_PROCEDURE_NOT_FOUND;
|
|
}
|
|
|
|
module_ = module;
|
|
XInputGetCapabilities_ = xigc;
|
|
XInputGetState_ = xigs;
|
|
XInputGetStateEx_ = xigsEx;
|
|
XInputGetKeystroke_ = xigk;
|
|
XInputSetState_ = xiss;
|
|
XInputEnable_ = xie;
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
constexpr uint64_t SKIP_INVALID_CONTROLLER_TIME = 1100;
|
|
static uint64_t last_invalid_time[4];
|
|
|
|
static DWORD should_skip(uint32_t user_index) {
|
|
uint64_t time = last_invalid_time[user_index];
|
|
if (time) {
|
|
uint64_t deltatime = xe::Clock::QueryHostUptimeMillis() - time;
|
|
|
|
if (deltatime < SKIP_INVALID_CONTROLLER_TIME) {
|
|
return ERROR_DEVICE_NOT_CONNECTED;
|
|
}
|
|
last_invalid_time[user_index] = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void set_skip(uint32_t user_index) {
|
|
last_invalid_time[user_index] = xe::Clock::QueryHostUptimeMillis();
|
|
}
|
|
|
|
X_RESULT XInputInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
|
|
X_INPUT_CAPABILITIES* out_caps) {
|
|
DWORD skipper = should_skip(user_index);
|
|
if (skipper) {
|
|
return skipper;
|
|
}
|
|
XINPUT_CAPABILITIES native_caps;
|
|
auto xigc = (decltype(&XInputGetCapabilities))XInputGetCapabilities_;
|
|
DWORD result = xigc(user_index, flags, &native_caps);
|
|
if (result) {
|
|
if (result == ERROR_DEVICE_NOT_CONNECTED) {
|
|
set_skip(user_index);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
out_caps->type = native_caps.Type;
|
|
out_caps->sub_type = native_caps.SubType;
|
|
out_caps->flags = native_caps.Flags;
|
|
out_caps->gamepad.buttons = native_caps.Gamepad.wButtons;
|
|
out_caps->gamepad.left_trigger = native_caps.Gamepad.bLeftTrigger;
|
|
out_caps->gamepad.right_trigger = native_caps.Gamepad.bRightTrigger;
|
|
out_caps->gamepad.thumb_lx = native_caps.Gamepad.sThumbLX;
|
|
out_caps->gamepad.thumb_ly = native_caps.Gamepad.sThumbLY;
|
|
out_caps->gamepad.thumb_rx = native_caps.Gamepad.sThumbRX;
|
|
out_caps->gamepad.thumb_ry = native_caps.Gamepad.sThumbRY;
|
|
out_caps->vibration.left_motor_speed = native_caps.Vibration.wLeftMotorSpeed;
|
|
out_caps->vibration.right_motor_speed =
|
|
native_caps.Vibration.wRightMotorSpeed;
|
|
|
|
return result;
|
|
}
|
|
|
|
X_RESULT XInputInputDriver::GetState(uint32_t user_index,
|
|
X_INPUT_STATE* out_state) {
|
|
DWORD skipper = should_skip(user_index);
|
|
if (skipper) {
|
|
return skipper;
|
|
}
|
|
|
|
struct {
|
|
XINPUT_STATE state;
|
|
unsigned int xinput_state_ex_padding; // Add padding in case we are using XInputGetStateEx
|
|
} native_state;
|
|
|
|
// If the guide button is enabled use XInputGetStateEx, otherwise use the default XInputGetState.
|
|
auto xigs = cvars::guide_button
|
|
? (decltype(&XInputGetState))XInputGetStateEx_
|
|
: (decltype(&XInputGetState))XInputGetState_;
|
|
|
|
DWORD result = xigs(user_index, &native_state.state);
|
|
if (result) {
|
|
if (result == ERROR_DEVICE_NOT_CONNECTED) {
|
|
set_skip(user_index);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
out_state->packet_number = native_state.state.dwPacketNumber;
|
|
out_state->gamepad.buttons = native_state.state.Gamepad.wButtons;
|
|
out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger;
|
|
out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger;
|
|
out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX;
|
|
out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY;
|
|
out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX;
|
|
out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY;
|
|
|
|
return result;
|
|
}
|
|
|
|
X_RESULT XInputInputDriver::SetState(uint32_t user_index,
|
|
X_INPUT_VIBRATION* vibration) {
|
|
DWORD skipper = should_skip(user_index);
|
|
if (skipper) {
|
|
return skipper;
|
|
}
|
|
XINPUT_VIBRATION native_vibration;
|
|
native_vibration.wLeftMotorSpeed = vibration->left_motor_speed;
|
|
native_vibration.wRightMotorSpeed = vibration->right_motor_speed;
|
|
auto xiss = (decltype(&XInputSetState))XInputSetState_;
|
|
DWORD result = xiss(user_index, &native_vibration);
|
|
if (result == ERROR_DEVICE_NOT_CONNECTED) {
|
|
set_skip(user_index);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
X_RESULT XInputInputDriver::GetKeystroke(uint32_t user_index, uint32_t flags,
|
|
X_INPUT_KEYSTROKE* out_keystroke) {
|
|
// We may want to filter flags/user_index before sending to native.
|
|
// flags is reserved on desktop.
|
|
DWORD result;
|
|
|
|
// XInputGetKeystroke on Windows has a bug where it will return
|
|
// ERROR_SUCCESS (0) even if the device is not connected:
|
|
// https://stackoverflow.com/questions/23669238/xinputgetkeystroke-returning-error-success-while-controller-is-unplugged
|
|
//
|
|
// So we first check if the device is connected via XInputGetCapabilities, so
|
|
// we are not passing back an uninitialized X_INPUT_KEYSTROKE structure.
|
|
// If any user (0xFF) is polled this bug does not occur but GetCapabilities
|
|
// would fail so we need to skip it.
|
|
if (user_index != 0xFF) {
|
|
XINPUT_CAPABILITIES caps;
|
|
auto xigc = (decltype(&XInputGetCapabilities))XInputGetCapabilities_;
|
|
result = xigc(user_index, 0, &caps);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
XINPUT_KEYSTROKE native_keystroke;
|
|
auto xigk = (decltype(&XInputGetKeystroke))XInputGetKeystroke_;
|
|
result = xigk(user_index, 0, &native_keystroke);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
|
|
out_keystroke->virtual_key = native_keystroke.VirtualKey;
|
|
out_keystroke->unicode = native_keystroke.Unicode;
|
|
out_keystroke->flags = native_keystroke.Flags;
|
|
out_keystroke->user_index = native_keystroke.UserIndex;
|
|
out_keystroke->hid_code = native_keystroke.HidCode;
|
|
// X_ERROR_EMPTY if no new keys
|
|
// X_ERROR_DEVICE_NOT_CONNECTED if no device
|
|
// X_ERROR_SUCCESS if key
|
|
return result;
|
|
}
|
|
|
|
} // namespace xinput
|
|
} // namespace hid
|
|
} // namespace xe
|