diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index c9cd0333a9..23a458f2b9 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -190,6 +190,8 @@ const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING{ const Info MAIN_WIIMOTE_ENABLE_SPEAKER{{System::Main, "Core", "WiimoteEnableSpeaker"}, false}; const Info MAIN_CONNECT_WIIMOTES_FOR_CONTROLLER_INTERFACE{ {System::Main, "Core", "WiimoteControllerInterface"}, false}; +const Info MAIN_USE_GC_ADAPTER_FOR_CONTROLLER_INTERFACE{ + {System::Main, "Core", "GCAdapterControllerInterface"}, false}; const Info MAIN_MMU{{System::Main, "Core", "MMU"}, false}; const Info MAIN_PAUSE_ON_PANIC{{System::Main, "Core", "PauseOnPanic"}, false}; const Info MAIN_BB_DUMP_PORT{{System::Main, "Core", "BBDumpPort"}, -1}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index b6e8f966c7..17a8cfb8fe 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -108,6 +108,7 @@ extern const Info MAIN_WII_KEYBOARD; extern const Info MAIN_WIIMOTE_CONTINUOUS_SCANNING; extern const Info MAIN_WIIMOTE_ENABLE_SPEAKER; extern const Info MAIN_CONNECT_WIIMOTES_FOR_CONTROLLER_INTERFACE; +extern const Info MAIN_USE_GC_ADAPTER_FOR_CONTROLLER_INTERFACE; extern const Info MAIN_MMU; extern const Info MAIN_PAUSE_ON_PANIC; extern const Info MAIN_BB_DUMP_PORT; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index a61f0d822a..30bc9b00cb 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -532,6 +532,7 @@ + @@ -1193,6 +1194,7 @@ + diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp index d548724c7e..05956f5820 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.cpp @@ -3,6 +3,7 @@ #include "DolphinQt/Config/GamecubeControllersWidget.h" +#include #include #include #include @@ -95,6 +96,10 @@ void GamecubeControllersWidget::CreateLayout() m_gc_layout->addWidget(gc_box, controller_row, 1); m_gc_layout->addWidget(gc_button, controller_row, 2); } + + m_gcpad_ciface = new QCheckBox(tr("Use Wii-U Adapter for Emulated Controllers")); + m_gc_layout->addWidget(m_gcpad_ciface, m_gc_layout->rowCount(), 0, 1, -1); + m_gc_box->setLayout(m_gc_layout); auto* layout = new QVBoxLayout; @@ -114,6 +119,8 @@ void GamecubeControllersWidget::ConnectWidgets() }); connect(m_gc_buttons[i], &QPushButton::clicked, this, [this, i] { OnGCPadConfigure(i); }); } + + connect(m_gcpad_ciface, &QCheckBox::toggled, this, &GamecubeControllersWidget::SaveSettings); } void GamecubeControllersWidget::OnGCTypeChanged(size_t index) @@ -184,6 +191,9 @@ void GamecubeControllersWidget::LoadSettings(Core::State state) OnGCTypeChanged(i); } } + + SignalBlocking(m_gcpad_ciface) + ->setChecked(Config::Get(Config::MAIN_USE_GC_ADAPTER_FOR_CONTROLLER_INTERFACE)); } void GamecubeControllersWidget::SaveSettings() @@ -203,11 +213,15 @@ void GamecubeControllersWidget::SaveSettings() system.GetSerialInterface().ChangeDevice(si_device, static_cast(i)); } } + + Config::SetBaseOrCurrent(Config::MAIN_USE_GC_ADAPTER_FOR_CONTROLLER_INTERFACE, + m_gcpad_ciface->isChecked()); } + + SConfig::GetInstance().SaveSettings(); + if (GCAdapter::UseAdapter()) GCAdapter::StartScanThread(); else GCAdapter::StopScanThread(); - - SConfig::GetInstance().SaveSettings(); } diff --git a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h index f4d1b69f60..385ab7c947 100644 --- a/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h +++ b/Source/Core/DolphinQt/Config/GamecubeControllersWidget.h @@ -7,6 +7,7 @@ #include +class QCheckBox; class QComboBox; class QHBoxLayout; class QGridLayout; @@ -40,4 +41,5 @@ private: std::array m_gc_controller_boxes; std::array m_gc_buttons; std::array m_gc_groups; + QCheckBox* m_gcpad_ciface; }; diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index ee144e32f4..38455f86ce 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -64,6 +64,8 @@ add_library(inputcommon ControllerInterface/MappingCommon.h ControllerInterface/Wiimote/WiimoteController.cpp ControllerInterface/Wiimote/WiimoteController.h + ControllerInterface/GCAdapter/GCAdapter.cpp + ControllerInterface/GCAdapter/GCAdapter.h ControlReference/ControlReference.cpp ControlReference/ControlReference.h ControlReference/ExpressionParser.cpp diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 769e6c5f0e..32a03a1f86 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -8,6 +8,7 @@ #include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Core/HW/WiimoteReal/WiimoteReal.h" +#include "InputCommon/ControllerInterface/GCAdapter/GCAdapter.h" #ifdef CIFACE_USE_WIN32 #include "InputCommon/ControllerInterface/Win32/Win32.h" @@ -86,6 +87,9 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi) m_input_backends.emplace_back(ciface::SteamDeck::CreateInputBackend(this)); #endif + // TODO: obey Config::Get(Config::MAIN_USE_GC_ADAPTER_FOR_CONTROLLER_INTERFACE) + m_input_backends.emplace_back(ciface::GCAdapter::CreateInputBackend(this)); + // Don't allow backends to add devices before the first RefreshDevices() as they will be cleaned // there. Or they'd end up waiting on the devices mutex if populated from another thread. m_is_init = true; diff --git a/Source/Core/InputCommon/ControllerInterface/GCAdapter/GCAdapter.cpp b/Source/Core/InputCommon/ControllerInterface/GCAdapter/GCAdapter.cpp new file mode 100644 index 0000000000..8d66e61892 --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/GCAdapter/GCAdapter.cpp @@ -0,0 +1,258 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "InputCommon/ControllerInterface/GCAdapter/GCAdapter.h" + +#include + +#include "Core/HW/SI/SI.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "InputCommon/GCAdapter.h" +#include "InputCommon/GCPadStatus.h" + +// TODO: namespace name matching the one in InputCommon/GCAdapter.h is annoying +namespace ciface::GCAdapter +{ +constexpr auto SOURCE_NAME = "GCAdapter"; + +class GCController; + +class InputBackend final : public ciface::InputBackend +{ +public: + InputBackend(ControllerInterface* controller_interface); + void PopulateDevices() override; + void UpdateInput(std::vector>& devices_to_remove) override; + +private: + std::array, SerialInterface::MAX_SI_CHANNELS> m_devices; +}; + +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) +{ + return std::make_unique(controller_interface); +} + +struct DigitalInputProps +{ + u16 value; + const char* name; +}; + +constexpr DigitalInputProps digital_inputs[] = { + {PAD_BUTTON_UP, "Up"}, + {PAD_BUTTON_DOWN, "Down"}, + {PAD_BUTTON_LEFT, "Left"}, + {PAD_BUTTON_RIGHT, "Right"}, + {PAD_BUTTON_A, "A"}, + {PAD_BUTTON_B, "B"}, + {PAD_BUTTON_X, "X"}, + {PAD_BUTTON_Y, "Y"}, + {PAD_TRIGGER_Z, "Z"}, + {PAD_TRIGGER_L, "L"}, + {PAD_TRIGGER_R, "R"}, + // TODO: "START" or "Start"? + {PAD_BUTTON_START, "Start"}, +}; + +struct AnalogInputProps +{ + u8 GCPadStatus::*ptr; + const char* name; +}; + +constexpr AnalogInputProps stick_inputs[] = { + {&GCPadStatus::stickX, "Main X"}, + {&GCPadStatus::stickY, "Main Y"}, + {&GCPadStatus::substickX, "C X"}, + {&GCPadStatus::substickY, "C Y"}, +}; + +constexpr AnalogInputProps trigger_inputs[] = { + {&GCPadStatus::triggerLeft, "L-Analog"}, + {&GCPadStatus::triggerRight, "R-Analog"}, +}; + +class DigitalInput : public Core::Device::Input +{ +public: + DigitalInput(const u16* button_state, u32 index) : m_button_state{*button_state}, m_index{index} + { + } + + std::string GetName() const override { return digital_inputs[m_index].name; } + + ControlState GetState() const override + { + return (digital_inputs[m_index].value & m_button_state) != 0; + } + +private: + const u16& m_button_state; + const u32 m_index; +}; + +class AnalogInput : public Core::Device::Input +{ +public: + AnalogInput(const GCPadStatus* pad_status, const AnalogInputProps& props, u8 neutral_value, + s32 range) + : m_base_name{props.name}, m_neutral_value{neutral_value}, m_range{range}, + m_state{pad_status->*props.ptr} + { + } + + ControlState GetState() const override + { + return ControlState(m_state - m_neutral_value) / m_range; + } + +protected: + const char* const m_base_name; + const u8 m_neutral_value; + const s32 m_range; + +private: + const u8& m_state; +}; + +class StickInput : public AnalogInput +{ +public: + using AnalogInput::AnalogInput; + + std::string GetName() const override + { + return std::string(m_base_name) + (m_range > 0 ? '+' : '-'); + } +}; + +class TriggerInput : public AnalogInput +{ +public: + using AnalogInput::AnalogInput; + + std::string GetName() const override { return m_base_name; } +}; + +class Motor : public Core::Device::Output +{ +public: + Motor(u32 index) : m_index{index} {} + + void SetState(ControlState value) override + { + const bool new_state = std::lround(value); + + if (new_state == m_last_state) + return; + + m_last_state = new_state; + + ::GCAdapter::Output(m_index, new_state); + } + + std::string GetName() const override { return "Motor"; } + +private: + u32 m_index; + bool m_last_state = false; +}; + +class GCController : public Core::Device +{ +public: + GCController(u32 index) : m_index{index} + { + // Add buttons. + for (u32 i = 0; i != std::size(digital_inputs); ++i) + AddInput(new DigitalInput{&m_pad_status.button, i}); + + // TODO: use origin values. + const auto origin = ::GCAdapter::GetOrigin(index); + + // Add sticks. + for (auto& props : stick_inputs) + { + // Add separate -/+ inputs. + AddInput(new StickInput{&m_pad_status, props, GCPadStatus::MAIN_STICK_CENTER_X, + -GCPadStatus::MAIN_STICK_RADIUS}); + AddInput(new StickInput{&m_pad_status, props, GCPadStatus::MAIN_STICK_CENTER_X, + GCPadStatus::MAIN_STICK_RADIUS}); + } + + // Add triggers. + for (auto& props : trigger_inputs) + AddInput(new TriggerInput{&m_pad_status, props, 0, std::numeric_limits::max()}); + + // Rumble. + AddOutput(new Motor{index}); + } + + std::optional GetPreferredId() const override { return m_index; } + + std::string GetName() const override + { + // TODO: name? + return "GCPad"; + } + + std::string GetSource() const override { return SOURCE_NAME; } + + int GetSortPriority() const override { return -3; } + + Core::DeviceRemoval UpdateInput() override + { + m_pad_status = ::GCAdapter::PeekInput(m_index); + return Core::DeviceRemoval::Keep; + } + +private: + GCPadStatus m_pad_status; + const u32 m_index; +}; + +InputBackend::InputBackend(ControllerInterface* controller_interface) + : ciface::InputBackend(controller_interface) +{ + ::GCAdapter::Init(); +} + +void InputBackend::PopulateDevices() +{ + for (int i = 0; i != SerialInterface::MAX_SI_CHANNELS; ++i) + { + if (::GCAdapter::DeviceConnected(i)) + { + auto new_device = std::make_shared(i); + m_devices[i] = new_device; + GetControllerInterface().AddDevice(std::move(new_device)); + } + } +} + +void InputBackend::UpdateInput(std::vector>& devices_to_remove) +{ + // "Hotplug" is handled here. + for (int i = 0; i != SerialInterface::MAX_SI_CHANNELS; ++i) + { + const bool is_device_connected = ::GCAdapter::DeviceConnected(i); + const auto device = m_devices[i].lock(); + + if (is_device_connected == (device != nullptr)) + continue; + + if (is_device_connected) + { + auto new_device = std::make_shared(i); + m_devices[i] = new_device; + GetControllerInterface().AddDevice(std::move(new_device)); + } + else + { + devices_to_remove.emplace_back(device); + } + } +} + +} // namespace ciface::GCAdapter diff --git a/Source/Core/InputCommon/ControllerInterface/GCAdapter/GCAdapter.h b/Source/Core/InputCommon/ControllerInterface/GCAdapter/GCAdapter.h new file mode 100644 index 0000000000..0dcb1a17fa --- /dev/null +++ b/Source/Core/InputCommon/ControllerInterface/GCAdapter/GCAdapter.h @@ -0,0 +1,14 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "InputCommon/ControllerInterface/InputBackend.h" + +namespace ciface::GCAdapter +{ +std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface); + +} // namespace ciface::GCAdapter diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 84b5022c99..6176b000d3 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -142,15 +142,6 @@ static Common::Event s_hotplug_event; static std::function s_detect_callback; -#if defined(__FreeBSD__) && __FreeBSD__ >= 11 -static bool s_libusb_hotplug_enabled = true; -#else -static bool s_libusb_hotplug_enabled = false; -#endif -#if LIBUSB_API_HAS_HOTPLUG -static libusb_hotplug_callback_handle s_hotplug_handle; -#endif - static std::unique_ptr s_libusb_context; static u8 s_endpoint_in = 0; @@ -346,24 +337,32 @@ static void ScanThreadFunc() #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION #if LIBUSB_API_HAS_HOTPLUG -#ifndef __FreeBSD__ - s_libusb_hotplug_enabled = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0; + +#if defined(__FreeBSD__) +#if __FreeBSD__ >= 11 + bool hotplug_enabled = true; +#else + bool hotplug_enabled = false; #endif - if (s_libusb_hotplug_enabled) +#else + bool hotplug_enabled = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0; +#endif + libusb_hotplug_callback_handle hotplug_handle = {}; + if (hotplug_enabled) { const int error = libusb_hotplug_register_callback( *s_libusb_context, (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), LIBUSB_HOTPLUG_ENUMERATE, 0x057e, 0x0337, LIBUSB_HOTPLUG_MATCH_ANY, HotplugCallback, - nullptr, &s_hotplug_handle); + nullptr, &hotplug_handle); if (error == LIBUSB_SUCCESS) { NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Using libUSB hotplug detection"); } else { - s_libusb_hotplug_enabled = false; + hotplug_enabled = false; ERROR_LOG_FMT(CONTROLLERINTERFACE, "Failed to add libUSB hotplug detection callback: {}", LibusbUtils::ErrorWrap(error)); } @@ -378,11 +377,17 @@ static void ScanThreadFunc() Setup(); } - if (s_libusb_hotplug_enabled) + if (hotplug_enabled) s_hotplug_event.Wait(); else Common::SleepCurrentThread(500); } + +#if LIBUSB_API_HAS_HOTPLUG + if (hotplug_enabled) + libusb_hotplug_deregister_callback(*s_libusb_context, hotplug_handle); +#endif + #elif GCADAPTER_USE_ANDROID_IMPLEMENTATION JNIEnv* const env = IDCache::GetEnvForThread(); @@ -410,12 +415,14 @@ void SetAdapterCallback(std::function func) static void RefreshConfig() { - s_is_adapter_wanted = false; + s_is_adapter_wanted = Config::Get(Config::MAIN_USE_GC_ADAPTER_FOR_CONTROLLER_INTERFACE); for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; ++i) { s_is_adapter_wanted |= Config::Get(Config::GetInfoForSIDevice(i)) == SerialInterface::SIDevices::SIDEVICE_WIIU_ADAPTER; + + // TODO: ControllerInterface shouldn't obey this setting. s_config_rumble_enabled[i] = Config::Get(Config::GetInfoForAdapterRumble(i)); } } @@ -676,12 +683,6 @@ static void AddGCAdapter(libusb_device* device) void Shutdown() { StopScanThread(); -#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION -#if LIBUSB_API_HAS_HOTPLUG - if (s_libusb_context->IsValid() && s_libusb_hotplug_enabled) - libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle); -#endif -#endif Reset(); #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION @@ -766,6 +767,18 @@ GCPadStatus Input(int chan) return pad_state.status; } +GCPadStatus PeekInput(int chan) +{ + std::lock_guard lk(s_read_mutex); + return s_port_states[chan].status; +} + +GCPadStatus GetOrigin(int chan) +{ + std::lock_guard lk(s_read_mutex); + return s_port_states[chan].origin; +} + // Get ControllerType from first byte in input payload. static ControllerType IdentifyControllerType(u8 data) { diff --git a/Source/Core/InputCommon/GCAdapter.h b/Source/Core/InputCommon/GCAdapter.h index e6f62e035b..6ce1044d0f 100644 --- a/Source/Core/InputCommon/GCAdapter.h +++ b/Source/Core/InputCommon/GCAdapter.h @@ -22,6 +22,11 @@ void StopScanThread(); // Netplay and CSIDevice_GCAdapter make use of this. GCPadStatus Input(int chan); +// Retreive the latest input without changing the new connection flag. +GCPadStatus PeekInput(int chan); + +GCPadStatus GetOrigin(int chan); + void Output(int chan, u8 rumble_command); bool IsDetected(const char** error_message); bool DeviceConnected(int chan);