From b8692803cf4026cea8401498d0a77b98821dc584 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:17:00 +0100 Subject: [PATCH 01/21] 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 9f7873a1..31a44a49 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 db185675..9ba56321 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 00000000..bcd669ef --- /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 00000000..8b980a24 --- /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 919d26f6a23a69c859622839c81db9135f8a1616 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:19:51 +0100 Subject: [PATCH 02/21] 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 7938bbdf..932b7bd9 100644 --- a/src/input/api/Wiimote/WiimoteDevice.h +++ b/src/input/api/Wiimote/WiimoteDevice.h @@ -10,7 +10,6 @@ public: 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 34f08a4edfef267ca6b0e0e4417809adb889cd68 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:37:15 +0100 Subject: [PATCH 03/21] 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 bcd669ef..35271146 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 ee60171b26757ab59324deb9491b9da27a9eb89b Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 01:42:24 +0100 Subject: [PATCH 04/21] 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 c80f3fbe..33b02262 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 858cb1f3..c914d007 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.h +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -19,5 +19,3 @@ private: 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 35271146..cb4c8fcd 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 8b980a24..e0f179fb 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 c0f4dd7e842d5885dfd2a2e4daaa42c6c6ba1d7d Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 02:43:27 +0100 Subject: [PATCH 05/21] 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 5b5cff6c..e014ed6d 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 00000000..007cdac9 --- /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 31a44a49..004dc2ba 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 7395452fb6ffc5ea2ed2d53c7852e9ddc05b793e Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 02:48:38 +0100 Subject: [PATCH 06/21] 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 cb4c8fcd..b65275b0 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 96f485196cfb7b4bdd2d131bb44bbd72b3ffbf5d Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 02:50:02 +0100 Subject: [PATCH 07/21] PairingDialog: Implement 'pairing' for bluez, reformat file --- src/gui/PairingDialog.cpp | 376 ++++++++++++--------- src/gui/PairingDialog.h | 2 +- src/input/api/Wiimote/l2cap/L2CapWiimote.h | 4 +- 3 files changed, 214 insertions(+), 168 deletions(-) diff --git a/src/gui/PairingDialog.cpp b/src/gui/PairingDialog.cpp index f90e6d13..189c7bab 100644 --- a/src/gui/PairingDialog.cpp +++ b/src/gui/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(); + PairingState state = (PairingState)event.GetInt(); - switch (state) - { - case PairingState::Pairing: - { - m_text->SetLabel(_("Found controller. Pairing...")); - m_gauge->SetValue(50); - break; - } + 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::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::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::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::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; - } + 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; - } - } + default: + { + break; + } + } } -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}}; +void PairingDialog::WorkerThread() +{ + const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; + const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; - const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = - { - .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) - }; + const GUID bthHidGuid = {0x00001124, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}}; - HANDLE radio = INVALID_HANDLE_VALUE; - HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); - if (radioFind == nullptr) - { - UpdateCallback(PairingState::NoBluetoothAvailable); - return; - } + const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = + { + .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)}; - BluetoothFindRadioClose(radioFind); + HANDLE radio = INVALID_HANDLE_VALUE; + HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); + if (radioFind == nullptr) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } - BLUETOOTH_RADIO_INFO radioInfo = - { - .dwSize = sizeof(BLUETOOTH_RADIO_INFO) - }; + BluetoothFindRadioClose(radioFind); - DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); - if (result != ERROR_SUCCESS) - { - UpdateCallback(PairingState::NoBluetoothAvailable); - return; - } + BLUETOOTH_RADIO_INFO radioInfo = + { + .dwSize = sizeof(BLUETOOTH_RADIO_INFO)}; - const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = - { - .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), + DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); + if (result != ERROR_SUCCESS) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } - .fReturnAuthenticated = FALSE, - .fReturnRemembered = FALSE, - .fReturnUnknown = TRUE, - .fReturnConnected = FALSE, + const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), - .fIssueInquiry = TRUE, - .cTimeoutMultiplier = 5, + .fReturnAuthenticated = FALSE, + .fReturnRemembered = FALSE, + .fReturnUnknown = TRUE, + .fReturnConnected = FALSE, - .hRadio = radio - }; + .fIssueInquiry = TRUE, + .cTimeoutMultiplier = 5, - BLUETOOTH_DEVICE_INFO info = - { - .dwSize = sizeof(BLUETOOTH_DEVICE_INFO) - }; + .hRadio = radio}; - while (!m_threadShouldQuit) - { - HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); - if (deviceFind == nullptr) - { - UpdateCallback(PairingState::BluetoothFailed); - return; - } + BLUETOOTH_DEVICE_INFO info = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_INFO)}; - while (!m_threadShouldQuit) - { - if (info.szName == wiimoteName || info.szName == wiiUProControllerName) - { - BluetoothFindDeviceClose(deviceFind); + while (!m_threadShouldQuit) + { + HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); + if (deviceFind == nullptr) + { + UpdateCallback(PairingState::BluetoothFailed); + return; + } - UpdateCallback(PairingState::Pairing); + while (!m_threadShouldQuit) + { + if (info.szName == wiimoteName || info.szName == wiiUProControllerName) + { + BluetoothFindDeviceClose(deviceFind); - 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; - } + UpdateCallback(PairingState::Pairing); - bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); - if (bthResult != ERROR_SUCCESS) - { - UpdateCallback(PairingState::PairingFailed); - return; - } + 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; + } - UpdateCallback(PairingState::Finished); - return; - } + bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } - BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); - if (nextDevResult == FALSE) - { - break; - } - } + UpdateCallback(PairingState::Finished); + return; + } - BluetoothFindDeviceClose(deviceFind); - } -#else - UpdateCallback(PairingState::BluetoothUnusable); -#endif + 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 +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/PairingDialog.h b/src/gui/PairingDialog.h index 6c7612d1..02cab4fc 100644 --- a/src/gui/PairingDialog.h +++ b/src/gui/PairingDialog.h @@ -17,7 +17,7 @@ private: 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 e0f179fb..d32d9b7b 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 e66984bd413a8ed004655e668888e28a0141ed3d Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 03:51:56 +0100 Subject: [PATCH 08/21] 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 b65275b0..515de491 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 b21c8dedd75c02de65d07045855c15456b762dec Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 05:52:40 +0100 Subject: [PATCH 09/21] `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 932b7bd9..8ea5b321 100644 --- a/src/input/api/Wiimote/WiimoteDevice.h +++ b/src/input/api/Wiimote/WiimoteDevice.h @@ -9,7 +9,7 @@ public: 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 9ba56321..5780909f 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 c914d007..952a36f0 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.h +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -10,7 +10,7 @@ public: 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 515de491..7808adee 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 d32d9b7b..04c9151b 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 75b5d285401171e77e3ada123d04aed2ee321364 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 06:06:57 +0100 Subject: [PATCH 10/21] Fix Windows build error --- src/gui/PairingDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/PairingDialog.cpp b/src/gui/PairingDialog.cpp index 189c7bab..000ddd06 100644 --- a/src/gui/PairingDialog.cpp +++ b/src/gui/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 873b6285b9d2bcb5832a7a098b4c28b15688aa5d Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 17:01:10 +0100 Subject: [PATCH 11/21] 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 7808adee..8a199236 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 04c9151b..cc8d071b 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 ae2f5d23b32c547b63c020dacac5c1f3fed8bf2c Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 21:18:36 +0100 Subject: [PATCH 12/21] `WiimoteControllerProvider`: Move device getting to another thread --- .../api/Wiimote/WiimoteControllerProvider.cpp | 117 ++++++++++-------- .../api/Wiimote/WiimoteControllerProvider.h | 6 +- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 12 +- 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 33b02262..731e6742 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 7629b641..9b191bdb 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -77,16 +77,17 @@ public: 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 @@ private: 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 8a199236..3a33319f 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 d52405935f429eed2e66cf125e47df95f6dea111 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 21:37:12 +0100 Subject: [PATCH 13/21] 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 731e6742..bb063a87 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 9b191bdb..90f28d5c 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -79,7 +79,7 @@ private: 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 @@ private: 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 e17b9d6a3b889a2abab7f3b590a74cf743f02956 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 21:40:10 +0100 Subject: [PATCH 14/21] 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 e014ed6d..cf04b235 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 bb063a87..830d987e 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 6f18012c423c59317d2478dd5c513e99d5bf2721 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 22:30:31 +0100 Subject: [PATCH 15/21] 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 72bbcf52..4c0fd7d5 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 44d69c6c..41de928e 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 8c3976c608d8ce7cbb4d0eeef5a371eb3bdc124d Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 26 Sep 2024 23:33:43 +0100 Subject: [PATCH 16/21] 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 4c0fd7d5..6ae4b892 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 4c93d4bde5732fd75b885de8e7480d3acc12b86a Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 27 Sep 2024 00:15:09 +0100 Subject: [PATCH 17/21] 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 830d987e..2444fa42 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 ff7da398c50fade4ed24424ea20fb9daf93cb13f Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 27 Sep 2024 00:18:27 +0100 Subject: [PATCH 18/21] 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 2444fa42..221d75a7 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 5079bc2ff730e75dc9ac3224567dc787cd7b7394 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Wed, 2 Oct 2024 00:49:01 +0100 Subject: [PATCH 19/21] 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 3a33319f..e924c586 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 c7abd7a42a6ea4c58d684baafae8822edcc8f54d Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Thu, 3 Oct 2024 19:14:10 +0100 Subject: [PATCH 20/21] Free BT resources acquired during pairing --- src/gui/PairingDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/PairingDialog.cpp b/src/gui/PairingDialog.cpp index 000ddd06..fb194aba 100644 --- a/src/gui/PairingDialog.cpp +++ b/src/gui/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 6a3096e36d88272d0ea6887c2696039843443c8c Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 4 Oct 2024 22:41:54 +0100 Subject: [PATCH 21/21] Show bluetooth address in correct order. (It would be nice if `std::ranges::reverse_view` worked properly with clang 15) --- src/gui/PairingDialog.cpp | 6 ++++-- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 10 ++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gui/PairingDialog.cpp b/src/gui/PairingDialog.cpp index fb194aba..414a0e91 100644 --- a/src/gui/PairingDialog.cpp +++ b/src/gui/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 e924c586..351c279f 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;