From 0ab92255184340f9fc2a3690b7283021a119d18d Mon Sep 17 00:00:00 2001 From: nullequal Date: Wed, 13 May 2026 13:51:32 +0300 Subject: [PATCH 01/14] input: add SDL backend --- CMakeLists.txt | 11 ++++ src/common/config.cpp | 13 +++++ src/common/config.hpp | 18 ++++++ src/core/CMakeLists.txt | 15 +++++ src/core/input/device_manager.cpp | 25 ++++++++- src/core/input/sdl/controller.cpp | 88 ++++++++++++++++++++++++++++++ src/core/input/sdl/controller.hpp | 22 ++++++++ src/core/input/sdl/device_list.cpp | 54 ++++++++++++++++++ src/core/input/sdl/device_list.hpp | 14 +++++ src/core/input/sdl/keyboard.cpp | 74 +++++++++++++++++++++++++ src/core/input/sdl/keyboard.hpp | 15 +++++ src/frontend/sdl3/window.cpp | 5 +- 12 files changed, 352 insertions(+), 2 deletions(-) create mode 100644 src/core/input/sdl/controller.cpp create mode 100644 src/core/input/sdl/controller.hpp create mode 100644 src/core/input/sdl/device_list.cpp create mode 100644 src/core/input/sdl/device_list.hpp create mode 100644 src/core/input/sdl/keyboard.cpp create mode 100644 src/core/input/sdl/keyboard.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dc50407..975bdad5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ else () endif () option(CUBEB_ENABLED "Enable the cubeb audio backend" ON) +option(SDL_ENABLED "Enable building SDL files" ON) if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") if (TARGET_ARCHS MATCHES "arm64") option(HYPERVISOR_ENABLED "Enable the Hypervisor CPU backend" ON) @@ -29,6 +30,10 @@ else () set(FRONTEND "SDL3") endif () +if (FRONTEND STREQUAL "SDL3") + set(SDL_ENABLED ON) +endif () + if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") option(MACOS_BUNDLE "Build a macOS app bundle" OFF) elseif (CMAKE_SYSTEM_NAME STREQUAL "iOS") @@ -161,6 +166,12 @@ else () add_compile_definitions(HYDRA_CUBEB_ENABLED=0) endif () +if (SDL_ENABLED) + add_compile_definitions(HYDRA_SDL_ENABLED=1) +else () + add_compile_definitions(HYDRA_SDL_ENABLED=0) +endif () + if (APPLE) set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC NO) endif () diff --git a/src/common/config.cpp b/src/common/config.cpp index 6b5adea1..85b4165e 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -12,6 +12,8 @@ TOML11_DEFINE_CONVERSION_ENUM(hydra::Resolution, Auto, "auto", _720p, "720p", _1080p, "1080p", _1440p, "1440p", _2160p, "2160p", _4320p, "4320p", AutoExact, "Auto exact", Custom, "custom") +TOML11_DEFINE_CONVERSION_ENUM(hydra::InputBackend, Sdl, "Sdl", GameController, + "GameController") TOML11_DEFINE_CONVERSION_ENUM(hydra::AudioBackend, Null, "Null", Cubeb, "Cubeb") TOML11_DEFINE_CONVERSION_ENUM(hydra::LogOutput, None, "none", StdOut, "stdout", File, "file") @@ -126,6 +128,7 @@ void Config::LoadDefaults() { shader_backend = GetDefaultShaderBackend(); display_resolution = GetDefaultDisplayResolution(); custom_display_resolution = GetDefaultCustomDisplayResolution(); + input_backend = GetDefaultInputBackend(); audio_backend = GetDefaultAudioBackend(); user_id = GetDefaultUserID(); firmware_path = GetDefaultFirmwarePath(); @@ -174,6 +177,7 @@ void Config::Serialize() { { auto& input = data.at("Input"); + input["backend"] = input_backend; input["profiles"] = input_profiles; } @@ -253,6 +257,8 @@ void Config::Deserialize() { } if (data.contains("Input")) { const auto& input = data.at("Input"); + input_backend = toml::find_or(input, "backend", + GetDefaultInputBackend()); input_profiles = toml::find_or>( input, "profiles", GetDefaultInputProfiles()); } @@ -340,6 +346,12 @@ void Config::Deserialize() { display_resolution); } + if (input_backend == InputBackend::Invalid) { + input_backend = GetDefaultInputBackend(); + LOG_WARN(Other, "Invalid input backend, falling back to {}", + input_backend); + } + if (audio_backend == AudioBackend::Invalid) { audio_backend = GetDefaultAudioBackend(); LOG_WARN(Other, "Invalid audio backend, falling back to {}", @@ -358,6 +370,7 @@ void Config::Log() { LOG_INFO(Other, "Display resolution: {}", display_resolution); LOG_INFO(Other, "Custom display resolution: {}x{}", custom_display_resolution.x(), custom_display_resolution.y()); + LOG_INFO(Other, "Input backend: {}", input_backend); LOG_INFO(Other, "Audio backend: {}", audio_backend); LOG_INFO(Other, "User ID: {:032x}", user_id); LOG_INFO(Other, "Firmware path: {}", firmware_path); diff --git a/src/common/config.hpp b/src/common/config.hpp index 113999e7..8a8997e9 100644 --- a/src/common/config.hpp +++ b/src/common/config.hpp @@ -44,6 +44,13 @@ enum class Resolution : u32 { STRONG_TYPEDEF(CustomResolution, uint2); +enum class InputBackend : u32 { + Invalid = 0, + + Sdl, + GameController, +}; + enum class AudioBackend : u32 { Invalid = 0, @@ -96,6 +103,7 @@ class Config { ShaderBackend shader_backend; Resolution display_resolution; uint2 custom_display_resolution; + InputBackend input_backend; AudioBackend audio_backend; uuid_t user_id; std::string firmware_path; @@ -130,6 +138,13 @@ class Config { ShaderBackend GetDefaultShaderBackend() const { return ShaderBackend::Msl; } Resolution GetDefaultDisplayResolution() const { return Resolution::Auto; } uint2 GetDefaultCustomDisplayResolution() const { return {1920, 1080}; } + InputBackend GetDefaultInputBackend() const { +#ifdef PLATFORM_APPLE + return InputBackend::GameController; +#else + return InputBackend::Sdl; +#endif + } AudioBackend GetDefaultAudioBackend() const { #if HYDRA_CUBEB_ENABLED return AudioBackend::Cubeb; @@ -170,6 +185,7 @@ class Config { REF_GETTER(shader_backend, GetShaderBackend); REF_GETTER(display_resolution, GetDisplayResolution); REF_GETTER(custom_display_resolution, GetCustomDisplayResolution); + REF_GETTER(input_backend, GetInputBackend) REF_GETTER(audio_backend, GetAudioBackend); REF_GETTER(user_id, GetUserId); REF_GETTER(firmware_path, GetFirmwarePath); @@ -200,6 +216,8 @@ ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, Resolution, resolution, Auto, "auto", _720p, "720p", _1080p, "1080p", _1440p, "1440p", _2160p, "2160p", _4320p, "4320p", AutoExact, "Auto exact", Custom, "custom") +ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, InputBackend, input_backend, Sdl, + "Sdl", GameController, "GameController") ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, AudioBackend, audio_backend, Null, "Null", Cubeb, "Cubeb") ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, LogOutput, output, None, "none", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0df8b0f0..55c612b2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -669,6 +669,17 @@ if (CUBEB_ENABLED) ) endif () +if (SDL_ENABLED) + target_sources(hydra-core PRIVATE + input/sdl/keyboard.cpp + input/sdl/keyboard.hpp + input/sdl/controller.cpp + input/sdl/controller.hpp + input/sdl/device_list.cpp + input/sdl/device_list.hpp + ) +endif () + target_include_directories(hydra-core PRIVATE ${CMAKE_SOURCE_DIR}/externals/libyaz0/include) # TODO: only link Apple frameworks if Apple target_link_libraries(hydra-core PRIVATE hydra_warnings "-framework Foundation" "-framework Metal" "-framework QuartzCore" "-framework GameController" dynarmic hatch libyaz0 nx2elf hydra-common) @@ -681,6 +692,10 @@ if (CUBEB_ENABLED) target_link_libraries(hydra-core PRIVATE cubeb) endif () +if (SDL_ENABLED) + target_link_libraries(hydra-core PRIVATE sdl3) +endif () + if (CMAKE_SYSTEM_NAME STREQUAL "iOS") set_target_properties(hydra-core PROPERTIES XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${IOS_DEPLOYMENT_TARGET} diff --git a/src/core/input/device_manager.cpp b/src/core/input/device_manager.cpp index 21fdfbfe..e9223769 100644 --- a/src/core/input/device_manager.cpp +++ b/src/core/input/device_manager.cpp @@ -1,6 +1,10 @@ #include "core/input/device_manager.hpp" +#ifdef PLATFORM_APPLE #include "core/input/apple_gc/device_list.hpp" +#endif + +#include "core/input/sdl/device_list.hpp" namespace hydra::input { @@ -16,7 +20,26 @@ DeviceManager::DeviceManager() { } // Device list - device_list = new apple_gc::DeviceList(); + switch (CONFIG_INSTANCE.GetInputBackend()) { + case InputBackend::Sdl: +#if HYDRA_SDL_ENABLED + device_list = new sdl::DeviceList(); +#else + LOG_FATAL(Other, "SDL not supported"); +#endif + break; + case InputBackend::GameController: +#ifdef PLATFORM_APPLE + device_list = new apple_gc::DeviceList(); +#else + LOG_FATAL(Other, "GameController not supported"); +#endif + break; + default: + LOG_FATAL(Other, "Unknown input backend {}", + CONFIG_INSTANCE.GetInputBackend()); + break; + } } DeviceManager::~DeviceManager() { delete device_list; } diff --git a/src/core/input/sdl/controller.cpp b/src/core/input/sdl/controller.cpp new file mode 100644 index 00000000..25ccab9e --- /dev/null +++ b/src/core/input/sdl/controller.cpp @@ -0,0 +1,88 @@ +#include "core/input/sdl/controller.hpp" + +#include + +#include "frontend/sdl3/const.hpp" + +namespace hydra::input::sdl { + +bool Controller::IsPressedImpl(ControllerInput input) { +#define BUTTON_CASE(input, sdl_button) \ + case ControllerInput::input: \ + return SDL_GetGamepadButton(handle, sdl_button); \ + break; + + switch (input) { + BUTTON_CASE(A, SDL_GAMEPAD_BUTTON_EAST) + BUTTON_CASE(B, SDL_GAMEPAD_BUTTON_SOUTH) + BUTTON_CASE(X, SDL_GAMEPAD_BUTTON_NORTH) + BUTTON_CASE(Y, SDL_GAMEPAD_BUTTON_SOUTH) + BUTTON_CASE(StickL, SDL_GAMEPAD_BUTTON_LEFT_STICK) + BUTTON_CASE(StickR, SDL_GAMEPAD_BUTTON_RIGHT_STICK) + BUTTON_CASE(L, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER) + BUTTON_CASE(R, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER) + case ControllerInput::ZL: + return SDL_GetGamepadAxis(handle, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + break; + case ControllerInput::ZR: + return SDL_GetGamepadAxis(handle, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + break; + // TODO: implement + case ControllerInput::Plus: + case ControllerInput::Minus: + case ControllerInput::Left: + case ControllerInput::Up: + case ControllerInput::Right: + case ControllerInput::Down: + case ControllerInput::LeftSL: + case ControllerInput::LeftSR: + case ControllerInput::RightSL: + case ControllerInput::RightSR: + default: + LOG_NOT_IMPLEMENTED(Input, "Controller button {}", input); + return false; + } +} + +f32 Controller::GetAxisValueImpl(ControllerInput input) { +#define AXIS_CASE(input, sdl_dpad, direction) \ + case ControllerInput::input: \ + value = SDL_GetGamepadAxis(handle, sdl_dpad); \ + dir = AnalogStickDirection::direction; \ + break; + + AnalogStickDirection dir; + i16 value; + + switch (input) { + AXIS_CASE(StickLLeft, SDL_GAMEPAD_AXIS_LEFTX, Left) + AXIS_CASE(StickLUp, SDL_GAMEPAD_AXIS_LEFTY, Up) + AXIS_CASE(StickLRight, SDL_GAMEPAD_AXIS_LEFTX, Right) + AXIS_CASE(StickLDown, SDL_GAMEPAD_AXIS_LEFTY, Down) + AXIS_CASE(StickRLeft, SDL_GAMEPAD_AXIS_RIGHTX, Left) + AXIS_CASE(StickRUp, SDL_GAMEPAD_AXIS_RIGHTY, Up) + AXIS_CASE(StickRRight, SDL_GAMEPAD_AXIS_RIGHTX, Right) + AXIS_CASE(StickRDown, SDL_GAMEPAD_AXIS_RIGHTY, Down) + default: + LOG_NOT_IMPLEMENTED(Input, "Controller axis {}", input); + return 0.0f; + } + + // TODO: make less ugly + switch (dir) { + case AnalogStickDirection::Right: + return std::max(static_cast(0), value) / + static_cast(std::numeric_limits::max()); + case AnalogStickDirection::Left: + return std::min(static_cast(0), value) / + static_cast(std::numeric_limits::min()); + case AnalogStickDirection::Up: + return std::min(static_cast(0), value) / + static_cast(std::numeric_limits::min()); + case AnalogStickDirection::Down: + return std::max(static_cast(0), value) / + static_cast(std::numeric_limits::max()); + } +} + +} // namespace hydra::input::sdl \ No newline at end of file diff --git a/src/core/input/sdl/controller.hpp b/src/core/input/sdl/controller.hpp new file mode 100644 index 00000000..801385c5 --- /dev/null +++ b/src/core/input/sdl/controller.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "core/input/controller.hpp" + +#include "frontend/sdl3/const.hpp" + +namespace hydra::input::sdl { + +class Controller : public IController { + public: + Controller(SDL_Gamepad* handle_) : handle{handle_} {} + ~Controller() { SDL_CloseGamepad(handle); } + + protected: + bool IsPressedImpl(ControllerInput input) override; + f32 GetAxisValueImpl(ControllerInput input) override; + + private: + SDL_Gamepad* handle; +}; + +} // namespace hydra::input::sdl diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp new file mode 100644 index 00000000..16d8f42f --- /dev/null +++ b/src/core/input/sdl/device_list.cpp @@ -0,0 +1,54 @@ +#include "core/input/sdl/device_list.hpp" + +#include "core/input/sdl/controller.hpp" +#include "core/input/sdl/keyboard.hpp" +#include "frontend/sdl3/const.hpp" + +namespace hydra::input::sdl { + +bool EventWatcher(void* userdata, SDL_Event* e) { + switch (e->type) { + case SDL_EVENT_GAMEPAD_ADDED: { + SDL_Gamepad* gp = SDL_OpenGamepad(e->gdevice.which); + std::string name = SDL_GetGamepadName(gp); + LOG_INFO(Input, "Controller connected: {}", name); + auto devices = static_cast*>(userdata); + devices->insert({name, new Controller(gp)}); + break; + } + case SDL_EVENT_GAMEPAD_REMOVED: { + SDL_Gamepad* gp = SDL_GetGamepadFromID(e->gdevice.which); + std::string name = SDL_GetGamepadName(gp); + LOG_INFO(Input, "Controller disconnected: {}", name); + auto devices = static_cast*>(userdata); + auto it = devices->find(name); + ASSERT(it != devices->end(), Input, "Controller not connected"); + delete it->second; + SDL_CloseGamepad(gp); + devices->erase(it); + } + } + return false; +} + +DeviceList::DeviceList() { + // TODO: more than one keyboard + devices["keyboard"] = new Keyboard(); + LOG_INFO(Input, "Keyboard connected: keyboard"); + SDL_AddEventWatch(EventWatcher, &devices); +} + +DeviceList::~DeviceList() { + LOG_INFO(Input, "Keyboard disconnected: keyboard"); + auto it = devices.find("keyboard"); + delete it->second; + devices.erase(it); + for (auto [name, device] : devices) { + LOG_INFO(Input, "Controller disconnected: {}", name); + delete device; + devices.erase(name); + } + SDL_RemoveEventWatch(EventWatcher, &devices); +} + +} // namespace hydra::input::sdl \ No newline at end of file diff --git a/src/core/input/sdl/device_list.hpp b/src/core/input/sdl/device_list.hpp new file mode 100644 index 00000000..1d9075ba --- /dev/null +++ b/src/core/input/sdl/device_list.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "core/input/device_list.hpp" +#include "frontend/sdl3/const.hpp" + +namespace hydra::input::sdl { + +class DeviceList : public IDeviceList { + public: + DeviceList(); + ~DeviceList(); +}; + +} // namespace hydra::input::sdl diff --git a/src/core/input/sdl/keyboard.cpp b/src/core/input/sdl/keyboard.cpp new file mode 100644 index 00000000..ee7cc5b8 --- /dev/null +++ b/src/core/input/sdl/keyboard.cpp @@ -0,0 +1,74 @@ +#include "core/input/sdl/keyboard.hpp" + +#include "frontend/sdl3/const.hpp" + +namespace hydra::input::sdl { + +bool Keyboard::IsPressedImpl(Key key) { +#define KEY_CASE(key, sdl_key) \ + case Key::key: \ + return st[sdl_key]; \ + break; + + const bool* st = SDL_GetKeyboardState(nullptr); + + switch (key) { + KEY_CASE(Q, SDL_SCANCODE_Q) + KEY_CASE(W, SDL_SCANCODE_W) + KEY_CASE(E, SDL_SCANCODE_E) + KEY_CASE(R, SDL_SCANCODE_R) + KEY_CASE(T, SDL_SCANCODE_T) + KEY_CASE(Y, SDL_SCANCODE_Y) + KEY_CASE(U, SDL_SCANCODE_U) + KEY_CASE(I, SDL_SCANCODE_I) + KEY_CASE(O, SDL_SCANCODE_O) + KEY_CASE(P, SDL_SCANCODE_P) + KEY_CASE(A, SDL_SCANCODE_A) + KEY_CASE(S, SDL_SCANCODE_S) + KEY_CASE(D, SDL_SCANCODE_D) + KEY_CASE(F, SDL_SCANCODE_F) + KEY_CASE(G, SDL_SCANCODE_G) + KEY_CASE(H, SDL_SCANCODE_H) + KEY_CASE(J, SDL_SCANCODE_J) + KEY_CASE(K, SDL_SCANCODE_K) + KEY_CASE(L, SDL_SCANCODE_L) + KEY_CASE(Z, SDL_SCANCODE_Z) + KEY_CASE(X, SDL_SCANCODE_X) + KEY_CASE(C, SDL_SCANCODE_C) + KEY_CASE(V, SDL_SCANCODE_V) + KEY_CASE(B, SDL_SCANCODE_B) + KEY_CASE(N, SDL_SCANCODE_N) + KEY_CASE(M, SDL_SCANCODE_M) + + // TODO: numbers + + // TODO: other stuff + + KEY_CASE(ArrowLeft, SDL_SCANCODE_LEFT) + KEY_CASE(ArrowRight, SDL_SCANCODE_RIGHT) + KEY_CASE(ArrowUp, SDL_SCANCODE_UP) + KEY_CASE(ArrowDown, SDL_SCANCODE_DOWN) + + KEY_CASE(Enter, SDL_SCANCODE_RETURN) + KEY_CASE(Tab, SDL_SCANCODE_TAB) + KEY_CASE(Backspace, SDL_SCANCODE_BACKSPACE) + KEY_CASE(Space, SDL_SCANCODE_SPACE) + + KEY_CASE(LeftShift, SDL_SCANCODE_LSHIFT) + KEY_CASE(RightShift, SDL_SCANCODE_RSHIFT) + KEY_CASE(LeftControl, SDL_SCANCODE_LCTRL) + KEY_CASE(RightControl, SDL_SCANCODE_RCTRL) + KEY_CASE(LeftAlt, SDL_SCANCODE_LALT) + KEY_CASE(RightAlt, SDL_SCANCODE_RALT) + // TODO: correct? + KEY_CASE(LeftSuper, SDL_SCANCODE_LGUI) + KEY_CASE(RightSuper, SDL_SCANCODE_RGUI) + default: + LOG_NOT_IMPLEMENTED(Input, "Key {}", key); + return false; + } + +#undef KEY_CASE +} + +} // namespace hydra::input::sdl \ No newline at end of file diff --git a/src/core/input/sdl/keyboard.hpp b/src/core/input/sdl/keyboard.hpp new file mode 100644 index 00000000..4d5bcd48 --- /dev/null +++ b/src/core/input/sdl/keyboard.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "core/input/keyboard.hpp" + +namespace hydra::input::sdl { + +class Keyboard : public IKeyboard { + public: + Keyboard() {} + + protected: + bool IsPressedImpl(Key key) override; +}; + +} // namespace hydra::input::sdl diff --git a/src/frontend/sdl3/window.cpp b/src/frontend/sdl3/window.cpp index 9e6136d9..8f401971 100644 --- a/src/frontend/sdl3/window.cpp +++ b/src/frontend/sdl3/window.cpp @@ -7,7 +7,10 @@ namespace hydra::frontend::sdl3 { Window::Window(int argc, const char* argv[]) : emulation_context(*this) { // SLD3 initialization - if (!SDL_Init(SDL_INIT_VIDEO)) { + u32 flags = CONFIG_INSTANCE.GetInputBackend() == InputBackend::Sdl + ? SDL_INIT_VIDEO | SDL_INIT_GAMEPAD + : SDL_INIT_VIDEO; + if (!SDL_Init(flags)) { LOG_FATAL(SDL3Window, "Failed to initialize SDL3: {}", SDL_GetError()); return; } From 54d5f1073f9cdf95c8935127e30fe653d75b6d66 Mon Sep 17 00:00:00 2001 From: nullequal Date: Wed, 13 May 2026 14:14:30 +0300 Subject: [PATCH 02/14] input/sdl: dont delete or log in DeviceList destructor --- src/core/input/sdl/device_list.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp index 16d8f42f..91af53eb 100644 --- a/src/core/input/sdl/device_list.cpp +++ b/src/core/input/sdl/device_list.cpp @@ -39,15 +39,6 @@ DeviceList::DeviceList() { } DeviceList::~DeviceList() { - LOG_INFO(Input, "Keyboard disconnected: keyboard"); - auto it = devices.find("keyboard"); - delete it->second; - devices.erase(it); - for (auto [name, device] : devices) { - LOG_INFO(Input, "Controller disconnected: {}", name); - delete device; - devices.erase(name); - } SDL_RemoveEventWatch(EventWatcher, &devices); } From 84e6e225a73a66c37de5e492615b76bd96851fa8 Mon Sep 17 00:00:00 2001 From: nullequal Date: Wed, 13 May 2026 14:17:22 +0300 Subject: [PATCH 03/14] cmake: find SDL3 before linking --- src/core/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 55c612b2..92f06b42 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -693,7 +693,9 @@ if (CUBEB_ENABLED) endif () if (SDL_ENABLED) - target_link_libraries(hydra-core PRIVATE sdl3) + find_package(SDL3 CONFIG REQUIRED) + + target_link_libraries(hydra-core PRIVATE SDL3::SDL3) endif () if (CMAKE_SYSTEM_NAME STREQUAL "iOS") From 6cf7f4c0e2c18ad3008b018213cec5f2e87fb8e1 Mon Sep 17 00:00:00 2001 From: nullequal Date: Wed, 13 May 2026 14:18:50 +0300 Subject: [PATCH 04/14] input/sdl: fixup EventWatcher switch --- src/core/input/sdl/device_list.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp index 91af53eb..e638a152 100644 --- a/src/core/input/sdl/device_list.cpp +++ b/src/core/input/sdl/device_list.cpp @@ -26,7 +26,10 @@ bool EventWatcher(void* userdata, SDL_Event* e) { delete it->second; SDL_CloseGamepad(gp); devices->erase(it); + break; } + default: + break; } return false; } From f28333e9962844f897311d29a4e1b335ff1abfe8 Mon Sep 17 00:00:00 2001 From: nullequal Date: Thu, 14 May 2026 11:24:03 +0300 Subject: [PATCH 05/14] input/sdl: refactor GetAxisValueImpl --- src/core/input/sdl/controller.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/core/input/sdl/controller.cpp b/src/core/input/sdl/controller.cpp index 25ccab9e..0c76490f 100644 --- a/src/core/input/sdl/controller.cpp +++ b/src/core/input/sdl/controller.cpp @@ -44,6 +44,16 @@ bool Controller::IsPressedImpl(ControllerInput input) { } } +f32 positive_axis_to_float(i16 value) { + return std::max(static_cast(0), value) / + static_cast(std::numeric_limits::max()); +} + +f32 negative_axis_to_float(i16 value) { + return std::min(static_cast(0), value) / + static_cast(std::numeric_limits::min()); +} + f32 Controller::GetAxisValueImpl(ControllerInput input) { #define AXIS_CASE(input, sdl_dpad, direction) \ case ControllerInput::input: \ @@ -68,20 +78,15 @@ f32 Controller::GetAxisValueImpl(ControllerInput input) { return 0.0f; } - // TODO: make less ugly switch (dir) { case AnalogStickDirection::Right: - return std::max(static_cast(0), value) / - static_cast(std::numeric_limits::max()); + return positive_axis_to_float(value); case AnalogStickDirection::Left: - return std::min(static_cast(0), value) / - static_cast(std::numeric_limits::min()); + return negative_axis_to_float(value); case AnalogStickDirection::Up: - return std::min(static_cast(0), value) / - static_cast(std::numeric_limits::min()); + return negative_axis_to_float(value); case AnalogStickDirection::Down: - return std::max(static_cast(0), value) / - static_cast(std::numeric_limits::max()); + return positive_axis_to_float(value); } } From 3359e8a7f1e8785392915da063cb5d1db4d13a1e Mon Sep 17 00:00:00 2001 From: nullequal Date: Thu, 14 May 2026 18:23:15 +0300 Subject: [PATCH 06/14] input/sdl: handle keyboard events --- src/core/input/sdl/device_list.cpp | 51 ++++++++++++++++++++---------- src/core/input/sdl/device_list.hpp | 5 +++ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp index e638a152..10764c6a 100644 --- a/src/core/input/sdl/device_list.cpp +++ b/src/core/input/sdl/device_list.cpp @@ -6,43 +6,62 @@ namespace hydra::input::sdl { -bool EventWatcher(void* userdata, SDL_Event* e) { +void DeviceList::EventWatcher(SDL_Event* e) { switch (e->type) { + case SDL_EVENT_KEYBOARD_ADDED: { + if (kb_count == 0) { + LOG_INFO(Input, "Keyboard connected: Generic Keyboard"); + devices.insert({"Generic Keyboard", new Keyboard()}); + kb_count++; + } else { + kb_count++; + } + break; + } + case SDL_EVENT_KEYBOARD_REMOVED: { + if (kb_count == 1) { + LOG_INFO(Input, "Keyboard disconnected: Generic Keyboard"); + auto it = devices.find("Generic Keyboard"); + ASSERT(it != devices.end(), Input, "Keyboard not connected"); + delete it->second; + devices.erase(it); + kb_count--; + } else { + kb_count--; + } + break; + } case SDL_EVENT_GAMEPAD_ADDED: { SDL_Gamepad* gp = SDL_OpenGamepad(e->gdevice.which); std::string name = SDL_GetGamepadName(gp); LOG_INFO(Input, "Controller connected: {}", name); - auto devices = static_cast*>(userdata); - devices->insert({name, new Controller(gp)}); + devices.insert({name, new Controller(gp)}); break; } case SDL_EVENT_GAMEPAD_REMOVED: { SDL_Gamepad* gp = SDL_GetGamepadFromID(e->gdevice.which); std::string name = SDL_GetGamepadName(gp); LOG_INFO(Input, "Controller disconnected: {}", name); - auto devices = static_cast*>(userdata); - auto it = devices->find(name); - ASSERT(it != devices->end(), Input, "Controller not connected"); + auto it = devices.find(name); + ASSERT(it != devices.end(), Input, "Controller not connected"); delete it->second; SDL_CloseGamepad(gp); - devices->erase(it); + devices.erase(it); break; } default: break; } - return false; } -DeviceList::DeviceList() { - // TODO: more than one keyboard - devices["keyboard"] = new Keyboard(); - LOG_INFO(Input, "Keyboard connected: keyboard"); - SDL_AddEventWatch(EventWatcher, &devices); +bool EWWrapper(void* userdata, SDL_Event* e) { + auto o = static_cast(userdata); + o->EventWatcher(e); + return false; } -DeviceList::~DeviceList() { - SDL_RemoveEventWatch(EventWatcher, &devices); -} +DeviceList::DeviceList() { SDL_AddEventWatch(EWWrapper, this); } + +DeviceList::~DeviceList() { SDL_RemoveEventWatch(EWWrapper, this); } } // namespace hydra::input::sdl \ No newline at end of file diff --git a/src/core/input/sdl/device_list.hpp b/src/core/input/sdl/device_list.hpp index 1d9075ba..48019472 100644 --- a/src/core/input/sdl/device_list.hpp +++ b/src/core/input/sdl/device_list.hpp @@ -9,6 +9,11 @@ class DeviceList : public IDeviceList { public: DeviceList(); ~DeviceList(); + + void EventWatcher(SDL_Event* e); + + private: + u32 kb_count; }; } // namespace hydra::input::sdl From dde98e8c99ba9ebd5efd9a21ab3a5a2ae627cc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Fri, 15 May 2026 09:09:43 +0200 Subject: [PATCH 07/14] config: cleanup input --- src/common/config.cpp | 24 +++++++++---------- src/common/config.hpp | 38 ++++++++++++++++--------------- src/core/input/device_manager.cpp | 4 ++-- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 85b4165e..df4733e3 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -1,9 +1,9 @@ #include "common/config.hpp" -#include "common/log.hpp" -#include "common/platform.hpp" #include "common/toml_helper.hpp" +TOML11_DEFINE_CONVERSION_ENUM(hydra::InputBackend, Sdl, "SDL", + AppleGameController, "Apple GameController") TOML11_DEFINE_CONVERSION_ENUM(hydra::CpuBackend, AppleHypervisor, "Apple Hypervisor", Dynarmic, "dynarmic") TOML11_DEFINE_CONVERSION_ENUM(hydra::GpuRenderer, Metal, "Metal") @@ -12,9 +12,7 @@ TOML11_DEFINE_CONVERSION_ENUM(hydra::Resolution, Auto, "auto", _720p, "720p", _1080p, "1080p", _1440p, "1440p", _2160p, "2160p", _4320p, "4320p", AutoExact, "Auto exact", Custom, "custom") -TOML11_DEFINE_CONVERSION_ENUM(hydra::InputBackend, Sdl, "Sdl", GameController, - "GameController") -TOML11_DEFINE_CONVERSION_ENUM(hydra::AudioBackend, Null, "Null", Cubeb, "Cubeb") +TOML11_DEFINE_CONVERSION_ENUM(hydra::AudioBackend, Null, "null", Cubeb, "Cubeb") TOML11_DEFINE_CONVERSION_ENUM(hydra::LogOutput, None, "none", StdOut, "stdout", File, "file") @@ -122,13 +120,13 @@ void Config::LoadDefaults() { game_paths = GetDefaultGamePaths(); loader_plugins = GetDefaultLoaderPlugins(); patch_paths = GetDefaultPatchPaths(); + input_backend = GetDefaultInputBackend(); input_profiles = GetDefaultInputProfiles(); cpu_backend = GetDefaultCpuBackend(); gpu_renderer = GetDefaultGpuRenderer(); shader_backend = GetDefaultShaderBackend(); display_resolution = GetDefaultDisplayResolution(); custom_display_resolution = GetDefaultCustomDisplayResolution(); - input_backend = GetDefaultInputBackend(); audio_backend = GetDefaultAudioBackend(); user_id = GetDefaultUserID(); firmware_path = GetDefaultFirmwarePath(); @@ -321,6 +319,12 @@ void Config::Deserialize() { } // Validate + if (input_backend == InputBackend::Invalid) { + input_backend = GetDefaultInputBackend(); + LOG_WARN(Other, "Invalid input backend, falling back to {}", + input_backend); + } + if (cpu_backend == CpuBackend::Invalid) { cpu_backend = GetDefaultCpuBackend(); LOG_WARN(Other, "Invalid CPU backend, falling back to {}", cpu_backend); @@ -346,12 +350,6 @@ void Config::Deserialize() { display_resolution); } - if (input_backend == InputBackend::Invalid) { - input_backend = GetDefaultInputBackend(); - LOG_WARN(Other, "Invalid input backend, falling back to {}", - input_backend); - } - if (audio_backend == AudioBackend::Invalid) { audio_backend = GetDefaultAudioBackend(); LOG_WARN(Other, "Invalid audio backend, falling back to {}", @@ -363,6 +361,7 @@ void Config::Log() { LOG_INFO(Other, "Game paths: [{}]", fmt::join(game_paths, ", ")); LOG_INFO(Other, "Loader plugins: [{}]", fmt::join(loader_plugins, ", ")); LOG_INFO(Other, "Patch paths: [{}]", fmt::join(patch_paths, ", ")); + LOG_INFO(Other, "Input backend: {}", input_backend); LOG_INFO(Other, "Input profiles: [{}]", fmt::join(input_profiles, ", ")); LOG_INFO(Other, "CPU backend: {}", cpu_backend); LOG_INFO(Other, "Gpu renderer: {}", gpu_renderer); @@ -370,7 +369,6 @@ void Config::Log() { LOG_INFO(Other, "Display resolution: {}", display_resolution); LOG_INFO(Other, "Custom display resolution: {}x{}", custom_display_resolution.x(), custom_display_resolution.y()); - LOG_INFO(Other, "Input backend: {}", input_backend); LOG_INFO(Other, "Audio backend: {}", audio_backend); LOG_INFO(Other, "User ID: {:032x}", user_id); LOG_INFO(Other, "Firmware path: {}", firmware_path); diff --git a/src/common/config.hpp b/src/common/config.hpp index 8a8997e9..be72895d 100644 --- a/src/common/config.hpp +++ b/src/common/config.hpp @@ -3,12 +3,20 @@ #include #include "common/log.hpp" +#include "common/platform.hpp" #include "common/types.hpp" #define CONFIG_INSTANCE Config::GetInstance() namespace hydra { +enum class InputBackend : u32 { + Invalid = 0, + + Sdl, + AppleGameController, +}; + enum class CpuBackend : u32 { Invalid = 0, @@ -44,13 +52,6 @@ enum class Resolution : u32 { STRONG_TYPEDEF(CustomResolution, uint2); -enum class InputBackend : u32 { - Invalid = 0, - - Sdl, - GameController, -}; - enum class AudioBackend : u32 { Invalid = 0, @@ -97,13 +98,13 @@ class Config { std::vector game_paths; std::vector loader_plugins; std::vector patch_paths; + InputBackend input_backend; std::vector input_profiles; CpuBackend cpu_backend; GpuRenderer gpu_renderer; ShaderBackend shader_backend; Resolution display_resolution; uint2 custom_display_resolution; - InputBackend input_backend; AudioBackend audio_backend; uuid_t user_id; std::string firmware_path; @@ -124,6 +125,13 @@ class Config { std::vector GetDefaultGamePaths() const { return {}; } std::vector GetDefaultLoaderPlugins() const { return {}; } std::vector GetDefaultPatchPaths() const { return {}; } + InputBackend GetDefaultInputBackend() const { +#ifdef PLATFORM_APPLE + return InputBackend::AppleGameController; +#else + return InputBackend::Sdl; +#endif + } std::vector GetDefaultInputProfiles() const { return {"Default", "", "", "", "", "", "", "", "", ""}; } @@ -138,13 +146,6 @@ class Config { ShaderBackend GetDefaultShaderBackend() const { return ShaderBackend::Msl; } Resolution GetDefaultDisplayResolution() const { return Resolution::Auto; } uint2 GetDefaultCustomDisplayResolution() const { return {1920, 1080}; } - InputBackend GetDefaultInputBackend() const { -#ifdef PLATFORM_APPLE - return InputBackend::GameController; -#else - return InputBackend::Sdl; -#endif - } AudioBackend GetDefaultAudioBackend() const { #if HYDRA_CUBEB_ENABLED return AudioBackend::Cubeb; @@ -179,13 +180,13 @@ class Config { REF_GETTER(game_paths, GetGamePaths); REF_GETTER(loader_plugins, GetLoaderPlugins); REF_GETTER(patch_paths, GetPatchPaths); + REF_GETTER(input_backend, GetInputBackend); REF_GETTER(input_profiles, GetInputProfiles); REF_GETTER(cpu_backend, GetCpuBackend); REF_GETTER(gpu_renderer, GetGpuRenderer); REF_GETTER(shader_backend, GetShaderBackend); REF_GETTER(display_resolution, GetDisplayResolution); REF_GETTER(custom_display_resolution, GetCustomDisplayResolution); - REF_GETTER(input_backend, GetInputBackend) REF_GETTER(audio_backend, GetAudioBackend); REF_GETTER(user_id, GetUserId); REF_GETTER(firmware_path, GetFirmwarePath); @@ -205,6 +206,9 @@ class Config { } // namespace hydra +ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, InputBackend, input_backend, Sdl, + "SDL", AppleGameController, + "Apple GameController") ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, CpuBackend, cpu_backend, AppleHypervisor, "Apple Hypervisor", Dynarmic, "dynarmic") @@ -216,8 +220,6 @@ ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, Resolution, resolution, Auto, "auto", _720p, "720p", _1080p, "1080p", _1440p, "1440p", _2160p, "2160p", _4320p, "4320p", AutoExact, "Auto exact", Custom, "custom") -ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, InputBackend, input_backend, Sdl, - "Sdl", GameController, "GameController") ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, AudioBackend, audio_backend, Null, "Null", Cubeb, "Cubeb") ENABLE_ENUM_FORMATTING_AND_CASTING(hydra, LogOutput, output, None, "none", diff --git a/src/core/input/device_manager.cpp b/src/core/input/device_manager.cpp index e9223769..49ec483f 100644 --- a/src/core/input/device_manager.cpp +++ b/src/core/input/device_manager.cpp @@ -28,11 +28,11 @@ DeviceManager::DeviceManager() { LOG_FATAL(Other, "SDL not supported"); #endif break; - case InputBackend::GameController: + case InputBackend::AppleGameController: #ifdef PLATFORM_APPLE device_list = new apple_gc::DeviceList(); #else - LOG_FATAL(Other, "GameController not supported"); + LOG_FATAL(Other, "Apple GameController not supported"); #endif break; default: From 3666354329be50a79370b2344bd631c75a8ddf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Fri, 15 May 2026 09:31:05 +0200 Subject: [PATCH 08/14] input: abstract device management away --- src/core/input/apple_gc/device_list.hpp | 6 --- src/core/input/apple_gc/device_list.mm | 60 ++++++++----------------- src/core/input/device_list.hpp | 16 ++++++- src/core/input/sdl/device_list.cpp | 58 +++++++++--------------- 4 files changed, 55 insertions(+), 85 deletions(-) diff --git a/src/core/input/apple_gc/device_list.hpp b/src/core/input/apple_gc/device_list.hpp index 309a0ff2..43e3daf0 100644 --- a/src/core/input/apple_gc/device_list.hpp +++ b/src/core/input/apple_gc/device_list.hpp @@ -9,12 +9,6 @@ class DeviceList : public IDeviceList { DeviceList(); ~DeviceList(); - // For the implementation - void _AddController(id controller); - void _RemoveController(id controller); - void _AddKeyboard(id keyboard); - void _RemoveKeyboard(id keyboard); - private: id impl; }; diff --git a/src/core/input/apple_gc/device_list.mm b/src/core/input/apple_gc/device_list.mm index 4a391892..3fa0e9a7 100644 --- a/src/core/input/apple_gc/device_list.mm +++ b/src/core/input/apple_gc/device_list.mm @@ -7,6 +7,18 @@ using DeviceList = hydra::input::apple_gc::DeviceList; +namespace hydra::input::apple_gc { + +namespace { + +std::string GetDeviceName(id device) { + return [[device vendorName] UTF8String]; +} + +} // namespace + +} // namespace hydra::input::apple_gc + @interface DeviceListImpl : NSObject @property DeviceList* parent; @@ -52,71 +64,35 @@ - (void)dealloc { - (void)controllerConnected:(NSNotification*)notification { GCController* controller = reinterpret_cast(notification.object); - _parent->_AddController(controller); + _parent->AddDevice(hydra::input::apple_gc::GetDeviceName(controller), + new hydra::input::apple_gc::Controller(controller)); } - (void)controllerDisconnected:(NSNotification*)notification { GCController* controller = reinterpret_cast(notification.object); - _parent->_RemoveController(controller); + _parent->RemoveDevice(hydra::input::apple_gc::GetDeviceName(controller)); } - (void)keyboardConnected:(NSNotification*)notification { GCKeyboard* keyboard = reinterpret_cast(notification.object); - _parent->_AddKeyboard(keyboard); + _parent->AddDevice(hydra::input::apple_gc::GetDeviceName(keyboard), + new hydra::input::apple_gc::Keyboard(keyboard)); } - (void)keyboardDisconnected:(NSNotification*)notification { GCKeyboard* keyboard = reinterpret_cast(notification.object); - _parent->_RemoveKeyboard(keyboard); + _parent->RemoveDevice(hydra::input::apple_gc::GetDeviceName(keyboard)); } @end namespace hydra::input::apple_gc { -namespace { - -std::string get_device_name(id device) { - return [[device vendorName] UTF8String]; -} - -} // namespace - DeviceList::DeviceList() { impl = [[DeviceListImpl alloc] initWithParent:this]; } DeviceList::~DeviceList() { [impl release]; } -void DeviceList::_AddController(id controller) { - auto name = get_device_name(controller); - LOG_INFO(Input, "Controller connected: {}", name); - devices[name] = new Controller(controller); -} - -void DeviceList::_RemoveController(id controller) { - auto name = get_device_name(controller); - LOG_INFO(Input, "Controller disconnected: {}", name); - auto it = devices.find(name); - ASSERT(it != devices.end(), Input, "Controller not connected"); - delete it->second; - devices.erase(it); -} - -void DeviceList::_AddKeyboard(id keyboard) { - auto name = get_device_name(keyboard); - LOG_INFO(Input, "Keyboard connected: {}", name); - devices[name] = new Keyboard(keyboard); -} - -void DeviceList::_RemoveKeyboard(id keyboard) { - auto name = get_device_name(keyboard); - LOG_INFO(Input, "Keyboard disconnected: {}", name); - auto it = devices.find(name); - ASSERT(it != devices.end(), Input, "Keyboard not connected"); - delete it->second; - devices.erase(it); -} - } // namespace hydra::input::apple_gc diff --git a/src/core/input/device_list.hpp b/src/core/input/device_list.hpp index 680dce1a..f0debda3 100644 --- a/src/core/input/device_list.hpp +++ b/src/core/input/device_list.hpp @@ -13,7 +13,21 @@ class IDeviceList { delete device; } - protected: + void AddDevice(std::string_view name, IDevice* device) { + const auto res = devices.emplace(name, device); + ASSERT(res.second, Input, "{} already connected", name); + LOG_INFO(Input, "Device connected: {}", name); + } + + void RemoveDevice(std::string_view name) { + const auto it = devices.find(std::string(name)); + ASSERT(it != devices.end(), Input, "{} not connected", name); + delete it->second; + devices.erase(it); + LOG_INFO(Input, "Device disconnected: {}", name); + } + + private: std::map devices; }; diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp index 10764c6a..523a123b 100644 --- a/src/core/input/sdl/device_list.cpp +++ b/src/core/input/sdl/device_list.cpp @@ -6,47 +6,43 @@ namespace hydra::input::sdl { +namespace { + +bool EWWrapper(void* userdata, SDL_Event* e) { + auto o = static_cast(userdata); + o->EventWatcher(e); + return false; +} + +} // namespace + +DeviceList::DeviceList() { SDL_AddEventWatch(EWWrapper, this); } + +DeviceList::~DeviceList() { SDL_RemoveEventWatch(EWWrapper, this); } + void DeviceList::EventWatcher(SDL_Event* e) { switch (e->type) { case SDL_EVENT_KEYBOARD_ADDED: { - if (kb_count == 0) { - LOG_INFO(Input, "Keyboard connected: Generic Keyboard"); - devices.insert({"Generic Keyboard", new Keyboard()}); - kb_count++; - } else { - kb_count++; - } + if (kb_count++ == 0) + AddDevice("Generic Keyboard", new Keyboard()); + kb_count++; break; } case SDL_EVENT_KEYBOARD_REMOVED: { - if (kb_count == 1) { - LOG_INFO(Input, "Keyboard disconnected: Generic Keyboard"); - auto it = devices.find("Generic Keyboard"); - ASSERT(it != devices.end(), Input, "Keyboard not connected"); - delete it->second; - devices.erase(it); - kb_count--; - } else { - kb_count--; - } + if (--kb_count == 0) + RemoveDevice("Generic Keyboard"); break; } case SDL_EVENT_GAMEPAD_ADDED: { SDL_Gamepad* gp = SDL_OpenGamepad(e->gdevice.which); std::string name = SDL_GetGamepadName(gp); - LOG_INFO(Input, "Controller connected: {}", name); - devices.insert({name, new Controller(gp)}); + AddDevice(name, new Controller(gp)); break; } case SDL_EVENT_GAMEPAD_REMOVED: { SDL_Gamepad* gp = SDL_GetGamepadFromID(e->gdevice.which); std::string name = SDL_GetGamepadName(gp); - LOG_INFO(Input, "Controller disconnected: {}", name); - auto it = devices.find(name); - ASSERT(it != devices.end(), Input, "Controller not connected"); - delete it->second; - SDL_CloseGamepad(gp); - devices.erase(it); + RemoveDevice(name); break; } default: @@ -54,14 +50,4 @@ void DeviceList::EventWatcher(SDL_Event* e) { } } -bool EWWrapper(void* userdata, SDL_Event* e) { - auto o = static_cast(userdata); - o->EventWatcher(e); - return false; -} - -DeviceList::DeviceList() { SDL_AddEventWatch(EWWrapper, this); } - -DeviceList::~DeviceList() { SDL_RemoveEventWatch(EWWrapper, this); } - -} // namespace hydra::input::sdl \ No newline at end of file +} // namespace hydra::input::sdl From 89a029553ec319bb8b09cb8de2c826326c38f550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Fri, 15 May 2026 09:50:10 +0200 Subject: [PATCH 09/14] input/sdl: get initial keyboards and controllers --- src/core/input/sdl/device_list.cpp | 54 +++++++++++++++++++++++++----- src/core/input/sdl/device_list.hpp | 6 +++- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp index 523a123b..c65decbd 100644 --- a/src/core/input/sdl/device_list.cpp +++ b/src/core/input/sdl/device_list.cpp @@ -16,27 +16,51 @@ bool EWWrapper(void* userdata, SDL_Event* e) { } // namespace -DeviceList::DeviceList() { SDL_AddEventWatch(EWWrapper, this); } +DeviceList::DeviceList() { + // Get initial keyboards + int kb_count = 0; + SDL_KeyboardID* keyboards = SDL_GetKeyboards(&kb_count); + if (keyboards) { + keyboard_count = static_cast(kb_count); + if (keyboard_count > 0) + ConnectGenericKeyboard(); + + SDL_free(keyboards); + } else { + keyboard_count = 0; + } + + // Get initial gamepads + int gp_count = 0; + SDL_JoystickID* gamepads = SDL_GetGamepads(&gp_count); + if (gamepads) { + for (int i = 0; i < gp_count; i++) + ConnectController(gamepads[i]); + + SDL_free(gamepads); + } + + // Register event watcher + SDL_AddEventWatch(EWWrapper, this); +} DeviceList::~DeviceList() { SDL_RemoveEventWatch(EWWrapper, this); } void DeviceList::EventWatcher(SDL_Event* e) { switch (e->type) { case SDL_EVENT_KEYBOARD_ADDED: { - if (kb_count++ == 0) - AddDevice("Generic Keyboard", new Keyboard()); - kb_count++; + if (keyboard_count++ == 0) + ConnectGenericKeyboard(); + keyboard_count++; break; } case SDL_EVENT_KEYBOARD_REMOVED: { - if (--kb_count == 0) + if (--keyboard_count == 0) RemoveDevice("Generic Keyboard"); break; } case SDL_EVENT_GAMEPAD_ADDED: { - SDL_Gamepad* gp = SDL_OpenGamepad(e->gdevice.which); - std::string name = SDL_GetGamepadName(gp); - AddDevice(name, new Controller(gp)); + ConnectController(e->gdevice.which); break; } case SDL_EVENT_GAMEPAD_REMOVED: { @@ -50,4 +74,18 @@ void DeviceList::EventWatcher(SDL_Event* e) { } } +void DeviceList::ConnectGenericKeyboard() { + AddDevice("Generic Keyboard", new Keyboard()); +} + +void DeviceList::ConnectController(SDL_JoystickID id) { + SDL_Gamepad* gp = SDL_OpenGamepad(id); + if (gp) { + std::string name = SDL_GetGamepadName(gp); + AddDevice(name, new Controller(gp)); + } else { + LOG_ERROR(Input, "Failed to get controller: {}", SDL_GetError()); + } +} + } // namespace hydra::input::sdl diff --git a/src/core/input/sdl/device_list.hpp b/src/core/input/sdl/device_list.hpp index 48019472..fc2e123e 100644 --- a/src/core/input/sdl/device_list.hpp +++ b/src/core/input/sdl/device_list.hpp @@ -13,7 +13,11 @@ class DeviceList : public IDeviceList { void EventWatcher(SDL_Event* e); private: - u32 kb_count; + u32 keyboard_count; + + // Helpers + void ConnectGenericKeyboard(); + void ConnectController(SDL_JoystickID id); }; } // namespace hydra::input::sdl From 64d5143f789e150b3867cfc3d2533705dabd5676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Fri, 15 May 2026 10:13:48 +0200 Subject: [PATCH 10/14] input: proper button mapping --- src/core/input/apple_gc/controller.mm | 22 +++++++++++++--------- src/core/input/sdl/controller.cpp | 25 ++++++++++++------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/core/input/apple_gc/controller.mm b/src/core/input/apple_gc/controller.mm index f72b66ba..eb36534a 100644 --- a/src/core/input/apple_gc/controller.mm +++ b/src/core/input/apple_gc/controller.mm @@ -5,6 +5,8 @@ namespace hydra::input::apple_gc { bool Controller::IsPressedImpl(ControllerInput input) { + const auto controller = reinterpret_cast(handle); + #define BUTTON_CASE(input, gc_button) \ case ControllerInput::input: \ gc_button_name = GCInput##gc_button; \ @@ -18,22 +20,26 @@ BUTTON_CASE(Y, ButtonX) BUTTON_CASE(StickL, LeftThumbstickButton) BUTTON_CASE(StickR, RightThumbstickButton) - BUTTON_CASE(L, LeftBumper) - BUTTON_CASE(R, RightBumper) + BUTTON_CASE(L, LeftShoulder) + BUTTON_CASE(R, RightShoulder) BUTTON_CASE(ZL, LeftTrigger) BUTTON_CASE(ZR, RightTrigger) - // TODO: implement - case ControllerInput::Plus: - case ControllerInput::Minus: + BUTTON_CASE(Plus, ButtonMenu) + BUTTON_CASE(Minus, ButtonOptions) case ControllerInput::Left: + return controller.extendedGamepad.dpad.left.isPressed; case ControllerInput::Up: + return controller.extendedGamepad.dpad.up.isPressed; case ControllerInput::Right: + return controller.extendedGamepad.dpad.right.isPressed; case ControllerInput::Down: + return controller.extendedGamepad.dpad.down.isPressed; + // TODO: implement case ControllerInput::LeftSL: case ControllerInput::LeftSR: case ControllerInput::RightSL: case ControllerInput::RightSR: - break; + return false; default: LOG_NOT_IMPLEMENTED(Input, "Controller button {}", input); return false; @@ -41,9 +47,7 @@ #undef BUTTON_CASE - return reinterpret_cast(handle) - .physicalInputProfile.buttons[gc_button_name] - .isPressed; + return controller.physicalInputProfile.buttons[gc_button_name].isPressed; } f32 Controller::GetAxisValueImpl(ControllerInput input) { diff --git a/src/core/input/sdl/controller.cpp b/src/core/input/sdl/controller.cpp index 0c76490f..9460249c 100644 --- a/src/core/input/sdl/controller.cpp +++ b/src/core/input/sdl/controller.cpp @@ -16,28 +16,27 @@ bool Controller::IsPressedImpl(ControllerInput input) { BUTTON_CASE(A, SDL_GAMEPAD_BUTTON_EAST) BUTTON_CASE(B, SDL_GAMEPAD_BUTTON_SOUTH) BUTTON_CASE(X, SDL_GAMEPAD_BUTTON_NORTH) - BUTTON_CASE(Y, SDL_GAMEPAD_BUTTON_SOUTH) + BUTTON_CASE(Y, SDL_GAMEPAD_BUTTON_WEST) BUTTON_CASE(StickL, SDL_GAMEPAD_BUTTON_LEFT_STICK) BUTTON_CASE(StickR, SDL_GAMEPAD_BUTTON_RIGHT_STICK) BUTTON_CASE(L, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER) BUTTON_CASE(R, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER) case ControllerInput::ZL: return SDL_GetGamepadAxis(handle, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); - break; case ControllerInput::ZR: return SDL_GetGamepadAxis(handle, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); - break; + BUTTON_CASE(Plus, SDL_GAMEPAD_BUTTON_START); + BUTTON_CASE(Minus, SDL_GAMEPAD_BUTTON_BACK); + BUTTON_CASE(Left, SDL_GAMEPAD_BUTTON_DPAD_LEFT); + BUTTON_CASE(Up, SDL_GAMEPAD_BUTTON_DPAD_UP); + BUTTON_CASE(Right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); + BUTTON_CASE(Down, SDL_GAMEPAD_BUTTON_DPAD_DOWN); // TODO: implement - case ControllerInput::Plus: - case ControllerInput::Minus: - case ControllerInput::Left: - case ControllerInput::Up: - case ControllerInput::Right: - case ControllerInput::Down: case ControllerInput::LeftSL: case ControllerInput::LeftSR: case ControllerInput::RightSL: case ControllerInput::RightSR: + return false; default: LOG_NOT_IMPLEMENTED(Input, "Controller button {}", input); return false; @@ -45,13 +44,13 @@ bool Controller::IsPressedImpl(ControllerInput input) { } f32 positive_axis_to_float(i16 value) { - return std::max(static_cast(0), value) / - static_cast(std::numeric_limits::max()); + return std::max(static_cast(0), value) / + static_cast(std::numeric_limits::max()); } f32 negative_axis_to_float(i16 value) { return std::min(static_cast(0), value) / - static_cast(std::numeric_limits::min()); + static_cast(std::numeric_limits::min()); } f32 Controller::GetAxisValueImpl(ControllerInput input) { @@ -90,4 +89,4 @@ f32 Controller::GetAxisValueImpl(ControllerInput input) { } } -} // namespace hydra::input::sdl \ No newline at end of file +} // namespace hydra::input::sdl From a0dc4ce818c84ef8eb2b8879480f80289efea633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Sun, 17 May 2026 07:54:40 +0200 Subject: [PATCH 11/14] input/apple_gc: check if devices already connected --- src/core/input/apple_gc/device_list.hpp | 5 +++ src/core/input/apple_gc/device_list.mm | 50 ++++++++++++++++--------- src/core/input/device_list.hpp | 7 +++- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/core/input/apple_gc/device_list.hpp b/src/core/input/apple_gc/device_list.hpp index 43e3daf0..beded509 100644 --- a/src/core/input/apple_gc/device_list.hpp +++ b/src/core/input/apple_gc/device_list.hpp @@ -9,6 +9,11 @@ class DeviceList : public IDeviceList { DeviceList(); ~DeviceList(); + void _AddController(id controller); + void _RemoveController(id controller); + void _AddKeyboard(id keyboard); + void _RemoveKeyboard(id keyboard); + private: id impl; }; diff --git a/src/core/input/apple_gc/device_list.mm b/src/core/input/apple_gc/device_list.mm index 75c6baa8..e1b30456 100644 --- a/src/core/input/apple_gc/device_list.mm +++ b/src/core/input/apple_gc/device_list.mm @@ -7,18 +7,6 @@ using DeviceList = hydra::input::apple_gc::DeviceList; -namespace hydra::input::apple_gc { - -namespace { - -std::string GetDeviceName(id device) { - return [[device vendorName] UTF8String]; -} - -} // namespace - -} // namespace hydra::input::apple_gc - @interface DeviceListImpl : NSObject @property DeviceList* parent; @@ -78,35 +66,61 @@ - (void)dealloc { - (void)controllerConnected:(NSNotification*)notification { GCController* controller = reinterpret_cast(notification.object); - _parent->AddDevice(hydra::input::apple_gc::GetDeviceName(controller), - new hydra::input::apple_gc::Controller(controller)); + _parent->_AddController(controller); } - (void)controllerDisconnected:(NSNotification*)notification { GCController* controller = reinterpret_cast(notification.object); - _parent->RemoveDevice(hydra::input::apple_gc::GetDeviceName(controller)); + _parent->_RemoveController(controller); } - (void)keyboardConnected:(NSNotification*)notification { GCKeyboard* keyboard = reinterpret_cast(notification.object); - _parent->AddDevice(hydra::input::apple_gc::GetDeviceName(keyboard), - new hydra::input::apple_gc::Keyboard(keyboard)); + _parent->_AddKeyboard(keyboard); } - (void)keyboardDisconnected:(NSNotification*)notification { GCKeyboard* keyboard = reinterpret_cast(notification.object); - _parent->RemoveDevice(hydra::input::apple_gc::GetDeviceName(keyboard)); + _parent->_RemoveKeyboard(keyboard); } @end namespace hydra::input::apple_gc { +namespace { + +std::string GetDeviceName(id device) { + return [[device vendorName] UTF8String]; +} + +} // namespace + DeviceList::DeviceList() { impl = [[DeviceListImpl alloc] initWithParent:this]; } DeviceList::~DeviceList() { [impl release]; } +void DeviceList::_AddController(id controller) { + const auto name = GetDeviceName(controller); + if (!HasDevice(name)) + AddDevice(name, new Controller(controller)); +} + +void DeviceList::_RemoveController(id controller) { + RemoveDevice(GetDeviceName(controller)); +} + +void DeviceList::_AddKeyboard(id keyboard) { + const auto name = GetDeviceName(keyboard); + if (!HasDevice(name)) + AddDevice(name, new Keyboard(keyboard)); +} + +void DeviceList::_RemoveKeyboard(id keyboard) { + RemoveDevice(GetDeviceName(keyboard)); +} + } // namespace hydra::input::apple_gc diff --git a/src/core/input/device_list.hpp b/src/core/input/device_list.hpp index f0debda3..faa350f9 100644 --- a/src/core/input/device_list.hpp +++ b/src/core/input/device_list.hpp @@ -13,6 +13,9 @@ class IDeviceList { delete device; } + protected: + bool HasDevice(std::string_view name) { return devices.contains(name); } + void AddDevice(std::string_view name, IDevice* device) { const auto res = devices.emplace(name, device); ASSERT(res.second, Input, "{} already connected", name); @@ -20,7 +23,7 @@ class IDeviceList { } void RemoveDevice(std::string_view name) { - const auto it = devices.find(std::string(name)); + const auto it = devices.find(name); ASSERT(it != devices.end(), Input, "{} not connected", name); delete it->second; devices.erase(it); @@ -28,7 +31,7 @@ class IDeviceList { } private: - std::map devices; + std::map> devices; }; } // namespace hydra::input From 8b909f8c8249b362df12d5e25708398c6bd1177d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Sun, 17 May 2026 08:21:38 +0200 Subject: [PATCH 12/14] input: cleanup & make thread safe --- src/core/input/apple_gc/device_list.hpp | 8 ++--- src/core/input/apple_gc/device_list.mm | 28 +++++++-------- src/core/input/device_list.hpp | 19 +++++++--- src/core/input/device_manager.cpp | 48 +++++++++++++------------ src/core/input/device_manager.hpp | 25 +++---------- 5 files changed, 61 insertions(+), 67 deletions(-) diff --git a/src/core/input/apple_gc/device_list.hpp b/src/core/input/apple_gc/device_list.hpp index beded509..e5fc1b59 100644 --- a/src/core/input/apple_gc/device_list.hpp +++ b/src/core/input/apple_gc/device_list.hpp @@ -9,10 +9,10 @@ class DeviceList : public IDeviceList { DeviceList(); ~DeviceList(); - void _AddController(id controller); - void _RemoveController(id controller); - void _AddKeyboard(id keyboard); - void _RemoveKeyboard(id keyboard); + void AddController(id controller); + void RemoveController(id controller); + void AddKeyboard(id keyboard); + void RemoveKeyboard(id keyboard); private: id impl; diff --git a/src/core/input/apple_gc/device_list.mm b/src/core/input/apple_gc/device_list.mm index e1b30456..45b84f2e 100644 --- a/src/core/input/apple_gc/device_list.mm +++ b/src/core/input/apple_gc/device_list.mm @@ -45,13 +45,13 @@ - (id)initWithParent:(DeviceList*)parent { if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCKeyboard* keyboard = [GCKeyboard coalescedKeyboard]; if (keyboard) { - self.parent->_AddKeyboard(keyboard); + self.parent->AddKeyboard(keyboard); } } // Connected controllers for (GCController* controller in [GCController controllers]) { - self.parent->_AddController(controller); + self.parent->AddController(controller); } } @@ -66,23 +66,23 @@ - (void)dealloc { - (void)controllerConnected:(NSNotification*)notification { GCController* controller = reinterpret_cast(notification.object); - _parent->_AddController(controller); + _parent->AddController(controller); } - (void)controllerDisconnected:(NSNotification*)notification { GCController* controller = reinterpret_cast(notification.object); - _parent->_RemoveController(controller); + _parent->RemoveController(controller); } - (void)keyboardConnected:(NSNotification*)notification { GCKeyboard* keyboard = reinterpret_cast(notification.object); - _parent->_AddKeyboard(keyboard); + _parent->AddKeyboard(keyboard); } - (void)keyboardDisconnected:(NSNotification*)notification { GCKeyboard* keyboard = reinterpret_cast(notification.object); - _parent->_RemoveKeyboard(keyboard); + _parent->RemoveKeyboard(keyboard); } @end @@ -103,23 +103,19 @@ - (void)keyboardDisconnected:(NSNotification*)notification { DeviceList::~DeviceList() { [impl release]; } -void DeviceList::_AddController(id controller) { - const auto name = GetDeviceName(controller); - if (!HasDevice(name)) - AddDevice(name, new Controller(controller)); +void DeviceList::AddController(id controller) { + AddDevice(GetDeviceName(controller), new Controller(controller)); } -void DeviceList::_RemoveController(id controller) { +void DeviceList::RemoveController(id controller) { RemoveDevice(GetDeviceName(controller)); } -void DeviceList::_AddKeyboard(id keyboard) { - const auto name = GetDeviceName(keyboard); - if (!HasDevice(name)) - AddDevice(name, new Keyboard(keyboard)); +void DeviceList::AddKeyboard(id keyboard) { + AddDevice(GetDeviceName(keyboard), new Keyboard(keyboard)); } -void DeviceList::_RemoveKeyboard(id keyboard) { +void DeviceList::RemoveKeyboard(id keyboard) { RemoveDevice(GetDeviceName(keyboard)); } diff --git a/src/core/input/device_list.hpp b/src/core/input/device_list.hpp index faa350f9..df623cde 100644 --- a/src/core/input/device_list.hpp +++ b/src/core/input/device_list.hpp @@ -5,24 +5,21 @@ namespace hydra::input { class IDeviceList { - friend class DeviceManager; - public: virtual ~IDeviceList() { for (auto [name, device] : devices) delete device; } - protected: - bool HasDevice(std::string_view name) { return devices.contains(name); } - void AddDevice(std::string_view name, IDevice* device) { + std::scoped_lock lock(mutex); const auto res = devices.emplace(name, device); ASSERT(res.second, Input, "{} already connected", name); LOG_INFO(Input, "Device connected: {}", name); } void RemoveDevice(std::string_view name) { + std::scoped_lock lock(mutex); const auto it = devices.find(name); ASSERT(it != devices.end(), Input, "{} not connected", name); delete it->second; @@ -30,8 +27,20 @@ class IDeviceList { LOG_INFO(Input, "Device disconnected: {}", name); } + IDevice* GetDevice(std::string_view name) { + auto it = devices.find(name); + if (it == devices.end()) + return nullptr; + + return it->second; + } + private: + std::mutex mutex; std::map> devices; + + public: + REF_GETTER(mutex, GetMutex); }; } // namespace hydra::input diff --git a/src/core/input/device_manager.cpp b/src/core/input/device_manager.cpp index 49ec483f..841c4e3f 100644 --- a/src/core/input/device_manager.cpp +++ b/src/core/input/device_manager.cpp @@ -8,41 +8,41 @@ namespace hydra::input { -DeviceManager::DeviceManager() { - // Profiles - for (u32 i = 0; i < horizon::services::hid::NPAD_COUNT; i++) { - const auto& name = CONFIG_INSTANCE.GetInputProfiles()[i]; - if (name.empty()) - continue; +namespace { - profiles[i] = Profile( - static_cast(i), name); - } - - // Device list - switch (CONFIG_INSTANCE.GetInputBackend()) { +IDeviceList* CreateDeviceList() { + const auto input_backend = CONFIG_INSTANCE.GetInputBackend(); + switch (input_backend) { case InputBackend::Sdl: #if HYDRA_SDL_ENABLED - device_list = new sdl::DeviceList(); + return new sdl::DeviceList(); #else LOG_FATAL(Other, "SDL not supported"); #endif - break; case InputBackend::AppleGameController: #ifdef PLATFORM_APPLE - device_list = new apple_gc::DeviceList(); + return new apple_gc::DeviceList(); #else LOG_FATAL(Other, "Apple GameController not supported"); #endif - break; default: - LOG_FATAL(Other, "Unknown input backend {}", - CONFIG_INSTANCE.GetInputBackend()); - break; + LOG_FATAL(Other, "Unknown input backend {}", input_backend); } } -DeviceManager::~DeviceManager() { delete device_list; } +} // namespace + +DeviceManager::DeviceManager() : device_list{CreateDeviceList()} { + // Profiles + for (u32 i = 0; i < horizon::services::hid::NPAD_COUNT; i++) { + const auto& name = CONFIG_INSTANCE.GetInputProfiles()[i]; + if (name.empty()) + continue; + + profiles[i] = Profile( + static_cast(i), name); + } +} NpadState DeviceManager::PollNpad(horizon::services::hid::internal::NpadIndex index) { @@ -54,7 +54,9 @@ DeviceManager::PollNpad(horizon::services::hid::internal::NpadIndex index) { const auto& profile = *profile_opt; for (const auto& device_name : profile.GetDeviceNames()) { - auto device = GetDevice(device_name); + std::scoped_lock lock(device_list->GetMutex()); + + auto device = device_list->GetDevice(device_name); if (!device) continue; @@ -106,12 +108,14 @@ DeviceManager::PollNpad(horizon::services::hid::internal::NpadIndex index) { } std::map DeviceManager::PollTouch() { + std::scoped_lock lock(device_list->GetMutex()); + std::map state; // TODO: get name from the config const std::string device_name = "cursor"; - auto device = GetDevice(device_name); + auto device = device_list->GetDevice(device_name); if (!device) return state; diff --git a/src/core/input/device_manager.hpp b/src/core/input/device_manager.hpp index e8790c1c..2cfa05e3 100644 --- a/src/core/input/device_manager.hpp +++ b/src/core/input/device_manager.hpp @@ -11,28 +11,21 @@ constexpr usize MAX_FINGER_COUNT = 16; class DeviceManager { public: DeviceManager(); - ~DeviceManager(); NpadState PollNpad(horizon::services::hid::internal::NpadIndex index); std::map PollTouch(); // Touch screen devices - void ConnectTouchScreenDevice(const std::string& name, IDevice* device) { - ASSERT(device->ActsAsTouchScreen(), Input, - "Device \"{}\" does not act as a touch screen", name); - device_list->devices.insert({name, device}); + void ConnectTouchScreenDevice(std::string_view name, IDevice* device) { + device_list->AddDevice(name, device); } - IDevice* DisconnectTouchScreenDevice(const std::string& name) { - auto it = device_list->devices.find(name); - ASSERT(it != device_list->devices.end(), Input, - "Touch screen \"{}\" not connected", name); - device_list->devices.erase(it); - return it->second; + void DisconnectTouchScreenDevice(std::string_view name) { + device_list->RemoveDevice(name); } private: - IDeviceList* device_list; + std::unique_ptr device_list; std::optional profiles[horizon::services::hid::NPAD_COUNT]; std::map active_touches; @@ -40,14 +33,6 @@ class DeviceManager { u16 available_finger_mask{0xffff}; // Helpers - IDevice* GetDevice(const std::string& name) { - auto it = device_list->devices.find(name); - if (it == device_list->devices.end()) - return nullptr; - - return it->second; - } - u32 BeginTouch(); void EndTouch(u32 finger_id); }; From 79c5206a831211421274bbaee29612c7cf13cd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Sun, 17 May 2026 08:36:00 +0200 Subject: [PATCH 13/14] frontend/sdl: fix initialization order --- src/core/input/device_manager.cpp | 6 +++--- src/frontend/sdl3/window.cpp | 10 +++++++--- src/frontend/sdl3/window.hpp | 8 ++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/core/input/device_manager.cpp b/src/core/input/device_manager.cpp index 841c4e3f..744eeadf 100644 --- a/src/core/input/device_manager.cpp +++ b/src/core/input/device_manager.cpp @@ -17,16 +17,16 @@ IDeviceList* CreateDeviceList() { #if HYDRA_SDL_ENABLED return new sdl::DeviceList(); #else - LOG_FATAL(Other, "SDL not supported"); + LOG_FATAL(Input, "SDL not supported"); #endif case InputBackend::AppleGameController: #ifdef PLATFORM_APPLE return new apple_gc::DeviceList(); #else - LOG_FATAL(Other, "Apple GameController not supported"); + LOG_FATAL(Input, "Apple GameController not supported"); #endif default: - LOG_FATAL(Other, "Unknown input backend {}", input_backend); + LOG_FATAL(Input, "Unknown input backend {}", input_backend); } } diff --git a/src/frontend/sdl3/window.cpp b/src/frontend/sdl3/window.cpp index abf09f2f..170be2d7 100644 --- a/src/frontend/sdl3/window.cpp +++ b/src/frontend/sdl3/window.cpp @@ -5,8 +5,7 @@ namespace hydra::frontend::sdl3 { -Window::Window(int argc, const char* argv[]) : system(*this) { - // SLD3 initialization +Context::Context() { u32 flags = CONFIG_INSTANCE.GetInputBackend() == InputBackend::Sdl ? SDL_INIT_VIDEO | SDL_INIT_GAMEPAD : SDL_INIT_VIDEO; @@ -14,7 +13,12 @@ Window::Window(int argc, const char* argv[]) : system(*this) { LOG_FATAL(SDL3Window, "Failed to initialize SDL3: {}", SDL_GetError()); return; } +} +Context::~Context() { SDL_Quit(); } + +Window::Window(int argc, const char* argv[]) : system(*this) { + // Window and renderer SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); @@ -37,8 +41,8 @@ Window::Window(int argc, const char* argv[]) : system(*this) { Window::~Window() { system.GetInputDeviceManager().DisconnectTouchScreenDevice("cursor"); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); - SDL_Quit(); } void Window::Run() { diff --git a/src/frontend/sdl3/window.hpp b/src/frontend/sdl3/window.hpp index a7226862..211e2c20 100644 --- a/src/frontend/sdl3/window.hpp +++ b/src/frontend/sdl3/window.hpp @@ -10,6 +10,12 @@ using Native = hydra::frontend::native::cocoa::Native; namespace hydra::frontend::sdl3 { +class Context { + public: + Context(); + ~Context(); +}; + class Window : public horizon::ui::IHandler { public: Window(int argc, const char* argv[]); @@ -28,6 +34,8 @@ class Window : public horizon::ui::IHandler { std::string& out_text) override; private: + Context context; + SDL_Window* window; SDL_Renderer* renderer; From 85d813e5780a47e691252888c9a3ee0718868257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=BD=C3=BAbor?= Date: Sun, 17 May 2026 09:43:40 +0200 Subject: [PATCH 14/14] input: support event pumping --- src/core/input/apple_gc/device_list.hpp | 2 +- src/core/input/device_list.hpp | 2 ++ src/core/input/device_manager.hpp | 2 ++ src/core/input/sdl/device_list.cpp | 18 +++++++++++++++++- src/core/input/sdl/device_list.hpp | 5 ++++- src/core/input/sdl/keyboard.cpp | 5 ++--- src/core/system.cpp | 3 +++ src/frontend/sdl3/window.cpp | 9 ++------- 8 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/core/input/apple_gc/device_list.hpp b/src/core/input/apple_gc/device_list.hpp index e5fc1b59..a41d70e2 100644 --- a/src/core/input/apple_gc/device_list.hpp +++ b/src/core/input/apple_gc/device_list.hpp @@ -7,7 +7,7 @@ namespace hydra::input::apple_gc { class DeviceList : public IDeviceList { public: DeviceList(); - ~DeviceList(); + ~DeviceList() override; void AddController(id controller); void RemoveController(id controller); diff --git a/src/core/input/device_list.hpp b/src/core/input/device_list.hpp index df623cde..e62db7f1 100644 --- a/src/core/input/device_list.hpp +++ b/src/core/input/device_list.hpp @@ -11,6 +11,8 @@ class IDeviceList { delete device; } + virtual void PumpEvents() {} + void AddDevice(std::string_view name, IDevice* device) { std::scoped_lock lock(mutex); const auto res = devices.emplace(name, device); diff --git a/src/core/input/device_manager.hpp b/src/core/input/device_manager.hpp index 2cfa05e3..78abe9c3 100644 --- a/src/core/input/device_manager.hpp +++ b/src/core/input/device_manager.hpp @@ -12,6 +12,8 @@ class DeviceManager { public: DeviceManager(); + void PumpEvents() { device_list->PumpEvents(); } + NpadState PollNpad(horizon::services::hid::internal::NpadIndex index); std::map PollTouch(); diff --git a/src/core/input/sdl/device_list.cpp b/src/core/input/sdl/device_list.cpp index c65decbd..51e728b9 100644 --- a/src/core/input/sdl/device_list.cpp +++ b/src/core/input/sdl/device_list.cpp @@ -17,6 +17,13 @@ bool EWWrapper(void* userdata, SDL_Event* e) { } // namespace DeviceList::DeviceList() { + has_frontend = SDL_WasInit(SDL_INIT_VIDEO); + + // Initialize + if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { + LOG_FATAL(SDL3Window, "Failed to initialize SDL: {}", SDL_GetError()); + } + // Get initial keyboards int kb_count = 0; SDL_KeyboardID* keyboards = SDL_GetKeyboards(&kb_count); @@ -44,7 +51,16 @@ DeviceList::DeviceList() { SDL_AddEventWatch(EWWrapper, this); } -DeviceList::~DeviceList() { SDL_RemoveEventWatch(EWWrapper, this); } +DeviceList::~DeviceList() { + SDL_RemoveEventWatch(EWWrapper, this); + SDL_QuitSubSystem(SDL_INIT_GAMEPAD); +} + +void DeviceList::PumpEvents() { + // If there is no frontend event loop, events must be pumped manually + if (!has_frontend) + SDL_PumpEvents(); +} void DeviceList::EventWatcher(SDL_Event* e) { switch (e->type) { diff --git a/src/core/input/sdl/device_list.hpp b/src/core/input/sdl/device_list.hpp index fc2e123e..f4d8d081 100644 --- a/src/core/input/sdl/device_list.hpp +++ b/src/core/input/sdl/device_list.hpp @@ -8,11 +8,14 @@ namespace hydra::input::sdl { class DeviceList : public IDeviceList { public: DeviceList(); - ~DeviceList(); + ~DeviceList() override; + + void PumpEvents() override; void EventWatcher(SDL_Event* e); private: + bool has_frontend; // Indicates whether the an SDL frontend is used u32 keyboard_count; // Helpers diff --git a/src/core/input/sdl/keyboard.cpp b/src/core/input/sdl/keyboard.cpp index ee7cc5b8..4a59faea 100644 --- a/src/core/input/sdl/keyboard.cpp +++ b/src/core/input/sdl/keyboard.cpp @@ -7,8 +7,7 @@ namespace hydra::input::sdl { bool Keyboard::IsPressedImpl(Key key) { #define KEY_CASE(key, sdl_key) \ case Key::key: \ - return st[sdl_key]; \ - break; + return st[sdl_key]; const bool* st = SDL_GetKeyboardState(nullptr); @@ -71,4 +70,4 @@ bool Keyboard::IsPressedImpl(Key key) { #undef KEY_CASE } -} // namespace hydra::input::sdl \ No newline at end of file +} // namespace hydra::input::sdl diff --git a/src/core/system.cpp b/src/core/system.cpp index fa8f1e87..c981cb24 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -459,6 +459,9 @@ void System::Resume() { void System::ProgressFrame(u32 width, u32 height, bool& out_dt_average_updated) { + // Pump input events + input_device_manager.PumpEvents(); + // Set the resolution for OS os.SetSurfaceResolution({width, height}); diff --git a/src/frontend/sdl3/window.cpp b/src/frontend/sdl3/window.cpp index 170be2d7..4cb002c3 100644 --- a/src/frontend/sdl3/window.cpp +++ b/src/frontend/sdl3/window.cpp @@ -6,12 +6,8 @@ namespace hydra::frontend::sdl3 { Context::Context() { - u32 flags = CONFIG_INSTANCE.GetInputBackend() == InputBackend::Sdl - ? SDL_INIT_VIDEO | SDL_INIT_GAMEPAD - : SDL_INIT_VIDEO; - if (!SDL_Init(flags)) { - LOG_FATAL(SDL3Window, "Failed to initialize SDL3: {}", SDL_GetError()); - return; + if (!SDL_InitSubSystem(SDL_INIT_VIDEO)) { + LOG_FATAL(SDL3Window, "Failed to initialize SDL: {}", SDL_GetError()); } } @@ -26,7 +22,6 @@ Window::Window(int argc, const char* argv[]) : system(*this) { &window, &renderer)) { LOG_FATAL(SDL3Window, "Failed to create window/renderer: {}", SDL_GetError()); - return; } // Parse arguments