diff options
| author | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
|---|---|---|
| committer | sumuel <samuel@yakubos.org> | 2026-08-17 20:44:55 +0000 |
| commit | 76424950e373d3b04ac3dd13019151bfba3e8423 (patch) | |
| tree | 4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/bluetoothradio | |
Add the files
Diffstat (limited to 'src/detection/bluetoothradio')
| -rw-r--r-- | src/detection/bluetoothradio/bluetoothradio.c | 161 | ||||
| -rw-r--r-- | src/detection/bluetoothradio/bluetoothradio.h | 18 | ||||
| -rw-r--r-- | src/detection/bluetoothradio/bluetoothradio_apple.m | 69 | ||||
| -rw-r--r-- | src/detection/bluetoothradio/bluetoothradio_linux.c | 153 | ||||
| -rw-r--r-- | src/detection/bluetoothradio/bluetoothradio_nosupport.c | 5 | ||||
| -rw-r--r-- | src/detection/bluetoothradio/bluetoothradio_windows.c | 99 |
6 files changed, 505 insertions, 0 deletions
diff --git a/src/detection/bluetoothradio/bluetoothradio.c b/src/detection/bluetoothradio/bluetoothradio.c new file mode 100644 index 0000000..117e225 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio.c @@ -0,0 +1,161 @@ +#include "bluetoothradio.h" + +// https://github.com/ziglang/zig/blob/a84951465b409495095a9598db0cae745f34fa7b/lib/libc/include/any-windows-any/bthdef.h#L187-L236 + +#define BTH_MFG_ERICSSON 0 +#define BTH_MFG_NOKIA 1 +#define BTH_MFG_INTEL 2 +#define BTH_MFG_IBM 3 +#define BTH_MFG_TOSHIBA 4 +#define BTH_MFG_3COM 5 +#define BTH_MFG_MICROSOFT 6 +#define BTH_MFG_LUCENT 7 +#define BTH_MFG_MOTOROLA 8 +#define BTH_MFG_INFINEON 9 +#define BTH_MFG_CSR 10 +#define BTH_MFG_SILICONWAVE 11 +#define BTH_MFG_DIGIANSWER 12 +#define BTH_MFG_TI 13 +#define BTH_MFG_PARTHUS 14 +#define BTH_MFG_BROADCOM 15 +#define BTH_MFG_MITEL 16 +#define BTH_MFG_WIDCOMM 17 +#define BTH_MFG_ZEEVO 18 +#define BTH_MFG_ATMEL 19 +#define BTH_MFG_MITSIBUSHI 20 +#define BTH_MFG_RTX_TELECOM 21 +#define BTH_MFG_KC_TECHNOLOGY 22 +#define BTH_MFG_NEWLOGIC 23 +#define BTH_MFG_TRANSILICA 24 +#define BTH_MFG_ROHDE_SCHWARZ 25 +#define BTH_MFG_TTPCOM 26 +#define BTH_MFG_SIGNIA 27 +#define BTH_MFG_CONEXANT 28 +#define BTH_MFG_QUALCOMM 29 +#define BTH_MFG_INVENTEL 30 +#define BTH_MFG_AVM_BERLIN 31 +#define BTH_MFG_BANDSPEED 32 +#define BTH_MFG_MANSELLA 33 +#define BTH_MFG_NEC 34 +#define BTH_MFG_WAVEPLUS_TECHNOLOGY_CO 35 +#define BTH_MFG_ALCATEL 36 +#define BTH_MFG_PHILIPS_SEMICONDUCTOR 37 +#define BTH_MFG_C_TECHNOLOGIES 38 +#define BTH_MFG_OPEN_INTERFACE 39 +#define BTH_MFG_RF_MICRO_DEVICES 40 +#define BTH_MFG_HITACHI 41 +#define BTH_MFG_SYMBOL_TECHNOLOGIES 42 +#define BTH_MFG_TENOVIS 43 +#define BTH_MFG_MACRONIX_INTERNATIONAL 44 +#define BTH_MFG_MARVELL 72 +#define BTH_MFG_APPLE 76 +#define BTH_MFG_NORDIC_SEMICONDUCTORS_ASA 89 +#define BTH_MFG_ARUBA_NETWORKS 283 +#define BTH_MFG_INTERNAL_USE 65535 + +const char* ffBluetoothRadioGetVendor(uint32_t manufacturerId) { + switch (manufacturerId) { + case BTH_MFG_ERICSSON: + return "Ericsson"; + case BTH_MFG_NOKIA: + return "Nokia"; + case BTH_MFG_INTEL: + return "Intel"; + case BTH_MFG_IBM: + return "IBM"; + case BTH_MFG_TOSHIBA: + return "Toshiba"; + case BTH_MFG_3COM: + return "3Com"; + case BTH_MFG_MICROSOFT: + return "Microsoft"; + case BTH_MFG_LUCENT: + return "Lucent"; + case BTH_MFG_MOTOROLA: + return "Motorola"; + case BTH_MFG_INFINEON: + return "Infineon"; + case BTH_MFG_CSR: + return "CSR"; + case BTH_MFG_SILICONWAVE: + return "Silicon-Wave"; + case BTH_MFG_DIGIANSWER: + return "Digi-Answer"; + case BTH_MFG_TI: + return "Ti"; + case BTH_MFG_PARTHUS: + return "Parthus"; + case BTH_MFG_BROADCOM: + return "Broadcom"; + case BTH_MFG_MITEL: + return "Mitel"; + case BTH_MFG_WIDCOMM: + return "Widcomm"; + case BTH_MFG_ZEEVO: + return "Zeevo"; + case BTH_MFG_ATMEL: + return "Atmel"; + case BTH_MFG_MITSIBUSHI: + return "Mitsubishi"; + case BTH_MFG_RTX_TELECOM: + return "RTX Telecom"; + case BTH_MFG_KC_TECHNOLOGY: + return "KC Technology"; + case BTH_MFG_NEWLOGIC: + return "Newlogic"; + case BTH_MFG_TRANSILICA: + return "Transilica"; + case BTH_MFG_ROHDE_SCHWARZ: + return "Rohde-Schwarz"; + case BTH_MFG_TTPCOM: + return "TTPCom"; + case BTH_MFG_SIGNIA: + return "Signia"; + case BTH_MFG_CONEXANT: + return "Conexant"; + case BTH_MFG_QUALCOMM: + return "Qualcomm"; + case BTH_MFG_INVENTEL: + return "Inventel"; + case BTH_MFG_AVM_BERLIN: + return "AVM Berlin"; + case BTH_MFG_BANDSPEED: + return "Bandspeed"; + case BTH_MFG_MANSELLA: + return "Mansella"; + case BTH_MFG_NEC: + return "NEC"; + case BTH_MFG_WAVEPLUS_TECHNOLOGY_CO: + return "Waveplus"; + case BTH_MFG_ALCATEL: + return "Alcatel"; + case BTH_MFG_PHILIPS_SEMICONDUCTOR: + return "Philips Semiconductors"; + case BTH_MFG_C_TECHNOLOGIES: + return "C Technologies"; + case BTH_MFG_OPEN_INTERFACE: + return "Open Interface"; + case BTH_MFG_RF_MICRO_DEVICES: + return "RF Micro Devices"; + case BTH_MFG_HITACHI: + return "Hitachi"; + case BTH_MFG_SYMBOL_TECHNOLOGIES: + return "Symbol Technologies"; + case BTH_MFG_TENOVIS: + return "Tenovis"; + case BTH_MFG_MACRONIX_INTERNATIONAL: + return "Macronix International"; + case BTH_MFG_MARVELL: + return "Marvell"; + case BTH_MFG_APPLE: + return "Apple"; + case BTH_MFG_NORDIC_SEMICONDUCTORS_ASA: + return "Nordic Semiconductor ASA"; + case BTH_MFG_ARUBA_NETWORKS: + return "Aruba Networks"; + case BTH_MFG_INTERNAL_USE: + return "Internal Use"; + default: + return "Unknown"; + } +} diff --git a/src/detection/bluetoothradio/bluetoothradio.h b/src/detection/bluetoothradio/bluetoothradio.h new file mode 100644 index 0000000..a3c1208 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio.h @@ -0,0 +1,18 @@ +#pragma once + +#include "fastfetch.h" +#include "modules/bluetoothradio/option.h" + +typedef struct FFBluetoothRadioResult { + FFstrbuf name; + FFstrbuf address; + FFstrbuf vendor; + int32_t lmpVersion; + int32_t lmpSubversion; + bool enabled; + bool discoverable; + bool connectable; +} FFBluetoothRadioResult; + +const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */); +const char* ffBluetoothRadioGetVendor(uint32_t manufacturerId); diff --git a/src/detection/bluetoothradio/bluetoothradio_apple.m b/src/detection/bluetoothradio/bluetoothradio_apple.m new file mode 100644 index 0000000..c5154ab --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_apple.m @@ -0,0 +1,69 @@ +#include "bluetoothradio.h" +#include "common/processing.h" + +#import <IOBluetooth/IOBluetooth.h> + +// For some reason the official declaration of IOBluetoothHostController doesn't include property `controllers` +@interface IOBluetoothHostController() ++ (id)controllers; +@end + +const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) +{ + NSArray<IOBluetoothHostController*>* ctrls = IOBluetoothHostController.controllers; + if(!ctrls) + return "IOBluetoothHostController.controllers returns nil"; + + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + if (ffProcessAppendStdOut(&buffer, (char* const[]) { + "system_profiler", + "SPBluetoothDataType", + "-xml", + "-detailLevel", + "basic", + NULL + }) != NULL) + return "Starting `system_profiler SPBluetoothDataType -xml -detailLevel basic` failed"; + + NSArray* arr = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytes:buffer.chars length:buffer.length] + options:NSPropertyListImmutable + format:nil + error:nil]; + if (!arr || !arr.count) + return "system_profiler SPBluetoothDataType returned an empty array"; + + for (IOBluetoothHostController* ctrl in ctrls) + { + FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices); + ffStrbufInitS(&device->name, ctrl.nameAsString.UTF8String); + ffStrbufInitS(&device->address, ctrl.addressAsString.UTF8String); + ffStrbufInitStatic(&device->vendor, "Apple"); + device->lmpVersion = INT_MIN; + device->lmpSubversion = INT_MIN; + device->enabled = ctrl.powerState == kBluetoothHCIPowerStateON; + device->discoverable = false; + device->connectable = true; + + for (NSDictionary* itemDict in arr[0][@"_items"]) + { + NSDictionary* props = itemDict[@"controller_properties"]; + if (!props) continue; + + if (![ctrl.addressAsString isEqualToString:props[@"controller_address"]]) continue; + + NSString* services = props[@"controller_supportedServices"]; + if ([services containsString:@" LEA "]) + device->lmpVersion = -11; + else if ([services containsString:@" GATT "]) + device->lmpVersion = -6; + + device->discoverable = ![props[@"controller_discoverable"] isEqualToString:@"attrib_off"]; + ffStrbufSetS(&device->vendor, ((NSString*) props[@"controller_vendorID"]).UTF8String); + ffStrbufSubstrAfterFirstC(&device->vendor, '('); + ffStrbufTrimRight(&device->vendor, ')'); + break; + } + } + + return NULL; +} diff --git a/src/detection/bluetoothradio/bluetoothradio_linux.c b/src/detection/bluetoothradio/bluetoothradio_linux.c new file mode 100644 index 0000000..a7d92a4 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_linux.c @@ -0,0 +1,153 @@ +#include "bluetoothradio.h" +#include "common/strutil.h" + +#ifdef FF_HAVE_DBUS + #include "common/dbus.h" + #include "common/io.h" + +/* Example dbus reply: +array [ + dict entry( + string "Address" + variant string "XX:XX:XX:XX:XX:XX" + ) + dict entry( + string "Name" + variant string "xxxxxxxx" + ) + dict entry( + string "Powered" + variant boolean true + ) + dict entry( + string "PowerState" + variant string "on" + ) + dict entry( + string "Manufacturer" + variant uint16 2 + ) + dict entry( + string "Version" + variant byte 12 + ) +] +*/ + +static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter) { + if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) { + return "Expected dict entry"; + } + + DBusMessageIter dictIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter); + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) { + return "Expected dict entry key to be a string"; + } + + 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, "Alias")) { + ffDBusGetString(dbus, &dictIter, &device->name); + } else if (ffStrEquals(deviceProperty, "Manufacturer")) { + uint64_t vendorId; + if (ffDBusGetUint(dbus, &dictIter, &vendorId)) { + ffStrbufSetStatic(&device->vendor, ffBluetoothRadioGetVendor((uint32_t) vendorId)); + } + } else if (ffStrEquals(deviceProperty, "Version")) { + uint64_t version; + if (ffDBusGetUint(dbus, &dictIter, &version)) { + device->lmpVersion = (int32_t) version; + } + } else if (ffStrEquals(deviceProperty, "Powered")) { + ffDBusGetBool(dbus, &dictIter, &device->enabled); + } else if (ffStrEquals(deviceProperty, "Discoverable")) { + ffDBusGetBool(dbus, &dictIter, &device->discoverable); + } else if (ffStrEquals(deviceProperty, "Pairable")) { + ffDBusGetBool(dbus, &dictIter, &device->connectable); + } + + return NULL; +} + +static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const char* hciName, FFDBusData* dbus) { + char objPath[300]; + snprintf(objPath, sizeof(objPath), "/org/bluez/%s", hciName); + + DBusMessage* properties = ffDBusGetMethodReply(dbus, "org.bluez", objPath, "org.freedesktop.DBus.Properties", "GetAll", "org.bluez.Adapter1", NULL); + if (!properties) { + return "Failed to call org.freedesktop.DBus.Properties.GetAll"; + } + + DBusMessageIter rootIter; + if (!dbus->lib->ffdbus_message_iter_init(properties, &rootIter)) { + dbus->lib->ffdbus_message_unref(properties); + return "Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll"; + } + + if (dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY) { + dbus->lib->ffdbus_message_unref(properties); + return "Expected array"; + } + + DBusMessageIter arrayIter; + dbus->lib->ffdbus_message_iter_recurse(&rootIter, &arrayIter); + + do { + detectBluetoothProperty(device, dbus, &arrayIter); + } while (dbus->lib->ffdbus_message_iter_next(&arrayIter)); + + dbus->lib->ffdbus_message_unref(properties); + return NULL; +} + +static const char* detectBluetooth(FFlist* devices) { + FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth"); + if (dirp == NULL) { + return "Failed to open /sys/class/bluetooth"; + } + + FF_DBUS_AUTO_DESTROY_DATA FFDBusData dbus = {}; + const char* error = ffDBusLoadData(DBUS_BUS_SYSTEM, &dbus); + if (error) { + return error; + } + + struct dirent* entry; + while ((entry = readdir(dirp)) != NULL) { + if (entry->d_name[0] == '.') { + continue; + } + + if (strchr(entry->d_name, ':') != NULL) { // ignore connected devices + continue; + } + + FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices); + ffStrbufInit(&device->name); + ffStrbufInit(&device->address); + ffStrbufInitStatic(&device->vendor, "Unknown"); + device->lmpVersion = INT_MIN; + device->lmpSubversion = INT_MIN; + device->enabled = false; + detectBluetoothRoot(device, entry->d_name, &dbus); + } + return NULL; +} + +#endif + +const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) { +#ifdef FF_HAVE_DBUS + return detectBluetooth(devices); +#else + FF_UNUSED(devices) + return "Fastfetch was compiled without DBus support"; +#endif +} diff --git a/src/detection/bluetoothradio/bluetoothradio_nosupport.c b/src/detection/bluetoothradio/bluetoothradio_nosupport.c new file mode 100644 index 0000000..08fb6f8 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_nosupport.c @@ -0,0 +1,5 @@ +#include "bluetoothradio.h" + +const char* ffDetectBluetoothRadio(FF_A_UNUSED FFlist* devices /* FFBluetoothRadioResult */) { + return "Not supported on this platform"; +} diff --git a/src/detection/bluetoothradio/bluetoothradio_windows.c b/src/detection/bluetoothradio/bluetoothradio_windows.c new file mode 100644 index 0000000..f0b55d4 --- /dev/null +++ b/src/detection/bluetoothradio/bluetoothradio_windows.c @@ -0,0 +1,99 @@ +#include "bluetoothradio.h" +#include "common/library.h" +#include "common/io.h" +#include "common/windows/unicode.h" + +#include <windows.h> +#include <bluetoothapis.h> +#include <winioctl.h> + +// #include <bthioctl.h> + +#define BTH_IOCTL_BASE 0 +#define BTH_CTL(id) CTL_CODE(FILE_DEVICE_BLUETOOTH, (id), METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_GET_LOCAL_INFO BTH_CTL(BTH_IOCTL_BASE + 0x00) +#define LMP_LE_SUPPORTED(x) ((x >> 38) & 1) + +typedef struct _BTH_RADIO_INFO { + // Supported LMP features of the radio. Use LMP_XXX() to extract + // the desired bits. + ULONGLONG lmpSupportedFeatures; + + // Manufacturer ID (possibly BTH_MFG_XXX) + USHORT mfg; + + // LMP subversion + USHORT lmpSubversion; + + // LMP version + UCHAR lmpVersion; +} FF_A_PACKED BTH_RADIO_INFO; + +typedef struct _BTH_LOCAL_RADIO_INFO { + // Local BTH_ADDR, class of device, and radio name + BTH_DEVICE_INFO localInfo; + + // Combo of LOCAL_RADIO_XXX values + ULONG flags; + + // HCI revision, see core spec + USHORT hciRevision; + + // HCI version, see core spec + UCHAR hciVersion; + + // More information about the local radio (LMP, MFG) + BTH_RADIO_INFO radioInfo; +} FF_A_PACKED BTH_LOCAL_RADIO_INFO; +static_assert(sizeof(BTH_LOCAL_RADIO_INFO) == 292, "BTH_LOCAL_RADIO_INFO should be 292 bytes"); + +#pragma GCC diagnostic ignored "-Wpointer-sign" + +const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */) { + // Actually bluetoothapis.dll, but it's missing on Windows 7 + FF_LIBRARY_LOAD_MESSAGE(bluetoothapis, "bluetoothapis.dll", 1) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindFirstRadio) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindNextRadio) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothFindRadioClose) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsConnectable) + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(bluetoothapis, BluetoothIsDiscoverable) + + HANDLE hRadio = NULL; + HBLUETOOTH_DEVICE_FIND hFind = ffBluetoothFindFirstRadio(&(BLUETOOTH_FIND_RADIO_PARAMS) { + .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) }, + &hRadio); + if (!hFind) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + return "No Bluetooth radios found or service disabled"; + } else { + return "BluetoothFindFirstRadio() failed"; + } + } + + do { + BTH_LOCAL_RADIO_INFO blri; + DWORD returned; + if (!DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, NULL, 0, &blri, sizeof(blri), &returned, NULL)) { + continue; + } + + FFBluetoothRadioResult* device = FF_LIST_ADD(FFBluetoothRadioResult, *devices); + ffStrbufInitS(&device->name, blri.localInfo.name); + + BLUETOOTH_ADDRESS_STRUCT addr = { .ullLong = blri.localInfo.address }; + ffStrbufInitF(&device->address, "%02X:%02X:%02X:%02X:%02X:%02X", addr.rgBytes[5], addr.rgBytes[4], addr.rgBytes[3], addr.rgBytes[2], addr.rgBytes[1], addr.rgBytes[0]); + + device->lmpVersion = blri.radioInfo.lmpVersion; + device->lmpSubversion = blri.radioInfo.lmpSubversion; + ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg)); + device->enabled = true; + device->connectable = ffBluetoothIsConnectable(hRadio); + device->discoverable = ffBluetoothIsDiscoverable(hRadio); + + NtClose(hRadio); + } while (ffBluetoothFindNextRadio(hFind, &hRadio)); + + ffBluetoothFindRadioClose(hFind); + + return NULL; +} |