From 0165e5e703c791451d6e6f064f53fb0ff0d6b4c3 Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Tue, 17 Jul 2018 22:29:48 +0200 Subject: [PATCH 1/5] GCAdapter: Close libusb handle if an open error occurs The handle was previously kept open, which was causing future adapter plug/unplug events to be ignored. --- Source/Core/InputCommon/GCAdapter.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 4e008317df..d52bdf561d 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -276,12 +276,18 @@ static bool CheckDeviceAccess(libusb_device* device) // this split is needed so that we don't avoid claiming the interface when // detaching the kernel driver is successful if (ret != 0 && ret != LIBUSB_ERROR_NOT_SUPPORTED) + { + libusb_close(s_handle); + s_handle = nullptr; return false; + } ret = libusb_claim_interface(s_handle, 0); if (ret) { ERROR_LOG(SERIALINTERFACE, "libusb_claim_interface failed with error: %d", ret); + libusb_close(s_handle); + s_handle = nullptr; return false; } From b08e2ec959a8e54bcbca92816400119d798129ca Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Tue, 17 Jul 2018 22:31:18 +0200 Subject: [PATCH 2/5] GCAdapter: Report libusb open errors to the user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If opening the adapter fails, report the libusb error message in the GUI instead of “No Adapter Detected”. The error condition is removed when the adapter is unplugged. --- Source/Core/Core/Analytics.cpp | 4 +- .../Config/Mapping/GCPadWiiUConfigDialog.cpp | 20 ++++++- Source/Core/InputCommon/GCAdapter.cpp | 57 +++++++++++++------ Source/Core/InputCommon/GCAdapter.h | 3 +- 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/Source/Core/Core/Analytics.cpp b/Source/Core/Core/Analytics.cpp index 4486a9708a..d83fb4762b 100644 --- a/Source/Core/Core/Analytics.cpp +++ b/Source/Core/Core/Analytics.cpp @@ -391,9 +391,9 @@ void DolphinAnalytics::MakePerGameBuilder() // We grab enough to tell what percentage of our users are playing with keyboard/mouse, some kind // of gamepad // or the official gamecube adapter. - builder.AddData("gcadapter-detected", GCAdapter::IsDetected()); + builder.AddData("gcadapter-detected", GCAdapter::IsDetected(nullptr)); builder.AddData("has-controller", Pad::GetConfig()->IsControllerControlledByGamepadDevice(0) || - GCAdapter::IsDetected()); + GCAdapter::IsDetected(nullptr)); m_per_game_builder = builder; } diff --git a/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp b/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp index 82e3e41a21..85cd35fb79 100644 --- a/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp @@ -28,9 +28,25 @@ void GCPadWiiUConfigDialog::CreateLayout() { setWindowTitle(tr("GameCube Adapter for Wii U at Port %1").arg(m_port + 1)); - const bool detected = GCAdapter::IsDetected(); + const char* error_message = nullptr; + const bool detected = GCAdapter::IsDetected(&error_message); + QString status_text; + + if (detected) + { + status_text = tr("Adapter Detected"); + } + else if (error_message) + { + status_text = tr("Error Opening Adapter: %1").arg(QString::fromUtf8(error_message)); + } + else + { + status_text = tr("No Adapter Detected"); + } + m_layout = new QVBoxLayout(); - m_status_label = new QLabel(detected ? tr("Adapter Detected") : tr("No Adapter Detected")); + m_status_label = new QLabel(status_text); m_rumble = new QCheckBox(tr("Enable Rumble")); m_simulate_bongos = new QCheckBox(tr("Simulate DK Bongos")); m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok); diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index d52bdf561d..174e374bf8 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -9,6 +9,7 @@ #include "Common/Event.h" #include "Common/Flag.h" #include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" #include "Common/Thread.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -29,7 +30,14 @@ static void ResetRumbleLockNeeded(); static void Reset(); static void Setup(); -static bool s_detected = false; +enum +{ + NO_ADAPTER_DETECTED = 0, + ADAPTER_DETECTED = 1, +}; + +// Current adapter status: detected/not detected/in error (holds the error code) +static int s_status = NO_ADAPTER_DETECTED; static libusb_device_handle* s_handle = nullptr; static u8 s_controller_type[SerialInterface::MAX_SI_CHANNELS] = { ControllerTypes::CONTROLLER_NONE, ControllerTypes::CONTROLLER_NONE, @@ -54,7 +62,6 @@ static Common::Flag s_adapter_detect_thread_running; static std::function s_detect_callback; -static bool s_libusb_driver_not_supported = false; #if defined(__FreeBSD__) && __FreeBSD__ >= 11 static bool s_libusb_hotplug_enabled = true; #else @@ -120,6 +127,10 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl { if (s_handle != nullptr && libusb_get_device(s_handle) == dev) Reset(); + + // Reset a potential error status now that the adapter is unplugged + if (s_status < 0) + s_status = 0; } return 0; } @@ -158,7 +169,7 @@ static void ScanThreadFunc() { std::lock_guard lk(s_init_mutex); Setup(); - if (s_detected && s_detect_callback != nullptr) + if (s_status == ADAPTER_DETECTED && s_detect_callback != nullptr) s_detect_callback(); } Common::SleepCurrentThread(500); @@ -184,7 +195,7 @@ void Init() s_last_init = CoreTiming::GetTicks(); } - s_libusb_driver_not_supported = false; + s_status = NO_ADAPTER_DETECTED; if (UseAdapter()) StartScanThread(); @@ -247,6 +258,9 @@ static bool CheckDeviceAccess(libusb_device* device) NOTICE_LOG(SERIALINTERFACE, "Found GC Adapter with Vendor: %X Product: %X Devnum: %d", desc.idVendor, desc.idProduct, 1); + // In case of failure, capture the libusb error code into the adapter status + Common::ScopeGuard status_guard([&ret] { s_status = ret; }); + u8 bus = libusb_get_bus_number(device); u8 port = libusb_get_device_address(device); ret = libusb_open(device, &s_handle); @@ -260,8 +274,6 @@ static bool CheckDeviceAccess(libusb_device* device) if (ret) { ERROR_LOG(SERIALINTERFACE, "libusb_open failed to open device with error = %d", ret); - if (ret == LIBUSB_ERROR_NOT_SUPPORTED) - s_libusb_driver_not_supported = true; return false; } @@ -291,6 +303,9 @@ static bool CheckDeviceAccess(libusb_device* device) return false; } + // Updating the adapter status will be done in AddGCAdapter + status_guard.Dismiss(); + return true; } @@ -323,7 +338,7 @@ static void AddGCAdapter(libusb_device* device) s_adapter_input_thread = std::thread(Read); s_adapter_output_thread = std::thread(Write); - s_detected = true; + s_status = ADAPTER_DETECTED; if (s_detect_callback != nullptr) s_detect_callback(); ResetRumbleLockNeeded(); @@ -338,7 +353,7 @@ void Shutdown() #endif Reset(); - s_libusb_driver_not_supported = false; + s_status = NO_ADAPTER_DETECTED; } static void Reset() @@ -346,7 +361,7 @@ static void Reset() std::unique_lock lock(s_init_mutex, std::defer_lock); if (!lock.try_lock()) return; - if (!s_detected) + if (s_status != ADAPTER_DETECTED) return; if (s_adapter_thread_running.TestAndClear()) @@ -359,7 +374,7 @@ static void Reset() for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; i++) s_controller_type[i] = ControllerTypes::CONTROLLER_NONE; - s_detected = false; + s_status = NO_ADAPTER_DETECTED; if (s_handle) { @@ -377,7 +392,7 @@ GCPadStatus Input(int chan) if (!UseAdapter()) return {}; - if (s_handle == nullptr || !s_detected) + if (s_handle == nullptr || s_status != ADAPTER_DETECTED) return {}; int payload_size = 0; @@ -497,7 +512,7 @@ void ResetRumble() // being called while the libusb state is being reset static void ResetRumbleLockNeeded() { - if (!UseAdapter() || (s_handle == nullptr || !s_detected)) + if (!UseAdapter() || (s_handle == nullptr || s_status != ADAPTER_DETECTED)) { return; } @@ -527,14 +542,20 @@ void Output(int chan, u8 rumble_command) } } -bool IsDetected() +bool IsDetected(const char** error_message) { - return s_detected; -} + if (s_status >= 0) + { + if (error_message) + *error_message = nullptr; -bool IsDriverDetected() -{ - return !s_libusb_driver_not_supported; + return s_status == ADAPTER_DETECTED; + } + + if (error_message) + *error_message = libusb_strerror(static_cast(s_status)); + + return false; } } // end of namespace GCAdapter diff --git a/Source/Core/InputCommon/GCAdapter.h b/Source/Core/InputCommon/GCAdapter.h index 02b15a3cd2..eb05335602 100644 --- a/Source/Core/InputCommon/GCAdapter.h +++ b/Source/Core/InputCommon/GCAdapter.h @@ -26,8 +26,7 @@ void StartScanThread(); void StopScanThread(); GCPadStatus Input(int chan); void Output(int chan, u8 rumble_command); -bool IsDetected(); -bool IsDriverDetected(); +bool IsDetected(const char** error_message); bool DeviceConnected(int chan); void ResetDeviceType(int chan); bool UseAdapter(); From 2ac1ca133fac669e876e80d4b31202bcc2826d71 Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Thu, 19 Jul 2018 08:23:16 +0200 Subject: [PATCH 3/5] GCPadWiiUConfigDialog: Update the adapter state dynamically Update the GC adapter config GUI if the adapter is plugged or unplugged. --- .../Config/Mapping/GCPadWiiUConfigDialog.cpp | 61 +++++++++++-------- .../Config/Mapping/GCPadWiiUConfigDialog.h | 4 ++ Source/Core/InputCommon/GCAdapter.cpp | 10 ++- 3 files changed, 50 insertions(+), 25 deletions(-) diff --git a/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp b/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp index 85cd35fb79..232957ea1f 100644 --- a/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp +++ b/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.cpp @@ -10,6 +10,7 @@ #include #include "Core/ConfigManager.h" +#include "DolphinQt/QtUtils/QueueOnObject.h" #include "InputCommon/GCAdapter.h" @@ -24,10 +25,43 @@ GCPadWiiUConfigDialog::GCPadWiiUConfigDialog(int port, QWidget* parent) ConnectWidgets(); } +GCPadWiiUConfigDialog::~GCPadWiiUConfigDialog() +{ + GCAdapter::SetAdapterCallback(nullptr); +} + void GCPadWiiUConfigDialog::CreateLayout() { setWindowTitle(tr("GameCube Adapter for Wii U at Port %1").arg(m_port + 1)); + m_layout = new QVBoxLayout(); + m_status_label = new QLabel(); + m_rumble = new QCheckBox(tr("Enable Rumble")); + m_simulate_bongos = new QCheckBox(tr("Simulate DK Bongos")); + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok); + + UpdateAdapterStatus(); + + auto callback = [this] { QueueOnObject(this, &GCPadWiiUConfigDialog::UpdateAdapterStatus); }; + GCAdapter::SetAdapterCallback(callback); + + m_layout->addWidget(m_status_label); + m_layout->addWidget(m_rumble); + m_layout->addWidget(m_simulate_bongos); + m_layout->addWidget(m_button_box); + + setLayout(m_layout); +} + +void GCPadWiiUConfigDialog::ConnectWidgets() +{ + connect(m_rumble, &QCheckBox::toggled, this, &GCPadWiiUConfigDialog::SaveSettings); + connect(m_simulate_bongos, &QCheckBox::toggled, this, &GCPadWiiUConfigDialog::SaveSettings); + connect(m_button_box, &QDialogButtonBox::accepted, this, &GCPadWiiUConfigDialog::accept); +} + +void GCPadWiiUConfigDialog::UpdateAdapterStatus() +{ const char* error_message = nullptr; const bool detected = GCAdapter::IsDetected(&error_message); QString status_text; @@ -45,31 +79,10 @@ void GCPadWiiUConfigDialog::CreateLayout() status_text = tr("No Adapter Detected"); } - m_layout = new QVBoxLayout(); - m_status_label = new QLabel(status_text); - m_rumble = new QCheckBox(tr("Enable Rumble")); - m_simulate_bongos = new QCheckBox(tr("Simulate DK Bongos")); - m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok); + m_status_label->setText(status_text); - m_layout->addWidget(m_status_label); - m_layout->addWidget(m_rumble); - m_layout->addWidget(m_simulate_bongos); - m_layout->addWidget(m_button_box); - - if (!detected) - { - m_rumble->setEnabled(false); - m_simulate_bongos->setEnabled(false); - } - - setLayout(m_layout); -} - -void GCPadWiiUConfigDialog::ConnectWidgets() -{ - connect(m_rumble, &QCheckBox::toggled, this, &GCPadWiiUConfigDialog::SaveSettings); - connect(m_simulate_bongos, &QCheckBox::toggled, this, &GCPadWiiUConfigDialog::SaveSettings); - connect(m_button_box, &QDialogButtonBox::accepted, this, &GCPadWiiUConfigDialog::accept); + m_rumble->setEnabled(detected); + m_simulate_bongos->setEnabled(detected); } void GCPadWiiUConfigDialog::LoadSettings() diff --git a/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h b/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h index 77cdb93355..bd1adb6d03 100644 --- a/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h +++ b/Source/Core/DolphinQt/Config/Mapping/GCPadWiiUConfigDialog.h @@ -16,6 +16,7 @@ class GCPadWiiUConfigDialog final : public QDialog Q_OBJECT public: explicit GCPadWiiUConfigDialog(int port, QWidget* parent = nullptr); + ~GCPadWiiUConfigDialog(); private: void LoadSettings(); @@ -24,6 +25,9 @@ private: void CreateLayout(); void ConnectWidgets(); +private: + void UpdateAdapterStatus(); + int m_port; QVBoxLayout* m_layout; diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 174e374bf8..a4a4cee5cf 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -122,6 +122,10 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl std::lock_guard lk(s_init_mutex); AddGCAdapter(dev); } + else if (s_status < 0 && s_detect_callback != nullptr) + { + s_detect_callback(); + } } else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { @@ -130,7 +134,11 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl // Reset a potential error status now that the adapter is unplugged if (s_status < 0) - s_status = 0; + { + s_status = NO_ADAPTER_DETECTED; + if (s_detect_callback != nullptr) + s_detect_callback(); + } } return 0; } From 9e7d4d2abbe62e2d4d6578e883effff18fed44c4 Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Wed, 25 Jul 2018 21:44:57 +0200 Subject: [PATCH 4/5] GCAdapter: Handle dynamic status updates for non-hotplug libusb Detect when the setup function found no adapter, or found one but could not connect to it, and report the new status in that case. --- Source/Core/InputCommon/GCAdapter.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index a4a4cee5cf..2d990a213c 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -177,8 +177,6 @@ static void ScanThreadFunc() { std::lock_guard lk(s_init_mutex); Setup(); - if (s_status == ADAPTER_DETECTED && s_detect_callback != nullptr) - s_detect_callback(); } Common::SleepCurrentThread(500); } @@ -229,6 +227,12 @@ void StopScanThread() static void Setup() { + int prev_status = s_status; + + // Reset the error status in case the adapter gets unplugged + if (s_status < 0) + s_status = NO_ADAPTER_DETECTED; + for (int i = 0; i < SerialInterface::MAX_SI_CHANNELS; i++) { s_controller_type[i] = ControllerTypes::CONTROLLER_NONE; @@ -244,6 +248,9 @@ static void Setup() } return true; }); + + if (s_status != ADAPTER_DETECTED && prev_status != s_status && s_detect_callback != nullptr) + s_detect_callback(); } static bool CheckDeviceAccess(libusb_device* device) From 2c3c8bbb905a251fd52a5c1552921acb67ad25a5 Mon Sep 17 00:00:00 2001 From: Vincent Duvert Date: Sun, 29 Jul 2018 22:37:48 +0200 Subject: [PATCH 5/5] GCAdapter: Update Android-specific source Fix the Android version of GCAdapter.cpp so it matches the new definitions in GCAdapter.h. --- Source/Core/InputCommon/GCAdapter_Android.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/Core/InputCommon/GCAdapter_Android.cpp b/Source/Core/InputCommon/GCAdapter_Android.cpp index 9c6092dc78..84ccc25f10 100644 --- a/Source/Core/InputCommon/GCAdapter_Android.cpp +++ b/Source/Core/InputCommon/GCAdapter_Android.cpp @@ -369,14 +369,10 @@ void Output(int chan, u8 rumble_command) } } -bool IsDetected() +bool IsDetected(const char** error_message) { return s_detected; } -bool IsDriverDetected() -{ - return true; -} bool DeviceConnected(int chan) { return s_controller_type[chan] != ControllerTypes::CONTROLLER_NONE;