ohmu
·
2026-10-15
1#include "bluetoothradio.h"
2#include "common/processing.h"
3
4#import <IOBluetooth/IOBluetooth.h>
5
6// For some reason the official declaration of IOBluetoothHostController doesn't include property `controllers`
7@interface IOBluetoothHostController()
8+ (id)controllers;
9@end
10
11const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
12{
13 NSArray<IOBluetoothHostController*>* ctrls = IOBluetoothHostController.controllers;
14 if(!ctrls)
15 return "IOBluetoothHostController.controllers returns nil";
16
17 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
18 if (ffProcessAppendStdOut(&buffer, (char* const[]) {
19 "system_profiler",
20 "SPBluetoothDataType",
21 "-xml",
22 "-detailLevel",
23 "basic",
24 nullptr
25 }) != nullptr)
26 return "Starting `system_profiler SPBluetoothDataType -xml -detailLevel basic` failed";
27
28 NSArray* arr = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length]
29 options:NSPropertyListImmutable
30 format:nil
31 error:nil];
32 if (!arr || !arr.count)
33 return "system_profiler SPBluetoothDataType returned an empty array";
34
35 for (IOBluetoothHostController* ctrl in ctrls)
36 {
37 FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices);
38 ffStrbufInitS(&device->name, ctrl.nameAsString.UTF8String);
39 ffStrbufInitS(&device->address, ctrl.addressAsString.UTF8String);
40 ffStrbufInitStatic(&device->vendor, "Apple");
41 device->lmpVersion = INT_MIN;
42 device->lmpSubversion = INT_MIN;
43 device->enabled = ctrl.powerState == kBluetoothHCIPowerStateON;
44 device->discoverable = false;
45 device->connectable = true;
46
47 for (NSDictionary* itemDict in arr[0][@"_items"])
48 {
49 NSDictionary* props = itemDict[@"controller_properties"];
50 if (!props) continue;
51
52 if (![ctrl.addressAsString isEqualToString:props[@"controller_address"]]) continue;
53
54 NSString* services = props[@"controller_supportedServices"];
55 if ([services containsString:@" LEA "])
56 device->lmpVersion = -11;
57 else if ([services containsString:@" GATT "])
58 device->lmpVersion = -6;
59
60 device->discoverable = ![props[@"controller_discoverable"] isEqualToString:@"attrib_off"];
61 ffStrbufSetS(&device->vendor, ((NSString*) props[@"controller_vendorID"]).UTF8String);
62 ffStrbufSubstrAfterFirstC(&device->vendor, '(');
63 ffStrbufTrimRight(&device->vendor, ')');
64 break;
65 }
66 }
67
68 return nullptr;
69}