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>
This commit is contained in:
Adrian 2023-01-13 08:17:43 +00:00 committed by GitHub
parent ea1003c6bf
commit 459497f0b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 455 additions and 50 deletions

View File

@ -31,6 +31,7 @@
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
#include "xenia/gpu/command_processor.h"
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
#include "xenia/gpu/graphics_system.h"
#include "xenia/hid/input_system.h"
#include "xenia/ui/file_picker.h"
@ -47,6 +48,14 @@
DECLARE_bool(debug);
DECLARE_string(hid);
DECLARE_bool(guide_button);
DECLARE_bool(d3d12_readback_resolve);
DECLARE_bool(d3d12_clear_memory_page_state);
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
"Display");
@ -134,6 +143,9 @@ using xe::ui::KeyEvent;
using xe::ui::MenuItem;
using xe::ui::UIEvent;
using namespace xe::hid;
using namespace xe::gpu;
const std::string kBaseTitle = "Xenia-canary";
EmulatorWindow::EmulatorWindow(Emulator* emulator,
@ -230,6 +242,15 @@ void EmulatorWindow::OnEmulatorInitialized() {
if (cvars::fullscreen) {
SetFullscreen(true);
}
if (IsUseNexusForGameBarEnabled()) {
XELOGE("Xbox Gamebar Enabled, using BACK button instead of GUIDE!!!");
}
// Create a thread to listen for controller hotkeys.
Gamepad_HotKeys_Listener =
threading::Thread::Create({}, [&] { GamepadHotKeys(); });
Gamepad_HotKeys_Listener->set_name("Gamepad HotKeys Listener");
}
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
@ -608,6 +629,9 @@ bool EmulatorWindow::Initialize() {
hid_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Toggle controller vibration", "",
std::bind(&EmulatorWindow::ToggleControllerVibration, this)));
hid_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, "&Display controller hotkeys", "",
std::bind(&EmulatorWindow::DisplayHotKeysConfig, this)));
}
main_menu->AddChild(std::move(hid_menu));
@ -888,10 +912,10 @@ void EmulatorWindow::InstallContent() {
if (result != X_STATUS_SUCCESS) {
XELOGE("Failed to install content! Error code: {:08X}", result);
MessageBoxA(nullptr,
"Failed to install content!\n\nCheck xenia.log for technical "
"details.",
"Failed to install content!", MB_ICONERROR);
xe::ui::ImGuiDialog::ShowMessageBox(
imgui_drawer_.get(), "Failed to install content!",
"Failed to install content!\n\nCheck xenia.log for technical "
"details.");
}
}
}
@ -1076,26 +1100,308 @@ void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
UpdateTitle();
}
void EmulatorWindow::RunPreviouslyPlayedTitle() {
if (recently_launched_titles_.size() >= 1) {
RunTitle(recently_launched_titles_[0].path_to_file);
// Notes:
// Assumes titles do not use the guide button.
// For titles that do such as dashboards these titles could be excluded based on
// their title ID.
//
// If the Xbox Gamebar overlay is enabled Windows will consume the guide
// button's input, this can be seen using hid-demo.
//
// Workaround: Detect if the Xbox Gamebar overlay is enabled then use the BACK
// button instead of the GUIDE button. Therefore BACK and GUIDE are reserved
// buttons for hotkeys.
//
// This is not an issue with DualShock controllers because Windows will not
// open the gamebar overlay using the PlayStation menu button.
//
// Steam:
// If guide button focus is enabled steam will open.
// Steam uses BACK + GUIDE to open an On-Screen keyboard, however this is not a
// problem since both these buttons are reserved.
const std::map<int, EmulatorWindow::ControllerHotKey> controller_hotkey_map = {
// Must use the Guide Button for all pass through hotkeys
{X_INPUT_GAMEPAD_A | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ReadbackResolve,
"A + Guide = Toggle Readback Resolve", true)},
{X_INPUT_GAMEPAD_B | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::CloseWindow,
"B + Guide = Close Window")},
{X_INPUT_GAMEPAD_Y | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ToggleFullscreen,
"Y + Guide = Toggle Fullscreen", true)},
{X_INPUT_GAMEPAD_X | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ClearMemoryPageState,
"X + Guide = Toggle Clear Memory Page State", true)},
{X_INPUT_GAMEPAD_RIGHT_SHOULDER | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ToggleControllerVibration,
"Right Shoulder + Guide = Toggle Controller Vibration", true)},
// CPU Time Scalar with no rumble feedback
{X_INPUT_GAMEPAD_DPAD_DOWN | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::CpuTimeScalarSetHalf,
"D-PAD Down + Guide = Half CPU Scalar")},
{X_INPUT_GAMEPAD_DPAD_UP | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::CpuTimeScalarSetDouble,
"D-PAD Up + Guide = Double CPU Scalar")},
{X_INPUT_GAMEPAD_DPAD_RIGHT | X_INPUT_GAMEPAD_GUIDE,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::CpuTimeScalarReset,
"D-PAD Right + Guide = Reset CPU Scalar")},
// non-pass through hotkeys
{X_INPUT_GAMEPAD_Y, EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::ToggleFullscreen,
"Y = Toggle Fullscreen", true, false)},
{X_INPUT_GAMEPAD_START,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::RunPreviouslyPlayedTitle,
"Start = Run previously played title", false, false)},
{X_INPUT_GAMEPAD_BACK | X_INPUT_GAMEPAD_START,
EmulatorWindow::ControllerHotKey(
EmulatorWindow::ButtonFunctions::CloseWindow,
"Back + Start = Close Window", false, false)}};
EmulatorWindow::ControllerHotKey EmulatorWindow::ProcessControllerHotkey(
int buttons) {
// Default return value
EmulatorWindow::ControllerHotKey Unknown_hotkey = {};
if (buttons == 0) return Unknown_hotkey;
// Hotkey cool-down to prevent toggling too fast
const std::chrono::milliseconds delay(75);
// If the Xbox Gamebar is enabled or the Guide button is disabled then
// replace the Guide button with the Back button without redeclaring the key
// mappings
if (IsUseNexusForGameBarEnabled() || !cvars::guide_button) {
if ((buttons & X_INPUT_GAMEPAD_BACK) == X_INPUT_GAMEPAD_BACK) {
buttons &= ~X_INPUT_GAMEPAD_BACK;
buttons |= X_INPUT_GAMEPAD_GUIDE;
}
}
auto it = controller_hotkey_map.find(buttons);
if (it == controller_hotkey_map.end()) {
return Unknown_hotkey;
}
// Do not activate hotkeys that are not intended for activation during
// gameplay
if (emulator_->is_title_open() && !it->second.title_passthru) {
return Unknown_hotkey;
}
EmulatorWindow::ControllerHotKey button_combination = it->second;
switch (button_combination.function) {
case ButtonFunctions::ToggleFullscreen:
ToggleFullscreen();
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::RunPreviouslyPlayedTitle:
RunPreviouslyPlayedTitle();
break;
case ButtonFunctions::ClearMemoryPageState:
ToggleGPUSetting(gpu_cvar::ClearMemoryPageState);
// Assume the user wants ClearCaches as well
if (cvars::d3d12_clear_memory_page_state) {
GpuClearCaches();
}
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::ReadbackResolve:
ToggleGPUSetting(gpu_cvar::ReadbackResolve);
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::CpuTimeScalarSetHalf:
CpuTimeScalarSetHalf();
break;
case ButtonFunctions::CpuTimeScalarSetDouble:
CpuTimeScalarSetDouble();
break;
case ButtonFunctions::CpuTimeScalarReset:
CpuTimeScalarReset();
break;
case ButtonFunctions::ToggleControllerVibration:
ToggleControllerVibration();
// Extra Sleep
xe::threading::Sleep(delay);
break;
case ButtonFunctions::CloseWindow:
window_->RequestClose();
break;
case ButtonFunctions::Unknown:
default:
break;
}
xe::threading::Sleep(delay);
return it->second;
}
void EmulatorWindow::VibrateController(xe::hid::InputSystem* input_sys,
bool toggle_rumble) {
const std::chrono::milliseconds rumble_duration(100);
// Hold lock while sleeping this thread for the duration of the rumble,
// otherwise the rumble may fail.
auto input_lock = input_sys->lock();
X_INPUT_VIBRATION vibration;
vibration.left_motor_speed = toggle_rumble ? UINT16_MAX : 0;
vibration.right_motor_speed = toggle_rumble ? UINT16_MAX : 0;
input_sys->SetState(0, &vibration);
// Vibration duration
if (toggle_rumble) {
xe::threading::Sleep(rumble_duration);
}
}
void EmulatorWindow::GamepadHotKeys() {
X_INPUT_STATE state;
const std::chrono::milliseconds thread_delay(75);
auto input_sys = emulator_->input_system();
if (input_sys) {
// SDL and XInput both support the guide button
while (true) {
// Block scope surrounding input_lock used to release the lock
{
auto input_lock = input_sys->lock();
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
input_sys->GetState(user_index, &state);
}
}
if (ProcessControllerHotkey(state.gamepad.buttons).rumble) {
// Enable Vibration
VibrateController(input_sys, true);
// Disable Vibration
VibrateController(input_sys, false);
}
xe::threading::Sleep(thread_delay);
}
}
}
void EmulatorWindow::ToggleGPUSetting(gpu_cvar value) {
switch (value) {
case gpu_cvar::ClearMemoryPageState:
D3D12SaveGPUSetting(D3D12GPUSetting::ClearMemoryPageState,
!cvars::d3d12_clear_memory_page_state);
break;
case gpu_cvar::ReadbackResolve:
D3D12SaveGPUSetting(D3D12GPUSetting::ReadbackResolve,
!cvars::d3d12_readback_resolve);
break;
}
}
// Determine if the Xbox Gamebar is enabled via the Windows registry
bool EmulatorWindow::IsUseNexusForGameBarEnabled() {
#ifdef _WIN32
const LPWSTR reg_path = L"SOFTWARE\\Microsoft\\GameBar";
const LPWSTR key = L"UseNexusForGameBarEnabled";
DWORD value = 0;
DWORD dataSize = sizeof(value);
RegGetValue(HKEY_CURRENT_USER, reg_path, key, RRF_RT_DWORD, nullptr, &value,
&dataSize);
return (bool)value;
#else
return false;
#endif
}
void EmulatorWindow::DisplayHotKeysConfig() {
std::string msg = "";
std::string msg_passthru = "";
bool guide_enabled = !IsUseNexusForGameBarEnabled() && cvars::guide_button;
for (auto const& [key, val] : controller_hotkey_map) {
std::string pretty_text = val.pretty;
if (!guide_enabled) {
pretty_text = std::regex_replace(
pretty_text,
std::regex("Guide", std::regex_constants::syntax_option_type::icase),
"Back");
}
if (emulator_->is_title_open() && !val.title_passthru) {
pretty_text += " (Disabled)";
}
if (val.title_passthru) {
msg += pretty_text + "\n";
} else {
msg_passthru += pretty_text + "\n";
}
}
// Add Title
msg.insert(0, "Gameplay Hotkeys\n");
// Prepend non-passthru hotkeys
msg_passthru += "\n";
msg.insert(0, msg_passthru);
msg += "\n";
msg += "Readback Resolve: " +
std::string(cvars::d3d12_readback_resolve ? "true\n" : "false\n");
msg +=
"Clear Memory Page State: " +
std::string(cvars::d3d12_clear_memory_page_state ? "true\n" : "false\n");
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(), "Controller Hotkeys",
msg);
}
xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
bool titleExists = !std::filesystem::exists(path);
if (path.empty() || titleExists) {
char* log_msg = path.empty() ? "Failed to launch title path is empty"
: "Failed to launch title path is invalid";
char* log_msg = path.empty() ? "Failed to launch title path is empty."
: "Failed to launch title path is invalid.";
XELOGE(log_msg);
MessageBoxA(nullptr, log_msg, "Title Launch Failed!", MB_ICONERROR);
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_.get(),
"Title Launch Failed!", log_msg);
return X_STATUS_NO_SUCH_FILE;
}
if (emulator_->is_title_open()) {
// Terminate the current title and start a new title.
// if (emulator_->TerminateTitle() == X_STATUS_SUCCESS) {
@ -1113,10 +1419,9 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
if (result) {
XELOGE("Failed to launch target: {:08X}", result);
MessageBoxA(
nullptr,
"Failed to launch title.\n\nCheck xenia.log for technical details.",
"Title Launch Failed!", MB_ICONERROR);
xe::ui::ImGuiDialog::ShowMessageBox(
imgui_drawer_.get(), "Title Launch Failed!",
"Failed to launch title.\n\nCheck xenia.log for technical details.");
} else {
AddRecentlyLaunchedTitle(path, emulator_->title_name());
}
@ -1124,6 +1429,12 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
return result;
}
void EmulatorWindow::RunPreviouslyPlayedTitle() {
if (recently_launched_titles_.size() >= 1) {
RunTitle(recently_launched_titles_[0].path_to_file);
}
}
void EmulatorWindow::FillRecentlyLaunchedTitlesMenu(
xe::ui::MenuItem* recent_menu) {
unsigned int index = 0;

View File

@ -25,6 +25,8 @@
#include "xenia/ui/windowed_app_context.h"
#include "xenia/xbox.h"
#define MAX_USERS 4
namespace xe {
namespace app {
@ -53,6 +55,8 @@ class EmulatorWindow {
static std::unique_ptr<EmulatorWindow> Create(
Emulator* emulator, ui::WindowedAppContext& app_context);
std::unique_ptr<xe::threading::Thread> Gamepad_HotKeys_Listener;
Emulator* emulator() const { return emulator_; }
ui::WindowedAppContext& app_context() const { return app_context_; }
ui::Window* window() const { return window_.get(); }
@ -69,6 +73,47 @@ class EmulatorWindow {
void ToggleFullscreen();
void SetInitializingShaderStorage(bool initializing);
// Types of button functions for hotkeys.
enum class ButtonFunctions {
ToggleFullscreen,
RunPreviouslyPlayedTitle,
CpuTimeScalarSetHalf,
CpuTimeScalarSetDouble,
CpuTimeScalarReset,
ToggleControllerVibration,
ClearMemoryPageState,
ReadbackResolve,
CloseWindow,
Unknown
};
enum class gpu_cvar {
ClearMemoryPageState,
ReadbackResolve,
};
class ControllerHotKey {
public:
// If true the hotkey can be activated while a title is running, otherwise
// false.
bool title_passthru;
// If true vibrate the controller after activating the hotkey, otherwise
// false.
bool rumble;
std::string pretty;
ButtonFunctions function;
ControllerHotKey(ButtonFunctions fn = ButtonFunctions::Unknown,
std::string pretty = "", bool rumble = false,
bool active = true) {
function = fn;
this->pretty = pretty;
title_passthru = active;
this->rumble = rumble;
}
};
private:
class EmulatorWindowListener final : public ui::WindowListener,
public ui::WindowInputListener {
@ -152,6 +197,13 @@ class EmulatorWindow {
void ShowFAQ();
void ShowBuildCommit();
EmulatorWindow::ControllerHotKey ProcessControllerHotkey(int buttons);
void VibrateController(xe::hid::InputSystem* input_sys, bool vibrate = true);
void GamepadHotKeys();
void ToggleGPUSetting(gpu_cvar index);
bool IsUseNexusForGameBarEnabled();
void DisplayHotKeysConfig();
xe::X_STATUS RunTitle(std::filesystem::path path);
void RunPreviouslyPlayedTitle();
void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu);

View File

@ -55,8 +55,21 @@ DEFINE_bool(d3d12_clear_memory_page_state, false,
namespace xe {
namespace gpu {
void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value) {
switch (setting) {
case D3D12GPUSetting::ClearMemoryPageState:
OVERRIDE_bool(d3d12_clear_memory_page_state, (bool)value);
break;
case D3D12GPUSetting::ReadbackResolve:
OVERRIDE_bool(d3d12_readback_resolve, (bool)value);
break;
}
}
namespace d3d12 {
// Generated with `xb buildshaders`.
namespace shaders {
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/apply_gamma_pwl_cs.h"

View File

@ -42,6 +42,14 @@
namespace xe {
namespace gpu {
enum class D3D12GPUSetting {
ReadbackResolve,
ClearMemoryPageState,
};
void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value);
namespace d3d12 {
struct MemExportRange {
uint32_t base_address_dwords;

View File

@ -345,6 +345,7 @@ void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
bool clear_log) const {
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty =
{
{ui::VirtualKey::kXInputPadGuide, "Guide"},
{ui::VirtualKey::kXInputPadA, "A"},
{ui::VirtualKey::kXInputPadB, "B"},
{ui::VirtualKey::kXInputPadX, "X"},

View File

@ -9,5 +9,5 @@
#include "xenia/hid/hid_flags.h"
DEFINE_bool(guide_button, false, "Forward guide button presses to guest.",
DEFINE_bool(guide_button, true, "Forward guide button presses to guest.",
"HID");

View File

@ -249,7 +249,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
// The order of this list is also the order in which events are send if
// multiple buttons change at once.
static_assert(sizeof(X_INPUT_GAMEPAD::buttons) == 2);
static constexpr std::array<ui::VirtualKey, 34> kVkLookup = {
static constexpr std::array<ui::VirtualKey, 35> kVkLookup = {
// 00 - True buttons from xinput button field
ui::VirtualKey::kXInputPadDpadUp,
ui::VirtualKey::kXInputPadDpadDown,
@ -261,7 +261,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
ui::VirtualKey::kXInputPadRThumbPress,
ui::VirtualKey::kXInputPadLShoulder,
ui::VirtualKey::kXInputPadRShoulder,
ui::VirtualKey::kNone, /* Guide has no VK */
// Guide has no VK (kNone), however using kXInputPadGuide.
ui::VirtualKey::kXInputPadGuide,
ui::VirtualKey::kNone, /* Unknown */
ui::VirtualKey::kXInputPadA,
ui::VirtualKey::kXInputPadB,

View File

@ -11,6 +11,7 @@
// constructing various tables.
// clang-format off
XE_HID_WINKEY_BINDING(Guide, "GUIDE" , keybind_guide , "0x08")
XE_HID_WINKEY_BINDING(DpadLeft, "DPAD_LEFT" , keybind_dpad_left , "^A" )
XE_HID_WINKEY_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" )
XE_HID_WINKEY_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" )

View File

@ -145,46 +145,49 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
IsKeyDown(b.input_key)) {
switch (b.output_key) {
case ui::VirtualKey::kXInputPadA:
buttons |= 0x1000; // XINPUT_GAMEPAD_A
buttons |= X_INPUT_GAMEPAD_A;
break;
case ui::VirtualKey::kXInputPadY:
buttons |= 0x8000; // XINPUT_GAMEPAD_Y
buttons |= X_INPUT_GAMEPAD_Y;
break;
case ui::VirtualKey::kXInputPadB:
buttons |= 0x2000; // XINPUT_GAMEPAD_B
buttons |= X_INPUT_GAMEPAD_B;
break;
case ui::VirtualKey::kXInputPadX:
buttons |= 0x4000; // XINPUT_GAMEPAD_X
buttons |= X_INPUT_GAMEPAD_X;
break;
case ui::VirtualKey::kXInputPadGuide:
buttons |= X_INPUT_GAMEPAD_GUIDE;
break;
case ui::VirtualKey::kXInputPadDpadLeft:
buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT
buttons |= X_INPUT_GAMEPAD_DPAD_LEFT;
break;
case ui::VirtualKey::kXInputPadDpadRight:
buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT
buttons |= X_INPUT_GAMEPAD_DPAD_RIGHT;
break;
case ui::VirtualKey::kXInputPadDpadDown:
buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN
buttons |= X_INPUT_GAMEPAD_DPAD_DOWN;
break;
case ui::VirtualKey::kXInputPadDpadUp:
buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP
buttons |= X_INPUT_GAMEPAD_DPAD_UP;
break;
case ui::VirtualKey::kXInputPadRThumbPress:
buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB
buttons |= X_INPUT_GAMEPAD_RIGHT_THUMB;
break;
case ui::VirtualKey::kXInputPadLThumbPress:
buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB
buttons |= X_INPUT_GAMEPAD_LEFT_THUMB;
break;
case ui::VirtualKey::kXInputPadBack:
buttons |= 0x0020; // XINPUT_GAMEPAD_BACK
buttons |= X_INPUT_GAMEPAD_BACK;
break;
case ui::VirtualKey::kXInputPadStart:
buttons |= 0x0010; // XINPUT_GAMEPAD_START
buttons |= X_INPUT_GAMEPAD_START;
break;
case ui::VirtualKey::kXInputPadLShoulder:
buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER
buttons |= X_INPUT_GAMEPAD_LEFT_SHOULDER;
break;
case ui::VirtualKey::kXInputPadRShoulder:
buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER
buttons |= X_INPUT_GAMEPAD_RIGHT_SHOULDER;
break;
case ui::VirtualKey::kXInputPadLTrigger:
left_trigger = 0xFF;

View File

@ -27,6 +27,7 @@ XInputInputDriver::XInputInputDriver(xe::ui::Window* window,
module_(nullptr),
XInputGetCapabilities_(nullptr),
XInputGetState_(nullptr),
XInputGetStateEx_(nullptr),
XInputGetKeystroke_(nullptr),
XInputSetState_(nullptr),
XInputEnable_(nullptr) {}
@ -37,6 +38,7 @@ XInputInputDriver::~XInputInputDriver() {
module_ = nullptr;
XInputGetCapabilities_ = nullptr;
XInputGetState_ = nullptr;
XInputGetStateEx_ = nullptr;
XInputGetKeystroke_ = nullptr;
XInputSetState_ = nullptr;
XInputEnable_ = nullptr;
@ -49,9 +51,13 @@ X_STATUS XInputInputDriver::Setup() {
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");
@ -67,17 +73,14 @@ X_STATUS XInputInputDriver::Setup() {
module_ = module;
XInputGetCapabilities_ = xigc;
XInputGetState_ = xigs;
XInputGetStateEx_ = xigsEx;
XInputGetKeystroke_ = xigk;
XInputSetState_ = xiss;
XInputEnable_ = xie;
if (cvars::guide_button) {
// Theoretically there is XInputGetStateEx
// but thats undocumented and milage varies.
XELOGW("XInput: Guide button support is not implemented.");
}
return X_STATUS_SUCCESS;
}
constexpr uint64_t SKIP_INVALID_CONTROLLER_TIME = 1100;
static uint64_t last_invalid_time[4];
@ -137,10 +140,18 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
if (skipper) {
return skipper;
}
XINPUT_STATE native_state;
auto xigs = (decltype(&XInputGetState))XInputGetState_;
struct {
XINPUT_STATE state;
unsigned int xinput_state_ex_padding; // Add padding in case we are using XInputGetStateEx
} native_state;
DWORD result = xigs(user_index, &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);
@ -148,14 +159,14 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
return result;
}
out_state->packet_number = native_state.dwPacketNumber;
out_state->gamepad.buttons = native_state.Gamepad.wButtons;
out_state->gamepad.left_trigger = native_state.Gamepad.bLeftTrigger;
out_state->gamepad.right_trigger = native_state.Gamepad.bRightTrigger;
out_state->gamepad.thumb_lx = native_state.Gamepad.sThumbLX;
out_state->gamepad.thumb_ly = native_state.Gamepad.sThumbLY;
out_state->gamepad.thumb_rx = native_state.Gamepad.sThumbRX;
out_state->gamepad.thumb_ry = native_state.Gamepad.sThumbRY;
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;
}

View File

@ -34,6 +34,7 @@ class XInputInputDriver final : public InputDriver {
void* module_;
void* XInputGetCapabilities_;
void* XInputGetState_;
void* XInputGetStateEx_;
void* XInputGetKeystroke_;
void* XInputSetState_;
void* XInputEnable_;

View File

@ -243,6 +243,7 @@ enum class VirtualKey : uint16_t {
// because XInput is the API used for the Xbox 360 controller.
// To avoid confusion between VK_GAMEPAD_* and VK_PAD_*, here they are
// prefixed with kXboxOne and kXInput respectively.
// https://learn.microsoft.com/en-us/uwp/api/windows.system.virtualkey
kXboxOneGamepadA = 0xC3,
kXboxOneGamepadB = 0xC4,
kXboxOneGamepadX = 0xC5,
@ -317,7 +318,7 @@ enum class VirtualKey : uint16_t {
// VK_PAD_* from XInput.h for XInputGetKeystroke. kXInput prefix added to
// distinguish from VK_GAMEPAD_*, added much later for the Xbox One
// controller.
// https://learn.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_keystroke
kXInputPadA = 0x5800,
kXInputPadB = 0x5801,
kXInputPadX = 0x5802,
@ -354,6 +355,8 @@ enum class VirtualKey : uint16_t {
kXInputPadRThumbUpRight = 0x5835,
kXInputPadRThumbDownRight = 0x5836,
kXInputPadRThumbDownLeft = 0x5837,
// Undocumented therefore kNone however using 0x5838 for now.
kXInputPadGuide = 0x5838,
};
} // namespace ui