ohmu
·
2026-10-15
1#include "bluetoothradio.h"
2#include "common/debug.h"
3#include "common/strutil.h"
4
5#ifdef FF_HAVE_DBUS
6 #include "common/dbus.h"
7 #include "common/io.h"
8
9/* Example dbus reply:
10array [
11 dict entry(
12 string "Address"
13 variant string "XX:XX:XX:XX:XX:XX"
14 )
15 dict entry(
16 string "Name"
17 variant string "xxxxxxxx"
18 )
19 dict entry(
20 string "Powered"
21 variant boolean true
22 )
23 dict entry(
24 string "PowerState"
25 variant string "on"
26 )
27 dict entry(
28 string "Manufacturer"
29 variant uint16 2
30 )
31 dict entry(
32 string "Version"
33 variant byte 12
34 )
35]
36*/
37
38static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter) {
39 if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) {
40 FF_DEBUG("Expected dict entry");
41 return "Expected dict entry";
42 }
43
44 DBusMessageIter dictIter;
45 dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);
46
47 if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) {
48 FF_DEBUG("Expected dict entry key to be a string");
49 return "Expected dict entry key to be a string";
50 }
51
52 const char* deviceProperty;
53 dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty);
54
55 dbus->lib->ffdbus_message_iter_next(&dictIter);
56
57 if (ffStrEquals(deviceProperty, "Address")) {
58 ffDBusGetString(dbus, &dictIter, &device->address);
59 } else if (ffStrEquals(deviceProperty, "Alias")) {
60 ffDBusGetString(dbus, &dictIter, &device->name);
61 } else if (ffStrEquals(deviceProperty, "Manufacturer")) {
62 uint64_t vendorId;
63 if (ffDBusGetUint(dbus, &dictIter, &vendorId)) {
64 ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor((uint32_t) vendorId));
65 }
66 } else if (ffStrEquals(deviceProperty, "Version")) {
67 uint64_t version;
68 if (ffDBusGetUint(dbus, &dictIter, &version)) {
69 device->lmpVersion = (int32_t) version;
70 }
71 } else if (ffStrEquals(deviceProperty, "Powered")) {
72 ffDBusGetBool(dbus, &dictIter, &device->enabled);
73 } else if (ffStrEquals(deviceProperty, "Discoverable")) {
74 ffDBusGetBool(dbus, &dictIter, &device->discoverable);
75 } else if (ffStrEquals(deviceProperty, "Pairable")) {
76 ffDBusGetBool(dbus, &dictIter, &device->connectable);
77 }
78
79 return nullptr;
80}
81
82static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const char* hciName, FFDBusData* dbus) {
83 char objPath[300];
84 snprintf(objPath, sizeof(objPath), "/org/bluez/%s", hciName);
85
86 DBusMessage* properties = ffDBusGetMethodReply(dbus, "org.bluez", objPath, "org.freedesktop.DBus.Properties", "GetAll", "org.bluez.Adapter1", nullptr);
87 if (!properties) {
88 FF_DEBUG("Failed to call org.freedesktop.DBus.Properties.GetAll");
89 return "Failed to call org.freedesktop.DBus.Properties.GetAll";
90 }
91
92 DBusMessageIter rootIter;
93 if (!dbus->lib->ffdbus_message_iter_init(properties, &rootIter)) {
94 dbus->lib->ffdbus_message_unref(properties);
95 FF_DEBUG("Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll");
96 return "Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll";
97 }
98
99 if (dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY) {
100 dbus->lib->ffdbus_message_unref(properties);
101 FF_DEBUG("Expected array");
102 return "Expected array";
103 }
104
105 DBusMessageIter arrayIter;
106 dbus->lib->ffdbus_message_iter_recurse(&rootIter, &arrayIter);
107
108 do {
109 detectBluetoothProperty(device, dbus, &arrayIter);
110 } while (dbus->lib->ffdbus_message_iter_next(&arrayIter));
111
112 dbus->lib->ffdbus_message_unref(properties);
113 return nullptr;
114}
115
116static const char* detectBluetooth(FFlist* devices) {
117 FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth");
118 if (dirp == nullptr) {
119 FF_DEBUG("opendir(\"/sys/class/bluetooth\") failed: %s", strerror(errno));
120 return "Failed to open /sys/class/bluetooth";
121 }
122
123 FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {};
124 const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus);
125 if (error) {
126 return error;
127 }
128
129 struct dirent* entry;
130 while ((entry = readdir(dirp)) != nullptr) {
131 if (entry->d_name[0] == '.') {
132 continue;
133 }
134
135 if (strchr(entry->d_name, ':') != nullptr) { // ignore connected devices
136 continue;
137 }
138
139 FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices);
140 ffStrbufInit(&device->name);
141 ffStrbufInit(&device->address);
142 ffStrbufInitStatic(&device->vendor, "Unknown");
143 device->lmpVersion = INT_MIN;
144 device->lmpSubversion = INT_MIN;
145 device->enabled = false;
146 detectBluetoothRoot(device, entry->d_name, &dbus);
147 }
148 return nullptr;
149}
150
151#endif
152
153const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) {
154#ifdef FF_HAVE_DBUS
155 return detectBluetooth(devices);
156#else
157 FF_UNUSED(devices)
158 FF_DEBUG("Fastfetch was compiled without DBus support");
159 return "Fastfetch was compiled without DBus support";
160#endif
161}