From 91679d5ba663ff61365d1471ac14f2c1159e9cba Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:17:00 +0100 Subject: [PATCH 01/25] Add `L2CapWiimote` --- src/input/CMakeLists.txt | 5 + .../api/Wiimote/hidapi/HidapiWiimote.cpp | 7 +- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 127 ++++++++++++++++++ src/input/api/Wiimote/l2cap/L2CapWiimote.h | 20 +++ 4 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/input/api/Wiimote/l2cap/L2CapWiimote.cpp create mode 100644 src/input/api/Wiimote/l2cap/L2CapWiimote.h diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 9f7873a11..31a44a493 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -73,6 +73,11 @@ if (ENABLE_WIIMOTE) api/Wiimote/hidapi/HidapiWiimote.cpp api/Wiimote/hidapi/HidapiWiimote.h ) + if (UNIX AND NOT APPLE) + target_sources(CemuInput PRIVATE + api/Wiimote/l2cap/L2CapWiimote.cpp + api/Wiimote/l2cap/L2CapWiimote.h) + endif() endif () diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp index db1856754..9ba563215 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -47,8 +47,11 @@ std::vector HidapiWiimote::get_devices() { return wiimote_devices; } -bool HidapiWiimote::operator==(WiimoteDevice& o) const { - return static_cast(o).m_path == m_path; +bool HidapiWiimote::operator==(WiimoteDevice& rhs) const { + auto other = dynamic_cast(&rhs); + if (!other) + return false; + return m_path == other->m_path; } HidapiWiimote::~HidapiWiimote() { diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp new file mode 100644 index 000000000..bcd669ef2 --- /dev/null +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -0,0 +1,127 @@ +#include "L2CapWiimote.h" +#include + +namespace { + // TODO: Add procedure to get addresses. Should add to PairingDialog + std::vector s_address; + std::mutex s_addressMutex; + + bool AttemptConnect(int sockFd, sockaddr_l2 const& addr) + { + for (auto i = 0; i < 3; ++i) + { + if (connect(sockFd, reinterpret_cast(&addr), + sizeof(sockaddr_l2)) == 0) + return true; + cemuLog_logDebug(LogType::Force, "Connection attempt {} failed with error {:x}: {} ", i + 1, errno, + std::strerror(errno)); + if (i == 2) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + } + return false; + } + bool AttemptSetNonBlock(int& sockFd) + { + return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; + } +} + +L2CapWiimote::L2CapWiimote(int recvFd,int sendFd) +: m_recvFd(recvFd), m_sendFd(sendFd) +{ + +} + +L2CapWiimote::~L2CapWiimote() +{ + ::close(m_recvFd); + ::close(m_sendFd); +} + +std::vector L2CapWiimote::get_devices() +{ + s_addressMutex.lock(); + const auto addresses = s_address; + s_addressMutex.unlock(); + + + std::vector outDevices; + for (auto addr : addresses) + { + // Socket for sending data to controller, PSM 0x11 + auto sendFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sendFd < 0) { + cemuLog_logDebug(LogType::Force, "Failed to open send socket: {}", strerror(errno)); + continue; + } + + sockaddr_l2 sendAddr{}; + sendAddr.l2_family = AF_BLUETOOTH; + sendAddr.l2_psm = htobs(0x11); + sendAddr.l2_bdaddr = addr; + + if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) { + cemuLog_logDebug(LogType::Force,"Failed to connect send socket to '{:02x}': {}", + fmt::join(addr.b, ":"), strerror(errno)); + close(sendFd); + continue; + } + + // Socket for receiving data from controller, PSM 0x13 + auto recvFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (recvFd < 0) { + cemuLog_logDebug(LogType::Force,"Failed to open recv socket: {}", strerror(errno)); + close(sendFd); + continue; + } + sockaddr_l2 recvAddr{}; + recvAddr.l2_family = AF_BLUETOOTH; + recvAddr.l2_psm = htobs(0x13); + recvAddr.l2_bdaddr = addr; + + if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) { + cemuLog_logDebug(LogType::Force,"Failed to connect recv socket to '{:02x}': {}", + fmt::join(addr.b, ":"), strerror(errno)); + close(sendFd); + close(recvFd); + continue; + } + + outDevices.push_back(std::make_unique(sendFd, recvFd)); + } + return outDevices; +} + +bool L2CapWiimote::write_data(const std::vector& data) +{ + const auto size = data.size(); + cemu_assert_debug(size < 23); + uint8 buffer[23]; + // All outgoing messages must be prefixed with 0xA2 + buffer[0] = 0xA2; + std::memcpy(buffer + 1, data.data(), size); + const auto outSize = size + 1; + return send(m_sendFd, buffer, outSize, 0) != outSize; +} + +std::optional> L2CapWiimote::read_data() +{ + uint8 buffer[23]; + const auto nBytes = recv(m_sendFd, buffer, 23, 0); + + // All incoming messages must be prefixed with 0xA1 + if (nBytes < 2 || buffer[0] != 0xA1) + return {}; + return std::vector(buffer + 1, buffer + 1 + nBytes - 1); +} + + +bool L2CapWiimote::operator==(WiimoteDevice& rhs) const +{ + auto mote = dynamic_cast(&rhs); + if (!mote) + return false; + return m_recvFd == mote->m_recvFd || m_recvFd == mote->m_sendFd; +} + diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h new file mode 100644 index 000000000..8b980a241 --- /dev/null +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +class L2CapWiimote : public WiimoteDevice +{ + public: + L2CapWiimote(int recvFd, int sendFd); + ~L2CapWiimote() override; + + bool write_data(const std::vector& data) override; + std::optional> read_data() override; + bool operator==(WiimoteDevice& o) const override; + + static std::vector get_devices(); + private: + int m_recvFd; + int m_sendFd; +}; + From 444a8decb1d3be0f5e6dc643711ce01b77544376 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:19:51 +0100 Subject: [PATCH 02/25] Remove unnecessary != overload --- src/input/api/Wiimote/WiimoteDevice.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/input/api/Wiimote/WiimoteDevice.h b/src/input/api/Wiimote/WiimoteDevice.h index 7938bbdf8..932b7bd99 100644 --- a/src/input/api/Wiimote/WiimoteDevice.h +++ b/src/input/api/Wiimote/WiimoteDevice.h @@ -10,7 +10,6 @@ class WiimoteDevice virtual std::optional> read_data() = 0; virtual bool operator==(WiimoteDevice& o) const = 0; - bool operator!=(WiimoteDevice& o) const { return *this == o; } }; using WiimoteDevicePtr = std::shared_ptr; From 32cb2bf3b58d2898294de7aa50a3f99ecff6b8d6 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:37:15 +0100 Subject: [PATCH 03/25] Create `L2CapWiimote` instances as `shared_ptr` --- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index bcd669ef2..352711463 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -88,7 +88,7 @@ std::vector L2CapWiimote::get_devices() continue; } - outDevices.push_back(std::make_unique(sendFd, recvFd)); + outDevices.emplace_back(std::make_shared(sendFd, recvFd)); } return outDevices; } From e5d1d0e6496a052fe3e544fbf700ad0af3542ed9 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:42:24 +0100 Subject: [PATCH 04/25] Allow `WiimoteControllerProvider` to receive `L2CapWiimote`s --- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 11 ++++++++++- src/input/api/Wiimote/hidapi/HidapiWiimote.h | 2 -- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 11 ++++++++--- src/input/api/Wiimote/l2cap/L2CapWiimote.h | 1 + 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index c80f3fbea..33b022627 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -4,6 +4,10 @@ #include "input/api/Wiimote/hidapi/HidapiWiimote.h" +#if BOOST_OS_LINUX +#include "input/api/Wiimote/l2cap/L2CapWiimote.h" +#endif + #include #include @@ -45,7 +49,12 @@ std::vector> WiimoteControllerProvider::get_cont return writeable && not_already_connected; }; - for (auto& device : WiimoteDevice_t::get_devices()) + auto devices = HidapiWiimote::get_devices(); +#if BOOST_OS_LINUX + const auto& l2capDevices = L2CapWiimote::get_devices(); + std::ranges::copy(l2capDevices, std::back_inserter(devices)); +#endif + for (auto& device : devices) { if (!valid_new_device(device)) continue; diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h index 858cb1f31..c914d0073 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.h +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -19,5 +19,3 @@ class HidapiWiimote : public WiimoteDevice { const std::string m_path; }; - -using WiimoteDevice_t = HidapiWiimote; \ No newline at end of file diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 352711463..cb4c8fcd4 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -2,7 +2,7 @@ #include namespace { - // TODO: Add procedure to get addresses. Should add to PairingDialog + // TODO: Get addresses upon user request via PairingDialog std::vector s_address; std::mutex s_addressMutex; @@ -39,6 +39,12 @@ L2CapWiimote::~L2CapWiimote() ::close(m_sendFd); } +void L2CapWiimote::AddCandidateAddresses(const std::vector& addrs) +{ + std::scoped_lock lock(s_addressMutex); + std::ranges::copy(addrs, std::back_inserter(s_address)); +} + std::vector L2CapWiimote::get_devices() { s_addressMutex.lock(); @@ -123,5 +129,4 @@ bool L2CapWiimote::operator==(WiimoteDevice& rhs) const if (!mote) return false; return m_recvFd == mote->m_recvFd || m_recvFd == mote->m_sendFd; -} - +} \ No newline at end of file diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h index 8b980a241..e0f179fb9 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.h +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -12,6 +12,7 @@ class L2CapWiimote : public WiimoteDevice std::optional> read_data() override; bool operator==(WiimoteDevice& o) const override; + static void AddCandidateAddresses(const std::vector& addrs); static std::vector get_devices(); private: int m_recvFd; From b71ace17d365110238f7be27be4f93e9cdb38634 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 02:43:27 +0100 Subject: [PATCH 05/25] Link `bluez` --- CMakeLists.txt | 1 + cmake/Findbluez.cmake | 20 ++++++++++++++++++++ src/input/CMakeLists.txt | 5 +++++ 3 files changed, 26 insertions(+) create mode 100644 cmake/Findbluez.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b5cff6c7..e014ed6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,6 +178,7 @@ if (UNIX AND NOT APPLE) add_compile_definitions(HAS_WAYLAND) endif() find_package(GTK3 REQUIRED) + find_package(bluez REQUIRED) endif() diff --git a/cmake/Findbluez.cmake b/cmake/Findbluez.cmake new file mode 100644 index 000000000..007cdac9f --- /dev/null +++ b/cmake/Findbluez.cmake @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2022 Andrea Pappacoda +# SPDX-License-Identifier: ISC + +find_package(bluez CONFIG) +if (NOT bluez_FOUND) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez) + if (bluez_FOUND) + add_library(bluez::bluez ALIAS PkgConfig::bluez) + endif () + endif () +endif () + +find_package_handle_standard_args(bluez + REQUIRED_VARS + bluez_LINK_LIBRARIES + bluez_FOUND + VERSION_VAR bluez_VERSION +) diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 31a44a493..004dc2bab 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -102,3 +102,8 @@ endif() if (ENABLE_WXWIDGETS) target_link_libraries(CemuInput PRIVATE wx::base wx::core) endif() + + +if (UNIX AND NOT APPLE) + target_link_libraries(CemuInput PRIVATE bluez::bluez) +endif () \ No newline at end of file From 0d4176aab05290b95764c719ecda37a883050fa9 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 02:48:38 +0100 Subject: [PATCH 06/25] Correct return value on `L2CapWiimote::write_data` --- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index cb4c8fcd4..b65275b0d 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -2,7 +2,6 @@ #include namespace { - // TODO: Get addresses upon user request via PairingDialog std::vector s_address; std::mutex s_addressMutex; @@ -39,10 +38,10 @@ L2CapWiimote::~L2CapWiimote() ::close(m_sendFd); } -void L2CapWiimote::AddCandidateAddresses(const std::vector& addrs) +void L2CapWiimote::AddCandidateAddress(bdaddr_t addr) { std::scoped_lock lock(s_addressMutex); - std::ranges::copy(addrs, std::back_inserter(s_address)); + s_address.push_back(addr); } std::vector L2CapWiimote::get_devices() @@ -108,7 +107,7 @@ bool L2CapWiimote::write_data(const std::vector& data) buffer[0] = 0xA2; std::memcpy(buffer + 1, data.data(), size); const auto outSize = size + 1; - return send(m_sendFd, buffer, outSize, 0) != outSize; + return send(m_sendFd, buffer, outSize, 0) == outSize; } std::optional> L2CapWiimote::read_data() From 00ff45d66b8d6b89d294804c8e47b9d5f626c4da Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 02:50:02 +0100 Subject: [PATCH 07/25] PairingDialog: Implement 'pairing' for bluez, reformat file --- src/gui/input/PairingDialog.cpp | 424 ++++++++++++--------- src/gui/input/PairingDialog.h | 2 +- src/input/api/Wiimote/l2cap/L2CapWiimote.h | 4 +- 3 files changed, 238 insertions(+), 192 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 03d6315b8..19aa5f9bd 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -4,233 +4,279 @@ #if BOOST_OS_WINDOWS #include #endif +#if BOOST_OS_LINUX +#include +#include +#include +#include +#endif wxDECLARE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); wxDEFINE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); PairingDialog::PairingDialog(wxWindow* parent) - : wxDialog(parent, wxID_ANY, _("Pairing..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) + : wxDialog(parent, wxID_ANY, _("Pairing..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) { - auto* sizer = new wxBoxSizer(wxVERTICAL); - m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 20), wxGA_HORIZONTAL); - m_gauge->SetValue(0); - sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 20), wxGA_HORIZONTAL); + m_gauge->SetValue(0); + sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); - auto* rows = new wxFlexGridSizer(0, 2, 0, 0); - rows->AddGrowableCol(1); + auto* rows = new wxFlexGridSizer(0, 2, 0, 0); + rows->AddGrowableCol(1); - m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); - rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); + rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); - { - auto* right_side = new wxBoxSizer(wxHORIZONTAL); + { + auto* right_side = new wxBoxSizer(wxHORIZONTAL); - m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); - m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); - right_side->Add(m_cancelButton, 0, wxALL, 5); + m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); + right_side->Add(m_cancelButton, 0, wxALL, 5); - rows->Add(right_side, 1, wxALIGN_RIGHT, 5); - } + rows->Add(right_side, 1, wxALIGN_RIGHT, 5); + } - sizer->Add(rows, 0, wxALL | wxEXPAND, 5); + sizer->Add(rows, 0, wxALL | wxEXPAND, 5); - SetSizerAndFit(sizer); - Centre(wxBOTH); + SetSizerAndFit(sizer); + Centre(wxBOTH); - Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); - Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); + Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); + Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); - m_thread = std::thread(&PairingDialog::WorkerThread, this); + m_thread = std::thread(&PairingDialog::WorkerThread, this); } PairingDialog::~PairingDialog() { - Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); + Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); } void PairingDialog::OnClose(wxCloseEvent& event) { - event.Skip(); + event.Skip(); - m_threadShouldQuit = true; - if (m_thread.joinable()) - m_thread.join(); + m_threadShouldQuit = true; + if (m_thread.joinable()) + m_thread.join(); } void PairingDialog::OnCancelButton(const wxCommandEvent& event) { - Close(); + Close(); } void PairingDialog::OnGaugeUpdate(wxCommandEvent& event) { - PairingState state = (PairingState)event.GetInt(); - - switch (state) - { - case PairingState::Pairing: - { - m_text->SetLabel(_("Found controller. Pairing...")); - m_gauge->SetValue(50); - break; - } - - case PairingState::Finished: - { - m_text->SetLabel(_("Successfully paired the controller.")); - m_gauge->SetValue(100); - m_cancelButton->SetLabel(_("Close")); - break; - } - - case PairingState::NoBluetoothAvailable: - { - m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } - - case PairingState::BluetoothFailed: - { - m_text->SetLabel(_("Failed to search for controllers.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } - - case PairingState::PairingFailed: - { - m_text->SetLabel(_("Failed to pair with the found controller.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } - - case PairingState::BluetoothUnusable: - { - m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } - - - default: - { - break; - } - } + PairingState state = (PairingState)event.GetInt(); + + switch (state) + { + case PairingState::Pairing: + { + m_text->SetLabel(_("Found controller. Pairing...")); + m_gauge->SetValue(50); + break; + } + + case PairingState::Finished: + { + m_text->SetLabel(_("Successfully paired the controller.")); + m_gauge->SetValue(100); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::NoBluetoothAvailable: + { + m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::SearchFailed: + { + m_text->SetLabel(_("Failed to find controllers.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::PairingFailed: + { + m_text->SetLabel(_("Failed to pair with the found controller.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + case PairingState::BluetoothUnusable: + { + m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } + + default: + { + break; + } + } } +#if BOOST_OS_WINDOWS void PairingDialog::WorkerThread() { - const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; - const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; - -#if BOOST_OS_WINDOWS - const GUID bthHidGuid = {0x00001124,0x0000,0x1000,{0x80,0x00,0x00,0x80,0x5F,0x9B,0x34,0xFB}}; - - const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = - { - .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) - }; - - HANDLE radio = INVALID_HANDLE_VALUE; - HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); - if (radioFind == nullptr) - { - UpdateCallback(PairingState::NoBluetoothAvailable); - return; - } - - BluetoothFindRadioClose(radioFind); - - BLUETOOTH_RADIO_INFO radioInfo = - { - .dwSize = sizeof(BLUETOOTH_RADIO_INFO) - }; - - DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); - if (result != ERROR_SUCCESS) - { - UpdateCallback(PairingState::NoBluetoothAvailable); - return; - } - - const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = - { - .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), - - .fReturnAuthenticated = FALSE, - .fReturnRemembered = FALSE, - .fReturnUnknown = TRUE, - .fReturnConnected = FALSE, - - .fIssueInquiry = TRUE, - .cTimeoutMultiplier = 5, - - .hRadio = radio - }; - - BLUETOOTH_DEVICE_INFO info = - { - .dwSize = sizeof(BLUETOOTH_DEVICE_INFO) - }; - - while (!m_threadShouldQuit) - { - HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); - if (deviceFind == nullptr) - { - UpdateCallback(PairingState::BluetoothFailed); - return; - } - - while (!m_threadShouldQuit) - { - if (info.szName == wiimoteName || info.szName == wiiUProControllerName) - { - BluetoothFindDeviceClose(deviceFind); - - UpdateCallback(PairingState::Pairing); - - wchar_t passwd[6] = { radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5] }; - DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); - if (bthResult != ERROR_SUCCESS) - { - UpdateCallback(PairingState::PairingFailed); - return; - } - - bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); - if (bthResult != ERROR_SUCCESS) - { - UpdateCallback(PairingState::PairingFailed); - return; - } - - UpdateCallback(PairingState::Finished); - return; - } - - BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); - if (nextDevResult == FALSE) - { - break; - } - } - - BluetoothFindDeviceClose(deviceFind); - } + const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; + const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; + + const GUID bthHidGuid = {0x00001124, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}}; + + const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = + { + .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)}; + + HANDLE radio = INVALID_HANDLE_VALUE; + HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); + if (radioFind == nullptr) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + BluetoothFindRadioClose(radioFind); + + BLUETOOTH_RADIO_INFO radioInfo = + { + .dwSize = sizeof(BLUETOOTH_RADIO_INFO)}; + + DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); + if (result != ERROR_SUCCESS) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), + + .fReturnAuthenticated = FALSE, + .fReturnRemembered = FALSE, + .fReturnUnknown = TRUE, + .fReturnConnected = FALSE, + + .fIssueInquiry = TRUE, + .cTimeoutMultiplier = 5, + + .hRadio = radio}; + + BLUETOOTH_DEVICE_INFO info = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_INFO)}; + + while (!m_threadShouldQuit) + { + HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); + if (deviceFind == nullptr) + { + UpdateCallback(PairingState::BluetoothFailed); + return; + } + + while (!m_threadShouldQuit) + { + if (info.szName == wiimoteName || info.szName == wiiUProControllerName) + { + BluetoothFindDeviceClose(deviceFind); + + UpdateCallback(PairingState::Pairing); + + wchar_t passwd[6] = {radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5]}; + DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } + + bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } + + UpdateCallback(PairingState::Finished); + return; + } + + BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); + if (nextDevResult == FALSE) + { + break; + } + } + + BluetoothFindDeviceClose(deviceFind); + } +} +#elif BOOST_OS_LINUX +void PairingDialog::WorkerThread() +{ + constexpr static uint8_t liacLap[] = {0x00, 0x8b, 0x9e}; + + constexpr static auto isWiimoteName = [](std::string_view name) { + return name == "Nintendo RVL-CNT-01" || name == "Nintendo RVL-CNT-01-TR"; + }; + + // Get default BT device + const auto hostId = hci_get_route(nullptr); + if (hostId < 0) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + // Search for device + inquiry_info* info = nullptr; + const auto respCount = hci_inquiry(hostId, 5, 1, liacLap, &info, IREQ_CACHE_FLUSH); + if (respCount <= 0) + { + UpdateCallback(PairingState::SearchFailed); + return; + } + + //! Open dev to read name + const auto hostDesc = hci_open_dev(hostId); + char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; + + // Get device name and compare. Would use product and vendor id from SDP, but many third-party Wiimotes don't store them + auto& addr = info->bdaddr; + if (hci_read_remote_name(hostDesc, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, + 2000) != 0 || !isWiimoteName(nameBuffer)) + { + UpdateCallback(PairingState::SearchFailed); + return; + } + cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address {:02x}", nameBuffer, fmt::join(addr.b, ":")); + + UpdateCallback(PairingState::Finished); + L2CapWiimote::AddCandidateAddress(addr); +} #else - UpdateCallback(PairingState::BluetoothUnusable); -#endif +void PairingDialog::WorkerThread() +{ + UpdateCallback(PairingState::BluetoothUnusable); } - +#endif void PairingDialog::UpdateCallback(PairingState state) { - auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); - event->SetInt((int)state); - wxQueueEvent(this, event); + auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); + event->SetInt((int)state); + wxQueueEvent(this, event); } \ No newline at end of file diff --git a/src/gui/input/PairingDialog.h b/src/gui/input/PairingDialog.h index 6c7612d17..02cab4fc9 100644 --- a/src/gui/input/PairingDialog.h +++ b/src/gui/input/PairingDialog.h @@ -17,7 +17,7 @@ class PairingDialog : public wxDialog Pairing, Finished, NoBluetoothAvailable, - BluetoothFailed, + SearchFailed, PairingFailed, BluetoothUnusable }; diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h index e0f179fb9..d32d9b7ba 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.h +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include class L2CapWiimote : public WiimoteDevice @@ -12,7 +12,7 @@ class L2CapWiimote : public WiimoteDevice std::optional> read_data() override; bool operator==(WiimoteDevice& o) const override; - static void AddCandidateAddresses(const std::vector& addrs); + static void AddCandidateAddress(bdaddr_t addr); static std::vector get_devices(); private: int m_recvFd; From e6e437be17aa7bb952700e0e643d984529ef12c5 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 03:51:56 +0100 Subject: [PATCH 08/25] Fix periodic hitching when controller paired and connected --- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index b65275b0d..515de491e 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -5,20 +5,10 @@ namespace { std::vector s_address; std::mutex s_addressMutex; - bool AttemptConnect(int sockFd, sockaddr_l2 const& addr) + bool AttemptConnect(int& sockFd, const sockaddr_l2& addr) { - for (auto i = 0; i < 3; ++i) - { - if (connect(sockFd, reinterpret_cast(&addr), - sizeof(sockaddr_l2)) == 0) - return true; - cemuLog_logDebug(LogType::Force, "Connection attempt {} failed with error {:x}: {} ", i + 1, errno, - std::strerror(errno)); - if (i == 2) - break; - std::this_thread::sleep_for(std::chrono::milliseconds(300)); - } - return false; + return connect(sockFd, reinterpret_cast(&addr), + sizeof(sockaddr_l2)) == 0; } bool AttemptSetNonBlock(int& sockFd) { From 447d7604828ce2d873f22608602aede8d496c9c7 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 05:52:40 +0100 Subject: [PATCH 09/25] `WiimoteDevice`: Make equality actually constant --- src/input/api/Wiimote/WiimoteDevice.h | 2 +- src/input/api/Wiimote/hidapi/HidapiWiimote.cpp | 4 ++-- src/input/api/Wiimote/hidapi/HidapiWiimote.h | 2 +- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 2 +- src/input/api/Wiimote/l2cap/L2CapWiimote.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteDevice.h b/src/input/api/Wiimote/WiimoteDevice.h index 932b7bd99..8ea5b321d 100644 --- a/src/input/api/Wiimote/WiimoteDevice.h +++ b/src/input/api/Wiimote/WiimoteDevice.h @@ -9,7 +9,7 @@ class WiimoteDevice virtual bool write_data(const std::vector& data) = 0; virtual std::optional> read_data() = 0; - virtual bool operator==(WiimoteDevice& o) const = 0; + virtual bool operator==(const WiimoteDevice& o) const = 0; }; using WiimoteDevicePtr = std::shared_ptr; diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp index 9ba563215..5780909f2 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -47,8 +47,8 @@ std::vector HidapiWiimote::get_devices() { return wiimote_devices; } -bool HidapiWiimote::operator==(WiimoteDevice& rhs) const { - auto other = dynamic_cast(&rhs); +bool HidapiWiimote::operator==(const WiimoteDevice& rhs) const { + auto other = dynamic_cast(&rhs); if (!other) return false; return m_path == other->m_path; diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h index c914d0073..952a36f05 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.h +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -10,7 +10,7 @@ class HidapiWiimote : public WiimoteDevice { bool write_data(const std::vector &data) override; std::optional> read_data() override; - bool operator==(WiimoteDevice& o) const override; + bool operator==(const WiimoteDevice& o) const override; static std::vector get_devices(); diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 515de491e..7808adeee 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -112,7 +112,7 @@ std::optional> L2CapWiimote::read_data() } -bool L2CapWiimote::operator==(WiimoteDevice& rhs) const +bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const { auto mote = dynamic_cast(&rhs); if (!mote) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h index d32d9b7ba..04c9151b2 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.h +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -10,7 +10,7 @@ class L2CapWiimote : public WiimoteDevice bool write_data(const std::vector& data) override; std::optional> read_data() override; - bool operator==(WiimoteDevice& o) const override; + bool operator==(const WiimoteDevice& o) const override; static void AddCandidateAddress(bdaddr_t addr); static std::vector get_devices(); From 3c7768cd9d4b3a082f909d566bdebd22024f35c2 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 06:06:57 +0100 Subject: [PATCH 10/25] Fix Windows build error --- src/gui/input/PairingDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 19aa5f9bd..195bec56e 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -184,7 +184,7 @@ void PairingDialog::WorkerThread() HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); if (deviceFind == nullptr) { - UpdateCallback(PairingState::BluetoothFailed); + UpdateCallback(PairingState::SearchFailed); return; } From 667bf968f994b86302d1d1ed288b66ffe3c55656 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 17:01:10 +0100 Subject: [PATCH 11/25] Use bdaddr to compare `L2CapWiimote`s --- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 8 ++++---- src/input/api/Wiimote/l2cap/L2CapWiimote.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 7808adeee..8a199236b 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -16,8 +16,8 @@ namespace { } } -L2CapWiimote::L2CapWiimote(int recvFd,int sendFd) -: m_recvFd(recvFd), m_sendFd(sendFd) +L2CapWiimote::L2CapWiimote(int recvFd,int sendFd, bdaddr_t addr) +: m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) { } @@ -83,7 +83,7 @@ std::vector L2CapWiimote::get_devices() continue; } - outDevices.emplace_back(std::make_shared(sendFd, recvFd)); + outDevices.emplace_back(std::make_shared(sendFd, recvFd, addr)); } return outDevices; } @@ -117,5 +117,5 @@ bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const auto mote = dynamic_cast(&rhs); if (!mote) return false; - return m_recvFd == mote->m_recvFd || m_recvFd == mote->m_sendFd; + return bacmp(&m_addr, &mote->m_addr) == 0; } \ No newline at end of file diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h index 04c9151b2..cc8d071bc 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.h +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -5,7 +5,7 @@ class L2CapWiimote : public WiimoteDevice { public: - L2CapWiimote(int recvFd, int sendFd); + L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr); ~L2CapWiimote() override; bool write_data(const std::vector& data) override; @@ -17,5 +17,6 @@ class L2CapWiimote : public WiimoteDevice private: int m_recvFd; int m_sendFd; + bdaddr_t m_addr; }; From 11656c3452f9a0a6a90984f21f4610ef0c706c01 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 21:18:36 +0100 Subject: [PATCH 12/25] `WiimoteControllerProvider`: Move device getting to another thread --- .../api/Wiimote/WiimoteControllerProvider.cpp | 119 ++++++++++-------- .../api/Wiimote/WiimoteControllerProvider.h | 6 +- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 12 +- 3 files changed, 83 insertions(+), 54 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 33b022627..731e6742f 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -1,9 +1,9 @@ #include "input/api/Wiimote/WiimoteControllerProvider.h" #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" - +#if HAS_HIDAPI #include "input/api/Wiimote/hidapi/HidapiWiimote.h" - +#endif #if BOOST_OS_LINUX #include "input/api/Wiimote/l2cap/L2CapWiimote.h" #endif @@ -16,6 +16,7 @@ WiimoteControllerProvider::WiimoteControllerProvider() { m_reader_thread = std::thread(&WiimoteControllerProvider::reader_thread, this); m_writer_thread = std::thread(&WiimoteControllerProvider::writer_thread, this); + m_connectionThread = std::jthread(&WiimoteControllerProvider::connectionThread, this); } WiimoteControllerProvider::~WiimoteControllerProvider() @@ -30,48 +31,40 @@ WiimoteControllerProvider::~WiimoteControllerProvider() std::vector> WiimoteControllerProvider::get_controllers() { + m_connectedDeviceMutex.lock(); + auto devices = m_connectedDevices; + m_connectedDeviceMutex.unlock(); + std::scoped_lock lock(m_device_mutex); - std::queue disconnected_wiimote_indices; - for (auto i{0u}; i < m_wiimotes.size(); ++i){ - if (!(m_wiimotes[i].connected = m_wiimotes[i].device->write_data({kStatusRequest, 0x00}))){ - disconnected_wiimote_indices.push(i); - } - } - - const auto valid_new_device = [&](std::shared_ptr & device) { - const auto writeable = device->write_data({kStatusRequest, 0x00}); - const auto not_already_connected = - std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), - [device](const auto& it) { - return (*it.device == *device) && it.connected; - }); - return writeable && not_already_connected; - }; - - auto devices = HidapiWiimote::get_devices(); -#if BOOST_OS_LINUX - const auto& l2capDevices = L2CapWiimote::get_devices(); - std::ranges::copy(l2capDevices, std::back_inserter(devices)); -#endif for (auto& device : devices) { - if (!valid_new_device(device)) + const auto writeable = device->write_data({kStatusRequest, 0x00}); + if (!writeable) continue; - // Replace disconnected wiimotes - if (!disconnected_wiimote_indices.empty()){ - const auto idx = disconnected_wiimote_indices.front(); - disconnected_wiimote_indices.pop(); - - m_wiimotes.replace(idx, std::make_unique(device)); - } - // Otherwise add them - else { - m_wiimotes.push_back(std::make_unique(device)); - } + + bool isDuplicate = false; + ssize_t lowestReplaceableIndex = -1; + for (ssize_t i = m_wiimotes.size() - 1; i >= 0; --i) + { + const auto& wiimote = m_wiimotes[i]; + if (wiimote.device && *wiimote.device == *device) + { + isDuplicate = true; + break; + } + lowestReplaceableIndex = i; + } + if (isDuplicate) + continue; + if (lowestReplaceableIndex != -1) + m_wiimotes.replace(lowestReplaceableIndex, std::make_unique(device)); + else + m_wiimotes.push_back(std::make_unique(device)); } std::vector> result; + result.reserve(m_wiimotes.size()); for (size_t i = 0; i < m_wiimotes.size(); ++i) { result.emplace_back(std::make_shared(i)); @@ -83,7 +76,7 @@ std::vector> WiimoteControllerProvider::get_cont bool WiimoteControllerProvider::is_connected(size_t index) { std::shared_lock lock(m_device_mutex); - return index < m_wiimotes.size() && m_wiimotes[index].connected; + return index < m_wiimotes.size() && m_wiimotes[index].device; } bool WiimoteControllerProvider::is_registered_device(size_t index) @@ -150,6 +143,30 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz return {}; } +void WiimoteControllerProvider::connectionThread(std::stop_token stopToken) +{ + SetThreadName("Wiimote-connect"); + while (!stopToken.stop_requested()) + { + std::vector devices; +#if HAS_HIDAPI + const auto& hidDevices = HidapiWiimote::get_devices(); + std::ranges::move(hidDevices, std::back_inserter(devices)); +#endif +#if BOOST_OS_LINUX + const auto& l2capDevices = L2CapWiimote::get_devices(); + std::ranges::move(l2capDevices, std::back_inserter(devices)); +#endif + { + std::scoped_lock lock(m_connectedDeviceMutex); + m_connectedDevices.clear(); + std::ranges::move(devices, std::back_inserter(m_connectedDevices)); + } + std::this_thread::sleep_for(std::chrono::seconds(2)); + } +} + + void WiimoteControllerProvider::reader_thread() { SetThreadName("Wiimote-reader"); @@ -157,7 +174,7 @@ void WiimoteControllerProvider::reader_thread() while (m_running.load(std::memory_order_relaxed)) { const auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - lastCheck) > std::chrono::seconds(2)) + if (std::chrono::duration_cast(now - lastCheck) > std::chrono::milliseconds(500)) { // check for new connected wiimotes get_controllers(); @@ -169,11 +186,16 @@ void WiimoteControllerProvider::reader_thread() for (size_t index = 0; index < m_wiimotes.size(); ++index) { auto& wiimote = m_wiimotes[index]; - if (!wiimote.connected) + if (!wiimote.device) continue; const auto read_data = wiimote.device->read_data(); - if (!read_data || read_data->empty()) + if (!read_data) + { + wiimote.device.reset(); + continue; + } + if (read_data->empty()) continue; receivedAnyPacket = true; @@ -930,18 +952,15 @@ void WiimoteControllerProvider::writer_thread() if (index != (size_t)-1 && !data.empty()) { - if (m_wiimotes[index].rumble) + auto& wiimote = m_wiimotes[index]; + if (wiimote.rumble) data[1] |= 1; - - m_wiimotes[index].connected = m_wiimotes[index].device->write_data(data); - if (m_wiimotes[index].connected) - { - m_wiimotes[index].data_ts = std::chrono::high_resolution_clock::now(); - } + if (!wiimote.device->write_data(data)) + wiimote.device.reset(); + if (wiimote.device) + wiimote.data_ts = std::chrono::high_resolution_clock::now(); else - { - m_wiimotes[index].rumble = false; - } + wiimote.rumble = false; } device_lock.unlock(); diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h index 7629b6414..9b191bdb8 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -77,16 +77,17 @@ class WiimoteControllerProvider : public ControllerProviderBase private: std::atomic_bool m_running = false; std::thread m_reader_thread, m_writer_thread; - std::shared_mutex m_device_mutex; + std::jthread m_connectionThread; + std::vector m_connectedDevices; + std::mutex m_connectedDeviceMutex; struct Wiimote { Wiimote(WiimoteDevicePtr device) : device(std::move(device)) {} WiimoteDevicePtr device; - std::atomic_bool connected = true; std::atomic_bool rumble = false; std::shared_mutex mutex; @@ -103,6 +104,7 @@ class WiimoteControllerProvider : public ControllerProviderBase void reader_thread(); void writer_thread(); + void connectionThread(std::stop_token); void calibrate(size_t index); IRMode set_ir_camera(size_t index, bool state); diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 8a199236b..3a33319f6 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -7,8 +7,14 @@ namespace { bool AttemptConnect(int& sockFd, const sockaddr_l2& addr) { + auto res = connect(sockFd, reinterpret_cast(&addr), + sizeof(sockaddr_l2)); + if (res == 0) + return true; + if (res != ECONNREFUSED) + return false; return connect(sockFd, reinterpret_cast(&addr), - sizeof(sockaddr_l2)) == 0; + sizeof(sockaddr_l2)) == 0; } bool AttemptSetNonBlock(int& sockFd) { @@ -105,9 +111,11 @@ std::optional> L2CapWiimote::read_data() uint8 buffer[23]; const auto nBytes = recv(m_sendFd, buffer, 23, 0); + if (nBytes < 0 && errno == EWOULDBLOCK) + return std::vector{}; // All incoming messages must be prefixed with 0xA1 if (nBytes < 2 || buffer[0] != 0xA1) - return {}; + return std::nullopt; return std::vector(buffer + 1, buffer + 1 + nBytes - 1); } From cc2212a441cb4b77cf052ee684386d8c8aa8fea5 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 21:37:12 +0100 Subject: [PATCH 13/25] Stop using `std::jthread`, because Mac doesn't support it --- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 7 ++++--- src/input/api/Wiimote/WiimoteControllerProvider.h | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 731e6742f..bb063a87c 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -16,7 +16,7 @@ WiimoteControllerProvider::WiimoteControllerProvider() { m_reader_thread = std::thread(&WiimoteControllerProvider::reader_thread, this); m_writer_thread = std::thread(&WiimoteControllerProvider::writer_thread, this); - m_connectionThread = std::jthread(&WiimoteControllerProvider::connectionThread, this); + m_connectionThread = std::thread(&WiimoteControllerProvider::connectionThread, this); } WiimoteControllerProvider::~WiimoteControllerProvider() @@ -26,6 +26,7 @@ WiimoteControllerProvider::~WiimoteControllerProvider() m_running = false; m_writer_thread.join(); m_reader_thread.join(); + m_connectionThread.join(); } } @@ -143,10 +144,10 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz return {}; } -void WiimoteControllerProvider::connectionThread(std::stop_token stopToken) +void WiimoteControllerProvider::connectionThread() { SetThreadName("Wiimote-connect"); - while (!stopToken.stop_requested()) + while (m_running.load(std::memory_order_relaxed)) { std::vector devices; #if HAS_HIDAPI diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h index 9b191bdb8..90f28d5ca 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -79,7 +79,7 @@ class WiimoteControllerProvider : public ControllerProviderBase std::thread m_reader_thread, m_writer_thread; std::shared_mutex m_device_mutex; - std::jthread m_connectionThread; + std::thread m_connectionThread; std::vector m_connectedDevices; std::mutex m_connectedDeviceMutex; struct Wiimote @@ -104,7 +104,7 @@ class WiimoteControllerProvider : public ControllerProviderBase void reader_thread(); void writer_thread(); - void connectionThread(std::stop_token); + void connectionThread(); void calibrate(size_t index); IRMode set_ir_camera(size_t index, bool state); From 182e90500faa758d73e1ae73f545f122d4cf8f10 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 21:40:10 +0100 Subject: [PATCH 14/25] Make Bluez optional --- CMakeLists.txt | 8 +++++++- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 9 +++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e014ed6d6..cf04b2354 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,7 @@ endif() if (UNIX AND NOT APPLE) option(ENABLE_WAYLAND "Build with Wayland support" ON) option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON) + option(ENABLE_BLUEZ "Build with Bluez support" ON) endif() option(ENABLE_OPENGL "Enables the OpenGL backend" ON) @@ -178,7 +179,12 @@ if (UNIX AND NOT APPLE) add_compile_definitions(HAS_WAYLAND) endif() find_package(GTK3 REQUIRED) - find_package(bluez REQUIRED) + + if(ENABLE_BLUEZ) + find_package(bluez REQUIRED) + set(ENABLE_WIIMOTE ON) + add_compile_definitions(HAS_BLUEZ) + endif() endif() diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index bb063a87c..830d987ea 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -1,10 +1,11 @@ #include "input/api/Wiimote/WiimoteControllerProvider.h" #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" -#if HAS_HIDAPI + +#ifdef HAS_HIDAPI #include "input/api/Wiimote/hidapi/HidapiWiimote.h" #endif -#if BOOST_OS_LINUX +#ifdef HAS_BLUEZ #include "input/api/Wiimote/l2cap/L2CapWiimote.h" #endif @@ -150,11 +151,11 @@ void WiimoteControllerProvider::connectionThread() while (m_running.load(std::memory_order_relaxed)) { std::vector devices; -#if HAS_HIDAPI +#ifdef HAS_HIDAPI const auto& hidDevices = HidapiWiimote::get_devices(); std::ranges::move(hidDevices, std::back_inserter(devices)); #endif -#if BOOST_OS_LINUX +#ifdef HAS_BLUEZ const auto& l2capDevices = L2CapWiimote::get_devices(); std::ranges::move(l2capDevices, std::back_inserter(devices)); #endif From 7f58a2047bb1a0e6f70beca204253d1a5ae67d46 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 22:30:31 +0100 Subject: [PATCH 15/25] Add bluez libs to dependency list --- .github/workflows/build.yml | 2 +- BUILD.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72bbcf522..4c0fd7d5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build + sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 diff --git a/BUILD.md b/BUILD.md index 44d69c6cc..41de928ef 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,10 +46,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required ### Dependencies #### For Arch and derivatives: -`sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` +`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` #### For Debian, Ubuntu and derivatives: -`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` +`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` +`sudo dnf install bluez-libs clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` ### Build Cemu From 55e240339c3161c2a25c03bb88db40cdd5a83aaa Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 23:33:43 +0100 Subject: [PATCH 16/25] Install `libbluetooth-dev` for AppImage too --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c0fd7d5b..6ae4b8922 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,7 +96,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream + sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev - name: "Build AppImage" run: | From e14c54b846f4762afb6f7116cd0146c80d0b6534 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 27 Sep 2024 00:15:09 +0100 Subject: [PATCH 17/25] Fix a crash from writing to a disconnected device --- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 830d987ea..2444fa42d 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -955,14 +955,17 @@ void WiimoteControllerProvider::writer_thread() if (index != (size_t)-1 && !data.empty()) { auto& wiimote = m_wiimotes[index]; + if (!wiimote.device) + continue; if (wiimote.rumble) data[1] |= 1; if (!wiimote.device->write_data(data)) + { wiimote.device.reset(); - if (wiimote.device) - wiimote.data_ts = std::chrono::high_resolution_clock::now(); - else wiimote.rumble = false; + } + else + wiimote.data_ts = std::chrono::high_resolution_clock::now(); } device_lock.unlock(); From 8ba4b051a1273dc786f00ca343002fbcd2ee4961 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 27 Sep 2024 00:18:27 +0100 Subject: [PATCH 18/25] Stop replacing non-duplicate controllers --- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 2444fa42d..221d75a78 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -49,12 +49,17 @@ std::vector> WiimoteControllerProvider::get_cont ssize_t lowestReplaceableIndex = -1; for (ssize_t i = m_wiimotes.size() - 1; i >= 0; --i) { - const auto& wiimote = m_wiimotes[i]; - if (wiimote.device && *wiimote.device == *device) + const auto& wiimoteDevice = m_wiimotes[i].device; + if (wiimoteDevice) { - isDuplicate = true; - break; + if (*wiimoteDevice == *device) + { + isDuplicate = true; + break; + } + continue; } + lowestReplaceableIndex = i; } if (isDuplicate) From f43f88f7281a09fdb28b966fff9c69b4d4916ea9 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Wed, 2 Oct 2024 00:49:01 +0100 Subject: [PATCH 19/25] Prevent storing duplicate addresses --- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 3a33319f6..e924c586b 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -22,6 +22,11 @@ namespace { } } +static bool operator==(const bdaddr_t& a, const bdaddr_t& b) +{ + return bacmp(&a, &b) == 0; +} + L2CapWiimote::L2CapWiimote(int recvFd,int sendFd, bdaddr_t addr) : m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) { @@ -37,7 +42,7 @@ L2CapWiimote::~L2CapWiimote() void L2CapWiimote::AddCandidateAddress(bdaddr_t addr) { std::scoped_lock lock(s_addressMutex); - s_address.push_back(addr); + vectorAppendUnique(s_address,addr); } std::vector L2CapWiimote::get_devices() @@ -125,5 +130,5 @@ bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const auto mote = dynamic_cast(&rhs); if (!mote) return false; - return bacmp(&m_addr, &mote->m_addr) == 0; + return m_addr == mote->m_addr; } \ No newline at end of file From 0340e5e7cbafe9b6ab6425baa40e3dc945fc99ce Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 3 Oct 2024 19:14:10 +0100 Subject: [PATCH 20/25] Free BT resources acquired during pairing --- src/gui/input/PairingDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 195bec56e..bbd8f38ba 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -250,9 +250,11 @@ void PairingDialog::WorkerThread() UpdateCallback(PairingState::SearchFailed); return; } + stdx::scope_exit freeInfo([info]() { bt_free(info);}); //! Open dev to read name const auto hostDesc = hci_open_dev(hostId); + stdx::scope_exit freeDev([hostDesc]() { hci_close_dev(hostDesc);}); char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; // Get device name and compare. Would use product and vendor id from SDP, but many third-party Wiimotes don't store them From 9046dca558f1a36802fd8d0c6fa95b2e31da75df Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 4 Oct 2024 22:41:54 +0100 Subject: [PATCH 21/25] Show bluetooth address in correct order. (It would be nice if `std::ranges::reverse_view` worked properly with clang 15) --- src/gui/input/PairingDialog.cpp | 6 ++++-- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index bbd8f38ba..92a7ff1e1 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -258,14 +258,16 @@ void PairingDialog::WorkerThread() char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; // Get device name and compare. Would use product and vendor id from SDP, but many third-party Wiimotes don't store them - auto& addr = info->bdaddr; + const auto& addr = info->bdaddr; if (hci_read_remote_name(hostDesc, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, 2000) != 0 || !isWiimoteName(nameBuffer)) { UpdateCallback(PairingState::SearchFailed); return; } - cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address {:02x}", nameBuffer, fmt::join(addr.b, ":")); + const auto& b = addr.b; + cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}'", + nameBuffer, b[5], b[4], b[3], b[2], b[1], b[0]); UpdateCallback(PairingState::Finished); L2CapWiimote::AddCandidateAddress(addr); diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index e924c586b..351c279f6 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -68,8 +68,9 @@ std::vector L2CapWiimote::get_devices() sendAddr.l2_bdaddr = addr; if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) { - cemuLog_logDebug(LogType::Force,"Failed to connect send socket to '{:02x}': {}", - fmt::join(addr.b, ":"), strerror(errno)); + const auto& b = addr.b; + cemuLog_logDebug(LogType::Force,"Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); close(sendFd); continue; } @@ -87,8 +88,9 @@ std::vector L2CapWiimote::get_devices() recvAddr.l2_bdaddr = addr; if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) { - cemuLog_logDebug(LogType::Force,"Failed to connect recv socket to '{:02x}': {}", - fmt::join(addr.b, ":"), strerror(errno)); + const auto& b = addr.b; + cemuLog_logDebug(LogType::Force,"Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); close(sendFd); close(recvFd); continue; From 5184e632e2aa0e44c7428f7ebcc6a8f4f9762f57 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 10 Oct 2024 15:12:43 +0100 Subject: [PATCH 22/25] Search for up to 4 devices at a time --- src/gui/input/PairingDialog.cpp | 39 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 92a7ff1e1..9995e9477 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -243,34 +243,41 @@ void PairingDialog::WorkerThread() } // Search for device - inquiry_info* info = nullptr; - const auto respCount = hci_inquiry(hostId, 5, 1, liacLap, &info, IREQ_CACHE_FLUSH); + inquiry_info* infos = nullptr; + const auto respCount = hci_inquiry(hostId, 5, 4, liacLap, &infos, IREQ_CACHE_FLUSH); if (respCount <= 0) { UpdateCallback(PairingState::SearchFailed); return; } - stdx::scope_exit freeInfo([info]() { bt_free(info);}); - //! Open dev to read name - const auto hostDesc = hci_open_dev(hostId); - stdx::scope_exit freeDev([hostDesc]() { hci_close_dev(hostDesc);}); + // Open dev to read name + const auto hostDevDesc = hci_open_dev(hostId); char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; + bool foundADevice = false; // Get device name and compare. Would use product and vendor id from SDP, but many third-party Wiimotes don't store them - const auto& addr = info->bdaddr; - if (hci_read_remote_name(hostDesc, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, - 2000) != 0 || !isWiimoteName(nameBuffer)) + for (const auto& devInfo : std::span(infos, respCount)) { - UpdateCallback(PairingState::SearchFailed); - return; + const auto& addr = devInfo.bdaddr; + if (hci_read_remote_name(hostDevDesc, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, + 2000) != 0 || !isWiimoteName(nameBuffer)) + { + continue; + } + L2CapWiimote::AddCandidateAddress(addr); + foundADevice = true; + const auto& b = addr.b; + cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}'", + nameBuffer, b[5], b[4], b[3], b[2], b[1], b[0]); } - const auto& b = addr.b; - cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}'", - nameBuffer, b[5], b[4], b[3], b[2], b[1], b[0]); + if (foundADevice) + UpdateCallback(PairingState::Finished); + else + UpdateCallback(PairingState::SearchFailed); - UpdateCallback(PairingState::Finished); - L2CapWiimote::AddCandidateAddress(addr); + bt_free(infos); + hci_close_dev(hostDevDesc); } #else void PairingDialog::WorkerThread() From cff8d908cfb3110737d23b90e70403b05ba0b075 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Mon, 21 Oct 2024 10:02:49 +0100 Subject: [PATCH 23/25] Make cancellable between name reads, disable cancel button during inquiry. --- src/gui/input/PairingDialog.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 9995e9477..e5449b73b 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -244,15 +244,23 @@ void PairingDialog::WorkerThread() // Search for device inquiry_info* infos = nullptr; + m_cancelButton->Disable(); const auto respCount = hci_inquiry(hostId, 5, 4, liacLap, &infos, IREQ_CACHE_FLUSH); + m_cancelButton->Enable(); if (respCount <= 0) { UpdateCallback(PairingState::SearchFailed); return; } + stdx::scope_exit infoFree([&]() { bt_free(infos);}); + + if (m_threadShouldQuit) + return; // Open dev to read name - const auto hostDevDesc = hci_open_dev(hostId); + const auto hostDev = hci_open_dev(hostId); + stdx::scope_exit devClose([&]() { hci_close_dev(hostDev);}); + char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; bool foundADevice = false; @@ -260,11 +268,13 @@ void PairingDialog::WorkerThread() for (const auto& devInfo : std::span(infos, respCount)) { const auto& addr = devInfo.bdaddr; - if (hci_read_remote_name(hostDevDesc, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, - 2000) != 0 || !isWiimoteName(nameBuffer)) - { + const auto err = hci_read_remote_name(hostDev, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, + 2000); + if (m_threadShouldQuit) + return; + if (err || !isWiimoteName(nameBuffer)) continue; - } + L2CapWiimote::AddCandidateAddress(addr); foundADevice = true; const auto& b = addr.b; @@ -275,9 +285,6 @@ void PairingDialog::WorkerThread() UpdateCallback(PairingState::Finished); else UpdateCallback(PairingState::SearchFailed); - - bt_free(infos); - hci_close_dev(hostDevDesc); } #else void PairingDialog::WorkerThread() From 08b0bfba8b248a6b82283a11ead5b5e71e42140e Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 22 Nov 2024 12:39:44 +0000 Subject: [PATCH 24/25] screaming case constexpr --- src/gui/input/PairingDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index e5449b73b..3cc5d7562 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -228,7 +228,7 @@ void PairingDialog::WorkerThread() #elif BOOST_OS_LINUX void PairingDialog::WorkerThread() { - constexpr static uint8_t liacLap[] = {0x00, 0x8b, 0x9e}; + constexpr static uint8_t LIAC_LAP[] = {0x00, 0x8b, 0x9e}; constexpr static auto isWiimoteName = [](std::string_view name) { return name == "Nintendo RVL-CNT-01" || name == "Nintendo RVL-CNT-01-TR"; @@ -245,7 +245,7 @@ void PairingDialog::WorkerThread() // Search for device inquiry_info* infos = nullptr; m_cancelButton->Disable(); - const auto respCount = hci_inquiry(hostId, 5, 4, liacLap, &infos, IREQ_CACHE_FLUSH); + const auto respCount = hci_inquiry(hostId, 5, 4, LIAC_LAP, &infos, IREQ_CACHE_FLUSH); m_cancelButton->Enable(); if (respCount <= 0) { From a648fc7a08fafb9aeca3e5ce5ed9d11a397a3f12 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Tue, 3 Dec 2024 16:36:42 +0000 Subject: [PATCH 25/25] Prevent connection attempts on still-connected controllers --- src/gui/input/PairingDialog.cpp | 2 +- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 94 +++++++++++--------- 2 files changed, 54 insertions(+), 42 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 3cc5d7562..350fce810 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -245,7 +245,7 @@ void PairingDialog::WorkerThread() // Search for device inquiry_info* infos = nullptr; m_cancelButton->Disable(); - const auto respCount = hci_inquiry(hostId, 5, 4, LIAC_LAP, &infos, IREQ_CACHE_FLUSH); + const auto respCount = hci_inquiry(hostId, 7, 4, LIAC_LAP, &infos, IREQ_CACHE_FLUSH); m_cancelButton->Enable(); if (respCount <= 0) { diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 351c279f6..28a123f35 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -1,63 +1,70 @@ #include "L2CapWiimote.h" #include -namespace { - std::vector s_address; - std::mutex s_addressMutex; +constexpr auto comparator = [](const bdaddr_t& a, const bdaddr_t& b) { + return bacmp(&a, &b); +}; - bool AttemptConnect(int& sockFd, const sockaddr_l2& addr) - { - auto res = connect(sockFd, reinterpret_cast(&addr), - sizeof(sockaddr_l2)); - if (res == 0) - return true; - if (res != ECONNREFUSED) - return false; - return connect(sockFd, reinterpret_cast(&addr), - sizeof(sockaddr_l2)) == 0; - } - bool AttemptSetNonBlock(int& sockFd) - { - return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; - } -} +static auto s_addresses = std::map(comparator); +static std::mutex s_addressMutex; -static bool operator==(const bdaddr_t& a, const bdaddr_t& b) +static bool AttemptConnect(int sockFd, const sockaddr_l2& addr) { - return bacmp(&a, &b) == 0; + auto res = connect(sockFd, reinterpret_cast(&addr), + sizeof(sockaddr_l2)); + if (res == 0) + return true; + return connect(sockFd, reinterpret_cast(&addr), + sizeof(sockaddr_l2)) == 0; } -L2CapWiimote::L2CapWiimote(int recvFd,int sendFd, bdaddr_t addr) -: m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) +static bool AttemptSetNonBlock(int sockFd) { + return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; +} +L2CapWiimote::L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr) + : m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) +{ } L2CapWiimote::~L2CapWiimote() { - ::close(m_recvFd); - ::close(m_sendFd); + close(m_recvFd); + close(m_sendFd); + const auto& b = m_addr.b; + cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]); + + // Re-add to candidate vec + s_addressMutex.lock(); + s_addresses[m_addr] = false; + s_addressMutex.unlock(); } void L2CapWiimote::AddCandidateAddress(bdaddr_t addr) { std::scoped_lock lock(s_addressMutex); - vectorAppendUnique(s_address,addr); + s_addresses.try_emplace(addr, false); } std::vector L2CapWiimote::get_devices() { s_addressMutex.lock(); - const auto addresses = s_address; + std::vector unconnected; + for (const auto& [addr, connected] : s_addresses) + { + if (!connected) + unconnected.push_back(addr); + } s_addressMutex.unlock(); - std::vector outDevices; - for (auto addr : addresses) + for (const auto& addr : unconnected) { // Socket for sending data to controller, PSM 0x11 auto sendFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if (sendFd < 0) { + if (sendFd < 0) + { cemuLog_logDebug(LogType::Force, "Failed to open send socket: {}", strerror(errno)); continue; } @@ -67,18 +74,20 @@ std::vector L2CapWiimote::get_devices() sendAddr.l2_psm = htobs(0x11); sendAddr.l2_bdaddr = addr; - if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) { + if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) + { const auto& b = addr.b; - cemuLog_logDebug(LogType::Force,"Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", - b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); + cemuLog_logDebug(LogType::Force, "Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); close(sendFd); continue; } // Socket for receiving data from controller, PSM 0x13 auto recvFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if (recvFd < 0) { - cemuLog_logDebug(LogType::Force,"Failed to open recv socket: {}", strerror(errno)); + if (recvFd < 0) + { + cemuLog_logDebug(LogType::Force, "Failed to open recv socket: {}", strerror(errno)); close(sendFd); continue; } @@ -87,16 +96,20 @@ std::vector L2CapWiimote::get_devices() recvAddr.l2_psm = htobs(0x13); recvAddr.l2_bdaddr = addr; - if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) { + if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) + { const auto& b = addr.b; - cemuLog_logDebug(LogType::Force,"Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", - b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); + cemuLog_logDebug(LogType::Force, "Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); close(sendFd); close(recvFd); continue; } - outDevices.emplace_back(std::make_shared(sendFd, recvFd, addr)); + + s_addressMutex.lock(); + s_addresses[addr] = true; + s_addressMutex.unlock(); } return outDevices; } @@ -126,11 +139,10 @@ std::optional> L2CapWiimote::read_data() return std::vector(buffer + 1, buffer + 1 + nBytes - 1); } - bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const { auto mote = dynamic_cast(&rhs); if (!mote) return false; - return m_addr == mote->m_addr; + return bacmp(&m_addr, &mote->m_addr) == 0; } \ No newline at end of file