|
1 | 1 | #include "bluetooth.h" |
2 | 2 | #include "common/library.h" |
| 3 | +#include "common/mallocHelper.h" |
3 | 4 | #include "common/windows/unicode.h" |
4 | 5 |
|
| 6 | +#define INITGUID |
5 | 7 | #include <windows.h> |
6 | 8 | #include <bluetoothapis.h> |
| 9 | +#include <cfgmgr32.h> |
| 10 | +#include <devpkey.h> |
7 | 11 |
|
8 | 12 | #pragma GCC diagnostic ignored "-Wpointer-sign" |
9 | 13 |
|
| 14 | +// https://github.com/wine-mirror/wine/blob/ab6f4584b89f28504b0b277c0b4c723a86b4d6b7/include/ddk/bthguid.h#L4 |
| 15 | +/* DEVPROP_TYPE_STRING */ |
| 16 | +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_DeviceAddress, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 1); |
| 17 | +/* DEVPROP_TYPE_UINT32 */ |
| 18 | +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ClassOfDevice, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 10); |
| 19 | +/* DEVPROP_TYPE_FILETIME */ |
| 20 | +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_LastConnectedTime, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 11); |
| 21 | +/* DEVPROP_TYPE_GUID */ |
| 22 | +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_ServiceGUID, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a, 2); |
| 23 | +/* DEVPROP_TYPE_UINT8 */ |
| 24 | +DEFINE_DEVPROPKEY(DEVPKEY_Bluetooth_BatteryLevel, 0x104ea319, 0x6ee2, 0x4701, 0xbd, 0x47, 0x8d, 0xdb, 0xf4, 0x25, 0xbb, 0xe5, 2); |
| 25 | + |
| 26 | +// TODO: use CM API to fetch bluetooth devices instead of BluetoothFindFirstDevice, if we find DEVPKEY_Bluetooth_IsConnected or similar |
| 27 | +#define GUID_DEVCLASS_BLUETOOTH_STRING L"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}" // Found in <devguid.h> |
| 28 | +#define GUID_DEVCLASS_MEDIA_STRING L"{4d36e96c-e325-11ce-bfc1-08002be10318}" // Found in <devguid.h> |
| 29 | + |
| 30 | +static const char* ffBluetoothDetectBattery(FFlist* devices) { |
| 31 | + ULONG idListLength = 0; |
| 32 | + CONFIGRET status = CM_Get_Device_ID_List_SizeW(&idListLength, GUID_DEVCLASS_MEDIA_STRING, CM_GETIDLIST_FILTER_PRESENT); |
| 33 | + if (status != CR_SUCCESS) { |
| 34 | + return "CM_Get_Device_ID_List_SizeW failed"; |
| 35 | + } |
| 36 | + |
| 37 | + if (idListLength == 0) { |
| 38 | + return NULL; |
| 39 | + } |
| 40 | + |
| 41 | + wchar_t* FF_AUTO_FREE idList = (wchar_t*) malloc((size_t) idListLength * sizeof(wchar_t)); |
| 42 | + if (!idList) { |
| 43 | + return "malloc() failed"; |
| 44 | + } |
| 45 | + |
| 46 | + status = CM_Get_Device_ID_ListW(GUID_DEVCLASS_MEDIA_STRING, idList, idListLength, CM_GETIDLIST_FILTER_PRESENT); |
| 47 | + if (status != CR_SUCCESS) { |
| 48 | + return "CM_Get_Device_ID_ListW failed"; |
| 49 | + } |
| 50 | + |
| 51 | + for (const wchar_t* deviceId = idList; *deviceId; deviceId += wcslen(deviceId) + 1) { |
| 52 | + DEVINST devInst = 0; |
| 53 | + |
| 54 | + // Hands-Free profile service; headsets often expose battery level through this media device node rather than the Bluetooth device node |
| 55 | + // BthHFEnum |
| 56 | + if (CM_Locate_DevNodeW(&devInst, (DEVINSTID_W) deviceId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) { |
| 57 | + continue; |
| 58 | + } |
| 59 | + |
| 60 | + uint8_t battery = 0; |
| 61 | + { |
| 62 | + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; |
| 63 | + ULONG propertySize = sizeof(battery); |
| 64 | + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_BatteryLevel, &devPropertyType, (PBYTE) &battery, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_BYTE || propertySize != sizeof(battery)) { |
| 65 | + continue; |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + WCHAR deviceAddress[13]; // 6 bytes in hex + null terminator |
| 70 | + { |
| 71 | + DEVPROPTYPE devPropertyType = DEVPROP_TYPE_EMPTY; |
| 72 | + ULONG propertySize = sizeof(deviceAddress); |
| 73 | + if (CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Bluetooth_DeviceAddress, &devPropertyType, (PBYTE) deviceAddress, &propertySize, 0) != CR_SUCCESS || devPropertyType != DEVPROP_TYPE_STRING || propertySize != sizeof(deviceAddress)) { |
| 74 | + continue; |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + FF_LIST_FOR_EACH (FFBluetoothResult, bt, *devices) { |
| 79 | + if (deviceAddress[0] == bt->address.chars[0] && |
| 80 | + deviceAddress[1] == bt->address.chars[1] && |
| 81 | + deviceAddress[2] == bt->address.chars[3] && |
| 82 | + deviceAddress[3] == bt->address.chars[4] && |
| 83 | + deviceAddress[4] == bt->address.chars[6] && |
| 84 | + deviceAddress[5] == bt->address.chars[7] && |
| 85 | + deviceAddress[6] == bt->address.chars[9] && |
| 86 | + deviceAddress[7] == bt->address.chars[10] && |
| 87 | + deviceAddress[8] == bt->address.chars[12] && |
| 88 | + deviceAddress[9] == bt->address.chars[13] && |
| 89 | + deviceAddress[10] == bt->address.chars[15] && |
| 90 | + deviceAddress[11] == bt->address.chars[16]) { |
| 91 | + bt->battery = battery; |
| 92 | + break; |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + return NULL; |
| 98 | +} |
| 99 | + |
10 | 100 | const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FFBluetoothResult */) { |
11 | 101 | FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) |
12 | 102 | FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstDevice) |
@@ -125,8 +215,9 @@ const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FF |
125 | 215 |
|
126 | 216 | ffBluetoothFindDeviceClose(hFind); |
127 | 217 |
|
128 | | - const char* ffBluetoothDetectBattery(FFlist * result); |
129 | | - ffBluetoothDetectBattery(devices); |
| 218 | + if (devices->length > 0) { |
| 219 | + ffBluetoothDetectBattery(devices); |
| 220 | + } |
130 | 221 |
|
131 | 222 | return NULL; |
132 | 223 | } |
0 commit comments