rexglue-skate3/src/input/input_system.cpp
2026-06-04 18:36:22 +01:00

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