mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-01-08 10:47:29 -03:00
Linux: Allow connecting Wiimotes via L2CAP (#1353)
This commit is contained in:
parent
934cb54605
commit
dd0af0a56f
14 changed files with 532 additions and 221 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
@ -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: |
|
||||
|
|
6
BUILD.md
6
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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
@ -179,6 +180,12 @@ if (UNIX AND NOT APPLE)
|
|||
endif()
|
||||
find_package(GTK3 REQUIRED)
|
||||
|
||||
if(ENABLE_BLUEZ)
|
||||
find_package(bluez REQUIRED)
|
||||
set(ENABLE_WIIMOTE ON)
|
||||
add_compile_definitions(HAS_BLUEZ)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
if (ENABLE_VULKAN)
|
||||
|
|
20
cmake/Findbluez.cmake
Normal file
20
cmake/Findbluez.cmake
Normal file
|
@ -0,0 +1,20 @@
|
|||
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
|
||||
# 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
|
||||
)
|
|
@ -4,233 +4,297 @@
|
|||
#if BOOST_OS_WINDOWS
|
||||
#include <bluetoothapis.h>
|
||||
#endif
|
||||
#if BOOST_OS_LINUX
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/hci.h>
|
||||
#include <bluetooth/hci_lib.h>
|
||||
#include <input/api/Wiimote/l2cap/L2CapWiimote.h>
|
||||
#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::SearchFailed);
|
||||
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 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";
|
||||
};
|
||||
|
||||
// Get default BT device
|
||||
const auto hostId = hci_get_route(nullptr);
|
||||
if (hostId < 0)
|
||||
{
|
||||
UpdateCallback(PairingState::NoBluetoothAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for device
|
||||
inquiry_info* infos = nullptr;
|
||||
m_cancelButton->Disable();
|
||||
const auto respCount = hci_inquiry(hostId, 7, 4, LIAC_LAP, &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 hostDev = hci_open_dev(hostId);
|
||||
stdx::scope_exit devClose([&]() { hci_close_dev(hostDev);});
|
||||
|
||||
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
|
||||
for (const auto& devInfo : std::span(infos, respCount))
|
||||
{
|
||||
const auto& addr = devInfo.bdaddr;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
#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);
|
||||
}
|
|
@ -17,7 +17,7 @@ private:
|
|||
Pairing,
|
||||
Finished,
|
||||
NoBluetoothAvailable,
|
||||
BluetoothFailed,
|
||||
SearchFailed,
|
||||
PairingFailed,
|
||||
BluetoothUnusable
|
||||
};
|
||||
|
|
|
@ -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 ()
|
||||
|
||||
|
||||
|
@ -97,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 ()
|
|
@ -2,7 +2,12 @@
|
|||
#include "input/api/Wiimote/NativeWiimoteController.h"
|
||||
#include "input/api/Wiimote/WiimoteMessages.h"
|
||||
|
||||
#ifdef HAS_HIDAPI
|
||||
#include "input/api/Wiimote/hidapi/HidapiWiimote.h"
|
||||
#endif
|
||||
#ifdef HAS_BLUEZ
|
||||
#include "input/api/Wiimote/l2cap/L2CapWiimote.h"
|
||||
#endif
|
||||
|
||||
#include <numbers>
|
||||
#include <queue>
|
||||
|
@ -12,6 +17,7 @@ WiimoteControllerProvider::WiimoteControllerProvider()
|
|||
{
|
||||
m_reader_thread = std::thread(&WiimoteControllerProvider::reader_thread, this);
|
||||
m_writer_thread = std::thread(&WiimoteControllerProvider::writer_thread, this);
|
||||
m_connectionThread = std::thread(&WiimoteControllerProvider::connectionThread, this);
|
||||
}
|
||||
|
||||
WiimoteControllerProvider::~WiimoteControllerProvider()
|
||||
|
@ -21,48 +27,51 @@ WiimoteControllerProvider::~WiimoteControllerProvider()
|
|||
m_running = false;
|
||||
m_writer_thread.join();
|
||||
m_reader_thread.join();
|
||||
m_connectionThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> WiimoteControllerProvider::get_controllers()
|
||||
{
|
||||
m_connectedDeviceMutex.lock();
|
||||
auto devices = m_connectedDevices;
|
||||
m_connectedDeviceMutex.unlock();
|
||||
|
||||
std::scoped_lock lock(m_device_mutex);
|
||||
|
||||
std::queue<uint32> 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<WiimoteDevice> & 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;
|
||||
};
|
||||
|
||||
for (auto& device : WiimoteDevice_t::get_devices())
|
||||
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<Wiimote>(device));
|
||||
}
|
||||
// Otherwise add them
|
||||
else {
|
||||
m_wiimotes.push_back(std::make_unique<Wiimote>(device));
|
||||
}
|
||||
bool isDuplicate = false;
|
||||
ssize_t lowestReplaceableIndex = -1;
|
||||
for (ssize_t i = m_wiimotes.size() - 1; i >= 0; --i)
|
||||
{
|
||||
const auto& wiimoteDevice = m_wiimotes[i].device;
|
||||
if (wiimoteDevice)
|
||||
{
|
||||
if (*wiimoteDevice == *device)
|
||||
{
|
||||
isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
lowestReplaceableIndex = i;
|
||||
}
|
||||
if (isDuplicate)
|
||||
continue;
|
||||
if (lowestReplaceableIndex != -1)
|
||||
m_wiimotes.replace(lowestReplaceableIndex, std::make_unique<Wiimote>(device));
|
||||
else
|
||||
m_wiimotes.push_back(std::make_unique<Wiimote>(device));
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ControllerBase>> result;
|
||||
result.reserve(m_wiimotes.size());
|
||||
for (size_t i = 0; i < m_wiimotes.size(); ++i)
|
||||
{
|
||||
result.emplace_back(std::make_shared<NativeWiimoteController>(i));
|
||||
|
@ -74,7 +83,7 @@ std::vector<std::shared_ptr<ControllerBase>> 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)
|
||||
|
@ -141,6 +150,30 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz
|
|||
return {};
|
||||
}
|
||||
|
||||
void WiimoteControllerProvider::connectionThread()
|
||||
{
|
||||
SetThreadName("Wiimote-connect");
|
||||
while (m_running.load(std::memory_order_relaxed))
|
||||
{
|
||||
std::vector<WiimoteDevicePtr> devices;
|
||||
#ifdef HAS_HIDAPI
|
||||
const auto& hidDevices = HidapiWiimote::get_devices();
|
||||
std::ranges::move(hidDevices, std::back_inserter(devices));
|
||||
#endif
|
||||
#ifdef HAS_BLUEZ
|
||||
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");
|
||||
|
@ -148,7 +181,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<std::chrono::seconds>(now - lastCheck) > std::chrono::seconds(2))
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck) > std::chrono::milliseconds(500))
|
||||
{
|
||||
// check for new connected wiimotes
|
||||
get_controllers();
|
||||
|
@ -160,11 +193,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;
|
||||
|
||||
|
@ -921,18 +959,18 @@ void WiimoteControllerProvider::writer_thread()
|
|||
|
||||
if (index != (size_t)-1 && !data.empty())
|
||||
{
|
||||
if (m_wiimotes[index].rumble)
|
||||
auto& wiimote = m_wiimotes[index];
|
||||
if (!wiimote.device)
|
||||
continue;
|
||||
if (wiimote.rumble)
|
||||
data[1] |= 1;
|
||||
|
||||
m_wiimotes[index].connected = m_wiimotes[index].device->write_data(data);
|
||||
if (m_wiimotes[index].connected)
|
||||
if (!wiimote.device->write_data(data))
|
||||
{
|
||||
m_wiimotes[index].data_ts = std::chrono::high_resolution_clock::now();
|
||||
wiimote.device.reset();
|
||||
wiimote.rumble = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_wiimotes[index].rumble = false;
|
||||
}
|
||||
wiimote.data_ts = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
device_lock.unlock();
|
||||
|
||||
|
|
|
@ -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::thread m_connectionThread;
|
||||
std::vector<WiimoteDevicePtr> 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();
|
||||
|
||||
void calibrate(size_t index);
|
||||
IRMode set_ir_camera(size_t index, bool state);
|
||||
|
|
|
@ -9,8 +9,7 @@ public:
|
|||
virtual bool write_data(const std::vector<uint8>& data) = 0;
|
||||
virtual std::optional<std::vector<uint8_t>> read_data() = 0;
|
||||
|
||||
virtual bool operator==(WiimoteDevice& o) const = 0;
|
||||
bool operator!=(WiimoteDevice& o) const { return *this == o; }
|
||||
virtual bool operator==(const WiimoteDevice& o) const = 0;
|
||||
};
|
||||
|
||||
using WiimoteDevicePtr = std::shared_ptr<WiimoteDevice>;
|
||||
|
|
|
@ -47,8 +47,11 @@ std::vector<WiimoteDevicePtr> HidapiWiimote::get_devices() {
|
|||
return wiimote_devices;
|
||||
}
|
||||
|
||||
bool HidapiWiimote::operator==(WiimoteDevice& o) const {
|
||||
return static_cast<HidapiWiimote const&>(o).m_path == m_path;
|
||||
bool HidapiWiimote::operator==(const WiimoteDevice& rhs) const {
|
||||
auto other = dynamic_cast<const HidapiWiimote*>(&rhs);
|
||||
if (!other)
|
||||
return false;
|
||||
return m_path == other->m_path;
|
||||
}
|
||||
|
||||
HidapiWiimote::~HidapiWiimote() {
|
||||
|
|
|
@ -10,7 +10,7 @@ public:
|
|||
|
||||
bool write_data(const std::vector<uint8> &data) override;
|
||||
std::optional<std::vector<uint8>> read_data() override;
|
||||
bool operator==(WiimoteDevice& o) const override;
|
||||
bool operator==(const WiimoteDevice& o) const override;
|
||||
|
||||
static std::vector<WiimoteDevicePtr> get_devices();
|
||||
|
||||
|
@ -19,5 +19,3 @@ private:
|
|||
const std::string m_path;
|
||||
|
||||
};
|
||||
|
||||
using WiimoteDevice_t = HidapiWiimote;
|
148
src/input/api/Wiimote/l2cap/L2CapWiimote.cpp
Normal file
148
src/input/api/Wiimote/l2cap/L2CapWiimote.cpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
#include "L2CapWiimote.h"
|
||||
#include <bluetooth/l2cap.h>
|
||||
|
||||
constexpr auto comparator = [](const bdaddr_t& a, const bdaddr_t& b) {
|
||||
return bacmp(&a, &b);
|
||||
};
|
||||
|
||||
static auto s_addresses = std::map<bdaddr_t, bool, decltype(comparator)>(comparator);
|
||||
static std::mutex s_addressMutex;
|
||||
|
||||
static bool AttemptConnect(int sockFd, const sockaddr_l2& addr)
|
||||
{
|
||||
auto res = connect(sockFd, reinterpret_cast<const sockaddr*>(&addr),
|
||||
sizeof(sockaddr_l2));
|
||||
if (res == 0)
|
||||
return true;
|
||||
return connect(sockFd, reinterpret_cast<const sockaddr*>(&addr),
|
||||
sizeof(sockaddr_l2)) == 0;
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
s_addresses.try_emplace(addr, false);
|
||||
}
|
||||
|
||||
std::vector<WiimoteDevicePtr> L2CapWiimote::get_devices()
|
||||
{
|
||||
s_addressMutex.lock();
|
||||
std::vector<bdaddr_t> unconnected;
|
||||
for (const auto& [addr, connected] : s_addresses)
|
||||
{
|
||||
if (!connected)
|
||||
unconnected.push_back(addr);
|
||||
}
|
||||
s_addressMutex.unlock();
|
||||
|
||||
std::vector<WiimoteDevicePtr> outDevices;
|
||||
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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
outDevices.emplace_back(std::make_shared<L2CapWiimote>(sendFd, recvFd, addr));
|
||||
|
||||
s_addressMutex.lock();
|
||||
s_addresses[addr] = true;
|
||||
s_addressMutex.unlock();
|
||||
}
|
||||
return outDevices;
|
||||
}
|
||||
|
||||
bool L2CapWiimote::write_data(const std::vector<uint8>& 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<std::vector<uint8>> L2CapWiimote::read_data()
|
||||
{
|
||||
uint8 buffer[23];
|
||||
const auto nBytes = recv(m_sendFd, buffer, 23, 0);
|
||||
|
||||
if (nBytes < 0 && errno == EWOULDBLOCK)
|
||||
return std::vector<uint8>{};
|
||||
// All incoming messages must be prefixed with 0xA1
|
||||
if (nBytes < 2 || buffer[0] != 0xA1)
|
||||
return std::nullopt;
|
||||
return std::vector(buffer + 1, buffer + 1 + nBytes - 1);
|
||||
}
|
||||
|
||||
bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const
|
||||
{
|
||||
auto mote = dynamic_cast<const L2CapWiimote*>(&rhs);
|
||||
if (!mote)
|
||||
return false;
|
||||
return bacmp(&m_addr, &mote->m_addr) == 0;
|
||||
}
|
22
src/input/api/Wiimote/l2cap/L2CapWiimote.h
Normal file
22
src/input/api/Wiimote/l2cap/L2CapWiimote.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#include <input/api/Wiimote/WiimoteDevice.h>
|
||||
#include <bluetooth/bluetooth.h>
|
||||
|
||||
class L2CapWiimote : public WiimoteDevice
|
||||
{
|
||||
public:
|
||||
L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr);
|
||||
~L2CapWiimote() override;
|
||||
|
||||
bool write_data(const std::vector<uint8>& data) override;
|
||||
std::optional<std::vector<uint8>> read_data() override;
|
||||
bool operator==(const WiimoteDevice& o) const override;
|
||||
|
||||
static void AddCandidateAddress(bdaddr_t addr);
|
||||
static std::vector<WiimoteDevicePtr> get_devices();
|
||||
private:
|
||||
int m_recvFd;
|
||||
int m_sendFd;
|
||||
bdaddr_t m_addr;
|
||||
};
|
||||
|
Loading…
Reference in a new issue