diff --git a/rpcs3/Input/ds4_pad_handler.cpp b/rpcs3/Input/ds4_pad_handler.cpp index e81008fba3..bc17964f37 100644 --- a/rpcs3/Input/ds4_pad_handler.cpp +++ b/rpcs3/Input/ds4_pad_handler.cpp @@ -246,17 +246,12 @@ std::shared_ptr 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 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& 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 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 ds4Dev = std::make_shared(); - 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 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(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& 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& device } } -bool ds4_pad_handler::Init() +void ds4_pad_handler::enumerate_devices() { - if (is_init) - return true; + std::set device_paths; + std::map 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()); + } + + 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(now - m_last_enumeration).count(); + if (elapsed > 2000) + { + m_last_enumeration = now; + enumerate_devices(); + } + + PadHandlerBase::ThreadProc(); +} + std::vector ds4_pad_handler::ListDevices() { std::vector ds4_pads_list; @@ -718,9 +794,9 @@ std::vector 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 ds4_pad_handler::get_device(const std::string& device) { std::shared_ptr 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& device) { auto ds4_dev = std::static_pointer_cast(device); - if (!ds4_dev) + if (!ds4_dev || ds4_dev->path.empty()) return connection::disconnected; if (ds4_dev->hidDevice == nullptr) diff --git a/rpcs3/Input/ds4_pad_handler.h b/rpcs3/Input/ds4_pad_handler.h index ce479c974b..6b28b060cc 100644 --- a/rpcs3/Input/ds4_pad_handler.h +++ b/rpcs3/Input/ds4_pad_handler.h @@ -99,7 +99,7 @@ class ds4_pad_handler final : public PadHandlerBase const std::array ds4Pids = { { 0xBA0, 0x5C4, 0x09CC } }; // pseudo 'controller id' to keep track of unique controllers - std::unordered_map> controllers; + std::map> controllers; CRCPP::CRC::Table crcTable{ CRCPP::CRC::CRC_32() }; public: @@ -107,6 +107,7 @@ public: ~ds4_pad_handler(); bool Init() override; + void ThreadProc() override; std::vector 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 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); // This function gets us usuable buffer from the rawbuffer of padData bool GetCalibrationData(const std::shared_ptr& 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& device); inline s16 ApplyCalibration(s32 rawValue, const DS4CalibData& calibData) { diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index f39b2259bc..a8f7e1afe7 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -104,7 +104,7 @@ void pad_thread::Init() const bool is_ldd_pad = pad_settings[i].ldd_handle == static_cast(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]; } diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 591e5b5066..262f795f1f 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -102,7 +102,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti connect(ui->chooseHandler, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeInputType); // Combobox: Devices - connect(ui->chooseDevice, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->chooseDevice, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { if (index < 0) { @@ -119,7 +119,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr 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_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_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 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