Files
Xenia-Canary/src/xenia/hid/xinput/xinput_input_driver.cc
Adrian 459497f0b6 Implemented Controller Hotkeys (#111)
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>
2023-01-13 09:17:43 +01:00

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