Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux: Allow connecting Wiimotes via L2CAP #1353

Merged
merged 25 commits into from
Dec 7, 2024
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
91679d5
Add `L2CapWiimote`
capitalistspz Sep 26, 2024
444a8de
Remove unnecessary != overload
capitalistspz Sep 26, 2024
32cb2bf
Create `L2CapWiimote` instances as `shared_ptr`
capitalistspz Sep 26, 2024
e5d1d0e
Allow `WiimoteControllerProvider` to receive `L2CapWiimote`s
capitalistspz Sep 26, 2024
b71ace1
Link `bluez`
capitalistspz Sep 26, 2024
0d4176a
Correct return value on `L2CapWiimote::write_data`
capitalistspz Sep 26, 2024
00ff45d
PairingDialog: Implement 'pairing' for bluez, reformat file
capitalistspz Sep 26, 2024
e6e437b
Fix periodic hitching when controller paired and connected
capitalistspz Sep 26, 2024
447d760
`WiimoteDevice`: Make equality actually constant
capitalistspz Sep 26, 2024
3c7768c
Fix Windows build error
capitalistspz Sep 26, 2024
667bf96
Use bdaddr to compare `L2CapWiimote`s
capitalistspz Sep 26, 2024
11656c3
`WiimoteControllerProvider`: Move device getting to another thread
capitalistspz Sep 26, 2024
cc2212a
Stop using `std::jthread`, because Mac doesn't support it
capitalistspz Sep 26, 2024
182e905
Make Bluez optional
capitalistspz Sep 26, 2024
7f58a20
Add bluez libs to dependency list
capitalistspz Sep 26, 2024
55e2403
Install `libbluetooth-dev` for AppImage too
capitalistspz Sep 26, 2024
e14c54b
Fix a crash from writing to a disconnected device
capitalistspz Sep 26, 2024
8ba4b05
Stop replacing non-duplicate controllers
capitalistspz Sep 26, 2024
f43f88f
Prevent storing duplicate addresses
capitalistspz Oct 1, 2024
0340e5e
Free BT resources acquired during pairing
capitalistspz Oct 3, 2024
9046dca
Show bluetooth address in correct order.
capitalistspz Oct 4, 2024
5184e63
Search for up to 4 devices at a time
capitalistspz Oct 10, 2024
cff8d90
Make cancellable between name reads, disable cancel button during inq…
capitalistspz Oct 21, 2024
08b0bfb
screaming case constexpr
capitalistspz Nov 22, 2024
a648fc7
Prevent connection attempts on still-connected controllers
capitalistspz Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WiimoteControllerProvider: Move device getting to another thread
  • Loading branch information
capitalistspz committed Dec 3, 2024

Verified

This commit was signed with the committer’s verified signature.
kushaldas Kushal Das
commit 11656c3452f9a0a6a90984f21f4610ef0c706c01
119 changes: 69 additions & 50 deletions src/input/api/Wiimote/WiimoteControllerProvider.cpp
Original file line number Diff line number Diff line change
@@ -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<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;
};

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<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& 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<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));
@@ -83,7 +76,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)
@@ -150,14 +143,38 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz
return {};
}

void WiimoteControllerProvider::connectionThread(std::stop_token stopToken)
{
SetThreadName("Wiimote-connect");
while (!stopToken.stop_requested())
{
std::vector<WiimoteDevicePtr> 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");
std::chrono::steady_clock::time_point lastCheck = {};
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();
@@ -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();

6 changes: 4 additions & 2 deletions src/input/api/Wiimote/WiimoteControllerProvider.h
Original file line number Diff line number Diff line change
@@ -77,16 +77,17 @@ class WiimoteControllerProvider : public ControllerProviderBase
private:
std::atomic_bool m_running = false;
std::thread m_reader_thread, m_writer_thread;

std::shared_mutex m_device_mutex;

std::jthread m_connectionThread;
std::vector<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 @@ class WiimoteControllerProvider : public ControllerProviderBase

void reader_thread();
void writer_thread();
void connectionThread(std::stop_token);

void calibrate(size_t index);
IRMode set_ir_camera(size_t index, bool state);
12 changes: 10 additions & 2 deletions src/input/api/Wiimote/l2cap/L2CapWiimote.cpp
Original file line number Diff line number Diff line change
@@ -7,8 +7,14 @@ namespace {

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;
if (res != ECONNREFUSED)
return false;
return connect(sockFd, reinterpret_cast<const sockaddr*>(&addr),
sizeof(sockaddr_l2)) == 0;
sizeof(sockaddr_l2)) == 0;
}
bool AttemptSetNonBlock(int& sockFd)
{
@@ -105,9 +111,11 @@ 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 {};
return std::nullopt;
return std::vector(buffer + 1, buffer + 1 + nBytes - 1);
}