master ohmu/fastfetch-openbsd / src / detection / bluetoothradio / bluetoothradio_apple.m
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}