summaryrefslogtreecommitdiffstats
path: root/src/detection/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/bluetooth')
-rw-r--r--src/detection/bluetooth/bluetooth.h14
-rw-r--r--src/detection/bluetooth/bluetooth_apple.m112
-rw-r--r--src/detection/bluetooth/bluetooth_bsd.c30
-rw-r--r--src/detection/bluetooth/bluetooth_haiku.cpp30
-rw-r--r--src/detection/bluetooth/bluetooth_linux.c256
-rw-r--r--src/detection/bluetooth/bluetooth_nosupport.c5
-rw-r--r--src/detection/bluetooth/bluetooth_windows.c223
7 files changed, 670 insertions, 0 deletions
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 <IOBluetooth/IOBluetooth.h>
+
+@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<IOBluetoothDevice*>* 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 <bluetooth.h>
+
+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 <bluetooth/LocalDevice.h>
+
+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 <windows.h>
+#include <bluetoothapis.h>
+#include <cfgmgr32.h>
+#include <devpkey.h>
+
+#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 <devguid.h>
+#define GUID_DEVCLASS_MEDIA_STRING L"{4d36e96c-e325-11ce-bfc1-08002be10318}" // Found in <devguid.h>
+
+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;
+}