From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/bluetooth/bluetooth.h | 14 ++ src/detection/bluetooth/bluetooth_apple.m | 112 +++++++++++ src/detection/bluetooth/bluetooth_bsd.c | 30 +++ src/detection/bluetooth/bluetooth_haiku.cpp | 30 +++ src/detection/bluetooth/bluetooth_linux.c | 256 ++++++++++++++++++++++++++ src/detection/bluetooth/bluetooth_nosupport.c | 5 + src/detection/bluetooth/bluetooth_windows.c | 223 ++++++++++++++++++++++ 7 files changed, 670 insertions(+) create mode 100644 src/detection/bluetooth/bluetooth.h create mode 100644 src/detection/bluetooth/bluetooth_apple.m create mode 100644 src/detection/bluetooth/bluetooth_bsd.c create mode 100644 src/detection/bluetooth/bluetooth_haiku.cpp create mode 100644 src/detection/bluetooth/bluetooth_linux.c create mode 100644 src/detection/bluetooth/bluetooth_nosupport.c create mode 100644 src/detection/bluetooth/bluetooth_windows.c (limited to 'src/detection/bluetooth') diff --git a/src/detection/bluetooth/bluetooth.h b/src/detection/bluetooth/bluetooth.h new file mode 100644 index 0000000..dde8085 --- /dev/null +++ b/src/detection/bluetooth/bluetooth.h @@ -0,0 +1,14 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/bluetooth/option.h" + +typedef struct FFBluetoothResult { + FFstrbuf name; + FFstrbuf address; + FFstrbuf type; + uint8_t battery; // 0-100% + bool connected; +} FFBluetoothResult; + +const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */); diff --git a/src/detection/bluetooth/bluetooth_apple.m b/src/detection/bluetooth/bluetooth_apple.m new file mode 100644 index 0000000..9cbf732 --- /dev/null +++ b/src/detection/bluetooth/bluetooth_apple.m @@ -0,0 +1,112 @@ +#include "bluetooth.h" + +#import + +@interface IOBluetoothDevice() + @property (nonatomic) uint8_t batteryPercentCase; + @property (nonatomic) uint8_t batteryPercentCombined; + @property (nonatomic) uint8_t batteryPercentLeft; + @property (nonatomic) uint8_t batteryPercentRight; + @property (nonatomic) uint8_t batteryPercentSingle; +@end + +const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) +{ + NSArray* ioDevices = IOBluetoothDevice.pairedDevices; + if(!ioDevices) + return "IOBluetoothDevice.pairedDevices failed"; + + for(IOBluetoothDevice* ioDevice in ioDevices) + { + if (!options->showDisconnected && !ioDevice.isConnected) + continue; + + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *devices); + ffStrbufInitS(&device->name, ioDevice.name.UTF8String); + ffStrbufInitS(&device->address, ioDevice.addressString.UTF8String); + ffStrbufReplaceAllC(&device->address, '-', ':'); + ffStrbufUpperCase(&device->address); + ffStrbufInit(&device->type); + + if (ioDevice.batteryPercentSingle) + device->battery = ioDevice.batteryPercentSingle; + else if (ioDevice.batteryPercentCombined) + device->battery = ioDevice.batteryPercentCombined; + else if (ioDevice.batteryPercentCase) + device->battery = ioDevice.batteryPercentCase; + + device->connected = !!ioDevice.isConnected; + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorLimitedDiscoverableMode) + ffStrbufAppendS(&device->type, "Limited Discoverable Mode, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorReserved1) + ffStrbufAppendS(&device->type, "LE audio, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorReserved2) + ffStrbufAppendS(&device->type, "Reserved for future use, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorPositioning) + ffStrbufAppendS(&device->type, "Positioning, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorNetworking) + ffStrbufAppendS(&device->type, "Networking, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorRendering) + ffStrbufAppendS(&device->type, "Rendering, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorCapturing) + ffStrbufAppendS(&device->type, "Capturing, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorObjectTransfer) + ffStrbufAppendS(&device->type, "Object Transfer, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorAudio) + ffStrbufAppendS(&device->type, "Audio, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorTelephony) + ffStrbufAppendS(&device->type, "Telephony, "); + if(ioDevice.serviceClassMajor & kBluetoothServiceClassMajorInformation) + ffStrbufAppendS(&device->type, "Information, "); + + if(device->type.length == 0) + { + switch(ioDevice.deviceClassMajor) + { + case kBluetoothDeviceClassMajorMiscellaneous: + ffStrbufAppendS(&device->type, "Miscellaneous"); + break; + case kBluetoothDeviceClassMajorComputer: + ffStrbufAppendS(&device->type, "Computer"); + break; + case kBluetoothDeviceClassMajorPhone: + ffStrbufAppendS(&device->type, "Phone"); + break; + case kBluetoothDeviceClassMajorLANAccessPoint: + ffStrbufAppendS(&device->type, "LAN/Network Access point"); + break; + case kBluetoothDeviceClassMajorAudio: + ffStrbufAppendS(&device->type, "Audio/Video"); + break; + case kBluetoothDeviceClassMajorPeripheral: + ffStrbufAppendS(&device->type, "Peripheral"); + break; + case kBluetoothDeviceClassMajorImaging: + ffStrbufAppendS(&device->type, "Imaging"); + break; + case kBluetoothDeviceClassMajorWearable: + ffStrbufAppendS(&device->type, "Wearable"); + break; + case kBluetoothDeviceClassMajorToy: + ffStrbufAppendS(&device->type, "Toy"); + break; + case kBluetoothDeviceClassMajorHealth: + ffStrbufAppendS(&device->type, "Health"); + break; + case kBluetoothDeviceClassMajorUnclassified: + ffStrbufAppendS(&device->type, "Uncategorized"); + break; + default: + ffStrbufAppendS(&device->type, "Unknown"); + break; + } + } + else + { + ffStrbufTrimRight(&device->type, ' '); + ffStrbufTrimRight(&device->type, ','); + } + } + + return NULL; +} diff --git a/src/detection/bluetooth/bluetooth_bsd.c b/src/detection/bluetooth/bluetooth_bsd.c new file mode 100644 index 0000000..db9f23c --- /dev/null +++ b/src/detection/bluetooth/bluetooth_bsd.c @@ -0,0 +1,30 @@ +#include "bluetooth.h" + +#define L2CAP_SOCKET_CHECKED +#include + +static int enumDev(FF_A_UNUSED int sockfd, struct bt_devinfo const* dev, FFlist* devices) { + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *devices); + ffStrbufInitS(&device->name, +#if __FreeBSD__ + bt_devremote_name_gen(dev->devname, &dev->bdaddr) +#else + dev->devname +#endif + ); + ffStrbufInitS(&device->address, bt_ntoa(&dev->bdaddr, NULL)); + ffStrbufUpperCase(&device->address); + ffStrbufInit(&device->type); + device->battery = 0; + device->connected = true; + return 0; +} + +const char* ffDetectBluetooth(FF_A_UNUSED FFBluetoothOptions* options, FF_A_UNUSED FFlist* devices /* FFBluetoothResult */) { + // struct hostent* ent = bt_gethostent(); + if (bt_devenum((void*) enumDev, devices) < 0) { + return "bt_devenum() failed"; + } + + return NULL; +} diff --git a/src/detection/bluetooth/bluetooth_haiku.cpp b/src/detection/bluetooth/bluetooth_haiku.cpp new file mode 100644 index 0000000..8ed52c5 --- /dev/null +++ b/src/detection/bluetooth/bluetooth_haiku.cpp @@ -0,0 +1,30 @@ +extern "C" { +#include "bluetooth.h" +#include "common/io.h" +} + +#include + +const char* ffDetectBluetooth(FF_A_UNUSED FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { + using namespace Bluetooth; + FF_SUPPRESS_IO(); + + LocalDevice* dev = LocalDevice::GetLocalDevice(); + if (!dev) { + return NULL; + } + + BString devClass; + dev->GetDeviceClass().DumpDeviceClass(devClass); + + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *devices); + ffStrbufInitS(&device->name, dev->GetFriendlyName()); + ffStrbufInitS(&device->address, bdaddrUtils::ToString(dev->GetBluetoothAddress()).String()); + ffStrbufInitS(&device->type, devClass.String()); + device->battery = 0; + device->connected = true; + + // TODO: more devices? + + return NULL; +} 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 +} diff --git a/src/detection/bluetooth/bluetooth_nosupport.c b/src/detection/bluetooth/bluetooth_nosupport.c new file mode 100644 index 0000000..ac93cee --- /dev/null +++ b/src/detection/bluetooth/bluetooth_nosupport.c @@ -0,0 +1,5 @@ +#include "bluetooth.h" + +const char* ffDetectBluetooth(FF_A_UNUSED FFBluetoothOptions* options, FF_A_UNUSED FFlist* devices /* FFBluetoothResult */) { + return "Not supported on this platform"; +} diff --git a/src/detection/bluetooth/bluetooth_windows.c b/src/detection/bluetooth/bluetooth_windows.c new file mode 100644 index 0000000..8932aef --- /dev/null +++ b/src/detection/bluetooth/bluetooth_windows.c @@ -0,0 +1,223 @@ +#include "bluetooth.h" +#include "common/library.h" +#include "common/mallocHelper.h" +#include "common/windows/unicode.h" + +#define INITGUID +#include +#include +#include +#include + +#pragma GCC diagnostic ignored "-Wpointer-sign" + +// https://github.com/wine-mirror/wine/blob/ab6f4584b89f28504b0b277c0b4c723a86b4d6b7/include/ddk/bthguid.h#L4 +/* DEVPROP_TYPE_STRING */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_DeviceAddress, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 1); +/* DEVPROP_TYPE_UINT32 */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ClassOfDevice, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 10); +/* DEVPROP_TYPE_FILETIME */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_LastConnectedTime, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 11); +/* DEVPROP_TYPE_GUID */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ServiceGUID, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 2); +/* DEVPROP_TYPE_UINT8 */ +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_BatteryLevel, 0x104ea319, 0x6ee2, 0x4701, 0xbd, 0x47, 0x8d, 0xdb, 0xf4, 0x25, 0xbb, 0xe5, 2); + +// TODO: use CM API to fetch bluetooth devices instead of BluetoothFindFirstDevice, if we find DEVPKEY_Bluetooth_IsConnected or similar +#define GUID_DEVCLASS_BLUETOOTH_STRING L"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}" // Found in +#define GUID_DEVCLASS_MEDIA_STRING L"{4d36e96c-e325-11ce-bfc1-08002be10318}" // Found in + +static const char* ffBluetoothDetectBattery(FFlist* devices) { + ULONG idListLength = 0; + CONFIGRET status = CM_Get_Device_ID_List_SizeW(&idListLength, GUID_DEVCLASS_MEDIA_STRING, CM_GETIDLIST_FILTER_PRESENT); + if (status != CR_SUCCESS) { + return "CM_Get_Device_ID_List_SizeW failed"; + } + + if (idListLength == 0) { + return NULL; + } + + wchar_t* FF_AUTO_FREE idList = (wchar_t*) malloc((size_t) idListLength * sizeof(wchar_t)); + if (!idList) { + return "malloc() failed"; + } + + status = CM_Get_Device_ID_ListW(GUID_DEVCLASS_MEDIA_STRING, idList, idListLength, CM_GETIDLIST_FILTER_PRESENT); + if (status != CR_SUCCESS) { + return "CM_Get_Device_ID_ListW failed"; + } + + for (const wchar_t* deviceId = idList; *deviceId; deviceId += wcslen(deviceId) + 1) { + DEVINST devInst = 0; + + // Hands-Free profile service; headsets often expose battery level through this media device node rather than the Bluetooth device node + // BthHFEnum + if (CM_Locate_DevNodeW(&devInst, (DEVINSTID_W) deviceId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { + continue; + } + + uint8_t battery = 0; + { + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; + ULONG propertySize = sizeof(battery); + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_BatteryLevel, &devPropertyType, (PBYTE) &battery, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_BYTE || propertySize != sizeof(battery)) { + continue; + } + } + + WCHAR deviceAddress[13]; // 6 bytes in hex + null terminator + { + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; + ULONG propertySize = sizeof(deviceAddress); + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_DeviceAddress, &devPropertyType, (PBYTE) deviceAddress, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_STRING || propertySize != sizeof(deviceAddress)) { + continue; + } + } + + FF_LIST_FOR_EACH (FFBluetoothResult, bt, *devices) { + if (deviceAddress[0] == bt->address.chars[0] && + deviceAddress[1] == bt->address.chars[1] && + deviceAddress[2] == bt->address.chars[3] && + deviceAddress[3] == bt->address.chars[4] && + deviceAddress[4] == bt->address.chars[6] && + deviceAddress[5] == bt->address.chars[7] && + deviceAddress[6] == bt->address.chars[9] && + deviceAddress[7] == bt->address.chars[10] && + deviceAddress[8] == bt->address.chars[12] && + deviceAddress[9] == bt->address.chars[13] && + deviceAddress[10] == bt->address.chars[15] && + deviceAddress[11] == bt->address.chars[16]) { + bt->battery = battery; + break; + } + } + } + + return NULL; +} + +const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { + FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstDevice) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextDevice) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindDeviceClose) + + BLUETOOTH_DEVICE_INFO btdi = { + .dwSize = sizeof(btdi) + }; + HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstDevice(&(BLUETOOTH_DEVICE_SEARCH_PARAMS) { + .fReturnConnected = TRUE, + .fReturnRemembered = options->showDisconnected, + .fReturnAuthenticated = options->showDisconnected, + .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS) }, + &btdi); + if (!hFind) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + return NULL; + } + return "BluetoothFindFirstDevice() failed"; + } + + do { + if (!options->showDisconnected && !btdi.fConnected) { + continue; + } + + FFBluetoothResult* device = FF_LIST_ADD(FFBluetoothResult, *devices); + ffStrbufInitWS(&device->name, btdi.szName); + ffStrbufInitF(&device->address, "%02X:%02X:%02X:%02X:%02X:%02X", btdi.Address.rgBytes[5], btdi.Address.rgBytes[4], btdi.Address.rgBytes[3], btdi.Address.rgBytes[2], btdi.Address.rgBytes[1], btdi.Address.rgBytes[0]); + ffStrbufInit(&device->type); + device->battery = 0; + device->connected = !!btdi.fConnected; + + // https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Assigned%20Numbers.pdf + + if (BitTest(&btdi.ulClassofDevice, 13)) { + ffStrbufAppendS(&device->type, "Limited Discoverable Mode, "); + } + if (BitTest(&btdi.ulClassofDevice, 14)) { + ffStrbufAppendS(&device->type, "LE audio, "); + } + if (BitTest(&btdi.ulClassofDevice, 15)) { + ffStrbufAppendS(&device->type, "Reserved for future use, "); + } + if (BitTest(&btdi.ulClassofDevice, 16)) { + ffStrbufAppendS(&device->type, "Positioning, "); + } + if (BitTest(&btdi.ulClassofDevice, 17)) { + ffStrbufAppendS(&device->type, "Networking, "); + } + if (BitTest(&btdi.ulClassofDevice, 18)) { + ffStrbufAppendS(&device->type, "Rendering, "); + } + if (BitTest(&btdi.ulClassofDevice, 19)) { + ffStrbufAppendS(&device->type, "Capturing, "); + } + if (BitTest(&btdi.ulClassofDevice, 20)) { + ffStrbufAppendS(&device->type, "Object Transfer, "); + } + if (BitTest(&btdi.ulClassofDevice, 21)) { + ffStrbufAppendS(&device->type, "Audio, "); + } + if (BitTest(&btdi.ulClassofDevice, 22)) { + ffStrbufAppendS(&device->type, "Telephony, "); + } + if (BitTest(&btdi.ulClassofDevice, 23)) { + ffStrbufAppendS(&device->type, "Information, "); + } + + if (device->type.length == 0) { + uint32_t majorDeviceClasses = (btdi.ulClassofDevice >> 8) & ~(UINT32_MAX << 5); + switch (majorDeviceClasses) { + case 0b00000: + ffStrbufAppendS(&device->type, "Miscellaneous"); + break; + case 0b00001: + ffStrbufAppendS(&device->type, "Computer"); + break; + case 0b00010: + ffStrbufAppendS(&device->type, "Phone"); + break; + case 0b00011: + ffStrbufAppendS(&device->type, "LAN/Network Access point"); + break; + case 0b00100: + ffStrbufAppendS(&device->type, "Audio/Video"); + break; + case 0b00101: + ffStrbufAppendS(&device->type, "Peripheral"); + break; + case 0b00110: + ffStrbufAppendS(&device->type, "Imaging"); + break; + case 0b00111: + ffStrbufAppendS(&device->type, "Wearable"); + break; + case 0b01000: + ffStrbufAppendS(&device->type, "Toy"); + break; + case 0b01001: + ffStrbufAppendS(&device->type, "Health"); + break; + case 0b11111: + ffStrbufAppendS(&device->type, "Uncategorized"); + break; + default: + ffStrbufAppendS(&device->type, "Unknown"); + break; + } + } else { + ffStrbufTrimRight(&device->type, ' '); + ffStrbufTrimRight(&device->type, ','); + } + } while (ffBluetoothFindNextDevice(hFind, &btdi)); + + ffBluetoothFindDeviceClose(hFind); + + if (devices->length > 0) { + ffBluetoothDetectBattery(devices); + } + + return NULL; +} -- cgit v1.2.3