225 lines
7.1 KiB
C++
225 lines
7.1 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. *
|
|
******************************************************************************
|
|
*
|
|
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
#include <rex/dbg.h>
|
|
#include <rex/input/flags.h>
|
|
#include <rex/input/input_driver.h>
|
|
#include <rex/input/input_system.h>
|
|
#include <rex/input/mnk/mnk_input_driver.h>
|
|
#include <rex/input/nop/nop_input_driver.h>
|
|
#include <rex/input/sdl/sdl_input_driver.h>
|
|
#include <rex/input/xinput/xinput_input_driver.h>
|
|
#include <rex/logging.h>
|
|
#include <rex/platform.h>
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kDefaultInputBackend =
|
|
#if REX_PLATFORM_WIN32
|
|
"xinput";
|
|
#else
|
|
"sdl";
|
|
#endif
|
|
|
|
} // namespace
|
|
|
|
REXCVAR_DEFINE_STRING(input_backend, kDefaultInputBackend, "Input", "Input backend: sdl, xinput")
|
|
.allowed({"sdl", "xinput"});
|
|
|
|
REXCVAR_DEFINE_BOOL(guide_button, false, "Input", "Enable guide button pass-through");
|
|
namespace rex::input {
|
|
|
|
InputSystem::InputSystem(rex::ui::Window* window) : window_(window) {}
|
|
|
|
InputSystem::~InputSystem() = default;
|
|
|
|
X_STATUS InputSystem::Setup() {
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
void InputSystem::Shutdown() {
|
|
drivers_.clear();
|
|
}
|
|
|
|
void InputSystem::AddDriver(std::unique_ptr<InputDriver> driver) {
|
|
drivers_.push_back(std::move(driver));
|
|
}
|
|
|
|
void InputSystem::AttachWindow(rex::ui::Window* window) {
|
|
window_ = window;
|
|
for (auto& driver : drivers_) {
|
|
driver->OnWindowAvailable(window);
|
|
}
|
|
}
|
|
|
|
void InputSystem::SetActiveCallback(std::function<bool()> callback) {
|
|
active_callback_ = callback;
|
|
for (auto& driver : drivers_) {
|
|
driver->set_is_active_callback(callback);
|
|
}
|
|
}
|
|
|
|
void InputSystem::SetMenuChordCallback(std::function<void()> callback) {
|
|
menu_chord_callback_ = std::move(callback);
|
|
}
|
|
|
|
X_RESULT InputSystem::GetCapabilities(uint32_t user_index, uint32_t flags,
|
|
X_INPUT_CAPABILITIES* out_caps) {
|
|
SCOPE_profile_cpu_f("hid");
|
|
|
|
bool any_connected = false;
|
|
for (auto& driver : drivers_) {
|
|
X_RESULT result = driver->GetCapabilities(user_index, flags, out_caps);
|
|
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
|
|
any_connected = true;
|
|
}
|
|
if (result == X_ERROR_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
X_RESULT InputSystem::GetState(uint32_t user_index, X_INPUT_STATE* out_state) {
|
|
SCOPE_profile_cpu_f("hid");
|
|
|
|
bool any_connected = false;
|
|
bool first_result = true;
|
|
X_INPUT_STATE merged = {};
|
|
|
|
for (auto& driver : drivers_) {
|
|
X_INPUT_STATE state = {};
|
|
X_RESULT result = driver->GetState(user_index, &state);
|
|
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
|
|
any_connected = true;
|
|
}
|
|
if (result == X_ERROR_SUCCESS) {
|
|
if (first_result) {
|
|
merged = state;
|
|
first_result = false;
|
|
} else {
|
|
// Merge: OR buttons, max triggers, max-magnitude sticks
|
|
merged.gamepad.buttons = static_cast<uint16_t>(merged.gamepad.buttons) |
|
|
static_cast<uint16_t>(state.gamepad.buttons);
|
|
merged.gamepad.left_trigger =
|
|
std::max(merged.gamepad.left_trigger, state.gamepad.left_trigger);
|
|
merged.gamepad.right_trigger =
|
|
std::max(merged.gamepad.right_trigger, state.gamepad.right_trigger);
|
|
|
|
auto merge_axis = [](int16_t a, int16_t b) -> int16_t {
|
|
return (std::abs(static_cast<int>(a)) >= std::abs(static_cast<int>(b))) ? a : b;
|
|
};
|
|
merged.gamepad.thumb_lx = merge_axis(merged.gamepad.thumb_lx, state.gamepad.thumb_lx);
|
|
merged.gamepad.thumb_ly = merge_axis(merged.gamepad.thumb_ly, state.gamepad.thumb_ly);
|
|
merged.gamepad.thumb_rx = merge_axis(merged.gamepad.thumb_rx, state.gamepad.thumb_rx);
|
|
merged.gamepad.thumb_ry = merge_axis(merged.gamepad.thumb_ry, state.gamepad.thumb_ry);
|
|
|
|
if (static_cast<uint32_t>(state.packet_number) >
|
|
static_cast<uint32_t>(merged.packet_number)) {
|
|
merged.packet_number = state.packet_number;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (first_result) {
|
|
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
constexpr uint16_t kMenuChordButtons = X_INPUT_GAMEPAD_BACK | X_INPUT_GAMEPAD_START;
|
|
const bool menu_chord_down =
|
|
(static_cast<uint16_t>(merged.gamepad.buttons) & kMenuChordButtons) == kMenuChordButtons;
|
|
if (menu_chord_down && !menu_chord_down_ && menu_chord_callback_) {
|
|
menu_chord_callback_();
|
|
}
|
|
menu_chord_down_ = menu_chord_down;
|
|
|
|
if (active_callback_ && !active_callback_()) {
|
|
std::memset(&merged.gamepad, 0, sizeof(merged.gamepad));
|
|
}
|
|
|
|
if (out_state) {
|
|
*out_state = merged;
|
|
}
|
|
return X_ERROR_SUCCESS;
|
|
}
|
|
|
|
X_RESULT InputSystem::SetState(uint32_t user_index, X_INPUT_VIBRATION* vibration) {
|
|
SCOPE_profile_cpu_f("hid");
|
|
|
|
bool any_connected = false;
|
|
for (auto& driver : drivers_) {
|
|
X_RESULT result = driver->SetState(user_index, vibration);
|
|
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
|
|
any_connected = true;
|
|
}
|
|
if (result == X_ERROR_SUCCESS) {
|
|
return result;
|
|
}
|
|
}
|
|
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
X_RESULT InputSystem::GetKeystroke(uint32_t user_index, uint32_t flags,
|
|
X_INPUT_KEYSTROKE* out_keystroke) {
|
|
SCOPE_profile_cpu_f("hid");
|
|
|
|
bool any_connected = false;
|
|
for (auto& driver : drivers_) {
|
|
X_RESULT result = driver->GetKeystroke(user_index, flags, out_keystroke);
|
|
if (result != X_ERROR_DEVICE_NOT_CONNECTED) {
|
|
any_connected = true;
|
|
}
|
|
if (result == X_ERROR_SUCCESS || result == X_ERROR_EMPTY) {
|
|
return result;
|
|
}
|
|
}
|
|
return any_connected ? X_ERROR_EMPTY : X_ERROR_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
std::unique_ptr<InputSystem> CreateDefaultInputSystem(bool tool_mode) {
|
|
auto input = std::make_unique<InputSystem>(nullptr);
|
|
|
|
if (!tool_mode) {
|
|
#if REX_PLATFORM_WIN32
|
|
if (REXCVAR_GET(input_backend) == "xinput") {
|
|
auto xinput_driver = std::make_unique<xinput::XinputInputDriver>(nullptr, 0);
|
|
if (xinput_driver->Setup() == X_STATUS_SUCCESS) {
|
|
input->AddDriver(std::move(xinput_driver));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (REXCVAR_GET(input_backend) == "sdl") {
|
|
auto sdl_driver = std::make_unique<sdl::SDLInputDriver>(nullptr, 0);
|
|
if (sdl_driver->Setup() == X_STATUS_SUCCESS) {
|
|
input->AddDriver(std::move(sdl_driver));
|
|
}
|
|
}
|
|
|
|
// MnK driver (keyboard/mouse -> controller emulation)
|
|
auto mnk_driver = std::make_unique<mnk::MnkInputDriver>(nullptr, 0);
|
|
if (mnk_driver->Setup() == X_STATUS_SUCCESS) {
|
|
input->AddDriver(std::move(mnk_driver));
|
|
}
|
|
}
|
|
|
|
// NOP driver (primary in tool mode, fallback otherwise)
|
|
uint8_t nop_index = tool_mode ? 0 : 1;
|
|
input->AddDriver(std::make_unique<nop::NopInputDriver>(nullptr, nop_index));
|
|
return input;
|
|
}
|
|
|
|
} // namespace rex::input
|