diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index ec5342b71f..d79af3b78c 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -487,6 +487,7 @@ + @@ -1111,6 +1112,7 @@ + diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 2caa334cca..20ae0f3f17 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -56,6 +56,8 @@ add_library(inputcommon ControllerInterface/ControllerInterface.h ControllerInterface/CoreDevice.cpp ControllerInterface/CoreDevice.h + ControllerInterface/InputBackend.cpp + ControllerInterface/InputBackend.h ControllerInterface/MappingCommon.cpp ControllerInterface/MappingCommon.h ControllerInterface/Wiimote/WiimoteController.cpp diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 442e5502fa..6a6affd478 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -64,19 +64,19 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) // nothing needed for OSX and Quartz #endif #ifdef CIFACE_USE_SDL - ciface::SDL::Init(); + m_input_backends.emplace_back(ciface::SDL::CreateInputBackend(this)); #endif #ifdef CIFACE_USE_ANDROID // nothing needed #endif #ifdef CIFACE_USE_EVDEV - ciface::evdev::Init(); + m_input_backends.emplace_back(ciface::evdev::CreateInputBackend(this)); #endif #ifdef CIFACE_USE_PIPES // nothing needed #endif #ifdef CIFACE_USE_DUALSHOCKUDPCLIENT - ciface::DualShockUDPClient::Init(); + m_input_backends.emplace_back(ciface::DualShockUDPClient::CreateInputBackend(this)); #endif // Don't allow backends to add devices before the first RefreshDevices() as they will be cleaned @@ -181,21 +181,15 @@ void ControllerInterface::RefreshDevices(RefreshReason reason) ciface::Quartz::PopulateDevices(m_wsi.render_window); } #endif -#ifdef CIFACE_USE_SDL - ciface::SDL::PopulateDevices(); -#endif #ifdef CIFACE_USE_ANDROID ciface::Android::PopulateDevices(); #endif -#ifdef CIFACE_USE_EVDEV - ciface::evdev::PopulateDevices(); -#endif #ifdef CIFACE_USE_PIPES ciface::Pipes::PopulateDevices(); #endif -#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT - ciface::DualShockUDPClient::PopulateDevices(); -#endif + + for (auto& backend : m_input_backends) + backend->PopulateDevices(); WiimoteReal::PopulateDevices(); @@ -242,18 +236,12 @@ void ControllerInterface::Shutdown() ciface::OSX::DeInit(); ciface::Quartz::DeInit(); #endif -#ifdef CIFACE_USE_SDL - ciface::SDL::DeInit(); -#endif #ifdef CIFACE_USE_ANDROID // nothing needed #endif -#ifdef CIFACE_USE_EVDEV - ciface::evdev::Shutdown(); -#endif -#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT - ciface::DualShockUDPClient::DeInit(); -#endif + + // Empty the container of input backends to deconstruct and deinitialize them. + m_input_backends.clear(); // Make sure no devices had been added within Shutdown() in the time // between checking they checked atomic m_is_init bool and we changed it. @@ -384,15 +372,19 @@ void ControllerInterface::UpdateInput() // TODO: if we are an emulation input channel, we should probably always lock // Prefer outdated values over blocking UI or CPU thread (avoids short but noticeable frame drop) - if (m_devices_mutex.try_lock()) + if (!m_devices_mutex.try_lock()) + return; + + std::lock_guard lk(m_devices_mutex, std::adopt_lock); + + for (auto& backend : m_input_backends) + backend->UpdateInput(); + + for (const auto& d : m_devices) { - std::lock_guard lk(m_devices_mutex, std::adopt_lock); - for (const auto& d : m_devices) - { - // Theoretically we could avoid updating input on devices that don't have any references to - // them, but in practice a few devices types could break in different ways, so we don't - d->UpdateInput(); - } + // Theoretically we could avoid updating input on devices that don't have any references to + // them, but in practice a few devices types could break in different ways, so we don't + d->UpdateInput(); } } diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 3a0276de41..59909aeec8 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -12,6 +12,7 @@ #include "Common/Matrix.h" #include "Common/WindowSystemInfo.h" #include "InputCommon/ControllerInterface/CoreDevice.h" +#include "InputCommon/ControllerInterface/InputBackend.h" // enable disable sources #ifdef _WIN32 @@ -133,6 +134,8 @@ private: WindowSystemInfo m_wsi; std::atomic m_aspect_ratio_adjustment = 1; std::atomic m_requested_mouse_centering = false; + + std::vector> m_input_backends; }; namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp index 15b0915381..12279b504b 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -130,7 +130,7 @@ private: public: void UpdateInput() override; - Device(std::string name, int index, std::string server_address, u16 server_port); + Device(std::string name, int index, std::string server_address, u16 server_port, u32 client_uid); std::string GetName() const final override; std::string GetSource() const final override; @@ -150,13 +150,15 @@ private: bool m_prev_touch_valid = false; int m_touch_x = 0; int m_touch_y = 0; - std::string m_server_address; - u16 m_server_port; + const std::string m_server_address; + const u16 m_server_port; s16 m_touch_x_min; s16 m_touch_y_min; s16 m_touch_x_max; s16 m_touch_y_max; + + const u32 m_client_uid; }; using MathUtil::GRAVITY_ACCELERATION; @@ -196,13 +198,34 @@ struct Server SteadyClock::time_point m_disconnect_time = SteadyClock::now(); }; -static bool s_has_init; -static bool s_servers_enabled; -static std::vector s_servers; -static u32 s_client_uid; -static SteadyClock::time_point s_next_listports_time; -static std::thread s_hotplug_thread; -static Common::Flag s_hotplug_thread_running; +class InputBackend final : public ciface::InputBackend +{ +public: + InputBackend(ControllerInterface* controller_interface); + ~InputBackend(); + void PopulateDevices() override; + +private: + void ConfigChanged(); + void Restart(); + + void HotplugThreadFunc(); + void StartHotplugThread(); + void StopHotplugThread(); + + bool m_servers_enabled; + std::vector m_servers; + u32 m_client_uid; + SteadyClock::time_point m_next_listports_time; + std::thread m_hotplug_thread; + Common::Flag m_hotplug_thread_running; + std::size_t m_config_change_callback_id; +}; + +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) +{ + return std::make_unique(controller_interface); +} static bool IsSameController(const Proto::MessageType::PortInfo& a, const Proto::MessageType::PortInfo& b) @@ -212,29 +235,29 @@ static bool IsSameController(const Proto::MessageType::PortInfo& a, std::tie(b.pad_id, b.pad_state, b.model, b.connection_type, b.pad_mac_address); } -static void HotplugThreadFunc() +void InputBackend::HotplugThreadFunc() { Common::SetCurrentThreadName("DualShockUDPClient Hotplug Thread"); INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread started"); Common::ScopeGuard thread_stop_guard{ [] { INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread stopped"); }}; - std::vector timed_out_servers(s_servers.size(), false); + std::vector timed_out_servers(m_servers.size(), false); - while (s_hotplug_thread_running.IsSet()) + while (m_hotplug_thread_running.IsSet()) { using namespace std::chrono; using namespace std::chrono_literals; const auto now = SteadyClock::now(); - if (now >= s_next_listports_time) + if (now >= m_next_listports_time) { - s_next_listports_time = now + SERVER_LISTPORTS_INTERVAL; + m_next_listports_time = now + SERVER_LISTPORTS_INTERVAL; - for (size_t i = 0; i < s_servers.size(); ++i) + for (size_t i = 0; i < m_servers.size(); ++i) { - auto& server = s_servers[i]; - Proto::Message msg(s_client_uid); + auto& server = m_servers[i]; + Proto::Message msg(m_client_uid); auto& list_ports = msg.m_message; // We ask for x possible devices. We will receive a message for every connected device. list_ports.pad_request_count = SERVER_ASKED_PADS; @@ -250,12 +273,12 @@ static void HotplugThreadFunc() } sf::SocketSelector selector; - for (auto& server : s_servers) + for (auto& server : m_servers) { selector.add(server.m_socket); } - auto timeout = duration_cast(s_next_listports_time - SteadyClock::now()); + auto timeout = duration_cast(m_next_listports_time - SteadyClock::now()); // Receive controller port info within a time from our request. // Run this even if we sent no new requests, to disconnect devices, @@ -271,9 +294,9 @@ static void HotplugThreadFunc() if (selector.wait(sf::milliseconds(current_timeout.count()))) { // Now check all the servers because we don't know which one(s) sent a reply - for (size_t i = 0; i < s_servers.size(); ++i) + for (size_t i = 0; i < m_servers.size(); ++i) { - auto& server = s_servers[i]; + auto& server = m_servers[i]; if (!selector.isReady(server.m_socket)) { continue; @@ -302,20 +325,20 @@ static void HotplugThreadFunc() { server.m_port_info[port_info->pad_id] = *port_info; // Just remove and re-add all the devices for simplicity - g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); }); + GetControllerInterface().PlatformPopulateDevices([this] { PopulateDevices(); }); } } } } - if (!s_hotplug_thread_running.IsSet()) // Avoid hanging the thread for too long + if (!m_hotplug_thread_running.IsSet()) // Avoid hanging the thread for too long return; } while (timeout > 0ms); // If we have failed to receive any information from the server (or even send it), // disconnect all devices from it (after enough time has elapsed, to avoid false positives). - for (size_t i = 0; i < s_servers.size(); ++i) + for (size_t i = 0; i < m_servers.size(); ++i) { - auto& server = s_servers[i]; + auto& server = m_servers[i]; if (timed_out_servers[i] && SteadyClock::now() >= server.m_disconnect_time) { bool any_connected = false; @@ -328,49 +351,49 @@ static void HotplugThreadFunc() } // We can't only remove devices added by this server as we wouldn't know which they are if (any_connected) - g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); }); + GetControllerInterface().PlatformPopulateDevices([this] { PopulateDevices(); }); } } } } -static void StartHotplugThread() +void InputBackend::StartHotplugThread() { // Mark the thread as running. - if (!s_hotplug_thread_running.TestAndSet()) + if (!m_hotplug_thread_running.TestAndSet()) { // It was already running. return; } - s_hotplug_thread = std::thread(HotplugThreadFunc); + m_hotplug_thread = std::thread(&InputBackend::HotplugThreadFunc, this); } -static void StopHotplugThread() +void InputBackend::StopHotplugThread() { // Tell the hotplug thread to stop. - if (!s_hotplug_thread_running.TestAndClear()) + if (!m_hotplug_thread_running.TestAndClear()) { // It wasn't running, we're done. return; } - s_hotplug_thread.join(); + m_hotplug_thread.join(); - for (auto& server : s_servers) + for (auto& server : m_servers) { server.m_socket.unbind(); // interrupt blocking socket } } // Also just start -static void Restart() +void InputBackend::Restart() { INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient Restart"); StopHotplugThread(); - for (auto& server : s_servers) + for (auto& server : m_servers) { for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++) { @@ -380,37 +403,34 @@ static void Restart() } // Only removes devices as servers have been cleaned - g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); }); + GetControllerInterface().PlatformPopulateDevices([this] { PopulateDevices(); }); - s_client_uid = Common::Random::GenerateValue(); - s_next_listports_time = SteadyClock::now(); + m_client_uid = Common::Random::GenerateValue(); + m_next_listports_time = SteadyClock::now(); - if (s_servers_enabled && !s_servers.empty()) + if (m_servers_enabled && !m_servers.empty()) StartHotplugThread(); } -static void ConfigChanged() +void InputBackend::ConfigChanged() { - if (!s_has_init) - return; - const bool servers_enabled = Config::Get(Settings::SERVERS_ENABLED); const std::string servers_setting = Config::Get(Settings::SERVERS); std::string new_servers_setting; - for (const auto& server : s_servers) + for (const auto& server : m_servers) { new_servers_setting += fmt::format("{}:{}:{};", server.m_description, server.m_address, server.m_port); } - if (servers_enabled != s_servers_enabled || servers_setting != new_servers_setting) + if (servers_enabled != m_servers_enabled || servers_setting != new_servers_setting) { - // Stop the thread before writing to s_servers + // Stop the thread before writing to m_servers StopHotplugThread(); - s_servers_enabled = servers_enabled; - s_servers.clear(); + m_servers_enabled = servers_enabled; + m_servers.clear(); const auto server_details = SplitString(servers_setting, ';'); for (const auto& server_detail : server_details) @@ -428,17 +448,15 @@ static void ConfigChanged() } u16 server_port = static_cast(port); - s_servers.emplace_back(description, server_address, server_port); + m_servers.emplace_back(description, server_address, server_port); } Restart(); } } -void Init() +InputBackend::InputBackend(ControllerInterface* controller_interface) + : ciface::InputBackend(controller_interface) { - // Does not support multiple init calls - s_has_init = true; - // The following is added for backwards compatibility const auto server_address_setting = Config::Get(Settings::SERVER_ADDRESS); const auto server_port_setting = Config::Get(Settings::SERVER_PORT); @@ -454,29 +472,30 @@ void Init() Config::SetBase(Settings::SERVER_PORT, 0); } - // It would be much better to unbind from this callback on DeInit but it's not possible as of now - Config::AddConfigChangedCallback(ConfigChanged); - ConfigChanged(); // Call it immediately to load settings + m_config_change_callback_id = + Config::AddConfigChangedCallback(std::bind(&InputBackend::ConfigChanged, this)); + // Call it immediately to load settings + ConfigChanged(); } // This can be called by the host thread as well as the hotplug thread, concurrently. // So use PlatformPopulateDevices(). -// s_servers is already safe because it can only be modified when the DSU thread is not running, +// m_servers is already safe because it can only be modified when the DSU thread is not running, // from the main thread -void PopulateDevices() +void InputBackend::PopulateDevices() { INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient PopulateDevices"); - // s_servers has already been updated so we can't use it to know which devices we removed, + // m_servers has already been updated so we can't use it to know which devices we removed, // also it's good to remove all of them before adding new ones so that their id will be set // correctly if they have the same name - g_controller_interface.RemoveDevice( + GetControllerInterface().RemoveDevice( [](const auto* dev) { return dev->GetSource() == DUALSHOCKUDP_SOURCE_NAME; }); // Users might have created more than one server on the same IP/Port. // Devices might end up being duplicated (if the server responds two all requests) // but they won't conflict. - for (const auto& server : s_servers) + for (const auto& server : m_servers) { for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++) { @@ -484,24 +503,23 @@ void PopulateDevices() if (port_info.pad_state != Proto::DsState::Connected) continue; - g_controller_interface.AddDevice(std::make_shared( - server.m_description, static_cast(port_index), server.m_address, server.m_port)); + GetControllerInterface().AddDevice( + std::make_shared(server.m_description, static_cast(port_index), + server.m_address, server.m_port, m_client_uid)); } } } -void DeInit() +InputBackend::~InputBackend() { + Config::RemoveConfigChangedCallback(m_config_change_callback_id); StopHotplugThread(); - - s_has_init = false; - s_servers_enabled = false; - s_servers.clear(); } -Device::Device(std::string name, int index, std::string server_address, u16 server_port) +Device::Device(std::string name, int index, std::string server_address, u16 server_port, + u32 client_uid) : m_name{std::move(name)}, m_index{index}, m_server_address{std::move(server_address)}, - m_server_port{server_port} + m_server_port{server_port}, m_client_uid(client_uid) { m_socket.setBlocking(false); @@ -604,7 +622,7 @@ void Device::UpdateInput() { m_next_reregister = now + SERVER_REREGISTER_INTERVAL; - Proto::Message msg(s_client_uid); + Proto::Message msg(m_client_uid); auto& data_req = msg.m_message; data_req.register_flags = Proto::RegisterFlags::PadID; data_req.pad_id_to_register = m_index; diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h index 1a2c2826ca..7f2d62fb58 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h @@ -4,6 +4,7 @@ #pragma once #include "Common/Config/Config.h" +#include "InputCommon/ControllerInterface/InputBackend.h" namespace ciface::DualShockUDPClient { @@ -20,7 +21,6 @@ extern const Config::Info SERVERS; extern const Config::Info SERVERS_ENABLED; } // namespace Settings -void Init(); -void PopulateDevices(); -void DeInit(); +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); + } // namespace ciface::DualShockUDPClient diff --git a/Source/Core/InputCommon/ControllerInterface/InputBackend.cpp b/Source/Core/InputCommon/ControllerInterface/InputBackend.cpp new file mode 100644 index 0000000000..91685eea00 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/InputBackend.cpp @@ -0,0 +1,24 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "InputCommon/ControllerInterface/InputBackend.h" + +namespace ciface +{ +InputBackend::InputBackend(ControllerInterface* controller_interface) + : m_controller_interface(*controller_interface) +{ +} + +InputBackend::~InputBackend() = default; + +void InputBackend::UpdateInput() +{ +} + +ControllerInterface& InputBackend::GetControllerInterface() +{ + return m_controller_interface; +} + +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/InputBackend.h b/Source/Core/InputCommon/ControllerInterface/InputBackend.h new file mode 100644 index 0000000000..653bc16df1 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/InputBackend.h @@ -0,0 +1,26 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +class ControllerInterface; + +namespace ciface +{ +class InputBackend +{ +public: + InputBackend(ControllerInterface* controller_interface); + + virtual ~InputBackend(); + + virtual void PopulateDevices() = 0; + virtual void UpdateInput(); + + ControllerInterface& GetControllerInterface(); + +private: + ControllerInterface& m_controller_interface; +}; + +} // namespace ciface diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp index 0ea15251ad..327002c3a3 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.cpp @@ -29,7 +29,33 @@ static std::string GetJoystickName(int index) #endif } -static void OpenAndAddDevice(int index) +class InputBackend final : public ciface::InputBackend +{ +public: + InputBackend(ControllerInterface* controller_interface); + ~InputBackend(); + void PopulateDevices() override; + void UpdateInput() override; + +private: + void OpenAndAddDevice(int index); + +#if SDL_VERSION_ATLEAST(2, 0, 0) + bool HandleEventAndContinue(const SDL_Event& e); + + Common::Event m_init_event; + Uint32 m_stop_event_type; + Uint32 m_populate_event_type; + std::thread m_hotplug_thread; +#endif +}; + +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) +{ + return std::make_unique(controller_interface); +} + +void InputBackend::OpenAndAddDevice(int index) { SDL_Joystick* const dev = SDL_JoystickOpen(index); if (dev) @@ -37,17 +63,13 @@ static void OpenAndAddDevice(int index) auto js = std::make_shared(dev, index); // only add if it has some inputs/outputs if (!js->Inputs().empty() || !js->Outputs().empty()) - g_controller_interface.AddDevice(std::move(js)); + GetControllerInterface().AddDevice(std::move(js)); } } #if SDL_VERSION_ATLEAST(2, 0, 0) -static Common::Event s_init_event; -static Uint32 s_stop_event_type; -static Uint32 s_populate_event_type; -static std::thread s_hotplug_thread; -static bool HandleEventAndContinue(const SDL_Event& e) +bool InputBackend::HandleEventAndContinue(const SDL_Event& e) { if (e.type == SDL_JOYDEVICEADDED) { @@ -55,20 +77,20 @@ static bool HandleEventAndContinue(const SDL_Event& e) } else if (e.type == SDL_JOYDEVICEREMOVED) { - g_controller_interface.RemoveDevice([&e](const auto* device) { + GetControllerInterface().RemoveDevice([&e](const auto* device) { return device->GetSource() == "SDL" && SDL_JoystickInstanceID(static_cast(device)->GetSDLJoystick()) == e.jdevice.which; }); } - else if (e.type == s_populate_event_type) + else if (e.type == m_populate_event_type) { - g_controller_interface.PlatformPopulateDevices([] { + GetControllerInterface().PlatformPopulateDevices([this] { for (int i = 0; i < SDL_NumJoysticks(); ++i) OpenAndAddDevice(i); }); } - else if (e.type == s_stop_event_type) + else if (e.type == m_stop_event_type) { return false; } @@ -144,7 +166,8 @@ static void EnableSDLLogging() nullptr); } -void Init() +InputBackend::InputBackend(ControllerInterface* controller_interface) + : ciface::InputBackend(controller_interface) { #if !SDL_VERSION_ATLEAST(2, 0, 0) if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) != 0) @@ -168,13 +191,13 @@ void Init() SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); #endif - s_hotplug_thread = std::thread([] { + m_hotplug_thread = std::thread([this] { Common::ScopeGuard quit_guard([] { // TODO: there seems to be some sort of memory leak with SDL, quit isn't freeing everything up SDL_Quit(); }); { - Common::ScopeGuard init_guard([] { s_init_event.Set(); }); + Common::ScopeGuard init_guard([this] { m_init_event.Set(); }); if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) != 0) { @@ -188,8 +211,8 @@ void Init() ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to register custom events"); return; } - s_stop_event_type = custom_events_start; - s_populate_event_type = custom_events_start + 1; + m_stop_event_type = custom_events_start; + m_populate_event_type = custom_events_start + 1; // Drain all of the events and add the initial joysticks before returning. Otherwise, the // individual joystick events as well as the custom populate event will be handled _after_ @@ -235,26 +258,26 @@ void Init() } }); - s_init_event.Wait(); + m_init_event.Wait(); #endif } -void DeInit() +InputBackend::~InputBackend() { #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_Quit(); #else - if (!s_hotplug_thread.joinable()) + if (!m_hotplug_thread.joinable()) return; - SDL_Event stop_event{s_stop_event_type}; + SDL_Event stop_event{m_stop_event_type}; SDL_PushEvent(&stop_event); - s_hotplug_thread.join(); + m_hotplug_thread.join(); #endif } -void PopulateDevices() +void InputBackend::PopulateDevices() { #if !SDL_VERSION_ATLEAST(2, 0, 0) if (!SDL_WasInit(SDL_INIT_JOYSTICK)) @@ -263,10 +286,10 @@ void PopulateDevices() for (int i = 0; i < SDL_NumJoysticks(); ++i) OpenAndAddDevice(i); #else - if (!s_hotplug_thread.joinable()) + if (!m_hotplug_thread.joinable()) return; - SDL_Event populate_event{s_populate_event_type}; + SDL_Event populate_event{m_populate_event_type}; SDL_PushEvent(&populate_event); #endif } @@ -617,9 +640,8 @@ void Joystick::Motor::SetState(ControlState state) } #endif -void Joystick::UpdateInput() +void InputBackend::UpdateInput() { - // TODO: Don't call this for every Joystick, only once per ControllerInterface::UpdateInput() SDL_JoystickUpdate(); } diff --git a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h index f789c8846a..1b37a22b9e 100644 --- a/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h +++ b/Source/Core/InputCommon/ControllerInterface/SDL/SDL.h @@ -22,12 +22,11 @@ #endif #include "InputCommon/ControllerInterface/CoreDevice.h" +#include "InputCommon/ControllerInterface/InputBackend.h" namespace ciface::SDL { -void Init(); -void DeInit(); -void PopulateDevices(); +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); class Joystick : public Core::Device { @@ -182,8 +181,6 @@ private: #endif public: - void UpdateInput() override; - Joystick(SDL_Joystick* const joystick, const int sdl_index); ~Joystick(); diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp index d3a222890b..cf811e5b41 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -25,6 +25,43 @@ namespace ciface::evdev { +class InputBackend final : public ciface::InputBackend +{ +public: + InputBackend(ControllerInterface* controller_interface); + ~InputBackend(); + void PopulateDevices() override; + + void RemoveDevnodeObject(const std::string&); + +private: + std::shared_ptr + FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* physical_location); + + void AddDeviceNode(const char* devnode); + + void StartHotplugThread(); + void StopHotplugThread(); + void HotplugThreadFunc(); + + std::thread m_hotplug_thread; + Common::Flag m_hotplug_thread_running; + int m_wakeup_eventfd; + + // There is no easy way to get the device name from only a dev node + // during a device removed event, since libevdev can't work on removed devices; + // sysfs is not stable, so this is probably the easiest way to get a name for a node. + // This can, and will be modified by different thread, possibly concurrently, + // as devices can be destroyed by any thread at any time. As of now it's protected + // by ControllerInterface::m_devices_population_mutex. + std::map> m_devnode_objects; +}; + +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) +{ + return std::make_unique(controller_interface); +} + class Input : public Core::Device::Input { public: @@ -195,25 +232,14 @@ public: bool IsDetectable() const override { return false; } }; -static std::thread s_hotplug_thread; -static Common::Flag s_hotplug_thread_running; -static int s_wakeup_eventfd; - -// There is no easy way to get the device name from only a dev node -// during a device removed event, since libevdev can't work on removed devices; -// sysfs is not stable, so this is probably the easiest way to get a name for a node. -// This can, and will be modified by different thread, possibly concurrently, -// as devices can be destroyed by any thread at any time. As of now it's protected -// by ControllerInterface::m_devices_population_mutex. -static std::map> s_devnode_objects; - -static std::shared_ptr -FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* physical_location) +std::shared_ptr +InputBackend::FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, + const char* physical_location) { if (!unique_id || !physical_location) return nullptr; - for (auto& [node, dev] : s_devnode_objects) + for (auto& [node, dev] : m_devnode_objects) { if (const auto device = dev.lock()) { @@ -231,7 +257,7 @@ FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* phy return nullptr; } -static void AddDeviceNode(const char* devnode) +void InputBackend::AddDeviceNode(const char* devnode) { // Unfortunately udev gives us no way to filter out the non event device interfaces. // So we open it and see if it works with evdev ioctls or not. @@ -265,30 +291,30 @@ static void AddDeviceNode(const char* devnode) // This will also give it the correct index and invoke device change callbacks. // Make sure to force the device removal immediately (as they are shared ptrs and // they could be kept alive, preventing us from re-creating the device) - g_controller_interface.RemoveDevice( + GetControllerInterface().RemoveDevice( [&evdev_device](const auto* device) { return static_cast(device) == evdev_device.get(); }, true); - g_controller_interface.AddDevice(evdev_device); + GetControllerInterface().AddDevice(evdev_device); } else { - evdev_device = std::make_shared(); + evdev_device = std::make_shared(this); const bool was_interesting = evdev_device->AddNode(devnode, fd, dev); if (was_interesting) - g_controller_interface.AddDevice(evdev_device); + GetControllerInterface().AddDevice(evdev_device); } - // If the devices failed to be added to g_controller_interface, it will be added here but then + // If the devices failed to be added to ControllerInterface, it will be added here but then // immediately removed in its destructor due to the shared ptr not having any references left - s_devnode_objects.emplace(devnode, std::move(evdev_device)); + m_devnode_objects.emplace(devnode, std::move(evdev_device)); } -static void HotplugThreadFunc() +void InputBackend::HotplugThreadFunc() { Common::SetCurrentThreadName("evdev Hotplug Thread"); NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread started"); @@ -306,16 +332,16 @@ static void HotplugThreadFunc() udev_monitor_enable_receiving(monitor); const int monitor_fd = udev_monitor_get_fd(monitor); - while (s_hotplug_thread_running.IsSet()) + while (m_hotplug_thread_running.IsSet()) { fd_set fds; FD_ZERO(&fds); FD_SET(monitor_fd, &fds); - FD_SET(s_wakeup_eventfd, &fds); + FD_SET(m_wakeup_eventfd, &fds); const int ret = - select(std::max(monitor_fd, s_wakeup_eventfd) + 1, &fds, nullptr, nullptr, nullptr); + select(std::max(monitor_fd, m_wakeup_eventfd) + 1, &fds, nullptr, nullptr, nullptr); if (ret < 1 || !FD_ISSET(monitor_fd, &fds)) continue; @@ -327,53 +353,54 @@ static void HotplugThreadFunc() if (!devnode) continue; - // Use g_controller_interface.PlatformPopulateDevices() to protect access around - // s_devnode_objects. Note that even if we get these events at the same time as a + // Use GetControllerInterface().PlatformPopulateDevices() to protect access around + // m_devnode_objects. Note that even if we get these events at the same time as a // a PopulateDevices() request (e.g. on start up, we might get all the add events // for connected devices), this won't ever cause duplicate devices as AddDeviceNode() // automatically removes the old one if it already existed if (strcmp(action, "remove") == 0) { - g_controller_interface.PlatformPopulateDevices([&devnode] { + GetControllerInterface().PlatformPopulateDevices([&devnode, this] { std::shared_ptr ptr; - const auto it = s_devnode_objects.find(devnode); - if (it != s_devnode_objects.end()) + const auto it = m_devnode_objects.find(devnode); + if (it != m_devnode_objects.end()) ptr = it->second.lock(); // If we don't recognize this device, ptr will be null and no device will be removed. - g_controller_interface.RemoveDevice([&ptr](const auto* device) { + GetControllerInterface().RemoveDevice([&ptr](const auto* device) { return static_cast(device) == ptr.get(); }); }); } else if (strcmp(action, "add") == 0) { - g_controller_interface.PlatformPopulateDevices([&devnode] { AddDeviceNode(devnode); }); + GetControllerInterface().PlatformPopulateDevices( + [&devnode, this] { AddDeviceNode(devnode); }); } } NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread stopped"); } -static void StartHotplugThread() +void InputBackend::StartHotplugThread() { // Mark the thread as running. - if (!s_hotplug_thread_running.TestAndSet()) + if (!m_hotplug_thread_running.TestAndSet()) { // It was already running. return; } - s_wakeup_eventfd = eventfd(0, 0); - ASSERT_MSG(CONTROLLERINTERFACE, s_wakeup_eventfd != -1, "Couldn't create eventfd."); - s_hotplug_thread = std::thread(HotplugThreadFunc); + m_wakeup_eventfd = eventfd(0, 0); + ASSERT_MSG(CONTROLLERINTERFACE, m_wakeup_eventfd != -1, "Couldn't create eventfd."); + m_hotplug_thread = std::thread(&InputBackend::HotplugThreadFunc, this); } -static void StopHotplugThread() +void InputBackend::StopHotplugThread() { // Tell the hotplug thread to stop. - if (!s_hotplug_thread_running.TestAndClear()) + if (!m_hotplug_thread_running.TestAndClear()) { // It wasn't running, we're done. return; @@ -381,22 +408,23 @@ static void StopHotplugThread() // Write something to efd so that select() stops blocking. const uint64_t value = 1; - static_cast(!write(s_wakeup_eventfd, &value, sizeof(uint64_t))); + static_cast(!write(m_wakeup_eventfd, &value, sizeof(uint64_t))); - s_hotplug_thread.join(); - close(s_wakeup_eventfd); + m_hotplug_thread.join(); + close(m_wakeup_eventfd); } -void Init() +InputBackend::InputBackend(ControllerInterface* controller_interface) + : ciface::InputBackend(controller_interface) { StartHotplugThread(); } // Only call this when ControllerInterface::m_devices_population_mutex is locked -void PopulateDevices() +void InputBackend::PopulateDevices() { // Don't run if we are not initialized - if (!s_hotplug_thread_running.IsSet()) + if (!m_hotplug_thread_running.IsSet()) { return; } @@ -431,7 +459,7 @@ void PopulateDevices() udev_unref(udev); } -void Shutdown() +InputBackend::~InputBackend() { StopHotplugThread(); } @@ -627,16 +655,25 @@ const char* evdevDevice::GetPhysicalLocation() const return libevdev_get_phys(m_nodes.front().device); } +evdevDevice::evdevDevice(InputBackend* input_backend) : m_input_backend(*input_backend) +{ +} + evdevDevice::~evdevDevice() { for (auto& node : m_nodes) { - s_devnode_objects.erase(node.devnode); + m_input_backend.RemoveDevnodeObject(node.devnode); libevdev_free(node.device); close(node.fd); } } +void InputBackend::RemoveDevnodeObject(const std::string& node) +{ + m_devnode_objects.erase(node); +} + void evdevDevice::UpdateInput() { // Run through all evdev events diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h index fd497ad8b8..ff5ab8a72a 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h @@ -11,9 +11,9 @@ namespace ciface::evdev { -void Init(); -void PopulateDevices(); -void Shutdown(); +class InputBackend; + +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); class evdevDevice : public Core::Device { @@ -75,6 +75,7 @@ public: void UpdateInput() override; bool IsValid() const override; + evdevDevice(InputBackend* input_backend); ~evdevDevice(); // Return true if node was "interesting". @@ -97,5 +98,7 @@ private: }; std::vector m_nodes; + + InputBackend& m_input_backend; }; } // namespace ciface::evdev