DS4: enumerate devices periodically

This commit is contained in:
Megamouse 2020-11-01 01:11:16 +01:00
parent b7c2bfbcde
commit d0cc5c0fc7
4 changed files with 144 additions and 63 deletions

View File

@ -246,17 +246,12 @@ std::shared_ptr<ds4_pad_handler::DS4Device> ds4_pad_handler::GetDS4Device(const
if (!Init())
return nullptr;
usz pos = padId.find(m_name_string);
if (pos == umax)
return nullptr;
std::string pad_serial = padId.substr(pos + 9);
std::shared_ptr<DS4Device> device = nullptr;
int i = 0; // Controllers 1-n in GUI
// Controllers 1-n in GUI
for (auto& cur_control : controllers)
{
if (pad_serial == std::to_string(++i) || pad_serial == cur_control.first)
if (padId == cur_control.first)
{
device = cur_control.second;
break;
@ -524,11 +519,26 @@ bool ds4_pad_handler::GetCalibrationData(const std::shared_ptr<DS4Device>& ds4De
return true;
}
void ds4_pad_handler::CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo)
void ds4_pad_handler::CheckAddDevice(hid_device* hidDevice, std::string_view path, std::wstring_view wide_serial)
{
std::shared_ptr<DS4Device> ds4_dev;
for (auto& controller : controllers)
{
if (!controller.second || !controller.second->hidDevice)
{
ds4_dev = controller.second;
break;
}
}
if (!ds4_dev)
{
return;
}
std::string serial;
std::shared_ptr<DS4Device> ds4Dev = std::make_shared<DS4Device>();
ds4Dev->hidDevice = hidDevice;
ds4_dev->hidDevice = hidDevice;
// There isnt a nice 'portable' way with hidapi to detect bt vs wired as the pid/vid's are the same
// Let's try getting 0x81 feature report, which should will return mac address on wired, and should error on bluetooth
std::array<u8, 64> buf{};
@ -545,7 +555,7 @@ void ds4_pad_handler::CheckAddDevice(hid_device* hidDevice, hid_device_info* hid
buf[1] = 0;
if (hid_get_feature_report(hidDevice, buf.data(), DS4_FEATURE_REPORT_0x12_SIZE) == -1)
{
ds4_log.error("CheckAddDevice: hid_get_feature_report 0x12 failed! Reason: %s", hid_error(ds4Dev->hidDevice));
ds4_log.error("CheckAddDevice: hid_get_feature_report 0x12 failed! Reason: %s", hid_error(hidDevice));
}
}
@ -553,13 +563,12 @@ void ds4_pad_handler::CheckAddDevice(hid_device* hidDevice, hid_device_info* hid
}
else
{
ds4Dev->btCon = true;
std::wstring_view wideSerial(hidDevInfo->serial_number);
for (wchar_t ch : wideSerial)
ds4_dev->btCon = true;
for (wchar_t ch : wide_serial)
serial += static_cast<uchar>(ch);
}
if (!GetCalibrationData(ds4Dev))
if (!GetCalibrationData(ds4_dev))
{
ds4_log.error("CheckAddDevice: GetCalibrationData failed!");
hid_close(hidDevice);
@ -573,17 +582,17 @@ void ds4_pad_handler::CheckAddDevice(hid_device* hidDevice, hid_device_info* hid
return;
}
ds4Dev->hasCalibData = true;
ds4Dev->path = hidDevInfo->path;
ds4_dev->hasCalibData = true;
ds4_dev->path = path;
controllers.emplace(serial, ds4Dev);
send_output_report(ds4_dev);
}
ds4_pad_handler::~ds4_pad_handler()
{
for (auto& controller : controllers)
{
if (controller.second->hidDevice)
if (controller.second && controller.second->hidDevice)
{
// Disable blinking and vibration
controller.second->smallVibrate = 0;
@ -603,7 +612,7 @@ ds4_pad_handler::~ds4_pad_handler()
int ds4_pad_handler::send_output_report(const std::shared_ptr<DS4Device>& device)
{
if (!device)
if (!device || !device->hidDevice)
return -2;
auto config = device->config;
@ -656,39 +665,72 @@ int ds4_pad_handler::send_output_report(const std::shared_ptr<DS4Device>& device
}
}
bool ds4_pad_handler::Init()
void ds4_pad_handler::enumerate_devices()
{
if (is_init)
return true;
std::set<std::string> device_paths;
std::map<std::string, std::wstring_view> serials;
const int res = hid_init();
if (res != 0)
fmt::throw_exception("hidapi-init error.threadproc");
// get all the possible controllers at start
bool warn_about_drivers = false;
for (auto pid : ds4Pids)
{
hid_device_info* devInfo = hid_enumerate(DS4_VID, pid);
hid_device_info* head = devInfo;
while (devInfo)
hid_device_info* dev_info = hid_enumerate(DS4_VID, pid);
hid_device_info* head = dev_info;
while (dev_info)
{
if (controllers.size() >= MAX_GAMEPADS)
break;
ensure(dev_info->path != nullptr);
device_paths.insert(dev_info->path);
serials[dev_info->path] = dev_info->serial_number ? std::wstring_view(dev_info->serial_number) : std::wstring_view{};
dev_info = dev_info->next;
}
hid_free_enumeration(head);
}
hid_device* dev = hid_open_path(devInfo->path);
if (m_last_enumerated_devices == device_paths)
{
return;
}
m_last_enumerated_devices = device_paths;
// Scrap devices that are not in the new list
for (auto it = controllers.begin(); it != controllers.end(); ++it)
{
if (it->second && !it->second->path.empty() && !device_paths.contains(it->second->path))
{
hid_close(it->second->hidDevice);
pad_config* config = it->second->config;
it->second.reset(new DS4Device());
it->second->config = config;
}
}
bool warn_about_drivers = false;
// Find and add new devices
for (const auto& path : device_paths)
{
// Check if we already have this controller
const auto it_found = std::find_if(controllers.cbegin(), controllers.cend(), [path](const auto& c) { return c.second && c.second->path == path; });
if (it_found == controllers.cend())
{
// Check if we have at least one virtual controller left
const auto it_free = std::find_if(controllers.cbegin(), controllers.cend(), [](const auto& c) { return !c.second || !c.second->hidDevice; });
if (it_free == controllers.cend())
{
break;
}
hid_device* dev = hid_open_path(path.c_str());
if (dev)
{
CheckAddDevice(dev, devInfo);
CheckAddDevice(dev, path, serials[path]);
}
else
{
ds4_log.error("hid_open_path failed! Reason: %s", hid_error(dev));
warn_about_drivers = true;
}
devInfo = devInfo->next;
}
hid_free_enumeration(head);
}
if (warn_about_drivers)
@ -698,19 +740,53 @@ bool ds4_pad_handler::Init()
ds4_log.error("Check https://wiki.rpcs3.net/index.php?title=Help:Controller_Configuration for intructions on how to solve this issue");
#endif
}
else if (controllers.empty())
{
ds4_log.warning("No controllers found!");
}
else
{
ds4_log.success("Controllers found: %d", controllers.size());
const size_t count = std::count_if(controllers.cbegin(), controllers.cend(), [](const auto& c) { return c.second && c.second->hidDevice; });
if (count > 0)
{
ds4_log.success("Controllers found: %d", count);
}
else
{
ds4_log.warning("No controllers found!");
}
}
}
bool ds4_pad_handler::Init()
{
if (is_init)
return true;
const int res = hid_init();
if (res != 0)
fmt::throw_exception("hidapi-init error.threadproc");
for (size_t i = 1; i <= MAX_GAMEPADS; i++) // Controllers 1-n in GUI
{
controllers.emplace(m_name_string + std::to_string(i), std::make_shared<DS4Device>());
}
enumerate_devices();
is_init = true;
return true;
}
void ds4_pad_handler::ThreadProc()
{
const auto now = std::chrono::system_clock::now();
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_last_enumeration).count();
if (elapsed > 2000)
{
m_last_enumeration = now;
enumerate_devices();
}
PadHandlerBase::ThreadProc();
}
std::vector<std::string> ds4_pad_handler::ListDevices()
{
std::vector<std::string> ds4_pads_list;
@ -718,9 +794,9 @@ std::vector<std::string> ds4_pad_handler::ListDevices()
if (!Init())
return ds4_pads_list;
for (usz i = 1; i <= controllers.size(); ++i) // Controllers 1-n in GUI
for (const auto& controller : controllers) // Controllers 1-n in GUI
{
ds4_pads_list.emplace_back(m_name_string + std::to_string(i));
ds4_pads_list.emplace_back(controller.first);
}
return ds4_pads_list;
@ -809,7 +885,7 @@ ds4_pad_handler::DS4DataStatus ds4_pad_handler::GetRawData(const std::shared_ptr
std::shared_ptr<PadDevice> ds4_pad_handler::get_device(const std::string& device)
{
std::shared_ptr<DS4Device> ds4device = GetDS4Device(device);
if (ds4device == nullptr || ds4device->hidDevice == nullptr)
if (ds4device == nullptr)
return nullptr;
return ds4device;
@ -870,7 +946,7 @@ u32 ds4_pad_handler::get_battery_color(u8 battery_level, int brightness)
PadHandlerBase::connection ds4_pad_handler::update_connection(const std::shared_ptr<PadDevice>& device)
{
auto ds4_dev = std::static_pointer_cast<DS4Device>(device);
if (!ds4_dev)
if (!ds4_dev || ds4_dev->path.empty())
return connection::disconnected;
if (ds4_dev->hidDevice == nullptr)

View File

@ -99,7 +99,7 @@ class ds4_pad_handler final : public PadHandlerBase
const std::array<u16, 3> ds4Pids = { { 0xBA0, 0x5C4, 0x09CC } };
// pseudo 'controller id' to keep track of unique controllers
std::unordered_map<std::string, std::shared_ptr<DS4Device>> controllers;
std::map<std::string, std::shared_ptr<DS4Device>> controllers;
CRCPP::CRC::Table<u32, 32> crcTable{ CRCPP::CRC::CRC_32() };
public:
@ -107,6 +107,7 @@ public:
~ds4_pad_handler();
bool Init() override;
void ThreadProc() override;
std::vector<std::string> ListDevices() override;
void SetPadData(const std::string& padId, u32 largeMotor, u32 smallMotor, s32 r, s32 g, s32 b, bool battery_led, u32 battery_led_brightness) override;
@ -116,6 +117,9 @@ public:
private:
bool is_init = false;
DS4DataStatus status;
std::chrono::system_clock::time_point m_last_enumeration;
std::set<std::string> m_last_enumerated_devices;
void enumerate_devices();
u32 get_battery_color(u8 battery_level, int brightness);
private:
@ -124,7 +128,7 @@ private:
DS4DataStatus GetRawData(const std::shared_ptr<DS4Device>& ds4Device);
// This function gets us usuable buffer from the rawbuffer of padData
bool GetCalibrationData(const std::shared_ptr<DS4Device>& ds4Device);
void CheckAddDevice(hid_device* hidDevice, hid_device_info* hidDevInfo);
void CheckAddDevice(hid_device* hidDevice, std::string_view path, std::wstring_view serial);
int send_output_report(const std::shared_ptr<DS4Device>& device);
inline s16 ApplyCalibration(s32 rawValue, const DS4CalibData& calibData)
{

View File

@ -104,7 +104,7 @@ void pad_thread::Init()
const bool is_ldd_pad = pad_settings[i].ldd_handle == static_cast<s32>(i);
const auto handler_type = is_ldd_pad ? pad_handler::null : g_cfg_input.player[i]->handler.get();
if (handlers.count(handler_type) != 0)
if (handlers.contains(handler_type))
{
cur_pad_handler = handlers[handler_type];
}

View File

@ -102,7 +102,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
connect(ui->chooseHandler, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeInputType);
// Combobox: Devices
connect(ui->chooseDevice, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index)
connect(ui->chooseDevice, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
{
if (index < 0)
{
@ -119,7 +119,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
});
// Combobox: Profiles
connect(ui->chooseProfile, &QComboBox::currentTextChanged, [this](const QString& prof)
connect(ui->chooseProfile, &QComboBox::currentTextChanged, this, [this](const QString& prof)
{
if (prof.isEmpty())
{
@ -136,7 +136,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
});
// Pushbutton: Add Profile
connect(ui->b_addProfile, &QAbstractButton::clicked, [this]()
connect(ui->b_addProfile, &QAbstractButton::clicked, this, [this]()
{
const int i = ui->tabWidget->currentIndex();
@ -175,7 +175,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr<gui_settings> gui_setti
ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Filter Noise"));
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button)
{
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
{
@ -304,7 +304,7 @@ void pad_settings_dialog::InitButtons()
connect(m_pad_buttons, &QButtonGroup::idClicked, this, &pad_settings_dialog::OnPadButtonClicked);
connect(&m_timer, &QTimer::timeout, [this]()
connect(&m_timer, &QTimer::timeout, this, [this]()
{
if (--m_seconds <= 0)
{
@ -314,7 +314,7 @@ void pad_settings_dialog::InitButtons()
m_pad_buttons->button(m_button_id)->setText(tr("[ Waiting %1 ]").arg(m_seconds));
});
connect(ui->chb_vibration_large, &QCheckBox::clicked, [this](bool checked)
connect(ui->chb_vibration_large, &QCheckBox::clicked, this, [this](bool checked)
{
if (!checked)
{
@ -330,7 +330,7 @@ void pad_settings_dialog::InitButtons()
});
});
connect(ui->chb_vibration_small, &QCheckBox::clicked, [this](bool checked)
connect(ui->chb_vibration_small, &QCheckBox::clicked, this, [this](bool checked)
{
if (!checked)
{
@ -346,7 +346,7 @@ void pad_settings_dialog::InitButtons()
});
});
connect(ui->chb_vibration_switch, &QCheckBox::clicked, [this](bool checked)
connect(ui->chb_vibration_switch, &QCheckBox::clicked, this, [this](bool checked)
{
checked ? SetPadData(m_min_force, m_max_force)
: SetPadData(m_max_force, m_min_force);
@ -363,18 +363,18 @@ void pad_settings_dialog::InitButtons()
});
});
connect(ui->slider_stick_left, &QSlider::valueChanged, [&](int value)
connect(ui->slider_stick_left, &QSlider::valueChanged, this, [&](int value)
{
RepaintPreviewLabel(ui->preview_stick_left, value, ui->slider_stick_left->size().width(), m_lx, m_ly, ui->squircle_left->value(), ui->stick_multi_left->value());
});
connect(ui->slider_stick_right, &QSlider::valueChanged, [&](int value)
connect(ui->slider_stick_right, &QSlider::valueChanged, this, [&](int value)
{
RepaintPreviewLabel(ui->preview_stick_right, value, ui->slider_stick_right->size().width(), m_rx, m_ry, ui->squircle_right->value(), ui->stick_multi_right->value());
});
// Open LED settings
connect(ui->b_led_settings, &QPushButton::clicked, [this]()
connect(ui->b_led_settings, &QPushButton::clicked, this, [this]()
{
// Allow LED battery indication while the dialog is open
m_handler->SetPadData(m_device_name, 0, 0, m_handler_cfg.colorR, m_handler_cfg.colorG, m_handler_cfg.colorB, m_handler_cfg.led_battery_indicator.get(), m_handler_cfg.led_battery_indicator_brightness);
@ -462,7 +462,7 @@ void pad_settings_dialog::InitButtons()
};
// Use timer to get button input
connect(&m_timer_input, &QTimer::timeout, [this, callback, fail_callback]()
connect(&m_timer_input, &QTimer::timeout, this, [this, callback, fail_callback]()
{
const std::vector<std::string> buttons =
{
@ -475,7 +475,7 @@ void pad_settings_dialog::InitButtons()
});
// Use timer to refresh pad connection status
connect(&m_timer_pad_refresh, &QTimer::timeout, [this]()
connect(&m_timer_pad_refresh, &QTimer::timeout, this, [this]()
{
for (int i = 0; i < ui->chooseDevice->count(); i++)
{
@ -1193,6 +1193,7 @@ void pad_settings_dialog::ChangeInputType()
// Get this player's current handler and it's currently available devices
m_handler = GetHandler(g_cfg_input.player[player]->handler);
ensure(m_handler);
const auto device_list = m_handler->ListDevices();
// Localized tooltips