From 57f458fe9bc380db1cf811cc8289490b9afaa93f Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 26 Apr 2015 23:53:36 +0200 Subject: [PATCH] GCAdapter: Add a background thread to detect the adapter This scanning thread either polls libusb or checks every 500ms for a change depending on host capabilities. The GC Adapter can now be plugged and unplugged at any time when dolphin is open, it will be used if the direct connect option is set. --- Source/Core/Core/HW/SI_GCAdapter.cpp | 264 ++++++++++++------ Source/Core/Core/HW/SI_GCAdapter.h | 6 +- .../Core/DolphinWX/ControllerConfigDiag.cpp | 49 +++- Source/Core/DolphinWX/ControllerConfigDiag.h | 5 + 4 files changed, 218 insertions(+), 106 deletions(-) diff --git a/Source/Core/Core/HW/SI_GCAdapter.cpp b/Source/Core/Core/HW/SI_GCAdapter.cpp index 13410ac592..f9704c90c9 100644 --- a/Source/Core/Core/HW/SI_GCAdapter.cpp +++ b/Source/Core/Core/HW/SI_GCAdapter.cpp @@ -19,6 +19,9 @@ enum ControllerTypes CONTROLLER_WIRELESS = 2 }; +static bool CheckDeviceAccess(libusb_device* device); +static void AddGCAdapter(libusb_device* device); + static bool s_detected = false; static libusb_device_handle* s_handle = nullptr; static u8 s_controller_type[MAX_SI_CHANNELS] = { CONTROLLER_NONE, CONTROLLER_NONE, CONTROLLER_NONE, CONTROLLER_NONE }; @@ -33,8 +36,15 @@ static int s_controller_payload_size = 0; static std::thread s_adapter_thread; static Common::Flag s_adapter_thread_running; +static std::thread s_adapter_detect_thread; +static Common::Flag s_adapter_detect_thread_running; + +static std::function s_detect_callback; + static bool s_libusb_driver_not_supported = false; static libusb_context* s_libusb_context = nullptr; +static bool s_libusb_hotplug_enabled = false; +static libusb_hotplug_callback_handle s_hotplug_handle; static u8 s_endpoint_in = 0; static u8 s_endpoint_out = 0; @@ -56,6 +66,61 @@ static void Read() } } +static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotplug_event event, void* user_data) +{ + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) + { + if (s_handle == nullptr && CheckDeviceAccess(dev)) + AddGCAdapter(dev); + } + else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) + { + if (s_handle != nullptr && libusb_get_device(s_handle) == dev) + Reset(); + } + return 0; +} + +static void ScanThreadFunc() +{ + Common::SetCurrentThreadName("GC Adapter Scanning Thread"); + NOTICE_LOG(SERIALINTERFACE, "GC Adapter scanning thread started"); + + s_libusb_hotplug_enabled = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0; + if (s_libusb_hotplug_enabled) + { + if (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, NULL, &s_hotplug_handle) != LIBUSB_SUCCESS) + s_libusb_hotplug_enabled = false; + if (s_libusb_hotplug_enabled) + NOTICE_LOG(SERIALINTERFACE, "Using libUSB hotplug detection"); + } + + while (s_adapter_detect_thread_running.IsSet()) + { + if (s_libusb_hotplug_enabled) + { + static timeval tv = {0, 500000}; + libusb_handle_events_timeout(s_libusb_context, &tv); + } + else + { + if (s_handle == nullptr) + { + Setup(); + if (s_detected && s_detect_callback != nullptr) + s_detect_callback(); + } + Common::SleepCurrentThread(500); + } + } + NOTICE_LOG(SERIALINTERFACE, "GC Adapter scanning thread stopped"); +} + +void SetAdapterCallback(std::function func) +{ + s_detect_callback = func; +} + void Init() { if (s_handle != nullptr) @@ -80,13 +145,26 @@ void Init() } else { - Setup(); + StartScanThread(); + } +} + +void StartScanThread() +{ + s_adapter_detect_thread_running.Set(true); + s_adapter_detect_thread = std::thread(ScanThreadFunc); +} + +void StopScanThread() +{ + if (s_adapter_detect_thread_running.TestAndClear()) + { + s_adapter_detect_thread.join(); } } void Setup() { - int ret; libusb_device** list; ssize_t cnt = libusb_get_device_list(s_libusb_context, &list); @@ -96,87 +174,89 @@ void Setup() s_controller_rumble[i] = 0; } - for (int d = 0; d < cnt; d++) { libusb_device* device = list[d]; - libusb_device_descriptor desc; - int dRet = libusb_get_device_descriptor(device, &desc); - if (dRet) - { - // could not acquire the descriptor, no point in trying to use it. - ERROR_LOG(SERIALINTERFACE, "libusb_get_device_descriptor failed with error: %d", dRet); - continue; - } - - if (desc.idVendor == 0x057e && desc.idProduct == 0x0337) - { - NOTICE_LOG(SERIALINTERFACE, "Found GC Adapter with Vendor: %X Product: %X Devnum: %d", desc.idVendor, desc.idProduct, 1); - - u8 bus = libusb_get_bus_number(device); - u8 port = libusb_get_device_address(device); - ret = libusb_open(device, &s_handle); - if (ret) - { - if (ret == LIBUSB_ERROR_ACCESS) - { - if (dRet) - { - ERROR_LOG(SERIALINTERFACE, "Dolphin does not have access to this device: Bus %03d Device %03d: ID ????:???? (couldn't get id).", - bus, - port - ); - } - else - { - ERROR_LOG(SERIALINTERFACE, "Dolphin does not have access to this device: Bus %03d Device %03d: ID %04X:%04X.", - bus, - port, - desc.idVendor, - desc.idProduct - ); - } - } - else - { - 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; - } - } - else if ((ret = libusb_kernel_driver_active(s_handle, 0)) == 1) - { - if ((ret = libusb_detach_kernel_driver(s_handle, 0)) && ret != LIBUSB_ERROR_NOT_SUPPORTED) - { - ERROR_LOG(SERIALINTERFACE, "libusb_detach_kernel_driver failed with error: %d", ret); - } - } - // 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) - { - continue; - } - else if ((ret = libusb_claim_interface(s_handle, 0))) - { - ERROR_LOG(SERIALINTERFACE, "libusb_claim_interface failed with error: %d", ret); - } - else - { - AddGCAdapter(device); - break; - } - } + if (CheckDeviceAccess(device)) + AddGCAdapter(device); } libusb_free_device_list(list, 1); - - if (!s_detected) - Shutdown(); } +static bool CheckDeviceAccess(libusb_device* device) +{ + int ret; + libusb_device_descriptor desc; + int dRet = libusb_get_device_descriptor(device, &desc); + if (dRet) + { + // could not acquire the descriptor, no point in trying to use it. + ERROR_LOG(SERIALINTERFACE, "libusb_get_device_descriptor failed with error: %d", dRet); + return false; + } -void AddGCAdapter(libusb_device* device) + if (desc.idVendor == 0x057e && desc.idProduct == 0x0337) + { + NOTICE_LOG(SERIALINTERFACE, "Found GC Adapter with Vendor: %X Product: %X Devnum: %d", desc.idVendor, desc.idProduct, 1); + + u8 bus = libusb_get_bus_number(device); + u8 port = libusb_get_device_address(device); + ret = libusb_open(device, &s_handle); + if (ret) + { + if (ret == LIBUSB_ERROR_ACCESS) + { + if (dRet) + { + ERROR_LOG(SERIALINTERFACE, "Dolphin does not have access to this device: Bus %03d Device %03d: ID ????:???? (couldn't get id).", + bus, + port + ); + } + else + { + ERROR_LOG(SERIALINTERFACE, "Dolphin does not have access to this device: Bus %03d Device %03d: ID %04X:%04X.", + bus, + port, + desc.idVendor, + desc.idProduct + ); + } + } + else + { + 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; + } + } + else if ((ret = libusb_kernel_driver_active(s_handle, 0)) == 1) + { + if ((ret = libusb_detach_kernel_driver(s_handle, 0)) && ret != LIBUSB_ERROR_NOT_SUPPORTED) + { + ERROR_LOG(SERIALINTERFACE, "libusb_detach_kernel_driver failed with error: %d", ret); + } + } + // 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) + { + return false; + } + else if ((ret = libusb_claim_interface(s_handle, 0))) + { + ERROR_LOG(SERIALINTERFACE, "libusb_claim_interface failed with error: %d", ret); + } + else + { + return true; + } + } + return false; +} + +static void AddGCAdapter(libusb_device* device) { libusb_config_descriptor *config = nullptr; libusb_get_config_descriptor(device, 0, &config); @@ -205,10 +285,15 @@ void AddGCAdapter(libusb_device* device) s_adapter_thread = std::thread(Read); s_detected = true; + if (s_detect_callback != nullptr) + s_detect_callback(); } void Shutdown() { + StopScanThread(); + if (s_libusb_hotplug_enabled) + libusb_hotplug_deregister_callback(s_libusb_context, s_hotplug_handle); Reset(); if (s_libusb_context) @@ -230,36 +315,29 @@ void Reset() s_adapter_thread.join(); } + for (int i = 0; i < MAX_SI_CHANNELS; i++) + s_controller_type[i] = CONTROLLER_NONE; + + s_detected = false; + if (s_handle) { libusb_release_interface(s_handle, 0); libusb_close(s_handle); s_handle = nullptr; } - - for (int i = 0; i < MAX_SI_CHANNELS; i++) - s_controller_type[i] = CONTROLLER_NONE; + if (s_detect_callback != nullptr) + s_detect_callback(); + NOTICE_LOG(SERIALINTERFACE, "GC Adapter detached"); } - void Input(int chan, GCPadStatus* pad) { if (!SConfig::GetInstance().m_GameCubeAdapter) return; - if (s_handle == nullptr) - { - if (s_detected) - { - Init(); - if (s_handle == nullptr) - return; - } - else - { - return; - } - } + if (s_handle == nullptr || !s_detected) + return; u8 controller_payload_copy[37]; @@ -271,7 +349,7 @@ void Input(int chan, GCPadStatus* pad) if (s_controller_payload_size != sizeof(controller_payload_copy) || controller_payload_copy[0] != LIBUSB_DT_HID) { INFO_LOG(SERIALINTERFACE, "error reading payload (size: %d, type: %02x)", s_controller_payload_size, controller_payload_copy[0]); - Shutdown(); + Reset(); } else { @@ -330,7 +408,7 @@ void Output(int chan, u8 rumble_command) if (size != 0x05 && size != 0x00) { INFO_LOG(SERIALINTERFACE, "error writing rumble (size: %d)", size); - Shutdown(); + Reset(); } } } diff --git a/Source/Core/Core/HW/SI_GCAdapter.h b/Source/Core/Core/HW/SI_GCAdapter.h index 925e044b94..8c81e043c1 100644 --- a/Source/Core/Core/HW/SI_GCAdapter.h +++ b/Source/Core/Core/HW/SI_GCAdapter.h @@ -4,7 +4,7 @@ #pragma once -struct libusb_device; +#include #include "Common/Thread.h" #include "Core/HW/SI.h" @@ -17,7 +17,9 @@ void Init(); void Reset(); void Setup(); void Shutdown(); -void AddGCAdapter(libusb_device* device); +void SetAdapterCallback(std::function func); +void StartScanThread(); +void StopScanThread(); void Input(int chan, GCPadStatus* pad); void Output(int chan, u8 rumble_command); bool IsDetected(); diff --git a/Source/Core/DolphinWX/ControllerConfigDiag.cpp b/Source/Core/DolphinWX/ControllerConfigDiag.cpp index 6f3ae906f5..b78d8a526e 100644 --- a/Source/Core/DolphinWX/ControllerConfigDiag.cpp +++ b/Source/Core/DolphinWX/ControllerConfigDiag.cpp @@ -52,6 +52,8 @@ const std::array ControllerConfigDiag::m_gc_pad_type_strs = {{ _("AM-Baseboard") }}; +wxDEFINE_EVENT(wxEVT_ADAPTER_UPDATE, wxCommandEvent); + ControllerConfigDiag::ControllerConfigDiag(wxWindow* const parent) : wxDialog(parent, wxID_ANY, _("Dolphin Controller Configuration")) { @@ -71,6 +73,7 @@ ControllerConfigDiag::ControllerConfigDiag(wxWindow* const parent) SetLayoutAdaptationMode(wxDIALOG_ADAPTATION_MODE_ENABLED); SetSizerAndFit(main_sizer); Center(); + Bind(wxEVT_ADAPTER_UPDATE, &ControllerConfigDiag::UpdateAdapter, this); } wxStaticBoxSizer* ControllerConfigDiag::CreateGamecubeSizer() @@ -147,39 +150,58 @@ wxStaticBoxSizer* ControllerConfigDiag::CreateGamecubeSizer() gamecube_static_sizer->Add(gamecube_flex_sizer, 1, wxEXPAND, 5); gamecube_static_sizer->AddSpacer(5); - wxStaticBoxSizer* const gamecube_adapter_group = new wxStaticBoxSizer(wxHORIZONTAL, this, _("GameCube Adapter")); + wxStaticBoxSizer* const gamecube_adapter_group = new wxStaticBoxSizer(wxVERTICAL, this, _("GameCube Adapter")); wxBoxSizer* const gamecube_adapter_sizer = new wxBoxSizer(wxHORIZONTAL); wxCheckBox* const gamecube_adapter = new wxCheckBox(this, wxID_ANY, _("Direct Connect")); gamecube_adapter->Bind(wxEVT_CHECKBOX, &ControllerConfigDiag::OnGameCubeAdapter, this); + m_adapter_status = new wxStaticText(this, wxID_ANY, _("Adapter Not Detected")); + gamecube_adapter_sizer->Add(m_adapter_status, 0, wxEXPAND); gamecube_adapter_sizer->Add(gamecube_adapter, 0, wxEXPAND); gamecube_adapter_group->Add(gamecube_adapter_sizer, 0, wxEXPAND); gamecube_static_sizer->Add(gamecube_adapter_group, 0, wxEXPAND); #if defined(__LIBUSB__) || defined (_WIN32) + gamecube_adapter->SetValue(SConfig::GetInstance().m_GameCubeAdapter); if (!SI_GCAdapter::IsDetected()) { if (!SI_GCAdapter::IsDriverDetected()) - gamecube_adapter->SetLabelText(_("Driver Not Detected")); - else - gamecube_adapter->SetLabelText(_("Adapter Not Detected")); - gamecube_adapter->SetValue(false); - gamecube_adapter->Disable(); + { + m_adapter_status->SetLabelText(_("Driver Not Detected")); + gamecube_adapter->Disable(); + gamecube_adapter->SetValue(false); + } } else { - gamecube_adapter->SetValue(SConfig::GetInstance().m_GameCubeAdapter); - if (Core::GetState() != Core::CORE_UNINITIALIZED) - { - gamecube_adapter->Disable(); - } + m_adapter_status->SetLabelText(_("Adapter Detected")); } + if (Core::GetState() != Core::CORE_UNINITIALIZED) + { + gamecube_adapter->Disable(); + } + SI_GCAdapter::SetAdapterCallback(std::bind(&ControllerConfigDiag::ScheduleAdapterUpdate, this)); #endif return gamecube_static_sizer; } +void ControllerConfigDiag::ScheduleAdapterUpdate() +{ + wxQueueEvent(this, new wxCommandEvent(wxEVT_ADAPTER_UPDATE)); +} + +void ControllerConfigDiag::UpdateAdapter(wxCommandEvent& ev) +{ + bool unpause = Core::PauseAndLock(true); + if (SI_GCAdapter::IsDetected()) + m_adapter_status->SetLabelText(_("Adapter Detected")); + else + m_adapter_status->SetLabelText(_("Adapter Not Detected")); + Core::PauseAndLock(false, unpause); +} + wxStaticBoxSizer* ControllerConfigDiag::CreateWiimoteConfigSizer() { wxStaticText* wiimote_label[4]; @@ -537,3 +559,8 @@ void ControllerConfigDiag::OnGameCubeConfigButton(wxCommandEvent& event) HotkeyManagerEmu::Enable(true); } + +ControllerConfigDiag::~ControllerConfigDiag() +{ + SI_GCAdapter::SetAdapterCallback(nullptr); +} diff --git a/Source/Core/DolphinWX/ControllerConfigDiag.h b/Source/Core/DolphinWX/ControllerConfigDiag.h index 2f846d310e..25c2153530 100644 --- a/Source/Core/DolphinWX/ControllerConfigDiag.h +++ b/Source/Core/DolphinWX/ControllerConfigDiag.h @@ -8,6 +8,7 @@ #include "Common/SysConf.h" #include "Core/ConfigManager.h" +#include "Core/HW/SI_GCAdapter.h" #include "Core/HW/Wiimote.h" class InputConfig; @@ -19,6 +20,7 @@ class ControllerConfigDiag : public wxDialog { public: ControllerConfigDiag(wxWindow* const parent); + ~ControllerConfigDiag(); private: void RefreshRealWiimotes(wxCommandEvent& event); @@ -82,6 +84,8 @@ private: void Cancel(wxCommandEvent& event); void OnGameCubePortChanged(wxCommandEvent& event); void OnGameCubeConfigButton(wxCommandEvent& event); + void ScheduleAdapterUpdate(); + void UpdateAdapter(wxCommandEvent& ev); std::map m_gc_port_choice_ids; std::map m_gc_port_config_ids; @@ -90,6 +94,7 @@ private: std::map m_wiimote_index_from_ctrl_id; unsigned int m_orig_wiimote_sources[MAX_BBMOTES]; + wxStaticText* m_adapter_status; wxButton* wiimote_configure_bt[MAX_WIIMOTES]; wxButton* gamecube_configure_bt[4]; std::map m_wiimote_index_from_conf_bt_id;