diff options
Diffstat (limited to 'src/detection/bluetooth/bluetooth_linux.c')
| -rw-r--r-- | src/detection/bluetooth/bluetooth_linux.c | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/src/detection/bluetooth/bluetooth_linux.c b/src/detection/bluetooth/bluetooth_linux.c new file mode 100644 index 0000000..fb4e422 --- /dev/null +++ b/src/detection/bluetooth/bluetooth_linux.c @@ -0,0 +1,256 @@ +#include "bluetooth.h" +#include "common/strutil.h" + +#ifdef FF_HAVE_DBUS + #include "common/dbus.h" + #include "common/io.h" + +/* Example dbus reply, striped to only the relevant parts: +array [ //root + dict entry( //object + object path "/org/bluez/hci0/dev_03_21_8B_91_16_4D" + array [ + dict entry( //property + string "org.bluez.Device1" + array [ + dict entry( //value + string "Address" + variant string "03:21:8B:91:16:4D" + ) + dict entry( //value + string "Name" + variant string "JBL TUNE160BT" + ) + dict entry( //value + string "Icon" + variant string "audio-headset" + ) + dict entry( //value + string "Connected" + variant boolean true + ) + ] + ) + dict entry( //property + string "org.bluez.Battery1" + array [ + dict entry( //value + string "Percentage" + variant byte 100 + ) + ] + ) + ] + ) +] +*/ + +static bool detectBluetoothValue(FFDBusData* dbus, DBusMessageIter* iter, FFBluetoothResult* device) { + if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) { + return true; + } + + DBusMessageIter dictIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) { + return true; + } + + const char* deviceProperty; + dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &deviceProperty); + + dbus->lib->ffdbus_message_iter_next(&dictIter); + + if (ffStrEquals(deviceProperty, "Address")) { + ffDBusGetString(dbus, &dictIter, &device->address); + } else if (ffStrEquals(deviceProperty, "Name")) { + ffDBusGetString(dbus, &dictIter, &device->name); + } else if (ffStrEquals(deviceProperty, "Icon")) { + ffDBusGetString(dbus, &dictIter, &device->type); + } else if (ffStrEquals(deviceProperty, "Percentage")) { + uint64_t percentage; + if (ffDBusGetUint(dbus, &dictIter, &percentage)) { + device->battery = (uint8_t) percentage; + } + } else if (ffStrEquals(deviceProperty, "Connected")) { + ffDBusGetBool(dbus, &dictIter, &device->connected); + } else if (ffStrEquals(deviceProperty, "Paired")) { + bool paired = true; + ffDBusGetBool(dbus, &dictIter, &paired); + if (!paired) { + return false; + } + } + return true; +} + +static void detectBluetoothProperty(FFDBusData* dbus, DBusMessageIter* iter, FFBluetoothResult* device) { + if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) { + return; + } + + DBusMessageIter dictIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) { + return; + } + + const char* propertyType; + dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &propertyType); + + if (!ffStrContains(propertyType, ".Device") && !ffStrContains(propertyType, ".Battery")) { + return; // We don't care about other properties + } + + dbus->lib->ffdbus_message_iter_next(&dictIter); + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY) { + return; + } + + DBusMessageIter arrayIter; + dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter); + + do { + bool shouldContinue = detectBluetoothValue(dbus, &arrayIter, device); + if (!shouldContinue) { + ffStrbufClear(&device->name); + break; + } + } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); +} + +static FFBluetoothResult* detectBluetoothObject(FFlist* devices, FFDBusData* dbus, DBusMessageIter* iter) { + if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) { + return NULL; + } + + DBusMessageIter dictIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_OBJECT_PATH) { + return NULL; + } + + const char* objectPath; + dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &objectPath); + + // We don't want adapter objects + if (!ffStrContains(objectPath, "/dev_")) { + return NULL; + } + + dbus->lib->ffdbus_message_iter_next(&dictIter); + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY) { + return NULL; + } + + DBusMessageIter arrayIter; + dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter); + + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *devices); + ffStrbufInit(&device->name); + ffStrbufInit(&device->address); + ffStrbufInit(&device->type); + device->battery = 0; + device->connected = false; + + do { + detectBluetoothProperty(dbus, &arrayIter, device); + } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); + + return device; +} + +static void detectBluetoothRoot(FFBluetoothOptions* options, FFlist* devices, FFDBusData* dbus, DBusMessageIter* iter, int32_t connectedCount) { + if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) { + return; + } + + DBusMessageIter arrayIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &arrayIter); + + do { + FFBluetoothResult* device = detectBluetoothObject(devices, dbus, &arrayIter); + + if (device) { + if (!options->showDisconnected && !device->connected) { + ffStrbufDestroy(&device->name); + ffStrbufDestroy(&device->address); + ffStrbufDestroy(&device->type); + --devices->length; + } + + if (device->name.length == 0) { + ffStrbufSetStatic(&device->name, "Unknown Device"); + } + + if (device->connected && --connectedCount == 0) { + break; + } + } + } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); +} + +static const char* detectBluetooth(FFBluetoothOptions* options, FFlist* devices, int32_t connectedCount) { + FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; + const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus); + if (error) { + return error; + } + + DBusMessage* managedObjects = ffDBusGetMethodReply(&dbus, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", NULL, NULL); + if (!managedObjects) { + return "Failed to call GetManagedObjects"; + } + + DBusMessageIter rootIter; + if (!dbus.lib->ffdbus_message_iter_init(managedObjects, &rootIter)) { + dbus.lib->ffdbus_message_unref(managedObjects); + return "Failed to get root iterator of GetManagedObjects"; + } + + detectBluetoothRoot(options, devices, &dbus, &rootIter, connectedCount); + + dbus.lib->ffdbus_message_unref(managedObjects); + return NULL; +} + +static uint32_t connectedDevices(void) { + FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth"); + if (dirp == NULL) { + return 0; + } + + uint32_t result = 0; + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (strchr(entry->d_name, ':') != NULL) { + ++result; + } + } + + return result; +} + +#endif + +const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { +#ifdef FF_HAVE_DBUS + int32_t connectedCount = -1; + if (!options->showDisconnected) { + connectedCount = (int32_t) connectedDevices(); + if (connectedCount == 0) { + return NULL; + } + } + + return detectBluetooth(options, devices, connectedCount); +#else + FF_UNUSED(options, devices); + return "Fastfetch was compiled without DBus support"; +#endif +} |