ohmu
·
2026-10-15
1#include "bluetoothradio.h"
2#include "common/debug.h"
3#include "common/library.h"
4#include "common/io.h"
5#include "common/windows/unicode.h"
6
7#include <windows.h>
8#include <bluetoothapis.h>
9#include <winioctl.h>
10
11// #include <bthioctl.h>
12
13#define BTH_IOCTL_BASE 0
14#define BTH_CTL(id) CTL_CODE(FILE_DEVICE_BLUETOOTH, (id), METHOD_BUFFERED, FILE_ANY_ACCESS)
15#define IOCTL_BTH_GET_LOCAL_INFO BTH_CTL(BTH_IOCTL_BASE + 0x00)
16#define LMP_LE_SUPPORTED(x) ((x >> 38) & 1)
17
18typedef struct [[gnu::packed]] _BTH_RADIO_INFO {
19 // Supported LMP features of the radio. Use LMP_XXX() to extract
20 // the desired bits.
21 ULONGLONG lmpSupportedFeatures;
22
23 // Manufacturer ID (possibly BTH_MFG_XXX)
24 USHORT mfg;
25
26 // LMP subversion
27 USHORT lmpSubversion;
28
29 // LMP version
30 UCHAR lmpVersion;
31} BTH_RADIO_INFO;
32
33typedef struct [[gnu::packed]] _BTH_LOCAL_RADIO_INFO {
34 // Local BTH_ADDR, class of device, and radio name
35 BTH_DEVICE_INFO localInfo;
36
37 // Combo of LOCAL_RADIO_XXX values
38 ULONG flags;
39
40 // HCI revision, see core spec
41 USHORT hciRevision;
42
43 // HCI version, see core spec
44 UCHAR hciVersion;
45
46 // More information about the local radio (LMP, MFG)
47 BTH_RADIO_INFO radioInfo;
48} BTH_LOCAL_RADIO_INFO;
49static_assert(sizeof(BTH_LOCAL_RADIO_INFO) == 292, "BTH_LOCAL_RADIO_INFO should be 292 bytes");
50
51#pragma GCC diagnostic ignored "-Wpointer-sign"
52
53const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) {
54 // Actually bluetoothapis.dll, but it's missing on Windows 7
55 FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1)
56 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstRadio)
57 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextRadio)
58 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindRadioClose)
59 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsConnectable)
60 FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsDiscoverable)
61
62 HANDLE hRadio = nullptr;
63 HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstRadio(&(BLUETOOTH_FIND_RADIO_PARAMS) {
64 .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) },
65 &hRadio);
66 if (!hFind) {
67 if (GetLastError() == ERROR_NO_MORE_ITEMS) {
68 FF_DEBUG("No Bluetooth radios found or service disabled");
69 return "No Bluetooth radios found or service disabled";
70 } else {
71 FF_DEBUG("BluetoothFindFirstRadio() failed: %s", ffDebugWin32Error(GetLastError()));
72 return "BluetoothFindFirstRadio() failed";
73 }
74 }
75
76 do {
77 FF_AUTO_CLOSE_FD HANDLE hCurrent = hRadio;
78
79 BTH_LOCAL_RADIO_INFO blri;
80 DWORD returned;
81 if (!DeviceIoControl(hCurrent, IOCTL_BTH_GET_LOCAL_INFO, nullptr, 0, &blri, sizeof(blri), &returned, nullptr)) {
82 continue;
83 }
84
85 FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices);
86 ffStrbufInitS(&device->name, blri.localInfo.name);
87
88 BLUETOOTH_ADDRESS_STRUCT addr = { .ullLong = blri.localInfo.address };
89 ffStrbufInitF(&device->address, "%02X:%02X:%02X:%02X:%02X:%02X", addr.rgBytes[5], addr.rgBytes[4], addr.rgBytes[3], addr.rgBytes[2], addr.rgBytes[1], addr.rgBytes[0]);
90
91 device->lmpVersion = blri.radioInfo.lmpVersion;
92 device->lmpSubversion = blri.radioInfo.lmpSubversion;
93 ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg));
94 device->enabled = true;
95 device->connectable = ffBluetoothIsConnectable(hCurrent);
96 device->discoverable = ffBluetoothIsDiscoverable(hCurrent);
97 } while (ffBluetoothFindNextRadio(hFind, &hRadio));
98
99 ffBluetoothFindRadioClose(hFind);
100
101 return nullptr;
102}