/** ****************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include 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 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 callback) { active_callback_ = callback; for (auto& driver : drivers_) { driver->set_is_active_callback(callback); } } void InputSystem::SetMenuChordCallback(std::function 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(merged.gamepad.buttons) | static_cast(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(a)) >= std::abs(static_cast(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(state.packet_number) > static_cast(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(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 CreateDefaultInputSystem(bool tool_mode) { auto input = std::make_unique(nullptr); if (!tool_mode) { #if REX_PLATFORM_WIN32 if (REXCVAR_GET(input_backend) == "xinput") { auto xinput_driver = std::make_unique(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(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(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(nullptr, nop_index)); return input; } } // namespace rex::input