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.
This commit is contained in:
mathieui
2015-04-26 23:53:36 +02:00
parent 989c0f427f
commit 57f458fe9b
4 changed files with 218 additions and 106 deletions

View File

@ -19,6 +19,9 @@ enum ControllerTypes
CONTROLLER_WIRELESS = 2 CONTROLLER_WIRELESS = 2
}; };
static bool CheckDeviceAccess(libusb_device* device);
static void AddGCAdapter(libusb_device* device);
static bool s_detected = false; static bool s_detected = false;
static libusb_device_handle* s_handle = nullptr; static libusb_device_handle* s_handle = nullptr;
static u8 s_controller_type[MAX_SI_CHANNELS] = { CONTROLLER_NONE, CONTROLLER_NONE, CONTROLLER_NONE, CONTROLLER_NONE }; 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 std::thread s_adapter_thread;
static Common::Flag s_adapter_thread_running; 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<void(void)> s_detect_callback;
static bool s_libusb_driver_not_supported = false; static bool s_libusb_driver_not_supported = false;
static libusb_context* s_libusb_context = nullptr; 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_in = 0;
static u8 s_endpoint_out = 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<void(void)> func)
{
s_detect_callback = func;
}
void Init() void Init()
{ {
if (s_handle != nullptr) if (s_handle != nullptr)
@ -80,13 +145,26 @@ void Init()
} }
else 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() void Setup()
{ {
int ret;
libusb_device** list; libusb_device** list;
ssize_t cnt = libusb_get_device_list(s_libusb_context, &list); ssize_t cnt = libusb_get_device_list(s_libusb_context, &list);
@ -96,87 +174,89 @@ void Setup()
s_controller_rumble[i] = 0; s_controller_rumble[i] = 0;
} }
for (int d = 0; d < cnt; d++) for (int d = 0; d < cnt; d++)
{ {
libusb_device* device = list[d]; libusb_device* device = list[d];
libusb_device_descriptor desc; if (CheckDeviceAccess(device))
int dRet = libusb_get_device_descriptor(device, &desc); AddGCAdapter(device);
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;
}
}
} }
libusb_free_device_list(list, 1); 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_config_descriptor *config = nullptr;
libusb_get_config_descriptor(device, 0, &config); libusb_get_config_descriptor(device, 0, &config);
@ -205,10 +285,15 @@ void AddGCAdapter(libusb_device* device)
s_adapter_thread = std::thread(Read); s_adapter_thread = std::thread(Read);
s_detected = true; s_detected = true;
if (s_detect_callback != nullptr)
s_detect_callback();
} }
void Shutdown() void Shutdown()
{ {
StopScanThread();
if (s_libusb_hotplug_enabled)
libusb_hotplug_deregister_callback(s_libusb_context, s_hotplug_handle);
Reset(); Reset();
if (s_libusb_context) if (s_libusb_context)
@ -230,36 +315,29 @@ void Reset()
s_adapter_thread.join(); 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) if (s_handle)
{ {
libusb_release_interface(s_handle, 0); libusb_release_interface(s_handle, 0);
libusb_close(s_handle); libusb_close(s_handle);
s_handle = nullptr; s_handle = nullptr;
} }
if (s_detect_callback != nullptr)
for (int i = 0; i < MAX_SI_CHANNELS; i++) s_detect_callback();
s_controller_type[i] = CONTROLLER_NONE; NOTICE_LOG(SERIALINTERFACE, "GC Adapter detached");
} }
void Input(int chan, GCPadStatus* pad) void Input(int chan, GCPadStatus* pad)
{ {
if (!SConfig::GetInstance().m_GameCubeAdapter) if (!SConfig::GetInstance().m_GameCubeAdapter)
return; return;
if (s_handle == nullptr) if (s_handle == nullptr || !s_detected)
{ return;
if (s_detected)
{
Init();
if (s_handle == nullptr)
return;
}
else
{
return;
}
}
u8 controller_payload_copy[37]; 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) 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]); INFO_LOG(SERIALINTERFACE, "error reading payload (size: %d, type: %02x)", s_controller_payload_size, controller_payload_copy[0]);
Shutdown(); Reset();
} }
else else
{ {
@ -330,7 +408,7 @@ void Output(int chan, u8 rumble_command)
if (size != 0x05 && size != 0x00) if (size != 0x05 && size != 0x00)
{ {
INFO_LOG(SERIALINTERFACE, "error writing rumble (size: %d)", size); INFO_LOG(SERIALINTERFACE, "error writing rumble (size: %d)", size);
Shutdown(); Reset();
} }
} }
} }

View File

@ -4,7 +4,7 @@
#pragma once #pragma once
struct libusb_device; #include <functional>
#include "Common/Thread.h" #include "Common/Thread.h"
#include "Core/HW/SI.h" #include "Core/HW/SI.h"
@ -17,7 +17,9 @@ void Init();
void Reset(); void Reset();
void Setup(); void Setup();
void Shutdown(); void Shutdown();
void AddGCAdapter(libusb_device* device); void SetAdapterCallback(std::function<void(void)> func);
void StartScanThread();
void StopScanThread();
void Input(int chan, GCPadStatus* pad); void Input(int chan, GCPadStatus* pad);
void Output(int chan, u8 rumble_command); void Output(int chan, u8 rumble_command);
bool IsDetected(); bool IsDetected();

View File

@ -52,6 +52,8 @@ const std::array<wxString, 8> ControllerConfigDiag::m_gc_pad_type_strs = {{
_("AM-Baseboard") _("AM-Baseboard")
}}; }};
wxDEFINE_EVENT(wxEVT_ADAPTER_UPDATE, wxCommandEvent);
ControllerConfigDiag::ControllerConfigDiag(wxWindow* const parent) ControllerConfigDiag::ControllerConfigDiag(wxWindow* const parent)
: wxDialog(parent, wxID_ANY, _("Dolphin Controller Configuration")) : wxDialog(parent, wxID_ANY, _("Dolphin Controller Configuration"))
{ {
@ -71,6 +73,7 @@ ControllerConfigDiag::ControllerConfigDiag(wxWindow* const parent)
SetLayoutAdaptationMode(wxDIALOG_ADAPTATION_MODE_ENABLED); SetLayoutAdaptationMode(wxDIALOG_ADAPTATION_MODE_ENABLED);
SetSizerAndFit(main_sizer); SetSizerAndFit(main_sizer);
Center(); Center();
Bind(wxEVT_ADAPTER_UPDATE, &ControllerConfigDiag::UpdateAdapter, this);
} }
wxStaticBoxSizer* ControllerConfigDiag::CreateGamecubeSizer() wxStaticBoxSizer* ControllerConfigDiag::CreateGamecubeSizer()
@ -147,39 +150,58 @@ wxStaticBoxSizer* ControllerConfigDiag::CreateGamecubeSizer()
gamecube_static_sizer->Add(gamecube_flex_sizer, 1, wxEXPAND, 5); gamecube_static_sizer->Add(gamecube_flex_sizer, 1, wxEXPAND, 5);
gamecube_static_sizer->AddSpacer(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); wxBoxSizer* const gamecube_adapter_sizer = new wxBoxSizer(wxHORIZONTAL);
wxCheckBox* const gamecube_adapter = new wxCheckBox(this, wxID_ANY, _("Direct Connect")); wxCheckBox* const gamecube_adapter = new wxCheckBox(this, wxID_ANY, _("Direct Connect"));
gamecube_adapter->Bind(wxEVT_CHECKBOX, &ControllerConfigDiag::OnGameCubeAdapter, this); 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_sizer->Add(gamecube_adapter, 0, wxEXPAND);
gamecube_adapter_group->Add(gamecube_adapter_sizer, 0, wxEXPAND); gamecube_adapter_group->Add(gamecube_adapter_sizer, 0, wxEXPAND);
gamecube_static_sizer->Add(gamecube_adapter_group, 0, wxEXPAND); gamecube_static_sizer->Add(gamecube_adapter_group, 0, wxEXPAND);
#if defined(__LIBUSB__) || defined (_WIN32) #if defined(__LIBUSB__) || defined (_WIN32)
gamecube_adapter->SetValue(SConfig::GetInstance().m_GameCubeAdapter);
if (!SI_GCAdapter::IsDetected()) if (!SI_GCAdapter::IsDetected())
{ {
if (!SI_GCAdapter::IsDriverDetected()) if (!SI_GCAdapter::IsDriverDetected())
gamecube_adapter->SetLabelText(_("Driver Not Detected")); {
else m_adapter_status->SetLabelText(_("Driver Not Detected"));
gamecube_adapter->SetLabelText(_("Adapter Not Detected")); gamecube_adapter->Disable();
gamecube_adapter->SetValue(false); gamecube_adapter->SetValue(false);
gamecube_adapter->Disable(); }
} }
else else
{ {
gamecube_adapter->SetValue(SConfig::GetInstance().m_GameCubeAdapter); m_adapter_status->SetLabelText(_("Adapter Detected"));
if (Core::GetState() != Core::CORE_UNINITIALIZED)
{
gamecube_adapter->Disable();
}
} }
if (Core::GetState() != Core::CORE_UNINITIALIZED)
{
gamecube_adapter->Disable();
}
SI_GCAdapter::SetAdapterCallback(std::bind(&ControllerConfigDiag::ScheduleAdapterUpdate, this));
#endif #endif
return gamecube_static_sizer; 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() wxStaticBoxSizer* ControllerConfigDiag::CreateWiimoteConfigSizer()
{ {
wxStaticText* wiimote_label[4]; wxStaticText* wiimote_label[4];
@ -537,3 +559,8 @@ void ControllerConfigDiag::OnGameCubeConfigButton(wxCommandEvent& event)
HotkeyManagerEmu::Enable(true); HotkeyManagerEmu::Enable(true);
} }
ControllerConfigDiag::~ControllerConfigDiag()
{
SI_GCAdapter::SetAdapterCallback(nullptr);
}

View File

@ -8,6 +8,7 @@
#include "Common/SysConf.h" #include "Common/SysConf.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/HW/SI_GCAdapter.h"
#include "Core/HW/Wiimote.h" #include "Core/HW/Wiimote.h"
class InputConfig; class InputConfig;
@ -19,6 +20,7 @@ class ControllerConfigDiag : public wxDialog
{ {
public: public:
ControllerConfigDiag(wxWindow* const parent); ControllerConfigDiag(wxWindow* const parent);
~ControllerConfigDiag();
private: private:
void RefreshRealWiimotes(wxCommandEvent& event); void RefreshRealWiimotes(wxCommandEvent& event);
@ -82,6 +84,8 @@ private:
void Cancel(wxCommandEvent& event); void Cancel(wxCommandEvent& event);
void OnGameCubePortChanged(wxCommandEvent& event); void OnGameCubePortChanged(wxCommandEvent& event);
void OnGameCubeConfigButton(wxCommandEvent& event); void OnGameCubeConfigButton(wxCommandEvent& event);
void ScheduleAdapterUpdate();
void UpdateAdapter(wxCommandEvent& ev);
std::map<wxWindowID, unsigned int> m_gc_port_choice_ids; std::map<wxWindowID, unsigned int> m_gc_port_choice_ids;
std::map<wxWindowID, unsigned int> m_gc_port_config_ids; std::map<wxWindowID, unsigned int> m_gc_port_config_ids;
@ -90,6 +94,7 @@ private:
std::map<wxWindowID, unsigned int> m_wiimote_index_from_ctrl_id; std::map<wxWindowID, unsigned int> m_wiimote_index_from_ctrl_id;
unsigned int m_orig_wiimote_sources[MAX_BBMOTES]; unsigned int m_orig_wiimote_sources[MAX_BBMOTES];
wxStaticText* m_adapter_status;
wxButton* wiimote_configure_bt[MAX_WIIMOTES]; wxButton* wiimote_configure_bt[MAX_WIIMOTES];
wxButton* gamecube_configure_bt[4]; wxButton* gamecube_configure_bt[4];
std::map<wxWindowID, unsigned int> m_wiimote_index_from_conf_bt_id; std::map<wxWindowID, unsigned int> m_wiimote_index_from_conf_bt_id;