mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-24 00:27:01 +02:00
Since the launch of the steam controller I think it's only best to push towards updating to SDL3 allowing for a wider range of controller support I went ahead and started on getting it working. Everything here should be functional, I've personally tested it all on Arch Linux. Still untested on windows, so looking for feedback on that Any feedback and help would be appreciated! Main changes: - Bump everything to SDL3 - Handle SDL3 audio and input - Add steam controller support, including HD Rumble - Improved battery reporting via the status icon by using real % rather than state alone Co-authored-by: crueter <crueter@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3952 Reviewed-by: crueter <crueter@eden-emu.dev> Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: Lizzie <lizzie@eden-emu.dev>
515 lines
15 KiB
C++
515 lines
15 KiB
C++
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include <memory>
|
|
#include "common/input.h"
|
|
#include "common/param_package.h"
|
|
#include "input_common/drivers/camera.h"
|
|
#include "input_common/drivers/keyboard.h"
|
|
#include "input_common/drivers/mouse.h"
|
|
#include "input_common/drivers/tas_input.h"
|
|
#include "input_common/drivers/touch_screen.h"
|
|
#include "input_common/drivers/udp_client.h"
|
|
#include "input_common/drivers/virtual_amiibo.h"
|
|
#include "input_common/drivers/virtual_gamepad.h"
|
|
#include "input_common/helpers/stick_from_buttons.h"
|
|
#include "input_common/helpers/touch_from_buttons.h"
|
|
#include "input_common/input_engine.h"
|
|
#include "input_common/input_mapping.h"
|
|
#include "input_common/input_poller.h"
|
|
#include "input_common/main.h"
|
|
|
|
#ifdef ENABLE_LIBUSB
|
|
#include "input_common/drivers/gc_adapter.h"
|
|
#endif
|
|
#ifdef HAVE_SDL3
|
|
#include "input_common/drivers/joycon.h"
|
|
#include "input_common/drivers/sdl_driver.h"
|
|
#endif
|
|
|
|
#ifdef ANDROID
|
|
#include "input_common/drivers/android.h"
|
|
#endif
|
|
|
|
namespace InputCommon {
|
|
|
|
/// Dummy engine to get periodic updates
|
|
class UpdateEngine final : public InputEngine {
|
|
public:
|
|
explicit UpdateEngine(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
|
|
PreSetController(identifier);
|
|
}
|
|
|
|
void PumpEvents() {
|
|
SetButton(identifier, 0, last_state);
|
|
last_state = !last_state;
|
|
}
|
|
|
|
private:
|
|
static constexpr PadIdentifier identifier = {
|
|
.guid = Common::UUID{},
|
|
.port = 0,
|
|
.pad = 0,
|
|
};
|
|
|
|
bool last_state{};
|
|
};
|
|
|
|
struct InputSubsystem::Impl {
|
|
template <typename Engine>
|
|
void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) {
|
|
MappingCallback mapping_callback{[this](const MappingData& data) { RegisterInput(data); }};
|
|
|
|
engine = std::make_shared<Engine>(name);
|
|
engine->SetMappingCallback(mapping_callback);
|
|
|
|
std::shared_ptr<InputFactory> input_factory = std::make_shared<InputFactory>(engine);
|
|
std::shared_ptr<OutputFactory> output_factory = std::make_shared<OutputFactory>(engine);
|
|
Common::Input::RegisterInputFactory(engine->GetEngineName(), std::move(input_factory));
|
|
Common::Input::RegisterOutputFactory(engine->GetEngineName(), std::move(output_factory));
|
|
}
|
|
|
|
void Initialize() {
|
|
mapping_factory = std::make_shared<MappingFactory>();
|
|
|
|
RegisterEngine("updater", update_engine);
|
|
RegisterEngine("keyboard", keyboard);
|
|
RegisterEngine("mouse", mouse);
|
|
RegisterEngine("touch", touch_screen);
|
|
#ifdef ENABLE_LIBUSB
|
|
RegisterEngine("gcpad", gcadapter);
|
|
#endif
|
|
RegisterEngine("cemuhookudp", udp_client);
|
|
RegisterEngine("tas", tas_input);
|
|
RegisterEngine("camera", camera);
|
|
#ifdef ANDROID
|
|
RegisterEngine("android", android);
|
|
#endif
|
|
RegisterEngine("virtual_amiibo", virtual_amiibo);
|
|
RegisterEngine("virtual_gamepad", virtual_gamepad);
|
|
#ifdef HAVE_SDL3
|
|
RegisterEngine("sdl", sdl);
|
|
RegisterEngine("joycon", joycon);
|
|
#endif
|
|
|
|
Common::Input::RegisterInputFactory("touch_from_button",
|
|
std::make_shared<TouchFromButton>());
|
|
Common::Input::RegisterInputFactory("analog_from_button",
|
|
std::make_shared<StickFromButton>());
|
|
}
|
|
|
|
template <typename Engine>
|
|
void UnregisterEngine(std::shared_ptr<Engine>& engine) {
|
|
Common::Input::UnregisterInputFactory(engine->GetEngineName());
|
|
Common::Input::UnregisterOutputFactory(engine->GetEngineName());
|
|
engine.reset();
|
|
}
|
|
|
|
void Shutdown() {
|
|
UnregisterEngine(update_engine);
|
|
UnregisterEngine(keyboard);
|
|
UnregisterEngine(mouse);
|
|
UnregisterEngine(touch_screen);
|
|
#ifdef ENABLE_LIBUSB
|
|
UnregisterEngine(gcadapter);
|
|
#endif
|
|
UnregisterEngine(udp_client);
|
|
UnregisterEngine(tas_input);
|
|
UnregisterEngine(camera);
|
|
#ifdef ANDROID
|
|
UnregisterEngine(android);
|
|
#endif
|
|
UnregisterEngine(virtual_amiibo);
|
|
UnregisterEngine(virtual_gamepad);
|
|
#ifdef HAVE_SDL3
|
|
UnregisterEngine(sdl);
|
|
UnregisterEngine(joycon);
|
|
#endif
|
|
|
|
Common::Input::UnregisterInputFactory("touch_from_button");
|
|
Common::Input::UnregisterInputFactory("analog_from_button");
|
|
}
|
|
|
|
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
|
|
std::vector<Common::ParamPackage> devices = {
|
|
Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
|
|
};
|
|
|
|
#ifndef ANDROID
|
|
auto keyboard_devices = keyboard->GetInputDevices();
|
|
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
|
|
auto mouse_devices = mouse->GetInputDevices();
|
|
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
|
|
#endif
|
|
#ifdef ANDROID
|
|
auto android_devices = android->GetInputDevices();
|
|
devices.insert(devices.end(), android_devices.begin(), android_devices.end());
|
|
#endif
|
|
#ifdef ENABLE_LIBUSB
|
|
auto gcadapter_devices = gcadapter->GetInputDevices();
|
|
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
|
|
#endif
|
|
auto udp_devices = udp_client->GetInputDevices();
|
|
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
|
|
#ifdef HAVE_SDL3
|
|
auto joycon_devices = joycon->GetInputDevices();
|
|
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
|
|
auto sdl_devices = sdl->GetInputDevices();
|
|
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
|
|
#endif
|
|
|
|
return devices;
|
|
}
|
|
|
|
[[nodiscard]] std::shared_ptr<InputEngine> GetInputEngine(
|
|
const Common::ParamPackage& params) const {
|
|
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
|
return nullptr;
|
|
}
|
|
const std::string engine = params.Get("engine", "");
|
|
if (engine == keyboard->GetEngineName()) {
|
|
return keyboard;
|
|
}
|
|
if (engine == mouse->GetEngineName()) {
|
|
return mouse;
|
|
}
|
|
#ifdef ANDROID
|
|
if (engine == android->GetEngineName()) {
|
|
return android;
|
|
}
|
|
#endif
|
|
#ifdef ENABLE_LIBUSB
|
|
if (engine == gcadapter->GetEngineName()) {
|
|
return gcadapter;
|
|
}
|
|
#endif
|
|
if (engine == udp_client->GetEngineName()) {
|
|
return udp_client;
|
|
}
|
|
#ifdef HAVE_SDL3
|
|
if (engine == sdl->GetEngineName()) {
|
|
return sdl;
|
|
}
|
|
if (engine == joycon->GetEngineName()) {
|
|
return joycon;
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
[[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
|
|
const Common::ParamPackage& params) const {
|
|
const auto input_engine = GetInputEngine(params);
|
|
|
|
if (input_engine == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
return input_engine->GetAnalogMappingForDevice(params);
|
|
}
|
|
|
|
[[nodiscard]] ButtonMapping GetButtonMappingForDevice(
|
|
const Common::ParamPackage& params) const {
|
|
const auto input_engine = GetInputEngine(params);
|
|
|
|
if (input_engine == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
return input_engine->GetButtonMappingForDevice(params);
|
|
}
|
|
|
|
[[nodiscard]] MotionMapping GetMotionMappingForDevice(
|
|
const Common::ParamPackage& params) const {
|
|
const auto input_engine = GetInputEngine(params);
|
|
|
|
if (input_engine == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
return input_engine->GetMotionMappingForDevice(params);
|
|
}
|
|
|
|
Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const {
|
|
if (!params.Has("engine") || params.Get("engine", "") == "any") {
|
|
return Common::Input::ButtonNames::Undefined;
|
|
}
|
|
const auto input_engine = GetInputEngine(params);
|
|
|
|
if (input_engine == nullptr) {
|
|
return Common::Input::ButtonNames::Invalid;
|
|
}
|
|
|
|
return input_engine->GetUIName(params);
|
|
}
|
|
|
|
bool IsStickInverted(const Common::ParamPackage& params) {
|
|
const auto input_engine = GetInputEngine(params);
|
|
|
|
if (input_engine == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
return input_engine->IsStickInverted(params);
|
|
}
|
|
|
|
bool IsController(const Common::ParamPackage& params) {
|
|
const std::string engine = params.Get("engine", "");
|
|
if (engine == mouse->GetEngineName()) {
|
|
return true;
|
|
}
|
|
#ifdef ANDROID
|
|
if (engine == android->GetEngineName()) {
|
|
return true;
|
|
}
|
|
#endif
|
|
#ifdef ENABLE_LIBUSB
|
|
if (engine == gcadapter->GetEngineName()) {
|
|
return true;
|
|
}
|
|
#endif
|
|
if (engine == udp_client->GetEngineName()) {
|
|
return true;
|
|
}
|
|
if (engine == tas_input->GetEngineName()) {
|
|
return true;
|
|
}
|
|
if (engine == virtual_gamepad->GetEngineName()) {
|
|
return true;
|
|
}
|
|
#ifdef HAVE_SDL3
|
|
if (engine == sdl->GetEngineName()) {
|
|
return true;
|
|
}
|
|
if (engine == joycon->GetEngineName()) {
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void BeginConfiguration() {
|
|
keyboard->BeginConfiguration();
|
|
mouse->BeginConfiguration();
|
|
#ifdef ANDROID
|
|
android->BeginConfiguration();
|
|
#endif
|
|
#ifdef ENABLE_LIBUSB
|
|
gcadapter->BeginConfiguration();
|
|
#endif
|
|
udp_client->BeginConfiguration();
|
|
#ifdef HAVE_SDL3
|
|
sdl->BeginConfiguration();
|
|
joycon->BeginConfiguration();
|
|
#endif
|
|
}
|
|
|
|
void EndConfiguration() {
|
|
keyboard->EndConfiguration();
|
|
mouse->EndConfiguration();
|
|
#ifdef ANDROID
|
|
android->EndConfiguration();
|
|
#endif
|
|
#ifdef ENABLE_LIBUSB
|
|
gcadapter->EndConfiguration();
|
|
#endif
|
|
udp_client->EndConfiguration();
|
|
#ifdef HAVE_SDL3
|
|
sdl->EndConfiguration();
|
|
joycon->EndConfiguration();
|
|
#endif
|
|
}
|
|
|
|
void PumpEvents() const {
|
|
update_engine->PumpEvents();
|
|
#ifdef HAVE_SDL3
|
|
sdl->PumpEvents();
|
|
#endif
|
|
}
|
|
|
|
void RegisterInput(const MappingData& data) {
|
|
mapping_factory->RegisterInput(data);
|
|
}
|
|
|
|
std::shared_ptr<MappingFactory> mapping_factory;
|
|
|
|
std::shared_ptr<UpdateEngine> update_engine;
|
|
std::shared_ptr<Keyboard> keyboard;
|
|
std::shared_ptr<Mouse> mouse;
|
|
std::shared_ptr<TouchScreen> touch_screen;
|
|
std::shared_ptr<TasInput::Tas> tas_input;
|
|
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
|
|
std::shared_ptr<Camera> camera;
|
|
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
|
|
std::shared_ptr<VirtualGamepad> virtual_gamepad;
|
|
|
|
#ifdef ENABLE_LIBUSB
|
|
std::shared_ptr<GCAdapter> gcadapter;
|
|
#endif
|
|
|
|
#ifdef HAVE_SDL3
|
|
std::shared_ptr<SDLDriver> sdl;
|
|
std::shared_ptr<Joycons> joycon;
|
|
#endif
|
|
|
|
#ifdef ANDROID
|
|
std::shared_ptr<Android> android;
|
|
#endif
|
|
};
|
|
|
|
InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
|
|
|
|
InputSubsystem::~InputSubsystem() = default;
|
|
|
|
void InputSubsystem::Initialize() {
|
|
impl->Initialize();
|
|
}
|
|
|
|
void InputSubsystem::Shutdown() {
|
|
impl->Shutdown();
|
|
}
|
|
|
|
Keyboard* InputSubsystem::GetKeyboard() {
|
|
return impl->keyboard.get();
|
|
}
|
|
|
|
const Keyboard* InputSubsystem::GetKeyboard() const {
|
|
return impl->keyboard.get();
|
|
}
|
|
|
|
Mouse* InputSubsystem::GetMouse() {
|
|
return impl->mouse.get();
|
|
}
|
|
|
|
const Mouse* InputSubsystem::GetMouse() const {
|
|
return impl->mouse.get();
|
|
}
|
|
|
|
TouchScreen* InputSubsystem::GetTouchScreen() {
|
|
return impl->touch_screen.get();
|
|
}
|
|
|
|
const TouchScreen* InputSubsystem::GetTouchScreen() const {
|
|
return impl->touch_screen.get();
|
|
}
|
|
|
|
TasInput::Tas* InputSubsystem::GetTas() {
|
|
return impl->tas_input.get();
|
|
}
|
|
|
|
const TasInput::Tas* InputSubsystem::GetTas() const {
|
|
return impl->tas_input.get();
|
|
}
|
|
|
|
Camera* InputSubsystem::GetCamera() {
|
|
return impl->camera.get();
|
|
}
|
|
|
|
const Camera* InputSubsystem::GetCamera() const {
|
|
return impl->camera.get();
|
|
}
|
|
|
|
#ifdef ANDROID
|
|
Android* InputSubsystem::GetAndroid() {
|
|
return impl->android.get();
|
|
}
|
|
|
|
const Android* InputSubsystem::GetAndroid() const {
|
|
return impl->android.get();
|
|
}
|
|
#endif
|
|
|
|
VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() {
|
|
return impl->virtual_amiibo.get();
|
|
}
|
|
|
|
const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const {
|
|
return impl->virtual_amiibo.get();
|
|
}
|
|
|
|
VirtualGamepad* InputSubsystem::GetVirtualGamepad() {
|
|
return impl->virtual_gamepad.get();
|
|
}
|
|
|
|
const VirtualGamepad* InputSubsystem::GetVirtualGamepad() const {
|
|
return impl->virtual_gamepad.get();
|
|
}
|
|
|
|
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
|
|
return impl->GetInputDevices();
|
|
}
|
|
|
|
AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
|
|
return impl->GetAnalogMappingForDevice(device);
|
|
}
|
|
|
|
ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
|
|
return impl->GetButtonMappingForDevice(device);
|
|
}
|
|
|
|
MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPackage& device) const {
|
|
return impl->GetMotionMappingForDevice(device);
|
|
}
|
|
|
|
Common::Input::ButtonNames InputSubsystem::GetButtonName(const Common::ParamPackage& params) const {
|
|
return impl->GetButtonName(params);
|
|
}
|
|
|
|
bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
|
|
return impl->IsController(params);
|
|
}
|
|
|
|
bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
|
|
if (params.Has("axis_x") && params.Has("axis_y")) {
|
|
return impl->IsStickInverted(params);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void InputSubsystem::ReloadInputDevices() {
|
|
impl->udp_client.get()->ReloadSockets();
|
|
}
|
|
|
|
void InputSubsystem::BeginMapping(Polling::InputType type) {
|
|
impl->BeginConfiguration();
|
|
impl->mapping_factory->BeginMapping(type);
|
|
}
|
|
|
|
Common::ParamPackage InputSubsystem::GetNextInput() const {
|
|
return impl->mapping_factory->GetNextInput();
|
|
}
|
|
|
|
void InputSubsystem::StopMapping() const {
|
|
impl->EndConfiguration();
|
|
impl->mapping_factory->StopMapping();
|
|
}
|
|
|
|
void InputSubsystem::PumpEvents() const {
|
|
impl->PumpEvents();
|
|
}
|
|
|
|
std::string GenerateKeyboardParam(int key_code) {
|
|
Common::ParamPackage param;
|
|
param.Set("engine", "keyboard");
|
|
param.Set("code", key_code);
|
|
param.Set("toggle", false);
|
|
return param.Serialize();
|
|
}
|
|
|
|
std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
|
|
int key_modifier, float modifier_scale) {
|
|
Common::ParamPackage circle_pad_param{
|
|
{"engine", "analog_from_button"},
|
|
{"up", GenerateKeyboardParam(key_up)},
|
|
{"down", GenerateKeyboardParam(key_down)},
|
|
{"left", GenerateKeyboardParam(key_left)},
|
|
{"right", GenerateKeyboardParam(key_right)},
|
|
{"modifier", GenerateKeyboardParam(key_modifier)},
|
|
{"modifier_scale", std::to_string(modifier_scale)},
|
|
};
|
|
return circle_pad_param.Serialize();
|
|
}
|
|
} // namespace InputCommon
|