// SPDX-FileCopyrightText: 2018 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" #include "common/math_util.h" #include "common/param_package.h" #include "common/settings.h" #include "common/thread.h" #include "common/vector_math.h" #include "input_common/drivers/sdl_driver.h" namespace InputCommon { namespace { Common::UUID GetGUID(SDL_Joystick* joystick) { const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); std::array data{}; std::memcpy(data.data(), guid.data, sizeof(data)); // Clear controller name crc std::memset(data.data() + 2, 0, sizeof(u16)); return Common::UUID{data}; } } // Anonymous namespace static int SDLEventWatcher(void* user_data, SDL_Event* event) { auto* const sdl_state = static_cast(user_data); sdl_state->HandleGameControllerEvent(*event); return 0; } SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { if (!Settings::values.enable_raw_input) { // Disable raw input. When enabled this setting causes SDL to die when a web applet opens SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); } // Prevent SDL from adding undesired axis SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and // not a generic one SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH, "1"); // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native // driver on Linux. SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); // If the frontend is going to manage the event loop, then we don't start one here const auto start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); return; } SDL_AddEventWatch(&SDLEventWatcher, this); initialized = true; if (start_thread) { vibration_thread = std::thread([this] { Common::SetCurrentThreadName("SDL_Vibration"); using namespace std::chrono_literals; while (initialized) { SendVibrations(); std::this_thread::sleep_for(10ms); } }); } // Because the events for joystick connection happens before we have our event watcher added, we // can just open all the joysticks right here for (int i = 0; i < SDL_NumJoysticks(); ++i) { InitJoystick(i); } } SDLDriver::~SDLDriver() { CloseJoysticks(); SDL_DelEventWatch(&SDLEventWatcher, this); if (initialized) { vibration_thread.join(); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); } initialized = false; } void SDLDriver::InitJoystick(int joystick_index) { SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); SDL_GameController* sdl_gamecontroller = nullptr; if (SDL_IsGameController(joystick_index)) { sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); } if (!sdl_joystick) { LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); return; } const auto guid = GetGUID(sdl_joystick); } void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { const auto guid = GetGUID(sdl_joystick); } void SDLDriver::CloseJoysticks() { std::scoped_lock lock{joystick_map_mutex}; } void SDLDriver::PumpEvents() const { if (initialized) { SDL_PumpEvents(); } } void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { switch (event.type) { case SDL_JOYBUTTONUP: { break; } case SDL_JOYBUTTONDOWN: { break; } case SDL_JOYHATMOTION: { break; } case SDL_JOYAXISMOTION: { break; } case SDL_CONTROLLERSENSORUPDATE: { break; } case SDL_JOYDEVICEREMOVED: LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); break; case SDL_JOYDEVICEADDED: LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); InitJoystick(event.jdevice.which); break; } } bool SDLDriver::IsStickInverted(const Common::ParamPackage& params) { if (!params.Has("guid") || !params.Has("port")) { return false; } return true; } bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) { return true; } Common::Input::VibrationError SDLDriver::SetVibration( const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { return Common::Input::VibrationError::None; } void SDLDriver::SendVibrations() { std::vector filtered_vibrations{}; while (!vibration_queue.Empty()) { VibrationRequest request; vibration_queue.Pop(request); } } std::vector SDLDriver::GetInputDevices() const { std::vector devices; return devices; } ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) { if (!params.Has("guid") || !params.Has("port")) { return {}; } return {}; } AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) { if (!params.Has("guid") || !params.Has("port")) { return {}; } return {}; } MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) { if (!params.Has("guid") || !params.Has("port")) { return {}; } return {}; } Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const { if (params.Has("button")) { // TODO(German77): Find how to substitute the values for real button names return Common::Input::ButtonNames::Value; } if (params.Has("hat")) { return Common::Input::ButtonNames::Value; } if (params.Has("axis")) { return Common::Input::ButtonNames::Value; } if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { return Common::Input::ButtonNames::Value; } if (params.Has("motion")) { return Common::Input::ButtonNames::Engine; } return Common::Input::ButtonNames::Invalid; } std::string SDLDriver::GetHatButtonName(u8 direction_value) const { switch (direction_value) { case SDL_HAT_UP: return "up"; case SDL_HAT_DOWN: return "down"; case SDL_HAT_LEFT: return "left"; case SDL_HAT_RIGHT: return "right"; default: return {}; } } u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const { Uint8 direction; if (direction_name == "up") { direction = SDL_HAT_UP; } else if (direction_name == "down") { direction = SDL_HAT_DOWN; } else if (direction_name == "left") { direction = SDL_HAT_LEFT; } else if (direction_name == "right") { direction = SDL_HAT_RIGHT; } else { direction = 0; } return direction; } } // namespace InputCommon