Add a GUI option to select a bluetooth device
This commit is contained in:
parent
826625c7be
commit
4773a6f323
|
@ -149,6 +149,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|||
option(ENABLE_VTUNE "Enable Intel VTune integration for JIT code." OFF)
|
||||
|
||||
if(NOT ANDROID)
|
||||
option(ENABLE_HWDB "Enables the udev hardware database" ON)
|
||||
option(ENABLE_EVDEV "Enables the evdev controller backend" ON)
|
||||
endif()
|
||||
endif()
|
||||
|
@ -571,6 +572,16 @@ if(OPROFILING)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_HWDB)
|
||||
find_package(LIBUDEV REQUIRED)
|
||||
if(LIBUDEV_FOUND)
|
||||
message(STATUS "libudev found, enabling hardware database")
|
||||
add_definitions(-DHAVE_LIBUDEV=1)
|
||||
else()
|
||||
message(FATAL_ERROR "Couldn't find libudev. Can't build hardware database.\nDisable ENABLE_HWDB if you wish to build without hardware database support")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_EVDEV)
|
||||
find_package(LIBUDEV REQUIRED)
|
||||
find_package(LIBEVDEV REQUIRED)
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "Core/Core.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/IOS/Device.h"
|
||||
#include "Core/IOS/USB/Host.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
|
||||
|
@ -38,27 +39,17 @@ constexpr u8 REQUEST_TYPE = static_cast<u8>(LIBUSB_ENDPOINT_OUT) |
|
|||
static_cast<u8>(LIBUSB_REQUEST_TYPE_CLASS) |
|
||||
static_cast<u8>(LIBUSB_RECIPIENT_INTERFACE);
|
||||
|
||||
static bool IsWantedDevice(const libusb_device_descriptor& descriptor)
|
||||
{
|
||||
const int vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
if (vid == -1 || pid == -1)
|
||||
return true;
|
||||
return descriptor.idVendor == vid && descriptor.idProduct == pid;
|
||||
}
|
||||
|
||||
static bool IsBluetoothDevice(const libusb_interface_descriptor& descriptor)
|
||||
static bool IsBluetoothDevice(const libusb_device_descriptor& descriptor)
|
||||
{
|
||||
constexpr u8 SUBCLASS = 0x01;
|
||||
constexpr u8 PROTOCOL_BLUETOOTH = 0x01;
|
||||
if (Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID) != -1 &&
|
||||
Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return descriptor.bInterfaceClass == LIBUSB_CLASS_WIRELESS &&
|
||||
descriptor.bInterfaceSubClass == SUBCLASS &&
|
||||
descriptor.bInterfaceProtocol == 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 BluetoothRealDevice::IsConfiguredBluetoothDevice(descriptor.idVendor,
|
||||
descriptor.idProduct) ||
|
||||
(descriptor.bDeviceClass == LIBUSB_CLASS_WIRELESS &&
|
||||
descriptor.bDeviceSubClass == SUBCLASS &&
|
||||
descriptor.bDeviceProtocol == PROTOCOL_BLUETOOTH);
|
||||
}
|
||||
|
||||
BluetoothRealDevice::BluetoothRealDevice(EmulationKernel& ios, const std::string& device_name)
|
||||
|
@ -82,6 +73,13 @@ BluetoothRealDevice::~BluetoothRealDevice()
|
|||
SaveLinkKeys();
|
||||
}
|
||||
|
||||
bool BluetoothRealDevice::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;
|
||||
}
|
||||
|
||||
std::optional<IPCReply> BluetoothRealDevice::Open(const OpenRequest& request)
|
||||
{
|
||||
if (!m_context.IsValid())
|
||||
|
@ -101,10 +99,7 @@ std::optional<IPCReply> BluetoothRealDevice::Open(const OpenRequest& request)
|
|||
return true;
|
||||
}
|
||||
|
||||
const libusb_interface& interface = config_descriptor->interface[INTERFACE];
|
||||
const libusb_interface_descriptor& descriptor = interface.altsetting[0];
|
||||
if (IsBluetoothDevice(descriptor) && IsWantedDevice(device_descriptor) &&
|
||||
OpenDevice(device_descriptor, device))
|
||||
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(
|
||||
|
@ -677,6 +672,42 @@ bool BluetoothRealDevice::OpenDevice(const libusb_device_descriptor& device_desc
|
|||
return true;
|
||||
}
|
||||
|
||||
std::vector<BluetoothRealDevice::BluetoothDeviceInfo> BluetoothRealDevice::ListDevices()
|
||||
{
|
||||
std::vector<BluetoothDeviceInfo> device_list;
|
||||
LibusbUtils::Context context;
|
||||
|
||||
if (!context.IsValid())
|
||||
return {};
|
||||
|
||||
int result = context.GetDeviceList([&device_list](libusb_device* device) {
|
||||
libusb_device_descriptor desc;
|
||||
|
||||
auto [config_ret, config] = LibusbUtils::MakeConfigDescriptor(device, 0);
|
||||
if (config_ret != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (IsBluetoothDevice(desc))
|
||||
{
|
||||
const std::string device_name =
|
||||
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;
|
||||
}
|
||||
|
||||
// The callbacks are called from libusb code on a separate thread.
|
||||
void BluetoothRealDevice::HandleCtrlTransfer(libusb_transfer* tr)
|
||||
{
|
||||
|
|
|
@ -58,6 +58,17 @@ public:
|
|||
void HandleCtrlTransfer(libusb_transfer* finished_transfer);
|
||||
void HandleBulkOrIntrTransfer(libusb_transfer* finished_transfer);
|
||||
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
|
||||
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
|
||||
|
|
|
@ -11,6 +11,14 @@
|
|||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#ifdef __LIBUSB__
|
||||
#include <libusb.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBUDEV
|
||||
#include <libudev.h>
|
||||
#endif
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
|
@ -45,6 +53,68 @@ std::optional<IPCReply> USBHost::Open(const OpenRequest& request)
|
|||
return IPCReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
std::string USBHost::GetDeviceNameFromVIDPID(u16 vid, u16 pid)
|
||||
{
|
||||
std::string device_name;
|
||||
#ifdef __LIBUSB__
|
||||
LibusbUtils::Context context;
|
||||
|
||||
if (!context.IsValid())
|
||||
return device_name;
|
||||
|
||||
context.GetDeviceList([&device_name, vid, pid](libusb_device* device) {
|
||||
libusb_device_descriptor desc;
|
||||
|
||||
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
|
||||
return true;
|
||||
|
||||
if (desc.idVendor == vid && desc.idProduct == pid)
|
||||
{
|
||||
libusb_device_handle* handle;
|
||||
if (libusb_open(device, &handle) == LIBUSB_SUCCESS)
|
||||
{
|
||||
unsigned char buffer[256];
|
||||
if (desc.iProduct &&
|
||||
libusb_get_string_descriptor_ascii(handle, desc.iProduct, buffer, sizeof(buffer)) > 0)
|
||||
{
|
||||
device_name = reinterpret_cast<char*>(buffer);
|
||||
libusb_close(handle);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!device_name.empty())
|
||||
return device_name;
|
||||
#endif
|
||||
#ifdef HAVE_LIBUDEV
|
||||
udev* udev = udev_new();
|
||||
if (!udev)
|
||||
return device_name;
|
||||
|
||||
udev_hwdb* hwdb = udev_hwdb_new(udev);
|
||||
if (hwdb)
|
||||
{
|
||||
const std::string modalias = fmt::format("usb:v{:04X}p{:04X}*", vid, pid);
|
||||
udev_list_entry* entries = udev_hwdb_get_properties_list_entry(hwdb, modalias.c_str(), 0);
|
||||
|
||||
if (entries)
|
||||
{
|
||||
udev_list_entry* device_name_entry =
|
||||
udev_list_entry_get_by_name(entries, "ID_MODEL_FROM_DATABASE");
|
||||
if (device_name_entry)
|
||||
{
|
||||
device_name = udev_list_entry_get_value(device_name_entry);
|
||||
}
|
||||
}
|
||||
udev_hwdb_unref(hwdb);
|
||||
}
|
||||
#endif
|
||||
return device_name;
|
||||
}
|
||||
|
||||
void USBHost::DoState(PointerWrap& p)
|
||||
{
|
||||
Device::DoState(p);
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
void DoState(PointerWrap& p) override;
|
||||
|
||||
void OnDevicesChanged(const USBScanner::DeviceMap& new_devices);
|
||||
static std::string GetDeviceNameFromVIDPID(u16 vid, u16 pid);
|
||||
|
||||
protected:
|
||||
enum class ChangeEvent
|
||||
|
|
|
@ -27,6 +27,7 @@ void ControllersWindow::showEvent(QShowEvent* event)
|
|||
{
|
||||
QDialog::showEvent(event);
|
||||
m_wiimote_controllers->UpdateBluetoothAvailableStatus();
|
||||
m_wiimote_controllers->RefreshBluetoothAdapters();
|
||||
}
|
||||
|
||||
void ControllersWindow::CreateMainLayout()
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QRadioButton>
|
||||
#include <QScreen>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
@ -57,6 +58,50 @@ void WiimoteControllersWidget::UpdateBluetoothAvailableStatus()
|
|||
m_bluetooth_unavailable->setHidden(WiimoteReal::IsScannerReady());
|
||||
}
|
||||
|
||||
void WiimoteControllersWidget::RefreshBluetoothAdapters()
|
||||
{
|
||||
m_bluetooth_adapters->clear();
|
||||
|
||||
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
bool found_configured_device = configured_vid == -1 || configured_pid == -1;
|
||||
|
||||
m_bluetooth_adapters->addItem(tr("Automatic"));
|
||||
|
||||
for (auto& device : IOS::HLE::BluetoothRealDevice::ListDevices())
|
||||
{
|
||||
std::string name = device.name.empty() ? tr("Unknown Device").toStdString() : device.name;
|
||||
QString device_info =
|
||||
QString::fromStdString(fmt::format("{} ({:04x}:{:04x})", name, device.vid, device.pid));
|
||||
m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(device));
|
||||
|
||||
if (!found_configured_device &&
|
||||
IOS::HLE::BluetoothRealDevice::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
{
|
||||
found_configured_device = true;
|
||||
m_bluetooth_adapters->setCurrentIndex(m_bluetooth_adapters->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_configured_device)
|
||||
{
|
||||
const QString name = QLatin1Char{'['} + tr("disconnected") + QLatin1Char(']');
|
||||
const std::string name_str = name.toStdString();
|
||||
|
||||
IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo disconnected_device;
|
||||
disconnected_device.vid = configured_vid;
|
||||
disconnected_device.pid = configured_pid;
|
||||
disconnected_device.name = name_str;
|
||||
|
||||
QString device_info = QString::fromStdString(
|
||||
fmt::format("{} ({:04x}:{:04x})", name_str, configured_vid, configured_pid));
|
||||
|
||||
m_bluetooth_adapters->insertSeparator(m_bluetooth_adapters->count());
|
||||
m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(disconnected_device));
|
||||
m_bluetooth_adapters->setCurrentIndex(m_bluetooth_adapters->count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetRadioButtonIndicatorWidth()
|
||||
{
|
||||
const QStyle* style = QApplication::style();
|
||||
|
@ -104,6 +149,8 @@ void WiimoteControllersWidget::CreateLayout()
|
|||
m_wiimote_box->setLayout(m_wiimote_layout);
|
||||
|
||||
m_wiimote_passthrough = new QRadioButton(tr("Passthrough a Bluetooth adapter"));
|
||||
m_bluetooth_adapters_label = new QLabel(tr("Bluetooth adapter"));
|
||||
m_bluetooth_adapters = new QComboBox();
|
||||
m_wiimote_sync = new NonDefaultQPushButton(tr("Sync"));
|
||||
m_wiimote_reset = new NonDefaultQPushButton(tr("Reset"));
|
||||
m_wiimote_refresh = new NonDefaultQPushButton(tr("Refresh"));
|
||||
|
@ -123,6 +170,10 @@ void WiimoteControllersWidget::CreateLayout()
|
|||
// Passthrough BT
|
||||
m_wiimote_layout->addWidget(m_wiimote_passthrough, m_wiimote_layout->rowCount(), 0, 1, -1);
|
||||
|
||||
int adapter_row = m_wiimote_layout->rowCount();
|
||||
m_wiimote_layout->addWidget(m_bluetooth_adapters_label, adapter_row, 1, 1, 1);
|
||||
m_wiimote_layout->addWidget(m_bluetooth_adapters, adapter_row, 2, 1, 2);
|
||||
|
||||
int sync_row = m_wiimote_layout->rowCount();
|
||||
m_wiimote_layout->addWidget(m_wiimote_pt_labels[0], sync_row, 1, 1, 2);
|
||||
m_wiimote_layout->addWidget(m_wiimote_sync, sync_row, 3);
|
||||
|
@ -189,6 +240,8 @@ void WiimoteControllersWidget::ConnectWidgets()
|
|||
&WiimoteControllersWidget::SaveSettings);
|
||||
connect(m_wiimote_speaker_data, &QCheckBox::toggled, this,
|
||||
&WiimoteControllersWidget::SaveSettings);
|
||||
connect(m_bluetooth_adapters, &QComboBox::activated, this,
|
||||
&WiimoteControllersWidget::OnBluetoothPassthroughDeviceChanged);
|
||||
connect(m_wiimote_sync, &QPushButton::clicked, this,
|
||||
&WiimoteControllersWidget::OnBluetoothPassthroughSyncPressed);
|
||||
connect(m_wiimote_reset, &QPushButton::clicked, this,
|
||||
|
@ -207,6 +260,25 @@ void WiimoteControllersWidget::ConnectWidgets()
|
|||
}
|
||||
}
|
||||
|
||||
void WiimoteControllersWidget::OnBluetoothPassthroughDeviceChanged(int index)
|
||||
{
|
||||
// "Automatic" selection
|
||||
if (index == 0)
|
||||
{
|
||||
Config::DeleteKey(Config::GetActiveLayerForConfig(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID),
|
||||
Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
Config::DeleteKey(Config::GetActiveLayerForConfig(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID),
|
||||
Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device_info = m_bluetooth_adapters->itemData(index)
|
||||
.value<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>();
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID, device_info.pid);
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID, device_info.vid);
|
||||
}
|
||||
|
||||
void WiimoteControllersWidget::OnBluetoothPassthroughResetPressed()
|
||||
{
|
||||
const auto ios = Core::System::GetInstance().GetIOS();
|
||||
|
@ -297,11 +369,15 @@ void WiimoteControllersWidget::LoadSettings(Core::State state)
|
|||
m_wiimote_passthrough->setEnabled(!running);
|
||||
|
||||
const bool running_gc = running && !Core::System::GetInstance().IsWii();
|
||||
const bool running_wii = running && Core::System::GetInstance().IsWii();
|
||||
const bool enable_passthrough = m_wiimote_passthrough->isChecked() && !running_gc;
|
||||
const bool enable_adapter_selection = m_wiimote_passthrough->isChecked() && !running_wii;
|
||||
const bool enable_emu_bt = !m_wiimote_passthrough->isChecked() && !running_gc;
|
||||
const bool is_netplay = NetPlay::IsNetPlayRunning();
|
||||
const bool running_netplay = running && is_netplay;
|
||||
|
||||
m_bluetooth_adapters_label->setEnabled(enable_adapter_selection);
|
||||
m_bluetooth_adapters->setEnabled(enable_adapter_selection);
|
||||
m_wiimote_sync->setEnabled(enable_passthrough);
|
||||
m_wiimote_reset->setEnabled(enable_passthrough);
|
||||
|
||||
|
|
|
@ -28,9 +28,11 @@ public:
|
|||
explicit WiimoteControllersWidget(QWidget* parent);
|
||||
|
||||
void UpdateBluetoothAvailableStatus();
|
||||
void RefreshBluetoothAdapters();
|
||||
|
||||
private:
|
||||
void SaveSettings();
|
||||
void OnBluetoothPassthroughDeviceChanged(int index);
|
||||
void OnBluetoothPassthroughSyncPressed();
|
||||
void OnBluetoothPassthroughResetPressed();
|
||||
void OnWiimoteRefreshPressed();
|
||||
|
@ -50,6 +52,8 @@ private:
|
|||
|
||||
QRadioButton* m_wiimote_emu;
|
||||
QRadioButton* m_wiimote_passthrough;
|
||||
QLabel* m_bluetooth_adapters_label;
|
||||
QComboBox* m_bluetooth_adapters;
|
||||
QPushButton* m_wiimote_sync;
|
||||
QPushButton* m_wiimote_reset;
|
||||
QCheckBox* m_wiimote_continuous_scanning;
|
||||
|
|
Loading…
Reference in New Issue