mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-24 06:39:46 -06:00
BTReal: Improvements:
Separate LibUSB logic into LibUSBBluetoothAdapter class. Submit transfers on thread with proper timing. Throttle before ACL input for reduced input latency. Immediately send IPC replies for outgoing data. Continuously submit libusb transfers to fill HCI/ACL input queues. Simplify endpoint handling and state saving. Other cleanups.
This commit is contained in:
@ -695,6 +695,8 @@ if(TARGET LibUSB::LibUSB)
|
||||
IOS/USB/LibusbDevice.h
|
||||
IOS/USB/Bluetooth/BTReal.cpp
|
||||
IOS/USB/Bluetooth/BTReal.h
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h
|
||||
)
|
||||
endif()
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,25 +6,22 @@
|
||||
#if defined(__LIBUSB__)
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/Timer.h"
|
||||
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTBase.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
#include "Core/IOS/USB/USBV0.h"
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
class PointerWrap;
|
||||
struct libusb_device;
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
@ -49,87 +46,55 @@ public:
|
||||
std::optional<IPCReply> Open(const OpenRequest& request) override;
|
||||
std::optional<IPCReply> Close(u32 fd) override;
|
||||
std::optional<IPCReply> IOCtlV(const IOCtlVRequest& request) override;
|
||||
void Update() override;
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
void UpdateSyncButtonState(bool is_held) override;
|
||||
void TriggerSyncButtonPressedEvent() override;
|
||||
void TriggerSyncButtonHeldEvent() override;
|
||||
|
||||
void HandleCtrlTransfer(libusb_transfer* finished_transfer);
|
||||
void HandleBulkOrIntrTransfer(libusb_transfer* finished_transfer);
|
||||
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
static bool HasConfiguredBluetoothDevice();
|
||||
|
||||
struct BluetoothDeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BluetoothDeviceInfo> ListDevices();
|
||||
|
||||
private:
|
||||
static constexpr u8 INTERFACE = 0x00;
|
||||
// Arbitrarily chosen value that allows emulated software to send commands often enough
|
||||
// so that the sync button event is triggered at least every 200ms.
|
||||
// Ideally this should be equal to 0, so we don't trigger unnecessary libusb transfers.
|
||||
static constexpr u32 TIMEOUT = 200;
|
||||
using BufferType = LibUSBBluetoothAdapter::BufferType;
|
||||
|
||||
std::unique_ptr<LibUSBBluetoothAdapter> m_lib_usb_bt_adapter;
|
||||
|
||||
static constexpr u32 SYNC_BUTTON_HOLD_MS_TO_RESET = 10000;
|
||||
|
||||
std::atomic<SyncButtonState> m_sync_button_state{SyncButtonState::Unpressed};
|
||||
Common::Timer m_sync_button_held_timer;
|
||||
|
||||
std::string m_last_open_error;
|
||||
|
||||
LibusbUtils::Context m_context;
|
||||
libusb_device* m_device = nullptr;
|
||||
libusb_device_handle* m_handle = nullptr;
|
||||
|
||||
std::mutex m_transfers_mutex;
|
||||
struct PendingTransfer
|
||||
{
|
||||
PendingTransfer(std::unique_ptr<USB::TransferCommand> command_, std::unique_ptr<u8[]> buffer_)
|
||||
: command(std::move(command_)), buffer(std::move(buffer_))
|
||||
{
|
||||
}
|
||||
std::unique_ptr<USB::TransferCommand> command;
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
};
|
||||
std::map<libusb_transfer*, PendingTransfer> m_current_transfers;
|
||||
|
||||
// Set when we received a command to which we need to fake a reply
|
||||
Common::Flag m_fake_read_buffer_size_reply;
|
||||
Common::Flag m_fake_vendor_command_reply;
|
||||
u16 m_fake_vendor_command_reply_opcode;
|
||||
// Enqueued when we observe a command to which we need to fake a reply.
|
||||
std::queue<std::function<void(USB::V0IntrMessage&)>> m_fake_replies;
|
||||
|
||||
// This stores the address of paired devices and associated link keys.
|
||||
// It is needed because some adapters forget all stored link keys when they are reset,
|
||||
// which breaks pairings because the Wii relies on the Bluetooth module to remember them.
|
||||
std::map<bdaddr_t, linkkey_t> m_link_keys;
|
||||
Common::Flag m_need_reset_keys;
|
||||
|
||||
// This flag is set when a libusb transfer failed (for reasons other than timing out)
|
||||
// and we showed an OSD message about it.
|
||||
Common::Flag m_showed_failed_transfer;
|
||||
// Endpoints provided by the emulated software.
|
||||
std::unique_ptr<USB::V0IntrMessage> m_hci_endpoint;
|
||||
std::unique_ptr<USB::V0BulkMessage> m_acl_endpoint;
|
||||
|
||||
bool m_is_wii_bt_module = false;
|
||||
// Used for proper Bluetooth packet timing, especially for Wii remote speaker data.
|
||||
TimePoint GetTargetTime() const;
|
||||
|
||||
void TryToFillHCIEndpoint();
|
||||
void TryToFillACLEndpoint();
|
||||
|
||||
[[nodiscard]] BufferType ProcessHCIEvent(BufferType buffer);
|
||||
|
||||
void WaitForHCICommandComplete(u16 opcode);
|
||||
void SendHCIResetCommand();
|
||||
void SendHCIDeleteLinkKeyCommand();
|
||||
bool SendHCIStoreLinkKeyCommand();
|
||||
void FakeVendorCommandReply(USB::V0IntrMessage& ctrl);
|
||||
void FakeReadBufferSizeReply(USB::V0IntrMessage& ctrl);
|
||||
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, const u8* payload, u8 size);
|
||||
|
||||
void FakeVendorCommandReply(u16 opcode, USB::V0IntrMessage& ctrl);
|
||||
|
||||
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, std::span<const u8> payload);
|
||||
void FakeSyncButtonPressedEvent(USB::V0IntrMessage& ctrl);
|
||||
void FakeSyncButtonHeldEvent(USB::V0IntrMessage& ctrl);
|
||||
|
||||
void LoadLinkKeys();
|
||||
void SaveLinkKeys();
|
||||
|
||||
bool OpenDevice(const libusb_device_descriptor& device_descriptor, libusb_device* device);
|
||||
};
|
||||
} // namespace IOS::HLE
|
||||
|
||||
|
596
Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp
Normal file
596
Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp
Normal file
@ -0,0 +1,596 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ranges>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <libusb.h>
|
||||
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
#include "Core/IOS/USB/Host.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::size_t BUFFER_SIZE = 1024;
|
||||
|
||||
constexpr auto HCI_COMMAND_TIMEOUT = std::chrono::milliseconds{1000};
|
||||
|
||||
constexpr u8 REQUEST_TYPE =
|
||||
u8(u8(LIBUSB_ENDPOINT_OUT) | u8(LIBUSB_REQUEST_TYPE_CLASS)) | u8(LIBUSB_RECIPIENT_INTERFACE);
|
||||
|
||||
constexpr u8 HCI_EVENT = 0x81;
|
||||
constexpr u8 ACL_DATA_IN = 0x82;
|
||||
|
||||
template <auto MemFun>
|
||||
constexpr libusb_transfer_cb_fn LibUSBMemFunCallback()
|
||||
{
|
||||
return [](libusb_transfer* tr) {
|
||||
std::invoke(MemFun, static_cast<Common::ObjectType<MemFun>*>(tr->user_data), tr);
|
||||
};
|
||||
}
|
||||
|
||||
bool IsBluetoothDevice(const libusb_device_descriptor& descriptor)
|
||||
{
|
||||
constexpr u8 SUBCLASS = 0x01;
|
||||
constexpr u8 PROTOCOL_BLUETOOTH = 0x01;
|
||||
|
||||
const bool is_bluetooth_protocol = descriptor.bDeviceClass == LIBUSB_CLASS_WIRELESS &&
|
||||
descriptor.bDeviceSubClass == SUBCLASS &&
|
||||
descriptor.bDeviceProtocol == PROTOCOL_BLUETOOTH;
|
||||
|
||||
// Some devices misreport their class, so we avoid relying solely on descriptor checks and allow
|
||||
// users to specify their own VID/PID.
|
||||
return is_bluetooth_protocol || LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(
|
||||
descriptor.idVendor, descriptor.idProduct);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool LibUSBBluetoothAdapter::IsWiiBTModule() const
|
||||
{
|
||||
return m_is_wii_bt_module;
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::HasConfiguredBluetoothDevice()
|
||||
{
|
||||
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
return configured_vid != -1 && configured_pid != -1;
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(u16 vid, u16 pid)
|
||||
{
|
||||
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
return configured_vid == vid && configured_pid == pid;
|
||||
}
|
||||
|
||||
LibUSBBluetoothAdapter::LibUSBBluetoothAdapter()
|
||||
{
|
||||
if (!m_context.IsValid())
|
||||
return;
|
||||
|
||||
m_last_open_error.clear();
|
||||
|
||||
const bool has_configured_bt = HasConfiguredBluetoothDevice();
|
||||
|
||||
const int ret = m_context.GetDeviceList([&](libusb_device* device) {
|
||||
libusb_device_descriptor device_descriptor;
|
||||
libusb_get_device_descriptor(device, &device_descriptor);
|
||||
auto [make_config_descriptor_ret, config_descriptor] =
|
||||
LibusbUtils::MakeConfigDescriptor(device);
|
||||
if (make_config_descriptor_ret != LIBUSB_SUCCESS || !config_descriptor)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Failed to get config descriptor for device {:04x}:{:04x}: {}",
|
||||
device_descriptor.idVendor, device_descriptor.idProduct,
|
||||
LibusbUtils::ErrorWrap(make_config_descriptor_ret));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (has_configured_bt &&
|
||||
!IsConfiguredBluetoothDevice(device_descriptor.idVendor, device_descriptor.idProduct))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsBluetoothDevice(device_descriptor) && OpenDevice(device_descriptor, device))
|
||||
{
|
||||
unsigned char manufacturer[50] = {}, product[50] = {}, serial_number[50] = {};
|
||||
const int manufacturer_ret = libusb_get_string_descriptor_ascii(
|
||||
m_handle, device_descriptor.iManufacturer, manufacturer, sizeof(manufacturer));
|
||||
if (manufacturer_ret < LIBUSB_SUCCESS)
|
||||
{
|
||||
WARN_LOG_FMT(IOS_WIIMOTE,
|
||||
"Failed to get string for manufacturer descriptor {:02x} for device "
|
||||
"{:04x}:{:04x} (rev {:x}): {}",
|
||||
device_descriptor.iManufacturer, device_descriptor.idVendor,
|
||||
device_descriptor.idProduct, device_descriptor.bcdDevice,
|
||||
LibusbUtils::ErrorWrap(manufacturer_ret));
|
||||
manufacturer[0] = '?';
|
||||
manufacturer[1] = '\0';
|
||||
}
|
||||
const int product_ret = libusb_get_string_descriptor_ascii(
|
||||
m_handle, device_descriptor.iProduct, product, sizeof(product));
|
||||
if (product_ret < LIBUSB_SUCCESS)
|
||||
{
|
||||
WARN_LOG_FMT(IOS_WIIMOTE,
|
||||
"Failed to get string for product descriptor {:02x} for device "
|
||||
"{:04x}:{:04x} (rev {:x}): {}",
|
||||
device_descriptor.iProduct, device_descriptor.idVendor,
|
||||
device_descriptor.idProduct, device_descriptor.bcdDevice,
|
||||
LibusbUtils::ErrorWrap(product_ret));
|
||||
product[0] = '?';
|
||||
product[1] = '\0';
|
||||
}
|
||||
const int serial_ret = libusb_get_string_descriptor_ascii(
|
||||
m_handle, device_descriptor.iSerialNumber, serial_number, sizeof(serial_number));
|
||||
if (serial_ret < LIBUSB_SUCCESS)
|
||||
{
|
||||
WARN_LOG_FMT(IOS_WIIMOTE,
|
||||
"Failed to get string for serial number descriptor {:02x} for device "
|
||||
"{:04x}:{:04x} (rev {:x}): {}",
|
||||
device_descriptor.iSerialNumber, device_descriptor.idVendor,
|
||||
device_descriptor.idProduct, device_descriptor.bcdDevice,
|
||||
LibusbUtils::ErrorWrap(serial_ret));
|
||||
serial_number[0] = '?';
|
||||
serial_number[1] = '\0';
|
||||
}
|
||||
NOTICE_LOG_FMT(IOS_WIIMOTE, "Using device {:04x}:{:04x} (rev {:x}) for Bluetooth: {} {} {}",
|
||||
device_descriptor.idVendor, device_descriptor.idProduct,
|
||||
device_descriptor.bcdDevice, reinterpret_cast<char*>(manufacturer),
|
||||
reinterpret_cast<char*>(product), reinterpret_cast<char*>(serial_number));
|
||||
m_is_wii_bt_module =
|
||||
device_descriptor.idVendor == 0x57e && device_descriptor.idProduct == 0x305;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (ret != LIBUSB_SUCCESS)
|
||||
{
|
||||
m_last_open_error =
|
||||
Common::FmtFormatT("GetDeviceList failed: {0}", LibusbUtils::ErrorWrap(ret));
|
||||
}
|
||||
|
||||
if (m_handle == nullptr)
|
||||
{
|
||||
if (m_last_open_error.empty())
|
||||
{
|
||||
CriticalAlertFmtT(
|
||||
"Could not find any usable Bluetooth USB adapter for Bluetooth Passthrough.\n\n"
|
||||
"The emulated console will now stop.");
|
||||
}
|
||||
else
|
||||
{
|
||||
CriticalAlertFmtT(
|
||||
"Could not find any usable Bluetooth USB adapter for Bluetooth Passthrough.\n"
|
||||
"The following error occurred when Dolphin tried to use an adapter:\n{0}\n\n"
|
||||
"The emulated console will now stop.",
|
||||
m_last_open_error);
|
||||
}
|
||||
Core::QueueHostJob(&Core::Stop);
|
||||
return;
|
||||
}
|
||||
|
||||
StartInputTransfers();
|
||||
|
||||
m_output_worker.Reset("Bluetooth Output",
|
||||
std::bind_front(&LibUSBBluetoothAdapter::SubmitTimedTransfer, this));
|
||||
}
|
||||
|
||||
LibUSBBluetoothAdapter::~LibUSBBluetoothAdapter()
|
||||
{
|
||||
if (m_handle == nullptr)
|
||||
return;
|
||||
|
||||
// Wait for completion (or time out) of all HCI commands.
|
||||
while (!m_pending_hci_transfers.empty() && !m_unacknowledged_commands.empty())
|
||||
{
|
||||
(void)ReceiveHCIEvent();
|
||||
Common::YieldCPU();
|
||||
}
|
||||
|
||||
m_output_worker.Shutdown();
|
||||
|
||||
// Stop the repeating input transfers.
|
||||
if (std::lock_guard lg{m_transfers_mutex}; true)
|
||||
m_run_input_transfers = false;
|
||||
|
||||
// Cancel all LibUSB transfers.
|
||||
std::ranges::for_each(m_transfer_buffers | std::ranges::views::keys, libusb_cancel_transfer);
|
||||
|
||||
// Wait for transfer callbacks and clean up all the buffers.
|
||||
while (!m_transfer_buffers.empty())
|
||||
{
|
||||
m_transfers_to_free.WaitForData();
|
||||
CleanCompletedTransfers();
|
||||
}
|
||||
|
||||
const int ret = libusb_release_interface(m_handle, 0);
|
||||
if (ret != LIBUSB_SUCCESS)
|
||||
WARN_LOG_FMT(IOS_WIIMOTE, "libusb_release_interface failed: {}", LibusbUtils::ErrorWrap(ret));
|
||||
|
||||
libusb_close(std::exchange(m_handle, nullptr));
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::Update()
|
||||
{
|
||||
// Remove timed out commands.
|
||||
const auto expired_time = Clock::now() - HCI_COMMAND_TIMEOUT;
|
||||
while (!m_unacknowledged_commands.empty() &&
|
||||
m_unacknowledged_commands.front().submit_time < expired_time)
|
||||
{
|
||||
WARN_LOG_FMT(IOS_WIIMOTE, "HCI command 0x{:04x} timed out.",
|
||||
m_unacknowledged_commands.front().opcode);
|
||||
m_unacknowledged_commands.pop_front();
|
||||
}
|
||||
|
||||
// Allow sending commands if none are pending acknowledgement.
|
||||
if (m_unacknowledged_commands.empty())
|
||||
m_num_hci_command_packets = 1;
|
||||
|
||||
// Push queued commands when the controller is ready for them.
|
||||
if (!m_pending_hci_transfers.empty() && IsControllerReadyForCommand())
|
||||
{
|
||||
DEBUG_LOG_FMT(IOS_WIIMOTE, "Submitting queued HCI command.");
|
||||
|
||||
PushHCICommand(m_pending_hci_transfers.front());
|
||||
m_pending_hci_transfers.pop();
|
||||
}
|
||||
|
||||
CleanCompletedTransfers();
|
||||
}
|
||||
|
||||
auto LibUSBBluetoothAdapter::ReceiveHCIEvent() -> BufferType
|
||||
{
|
||||
if (m_hci_event_queue.Empty())
|
||||
{
|
||||
Update();
|
||||
return {};
|
||||
}
|
||||
|
||||
auto buffer = std::move(m_hci_event_queue.Front());
|
||||
m_hci_event_queue.Pop();
|
||||
|
||||
// We only care about "COMMAND_COMPL" and "COMMAND_STATUS" events here.
|
||||
// The controller will reply with one of those for every command.
|
||||
// We track which commands are still "in flight" to properly obey Num_HCI_Command_Packets.
|
||||
const auto event = buffer[0];
|
||||
if (event == HCI_EVENT_COMMAND_COMPL)
|
||||
{
|
||||
AcknowledgeCommand<hci_command_compl_ep>(buffer);
|
||||
}
|
||||
else if (event == HCI_EVENT_COMMAND_STATUS)
|
||||
{
|
||||
AcknowledgeCommand<hci_command_status_ep>(buffer);
|
||||
}
|
||||
|
||||
Update();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
auto LibUSBBluetoothAdapter::ReceiveACLData() -> BufferType
|
||||
{
|
||||
BufferType buffer;
|
||||
m_acl_data_queue.Pop(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::IsControllerReadyForCommand() const
|
||||
{
|
||||
return m_num_hci_command_packets > m_unacknowledged_commands.size();
|
||||
}
|
||||
|
||||
template <typename EventType>
|
||||
void LibUSBBluetoothAdapter::AcknowledgeCommand(std::span<const u8> buffer)
|
||||
{
|
||||
if (buffer.size() < sizeof(hci_event_hdr_t) + sizeof(EventType)) [[unlikely]]
|
||||
{
|
||||
WARN_LOG_FMT(IOS_WIIMOTE, "Undersized HCI event");
|
||||
return;
|
||||
}
|
||||
|
||||
EventType ev;
|
||||
std::memcpy(&ev, buffer.data() + sizeof(hci_event_hdr_t), sizeof(ev));
|
||||
|
||||
const auto it =
|
||||
std::ranges::find(m_unacknowledged_commands, ev.opcode, &OutstandingCommand::opcode);
|
||||
if (it != m_unacknowledged_commands.end())
|
||||
{
|
||||
DEBUG_LOG_FMT(IOS_WIIMOTE, "HCI command acknowledged: 0x{:04x}", ev.opcode);
|
||||
m_unacknowledged_commands.erase(it);
|
||||
}
|
||||
else if (ev.opcode != 0x0000)
|
||||
{
|
||||
WARN_LOG_FMT(IOS_WIIMOTE, "Unexpected opcode acknowledgement: 0x{:04x}", ev.opcode);
|
||||
}
|
||||
|
||||
m_num_hci_command_packets = ev.num_cmd_pkts;
|
||||
}
|
||||
|
||||
libusb_transfer* LibUSBBluetoothAdapter::AllocateTransfer(std::size_t buffer_size)
|
||||
{
|
||||
auto& item =
|
||||
*m_transfer_buffers.emplace(libusb_alloc_transfer(0), Common::UniqueBuffer<u8>{buffer_size})
|
||||
.first;
|
||||
|
||||
auto* const transfer = item.first;
|
||||
transfer->buffer = item.second.get();
|
||||
|
||||
return transfer;
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::FreeTransfer(libusb_transfer* transfer)
|
||||
{
|
||||
libusb_free_transfer(transfer);
|
||||
m_transfer_buffers.erase(transfer);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::CleanCompletedTransfers()
|
||||
{
|
||||
libusb_transfer* transfer{};
|
||||
while (m_transfers_to_free.Pop(transfer))
|
||||
FreeTransfer(transfer);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::PushHCICommand(TimedTransfer transfer)
|
||||
{
|
||||
hci_cmd_hdr_t cmd;
|
||||
std::memcpy(&cmd, libusb_control_transfer_get_data(transfer.transfer), sizeof(cmd));
|
||||
|
||||
// Push the time forward so our timeouts work properly if command was queued and delayed.
|
||||
const auto submit_time = std::max(transfer.target_time, Clock::now());
|
||||
|
||||
m_unacknowledged_commands.emplace_back(OutstandingCommand{cmd.opcode, submit_time});
|
||||
|
||||
// This function is only invoked when this value is at least 1.
|
||||
assert(m_num_hci_command_packets >= 1);
|
||||
--m_num_hci_command_packets;
|
||||
|
||||
m_output_worker.EmplaceItem(transfer);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::SubmitTimedTransfer(TimedTransfer transfer)
|
||||
{
|
||||
if (Config::Get(Config::MAIN_PRECISION_FRAME_TIMING))
|
||||
m_precision_timer.SleepUntil(transfer.target_time);
|
||||
else
|
||||
std::this_thread::sleep_until(transfer.target_time);
|
||||
|
||||
SubmitTransfer(transfer.transfer);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::SubmitTransfer(libusb_transfer* transfer)
|
||||
{
|
||||
const int ret = libusb_submit_transfer(transfer);
|
||||
|
||||
if (ret == LIBUSB_SUCCESS)
|
||||
return;
|
||||
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "libusb_submit_transfer: {}", LibusbUtils::ErrorWrap(ret));
|
||||
|
||||
// Failed transers will not invoke callbacks so we must mark the buffer to be free'd here.
|
||||
std::lock_guard lk{m_transfers_mutex};
|
||||
m_transfers_to_free.Emplace(transfer);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data,
|
||||
TimePoint target_time)
|
||||
{
|
||||
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
|
||||
|
||||
auto* const transfer = AllocateTransfer(data.size());
|
||||
libusb_fill_bulk_transfer(transfer, m_handle, endpoint, transfer->buffer, int(data.size()),
|
||||
callback, this, 0);
|
||||
|
||||
std::ranges::copy(data, transfer->buffer);
|
||||
|
||||
m_output_worker.EmplaceItem(transfer, target_time);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index,
|
||||
std::span<const u8> data,
|
||||
TimePoint target_time)
|
||||
{
|
||||
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
|
||||
|
||||
auto* const transfer = AllocateTransfer(data.size() + LIBUSB_CONTROL_SETUP_SIZE);
|
||||
|
||||
libusb_fill_control_setup(transfer->buffer, type, request, value, index, u16(data.size()));
|
||||
libusb_fill_control_transfer(transfer, m_handle, transfer->buffer, callback, this, 0);
|
||||
|
||||
std::ranges::copy(data, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE);
|
||||
|
||||
const TimedTransfer timed_transfer{transfer, target_time};
|
||||
|
||||
if (IsControllerReadyForCommand())
|
||||
{
|
||||
PushHCICommand(timed_transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG_FMT(IOS_WIIMOTE, "Queueing HCI command while controller is busy.");
|
||||
m_pending_hci_transfers.emplace(timed_transfer);
|
||||
}
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::SendControlTransfer(std::span<const u8> data)
|
||||
{
|
||||
ScheduleControlTransfer(REQUEST_TYPE, 0, 0, 0, data, Clock::now());
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::StartInputTransfers()
|
||||
{
|
||||
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleInputTransfer>();
|
||||
|
||||
// Incoming HCI events.
|
||||
{
|
||||
auto* const transfer = AllocateTransfer(BUFFER_SIZE);
|
||||
libusb_fill_interrupt_transfer(transfer, m_handle, HCI_EVENT, transfer->buffer, BUFFER_SIZE,
|
||||
callback, this, 0);
|
||||
SubmitTransfer(transfer);
|
||||
}
|
||||
|
||||
// Incoming ACL data.
|
||||
{
|
||||
auto* const transfer = AllocateTransfer(BUFFER_SIZE);
|
||||
libusb_fill_bulk_transfer(transfer, m_handle, ACL_DATA_IN, transfer->buffer, BUFFER_SIZE,
|
||||
callback, this, 0);
|
||||
SubmitTransfer(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
bool LibUSBBluetoothAdapter::OpenDevice(const libusb_device_descriptor& device_descriptor,
|
||||
libusb_device* device)
|
||||
{
|
||||
const int ret = libusb_open(device, &m_handle);
|
||||
if (ret != LIBUSB_SUCCESS)
|
||||
{
|
||||
m_last_open_error = Common::FmtFormatT("Failed to open Bluetooth device {:04x}:{:04x}: {}",
|
||||
device_descriptor.idVendor, device_descriptor.idProduct,
|
||||
LibusbUtils::ErrorWrap(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detaching always fails as a regular user on FreeBSD
|
||||
// https://lists.freebsd.org/pipermail/freebsd-usb/2016-March/014161.html
|
||||
#ifndef __FreeBSD__
|
||||
int result = libusb_set_auto_detach_kernel_driver(m_handle, 1);
|
||||
if (result != LIBUSB_SUCCESS)
|
||||
{
|
||||
result = libusb_detach_kernel_driver(m_handle, INTERFACE);
|
||||
if (result != LIBUSB_SUCCESS && result != LIBUSB_ERROR_NOT_FOUND &&
|
||||
result != LIBUSB_ERROR_NOT_SUPPORTED)
|
||||
{
|
||||
m_last_open_error = Common::FmtFormatT(
|
||||
"Failed to detach kernel driver for BT passthrough: {0}", LibusbUtils::ErrorWrap(result));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (const int result2 = libusb_claim_interface(m_handle, INTERFACE); result2 != LIBUSB_SUCCESS)
|
||||
{
|
||||
m_last_open_error = Common::FmtFormatT("Failed to claim interface for BT passthrough: {0}",
|
||||
LibusbUtils::ErrorWrap(result2));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo> LibUSBBluetoothAdapter::ListDevices()
|
||||
{
|
||||
std::vector<BluetoothDeviceInfo> device_list;
|
||||
LibusbUtils::Context context;
|
||||
|
||||
if (!context.IsValid())
|
||||
return {};
|
||||
|
||||
int result = context.GetDeviceList([&device_list](libusb_device* device) {
|
||||
auto [config_ret, config] = LibusbUtils::MakeConfigDescriptor(device, 0);
|
||||
if (config_ret != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
libusb_device_descriptor desc;
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (IsBluetoothDevice(desc))
|
||||
{
|
||||
const std::string device_name =
|
||||
IOS::HLE::USBHost::GetDeviceNameFromVIDPID(desc.idVendor, desc.idProduct);
|
||||
device_list.push_back({desc.idVendor, desc.idProduct, device_name});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "Failed to get device list: {}", LibusbUtils::ErrorWrap(result));
|
||||
return device_list;
|
||||
}
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::HandleOutputTransfer(libusb_transfer* tr)
|
||||
{
|
||||
HandleTransferError(tr);
|
||||
|
||||
std::lock_guard lg{m_transfers_mutex};
|
||||
m_transfers_to_free.Emplace(tr);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::HandleInputTransfer(libusb_transfer* tr)
|
||||
{
|
||||
if (tr->status == LIBUSB_TRANSFER_COMPLETED)
|
||||
{
|
||||
const bool is_hci_event = tr->endpoint == HCI_EVENT;
|
||||
auto& queue = is_hci_event ? m_hci_event_queue : m_acl_data_queue;
|
||||
|
||||
if (queue.Size() < MAX_INPUT_QUEUE_SIZE)
|
||||
{
|
||||
if (tr->actual_length != 0)
|
||||
{
|
||||
BufferType buffer(tr->actual_length);
|
||||
std::copy_n(tr->buffer, tr->actual_length, buffer.data());
|
||||
queue.Emplace(std::move(buffer));
|
||||
}
|
||||
|
||||
m_showed_long_queue_drop = false;
|
||||
}
|
||||
else if (!std::exchange(m_showed_long_queue_drop, true))
|
||||
{
|
||||
// This will happen when pausing the emulator.
|
||||
WARN_LOG_FMT(IOS_WIIMOTE, "{} queue is too long. Packets will be dropped.",
|
||||
(is_hci_event ? "HCI" : "ACL"));
|
||||
}
|
||||
}
|
||||
|
||||
HandleTransferError(tr);
|
||||
|
||||
std::lock_guard lg{m_transfers_mutex};
|
||||
|
||||
if (m_run_input_transfers)
|
||||
{
|
||||
// Continuously repeat this input transfer so long as we're still running.
|
||||
const auto ret = libusb_submit_transfer(tr);
|
||||
if (ret == LIBUSB_SUCCESS)
|
||||
return;
|
||||
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "HandleInputTransfer libusb_submit_transfer: {}",
|
||||
LibusbUtils::ErrorWrap(ret));
|
||||
}
|
||||
|
||||
m_transfers_to_free.Emplace(tr);
|
||||
}
|
||||
|
||||
void LibUSBBluetoothAdapter::HandleTransferError(libusb_transfer* transfer)
|
||||
{
|
||||
if (transfer->status != LIBUSB_TRANSFER_COMPLETED &&
|
||||
transfer->status != LIBUSB_TRANSFER_CANCELLED)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "libusb transfer failed: {}",
|
||||
LibusbUtils::ErrorWrap(transfer->status));
|
||||
if (!std::exchange(m_showed_failed_transfer, true))
|
||||
{
|
||||
Core::DisplayMessage("Failed to send a command to the Bluetooth adapter.", 10000);
|
||||
Core::DisplayMessage("It may not be compatible with passthrough mode.", 10000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_showed_failed_transfer = false;
|
||||
}
|
||||
}
|
157
Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h
Normal file
157
Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h
Normal file
@ -0,0 +1,157 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
class LibUSBBluetoothAdapter
|
||||
{
|
||||
public:
|
||||
using BufferType = Common::UniqueBuffer<u8>;
|
||||
|
||||
struct BluetoothDeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BluetoothDeviceInfo> ListDevices();
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
static bool HasConfiguredBluetoothDevice();
|
||||
|
||||
// Public interface is intended to be used by a single thread.
|
||||
|
||||
LibUSBBluetoothAdapter();
|
||||
~LibUSBBluetoothAdapter();
|
||||
|
||||
// Return a packet at the front of the queue, or an empty buffer when nothing is queued.
|
||||
// ReceiveHCIEvent is expected to be called regularly to do some internal processing.
|
||||
[[nodiscard]] BufferType ReceiveHCIEvent();
|
||||
[[nodiscard]] BufferType ReceiveACLData();
|
||||
|
||||
// Schedule a transfer to submit at a specific time.
|
||||
void ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data, TimePoint target_time);
|
||||
void ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span<const u8> data,
|
||||
TimePoint target_time);
|
||||
|
||||
// Schedule a transfer to be submitted as soon as possible.
|
||||
void SendControlTransfer(std::span<const u8> data);
|
||||
|
||||
bool IsWiiBTModule() const;
|
||||
|
||||
private:
|
||||
// Inputs will be dropped when queue is full.
|
||||
static constexpr std::size_t MAX_INPUT_QUEUE_SIZE = 100;
|
||||
|
||||
static constexpr u8 INTERFACE = 0x00;
|
||||
|
||||
std::string m_last_open_error;
|
||||
|
||||
LibusbUtils::Context m_context;
|
||||
libusb_device_handle* m_handle = nullptr;
|
||||
|
||||
// Protects run-flag and Push'ing to the completed transfer queue.
|
||||
std::mutex m_transfers_mutex;
|
||||
|
||||
bool m_run_input_transfers = true;
|
||||
|
||||
// callback/worker threads push transfers here that are ready for clean up.
|
||||
// This avoids locking around m_transfer_buffers.
|
||||
Common::WaitableSPSCQueue<libusb_transfer*> m_transfers_to_free;
|
||||
|
||||
// Incoming packets Push'd from libusb callback thread.
|
||||
Common::SPSCQueue<BufferType> m_hci_event_queue;
|
||||
Common::SPSCQueue<BufferType> m_acl_data_queue;
|
||||
|
||||
// All transfers are alloc'd and kept here until complete.
|
||||
std::map<libusb_transfer*, Common::UniqueBuffer<u8>> m_transfer_buffers;
|
||||
|
||||
struct TimedTransfer
|
||||
{
|
||||
libusb_transfer* transfer{};
|
||||
TimePoint target_time{};
|
||||
};
|
||||
|
||||
// Outgoing data is submitted on a worker thread.
|
||||
// This allows proper packet timing without throttling the core thread.
|
||||
Common::WorkQueueThreadSP<TimedTransfer> m_output_worker;
|
||||
|
||||
// Used by the output worker.
|
||||
Common::PrecisionTimer m_precision_timer;
|
||||
|
||||
// Set to reduce OSD and log spam.
|
||||
bool m_showed_failed_transfer = false;
|
||||
bool m_showed_long_queue_drop = false;
|
||||
|
||||
// Some responses need to be fabricated if we aren't using the Nintendo BT module.
|
||||
bool m_is_wii_bt_module = false;
|
||||
|
||||
// Bluetooth spec's Num_HCI_Command_Packets.
|
||||
// This is the number of hci commands that the host is allowed to send.
|
||||
// We track this to send commands only when the controller is ready.
|
||||
u8 m_num_hci_command_packets = 1;
|
||||
|
||||
// HCI commands are queued when the controller isn't ready for them.
|
||||
// They will be sent after COMMAND_COMPL or COMMAND_STATUS signal readiness.
|
||||
std::queue<TimedTransfer> m_pending_hci_transfers;
|
||||
|
||||
struct OutstandingCommand
|
||||
{
|
||||
u16 opcode{};
|
||||
TimePoint submit_time{};
|
||||
};
|
||||
|
||||
// Sent HCI commands that have yet to receive a COMMAND_COMPL or COMMAND_STATUS response.
|
||||
// Container size will be small, around 2.
|
||||
std::deque<OutstandingCommand> m_unacknowledged_commands;
|
||||
|
||||
bool IsControllerReadyForCommand() const;
|
||||
|
||||
// Give the transfer to the worker and track the command appropriately.
|
||||
// This should only be used when IsControllerReadyForCommand is true.
|
||||
void PushHCICommand(TimedTransfer);
|
||||
|
||||
template <typename EventType>
|
||||
void AcknowledgeCommand(std::span<const u8> buffer);
|
||||
|
||||
libusb_transfer* AllocateTransfer(std::size_t buffer_size);
|
||||
void FreeTransfer(libusb_transfer*);
|
||||
void CleanCompletedTransfers();
|
||||
|
||||
// Do some HCI logic and clean up completed transfers.
|
||||
void Update();
|
||||
|
||||
// Sleep until the target time and then submit the transfer.
|
||||
// Used by worker thread.
|
||||
void SubmitTimedTransfer(TimedTransfer);
|
||||
|
||||
// Immediately submit transfer with libusb.
|
||||
void SubmitTransfer(libusb_transfer*);
|
||||
|
||||
// Start repetitive transfers to fill the hci-event and acl-data queues.
|
||||
void StartInputTransfers();
|
||||
|
||||
// LibUSB callbacks. Invoked from the LibUSB context thread.
|
||||
void HandleOutputTransfer(libusb_transfer*);
|
||||
void HandleInputTransfer(libusb_transfer*);
|
||||
|
||||
// Error logging.
|
||||
void HandleTransferError(libusb_transfer*);
|
||||
|
||||
bool OpenDevice(const libusb_device_descriptor& device_descriptor, libusb_device* device);
|
||||
};
|
@ -402,6 +402,7 @@
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTEmu.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTReal.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTStub.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\LibUSBBluetoothAdapter.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\hci.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\l2cap.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||
@ -1079,6 +1080,7 @@
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTEmu.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTReal.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTStub.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\LibUSBBluetoothAdapter.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
|
@ -15,9 +15,6 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
@ -28,7 +25,7 @@
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
@ -40,8 +37,6 @@
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
WiimoteControllersWidget::WiimoteControllersWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
CreateLayout();
|
||||
@ -80,7 +75,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh()
|
||||
|
||||
const auto scan_func = [this]() {
|
||||
INFO_LOG_FMT(COMMON, "Refreshing Bluetooth adapter list...");
|
||||
auto device_list = IOS::HLE::BluetoothRealDevice::ListDevices();
|
||||
auto device_list = LibUSBBluetoothAdapter::ListDevices();
|
||||
INFO_LOG_FMT(COMMON, "{} Bluetooth adapters available.", device_list.size());
|
||||
const auto refresh_complete_func = [this, devices = std::move(device_list)]() {
|
||||
OnBluetoothAdapterRefreshComplete(devices);
|
||||
@ -92,7 +87,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh()
|
||||
}
|
||||
|
||||
void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
const std::vector<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>& devices)
|
||||
const std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo>& devices)
|
||||
{
|
||||
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
@ -114,7 +109,7 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(device));
|
||||
|
||||
if (!found_configured_device &&
|
||||
IOS::HLE::BluetoothRealDevice::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
{
|
||||
found_configured_device = true;
|
||||
m_bluetooth_adapters->setCurrentIndex(m_bluetooth_adapters->count() - 1);
|
||||
@ -126,7 +121,7 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
const QString name = QLatin1Char{'['} + tr("disconnected") + QLatin1Char(']');
|
||||
const std::string name_str = name.toStdString();
|
||||
|
||||
IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo disconnected_device;
|
||||
LibUSBBluetoothAdapter::BluetoothDeviceInfo disconnected_device;
|
||||
disconnected_device.vid = configured_vid;
|
||||
disconnected_device.pid = configured_pid;
|
||||
disconnected_device.name = name_str;
|
||||
@ -314,8 +309,8 @@ void WiimoteControllersWidget::OnBluetoothPassthroughDeviceChanged(int index)
|
||||
return;
|
||||
}
|
||||
|
||||
auto device_info = m_bluetooth_adapters->itemData(index)
|
||||
.value<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>();
|
||||
auto device_info =
|
||||
m_bluetooth_adapters->itemData(index).value<LibUSBBluetoothAdapter::BluetoothDeviceInfo>();
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID, device_info.pid);
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID, device_info.vid);
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
@ -39,7 +39,7 @@ private:
|
||||
void OnBluetoothPassthroughSyncPressed();
|
||||
void OnBluetoothPassthroughResetPressed();
|
||||
void OnBluetoothAdapterRefreshComplete(
|
||||
const std::vector<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>& devices);
|
||||
const std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo>& devices);
|
||||
void OnWiimoteRefreshPressed();
|
||||
void OnWiimoteConfigure(size_t index);
|
||||
void StartBluetoothAdapterRefresh();
|
||||
|
Reference in New Issue
Block a user