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;