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:
parent
ea1003c6bf
commit
459497f0b6
|
@ -31,6 +31,7 @@
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/gpu/command_processor.h"
|
#include "xenia/gpu/command_processor.h"
|
||||||
|
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||||
#include "xenia/gpu/graphics_system.h"
|
#include "xenia/gpu/graphics_system.h"
|
||||||
#include "xenia/hid/input_system.h"
|
#include "xenia/hid/input_system.h"
|
||||||
#include "xenia/ui/file_picker.h"
|
#include "xenia/ui/file_picker.h"
|
||||||
|
@ -47,6 +48,14 @@
|
||||||
|
|
||||||
DECLARE_bool(debug);
|
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.",
|
DEFINE_bool(fullscreen, false, "Whether to launch the emulator in fullscreen.",
|
||||||
"Display");
|
"Display");
|
||||||
|
|
||||||
|
@ -134,6 +143,9 @@ using xe::ui::KeyEvent;
|
||||||
using xe::ui::MenuItem;
|
using xe::ui::MenuItem;
|
||||||
using xe::ui::UIEvent;
|
using xe::ui::UIEvent;
|
||||||
|
|
||||||
|
using namespace xe::hid;
|
||||||
|
using namespace xe::gpu;
|
||||||
|
|
||||||
const std::string kBaseTitle = "Xenia-canary";
|
const std::string kBaseTitle = "Xenia-canary";
|
||||||
|
|
||||||
EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
||||||
|
@ -230,6 +242,15 @@ void EmulatorWindow::OnEmulatorInitialized() {
|
||||||
if (cvars::fullscreen) {
|
if (cvars::fullscreen) {
|
||||||
SetFullscreen(true);
|
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) {
|
void EmulatorWindow::EmulatorWindowListener::OnClosing(ui::UIEvent& e) {
|
||||||
|
@ -608,6 +629,9 @@ bool EmulatorWindow::Initialize() {
|
||||||
hid_menu->AddChild(MenuItem::Create(
|
hid_menu->AddChild(MenuItem::Create(
|
||||||
MenuItem::Type::kString, "&Toggle controller vibration", "",
|
MenuItem::Type::kString, "&Toggle controller vibration", "",
|
||||||
std::bind(&EmulatorWindow::ToggleControllerVibration, this)));
|
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));
|
main_menu->AddChild(std::move(hid_menu));
|
||||||
|
|
||||||
|
@ -888,10 +912,10 @@ void EmulatorWindow::InstallContent() {
|
||||||
if (result != X_STATUS_SUCCESS) {
|
if (result != X_STATUS_SUCCESS) {
|
||||||
XELOGE("Failed to install content! Error code: {:08X}", result);
|
XELOGE("Failed to install content! Error code: {:08X}", result);
|
||||||
|
|
||||||
MessageBoxA(nullptr,
|
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||||
"Failed to install content!\n\nCheck xenia.log for technical "
|
imgui_drawer_.get(), "Failed to install content!",
|
||||||
"details.",
|
"Failed to install content!\n\nCheck xenia.log for technical "
|
||||||
"Failed to install content!", MB_ICONERROR);
|
"details.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1076,26 +1100,308 @@ void EmulatorWindow::SetInitializingShaderStorage(bool initializing) {
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmulatorWindow::RunPreviouslyPlayedTitle() {
|
// Notes:
|
||||||
if (recently_launched_titles_.size() >= 1) {
|
// Assumes titles do not use the guide button.
|
||||||
RunTitle(recently_launched_titles_[0].path_to_file);
|
// 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) {
|
xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
|
||||||
bool titleExists = !std::filesystem::exists(path);
|
bool titleExists = !std::filesystem::exists(path);
|
||||||
|
|
||||||
if (path.empty() || titleExists) {
|
if (path.empty() || titleExists) {
|
||||||
char* log_msg = path.empty() ? "Failed to launch title path is empty"
|
char* log_msg = path.empty() ? "Failed to launch title path is empty."
|
||||||
: "Failed to launch title path is invalid";
|
: "Failed to launch title path is invalid.";
|
||||||
|
|
||||||
XELOGE(log_msg);
|
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;
|
return X_STATUS_NO_SUCH_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (emulator_->is_title_open()) {
|
if (emulator_->is_title_open()) {
|
||||||
// Terminate the current title and start a new title.
|
// Terminate the current title and start a new title.
|
||||||
// if (emulator_->TerminateTitle() == X_STATUS_SUCCESS) {
|
// if (emulator_->TerminateTitle() == X_STATUS_SUCCESS) {
|
||||||
|
@ -1113,10 +1419,9 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
|
||||||
if (result) {
|
if (result) {
|
||||||
XELOGE("Failed to launch target: {:08X}", result);
|
XELOGE("Failed to launch target: {:08X}", result);
|
||||||
|
|
||||||
MessageBoxA(
|
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||||
nullptr,
|
imgui_drawer_.get(), "Title Launch Failed!",
|
||||||
"Failed to launch title.\n\nCheck xenia.log for technical details.",
|
"Failed to launch title.\n\nCheck xenia.log for technical details.");
|
||||||
"Title Launch Failed!", MB_ICONERROR);
|
|
||||||
} else {
|
} else {
|
||||||
AddRecentlyLaunchedTitle(path, emulator_->title_name());
|
AddRecentlyLaunchedTitle(path, emulator_->title_name());
|
||||||
}
|
}
|
||||||
|
@ -1124,6 +1429,12 @@ xe::X_STATUS EmulatorWindow::RunTitle(std::filesystem::path path) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmulatorWindow::RunPreviouslyPlayedTitle() {
|
||||||
|
if (recently_launched_titles_.size() >= 1) {
|
||||||
|
RunTitle(recently_launched_titles_[0].path_to_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EmulatorWindow::FillRecentlyLaunchedTitlesMenu(
|
void EmulatorWindow::FillRecentlyLaunchedTitlesMenu(
|
||||||
xe::ui::MenuItem* recent_menu) {
|
xe::ui::MenuItem* recent_menu) {
|
||||||
unsigned int index = 0;
|
unsigned int index = 0;
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
#define MAX_USERS 4
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
|
@ -53,6 +55,8 @@ class EmulatorWindow {
|
||||||
static std::unique_ptr<EmulatorWindow> Create(
|
static std::unique_ptr<EmulatorWindow> Create(
|
||||||
Emulator* emulator, ui::WindowedAppContext& app_context);
|
Emulator* emulator, ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
|
std::unique_ptr<xe::threading::Thread> Gamepad_HotKeys_Listener;
|
||||||
|
|
||||||
Emulator* emulator() const { return emulator_; }
|
Emulator* emulator() const { return emulator_; }
|
||||||
ui::WindowedAppContext& app_context() const { return app_context_; }
|
ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||||
ui::Window* window() const { return window_.get(); }
|
ui::Window* window() const { return window_.get(); }
|
||||||
|
@ -69,6 +73,47 @@ class EmulatorWindow {
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
void SetInitializingShaderStorage(bool initializing);
|
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:
|
private:
|
||||||
class EmulatorWindowListener final : public ui::WindowListener,
|
class EmulatorWindowListener final : public ui::WindowListener,
|
||||||
public ui::WindowInputListener {
|
public ui::WindowInputListener {
|
||||||
|
@ -152,6 +197,13 @@ class EmulatorWindow {
|
||||||
void ShowFAQ();
|
void ShowFAQ();
|
||||||
void ShowBuildCommit();
|
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);
|
xe::X_STATUS RunTitle(std::filesystem::path path);
|
||||||
void RunPreviouslyPlayedTitle();
|
void RunPreviouslyPlayedTitle();
|
||||||
void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu);
|
void FillRecentlyLaunchedTitlesMenu(xe::ui::MenuItem* recent_menu);
|
||||||
|
|
|
@ -55,8 +55,21 @@ DEFINE_bool(d3d12_clear_memory_page_state, false,
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
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 {
|
namespace d3d12 {
|
||||||
|
|
||||||
|
|
||||||
// Generated with `xb buildshaders`.
|
// Generated with `xb buildshaders`.
|
||||||
namespace shaders {
|
namespace shaders {
|
||||||
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/apply_gamma_pwl_cs.h"
|
#include "xenia/gpu/shaders/bytecode/d3d12_5_1/apply_gamma_pwl_cs.h"
|
||||||
|
|
|
@ -42,6 +42,14 @@
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
|
|
||||||
|
enum class D3D12GPUSetting {
|
||||||
|
ReadbackResolve,
|
||||||
|
ClearMemoryPageState,
|
||||||
|
};
|
||||||
|
|
||||||
|
void D3D12SaveGPUSetting(D3D12GPUSetting setting, uint64_t value);
|
||||||
|
|
||||||
namespace d3d12 {
|
namespace d3d12 {
|
||||||
struct MemExportRange {
|
struct MemExportRange {
|
||||||
uint32_t base_address_dwords;
|
uint32_t base_address_dwords;
|
||||||
|
|
|
@ -345,6 +345,7 @@ void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||||
bool clear_log) const {
|
bool clear_log) const {
|
||||||
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty =
|
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty =
|
||||||
{
|
{
|
||||||
|
{ui::VirtualKey::kXInputPadGuide, "Guide"},
|
||||||
{ui::VirtualKey::kXInputPadA, "A"},
|
{ui::VirtualKey::kXInputPadA, "A"},
|
||||||
{ui::VirtualKey::kXInputPadB, "B"},
|
{ui::VirtualKey::kXInputPadB, "B"},
|
||||||
{ui::VirtualKey::kXInputPadX, "X"},
|
{ui::VirtualKey::kXInputPadX, "X"},
|
||||||
|
|
|
@ -9,5 +9,5 @@
|
||||||
|
|
||||||
#include "xenia/hid/hid_flags.h"
|
#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");
|
"HID");
|
||||||
|
|
|
@ -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
|
// The order of this list is also the order in which events are send if
|
||||||
// multiple buttons change at once.
|
// multiple buttons change at once.
|
||||||
static_assert(sizeof(X_INPUT_GAMEPAD::buttons) == 2);
|
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
|
// 00 - True buttons from xinput button field
|
||||||
ui::VirtualKey::kXInputPadDpadUp,
|
ui::VirtualKey::kXInputPadDpadUp,
|
||||||
ui::VirtualKey::kXInputPadDpadDown,
|
ui::VirtualKey::kXInputPadDpadDown,
|
||||||
|
@ -261,7 +261,8 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
||||||
ui::VirtualKey::kXInputPadRThumbPress,
|
ui::VirtualKey::kXInputPadRThumbPress,
|
||||||
ui::VirtualKey::kXInputPadLShoulder,
|
ui::VirtualKey::kXInputPadLShoulder,
|
||||||
ui::VirtualKey::kXInputPadRShoulder,
|
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::kNone, /* Unknown */
|
||||||
ui::VirtualKey::kXInputPadA,
|
ui::VirtualKey::kXInputPadA,
|
||||||
ui::VirtualKey::kXInputPadB,
|
ui::VirtualKey::kXInputPadB,
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
// constructing various tables.
|
// constructing various tables.
|
||||||
|
|
||||||
// clang-format off
|
// 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(DpadLeft, "DPAD_LEFT" , keybind_dpad_left , "^A" )
|
||||||
XE_HID_WINKEY_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" )
|
XE_HID_WINKEY_BINDING(DpadRight, "DPAD_RIGHT" , keybind_dpad_right , "^D" )
|
||||||
XE_HID_WINKEY_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" )
|
XE_HID_WINKEY_BINDING(DpadDown, "DPAD_DOWN" , keybind_dpad_down , "^S" )
|
||||||
|
|
|
@ -145,46 +145,49 @@ X_RESULT WinKeyInputDriver::GetState(uint32_t user_index,
|
||||||
IsKeyDown(b.input_key)) {
|
IsKeyDown(b.input_key)) {
|
||||||
switch (b.output_key) {
|
switch (b.output_key) {
|
||||||
case ui::VirtualKey::kXInputPadA:
|
case ui::VirtualKey::kXInputPadA:
|
||||||
buttons |= 0x1000; // XINPUT_GAMEPAD_A
|
buttons |= X_INPUT_GAMEPAD_A;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadY:
|
case ui::VirtualKey::kXInputPadY:
|
||||||
buttons |= 0x8000; // XINPUT_GAMEPAD_Y
|
buttons |= X_INPUT_GAMEPAD_Y;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadB:
|
case ui::VirtualKey::kXInputPadB:
|
||||||
buttons |= 0x2000; // XINPUT_GAMEPAD_B
|
buttons |= X_INPUT_GAMEPAD_B;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadX:
|
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;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadDpadLeft:
|
case ui::VirtualKey::kXInputPadDpadLeft:
|
||||||
buttons |= 0x0004; // XINPUT_GAMEPAD_DPAD_LEFT
|
buttons |= X_INPUT_GAMEPAD_DPAD_LEFT;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadDpadRight:
|
case ui::VirtualKey::kXInputPadDpadRight:
|
||||||
buttons |= 0x0008; // XINPUT_GAMEPAD_DPAD_RIGHT
|
buttons |= X_INPUT_GAMEPAD_DPAD_RIGHT;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadDpadDown:
|
case ui::VirtualKey::kXInputPadDpadDown:
|
||||||
buttons |= 0x0002; // XINPUT_GAMEPAD_DPAD_DOWN
|
buttons |= X_INPUT_GAMEPAD_DPAD_DOWN;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadDpadUp:
|
case ui::VirtualKey::kXInputPadDpadUp:
|
||||||
buttons |= 0x0001; // XINPUT_GAMEPAD_DPAD_UP
|
buttons |= X_INPUT_GAMEPAD_DPAD_UP;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadRThumbPress:
|
case ui::VirtualKey::kXInputPadRThumbPress:
|
||||||
buttons |= 0x0080; // XINPUT_GAMEPAD_RIGHT_THUMB
|
buttons |= X_INPUT_GAMEPAD_RIGHT_THUMB;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadLThumbPress:
|
case ui::VirtualKey::kXInputPadLThumbPress:
|
||||||
buttons |= 0x0040; // XINPUT_GAMEPAD_LEFT_THUMB
|
buttons |= X_INPUT_GAMEPAD_LEFT_THUMB;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadBack:
|
case ui::VirtualKey::kXInputPadBack:
|
||||||
buttons |= 0x0020; // XINPUT_GAMEPAD_BACK
|
buttons |= X_INPUT_GAMEPAD_BACK;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadStart:
|
case ui::VirtualKey::kXInputPadStart:
|
||||||
buttons |= 0x0010; // XINPUT_GAMEPAD_START
|
buttons |= X_INPUT_GAMEPAD_START;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadLShoulder:
|
case ui::VirtualKey::kXInputPadLShoulder:
|
||||||
buttons |= 0x0100; // XINPUT_GAMEPAD_LEFT_SHOULDER
|
buttons |= X_INPUT_GAMEPAD_LEFT_SHOULDER;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadRShoulder:
|
case ui::VirtualKey::kXInputPadRShoulder:
|
||||||
buttons |= 0x0200; // XINPUT_GAMEPAD_RIGHT_SHOULDER
|
buttons |= X_INPUT_GAMEPAD_RIGHT_SHOULDER;
|
||||||
break;
|
break;
|
||||||
case ui::VirtualKey::kXInputPadLTrigger:
|
case ui::VirtualKey::kXInputPadLTrigger:
|
||||||
left_trigger = 0xFF;
|
left_trigger = 0xFF;
|
||||||
|
|
|
@ -27,6 +27,7 @@ XInputInputDriver::XInputInputDriver(xe::ui::Window* window,
|
||||||
module_(nullptr),
|
module_(nullptr),
|
||||||
XInputGetCapabilities_(nullptr),
|
XInputGetCapabilities_(nullptr),
|
||||||
XInputGetState_(nullptr),
|
XInputGetState_(nullptr),
|
||||||
|
XInputGetStateEx_(nullptr),
|
||||||
XInputGetKeystroke_(nullptr),
|
XInputGetKeystroke_(nullptr),
|
||||||
XInputSetState_(nullptr),
|
XInputSetState_(nullptr),
|
||||||
XInputEnable_(nullptr) {}
|
XInputEnable_(nullptr) {}
|
||||||
|
@ -37,6 +38,7 @@ XInputInputDriver::~XInputInputDriver() {
|
||||||
module_ = nullptr;
|
module_ = nullptr;
|
||||||
XInputGetCapabilities_ = nullptr;
|
XInputGetCapabilities_ = nullptr;
|
||||||
XInputGetState_ = nullptr;
|
XInputGetState_ = nullptr;
|
||||||
|
XInputGetStateEx_ = nullptr;
|
||||||
XInputGetKeystroke_ = nullptr;
|
XInputGetKeystroke_ = nullptr;
|
||||||
XInputSetState_ = nullptr;
|
XInputSetState_ = nullptr;
|
||||||
XInputEnable_ = nullptr;
|
XInputEnable_ = nullptr;
|
||||||
|
@ -49,9 +51,13 @@ X_STATUS XInputInputDriver::Setup() {
|
||||||
return X_STATUS_DLL_NOT_FOUND;
|
return X_STATUS_DLL_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support guide button with XInput using XInputGetStateEx
|
||||||
|
auto const XInputGetStateEx = (LPCSTR)100;
|
||||||
|
|
||||||
// Required.
|
// Required.
|
||||||
auto xigc = GetProcAddress(module, "XInputGetCapabilities");
|
auto xigc = GetProcAddress(module, "XInputGetCapabilities");
|
||||||
auto xigs = GetProcAddress(module, "XInputGetState");
|
auto xigs = GetProcAddress(module, "XInputGetState");
|
||||||
|
auto xigsEx = GetProcAddress(module, XInputGetStateEx);
|
||||||
auto xigk = GetProcAddress(module, "XInputGetKeystroke");
|
auto xigk = GetProcAddress(module, "XInputGetKeystroke");
|
||||||
auto xiss = GetProcAddress(module, "XInputSetState");
|
auto xiss = GetProcAddress(module, "XInputSetState");
|
||||||
|
|
||||||
|
@ -67,17 +73,14 @@ X_STATUS XInputInputDriver::Setup() {
|
||||||
module_ = module;
|
module_ = module;
|
||||||
XInputGetCapabilities_ = xigc;
|
XInputGetCapabilities_ = xigc;
|
||||||
XInputGetState_ = xigs;
|
XInputGetState_ = xigs;
|
||||||
|
XInputGetStateEx_ = xigsEx;
|
||||||
XInputGetKeystroke_ = xigk;
|
XInputGetKeystroke_ = xigk;
|
||||||
XInputSetState_ = xiss;
|
XInputSetState_ = xiss;
|
||||||
XInputEnable_ = xie;
|
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;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr uint64_t SKIP_INVALID_CONTROLLER_TIME = 1100;
|
constexpr uint64_t SKIP_INVALID_CONTROLLER_TIME = 1100;
|
||||||
static uint64_t last_invalid_time[4];
|
static uint64_t last_invalid_time[4];
|
||||||
|
|
||||||
|
@ -137,10 +140,18 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
|
||||||
if (skipper) {
|
if (skipper) {
|
||||||
return 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) {
|
||||||
if (result == ERROR_DEVICE_NOT_CONNECTED) {
|
if (result == ERROR_DEVICE_NOT_CONNECTED) {
|
||||||
set_skip(user_index);
|
set_skip(user_index);
|
||||||
|
@ -148,14 +159,14 @@ X_RESULT XInputInputDriver::GetState(uint32_t user_index,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
out_state->packet_number = native_state.dwPacketNumber;
|
out_state->packet_number = native_state.state.dwPacketNumber;
|
||||||
out_state->gamepad.buttons = native_state.Gamepad.wButtons;
|
out_state->gamepad.buttons = native_state.state.Gamepad.wButtons;
|
||||||
out_state->gamepad.left_trigger = native_state.Gamepad.bLeftTrigger;
|
out_state->gamepad.left_trigger = native_state.state.Gamepad.bLeftTrigger;
|
||||||
out_state->gamepad.right_trigger = native_state.Gamepad.bRightTrigger;
|
out_state->gamepad.right_trigger = native_state.state.Gamepad.bRightTrigger;
|
||||||
out_state->gamepad.thumb_lx = native_state.Gamepad.sThumbLX;
|
out_state->gamepad.thumb_lx = native_state.state.Gamepad.sThumbLX;
|
||||||
out_state->gamepad.thumb_ly = native_state.Gamepad.sThumbLY;
|
out_state->gamepad.thumb_ly = native_state.state.Gamepad.sThumbLY;
|
||||||
out_state->gamepad.thumb_rx = native_state.Gamepad.sThumbRX;
|
out_state->gamepad.thumb_rx = native_state.state.Gamepad.sThumbRX;
|
||||||
out_state->gamepad.thumb_ry = native_state.Gamepad.sThumbRY;
|
out_state->gamepad.thumb_ry = native_state.state.Gamepad.sThumbRY;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ class XInputInputDriver final : public InputDriver {
|
||||||
void* module_;
|
void* module_;
|
||||||
void* XInputGetCapabilities_;
|
void* XInputGetCapabilities_;
|
||||||
void* XInputGetState_;
|
void* XInputGetState_;
|
||||||
|
void* XInputGetStateEx_;
|
||||||
void* XInputGetKeystroke_;
|
void* XInputGetKeystroke_;
|
||||||
void* XInputSetState_;
|
void* XInputSetState_;
|
||||||
void* XInputEnable_;
|
void* XInputEnable_;
|
||||||
|
|
|
@ -243,6 +243,7 @@ enum class VirtualKey : uint16_t {
|
||||||
// because XInput is the API used for the Xbox 360 controller.
|
// because XInput is the API used for the Xbox 360 controller.
|
||||||
// To avoid confusion between VK_GAMEPAD_* and VK_PAD_*, here they are
|
// To avoid confusion between VK_GAMEPAD_* and VK_PAD_*, here they are
|
||||||
// prefixed with kXboxOne and kXInput respectively.
|
// prefixed with kXboxOne and kXInput respectively.
|
||||||
|
// https://learn.microsoft.com/en-us/uwp/api/windows.system.virtualkey
|
||||||
kXboxOneGamepadA = 0xC3,
|
kXboxOneGamepadA = 0xC3,
|
||||||
kXboxOneGamepadB = 0xC4,
|
kXboxOneGamepadB = 0xC4,
|
||||||
kXboxOneGamepadX = 0xC5,
|
kXboxOneGamepadX = 0xC5,
|
||||||
|
@ -317,7 +318,7 @@ enum class VirtualKey : uint16_t {
|
||||||
// VK_PAD_* from XInput.h for XInputGetKeystroke. kXInput prefix added to
|
// VK_PAD_* from XInput.h for XInputGetKeystroke. kXInput prefix added to
|
||||||
// distinguish from VK_GAMEPAD_*, added much later for the Xbox One
|
// distinguish from VK_GAMEPAD_*, added much later for the Xbox One
|
||||||
// controller.
|
// controller.
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/xinput/ns-xinput-xinput_keystroke
|
||||||
kXInputPadA = 0x5800,
|
kXInputPadA = 0x5800,
|
||||||
kXInputPadB = 0x5801,
|
kXInputPadB = 0x5801,
|
||||||
kXInputPadX = 0x5802,
|
kXInputPadX = 0x5802,
|
||||||
|
@ -354,6 +355,8 @@ enum class VirtualKey : uint16_t {
|
||||||
kXInputPadRThumbUpRight = 0x5835,
|
kXInputPadRThumbUpRight = 0x5835,
|
||||||
kXInputPadRThumbDownRight = 0x5836,
|
kXInputPadRThumbDownRight = 0x5836,
|
||||||
kXInputPadRThumbDownLeft = 0x5837,
|
kXInputPadRThumbDownLeft = 0x5837,
|
||||||
|
// Undocumented therefore kNone however using 0x5838 for now.
|
||||||
|
kXInputPadGuide = 0x5838,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
Loading…
Reference in New Issue