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
};
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<void(void)> 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<void(void)> 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();
}
}
}

View File

@ -4,7 +4,7 @@
#pragma once
struct libusb_device;
#include <functional>
#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<void(void)> func);
void StartScanThread();
void StopScanThread();
void Input(int chan, GCPadStatus* pad);
void Output(int chan, u8 rumble_command);
bool IsDetected();

View File

@ -52,6 +52,8 @@ const std::array<wxString, 8> 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);
}

View File

@ -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<wxWindowID, unsigned int> m_gc_port_choice_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;
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<wxWindowID, unsigned int> m_wiimote_index_from_conf_bt_id;