From 25e81b39f9539a440c3927e4f1f8e3bc6453912d Mon Sep 17 00:00:00 2001 From: Linus Dierheimer Date: Tue, 24 Jan 2023 10:39:33 +0100 Subject: [PATCH] Bluetooth module --- CHANGELOG.md | 5 + CMakeLists.txt | 4 + README.md | 4 +- completions/bash | 4 + presets/all | 2 +- presets/verbose | 1 + src/common/dbus.c | 201 +++++++++++++++++ src/common/dbus.h | 57 +++++ src/common/init.c | 2 + src/data/config_user.txt | 3 + src/data/modules.txt | 1 + src/detection/bluetooth/bluetooth.c | 21 ++ src/detection/bluetooth/bluetooth.h | 19 ++ src/detection/bluetooth/bluetooth_linux.c | 200 +++++++++++++++++ src/detection/media/media_linux.c | 262 +++++----------------- src/fastfetch.c | 12 + src/fastfetch.h | 2 + src/modules/bluetooth.c | 36 +++ 18 files changed, 622 insertions(+), 214 deletions(-) create mode 100644 src/common/dbus.c create mode 100644 src/common/dbus.h create mode 100644 src/detection/bluetooth/bluetooth.c create mode 100644 src/detection/bluetooth/bluetooth.h create mode 100644 src/detection/bluetooth/bluetooth_linux.c create mode 100644 src/modules/bluetooth.c diff --git a/CHANGELOG.md b/CHANGELOG.md index b4fba05d4..5400ffdcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# dev + +Features: +* Bluetooth module + # 1.9.0 Notable Changes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d257dab7..29e6c62de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,6 +232,7 @@ set(LIBFASTFETCH_SRC src/common/printing.c src/common/properties.c src/common/settings.c + src/detection/bluetooth/bluetooth.c src/detection/cpu/cpu.c src/detection/cpuUsage/cpuUsage.c src/detection/datetime.c @@ -254,6 +255,7 @@ set(LIBFASTFETCH_SRC src/logo/logo.c src/modules/battery.c src/modules/bios.c + src/modules/bluetooth.c src/modules/board.c src/modules/brightness.c src/modules/break.c @@ -308,6 +310,7 @@ set(LIBFASTFETCH_SRC if(LINUX) list(APPEND LIBFASTFETCH_SRC + src/common/dbus.c src/common/networking_linux.c src/common/processing_linux.c src/detection/battery/battery_linux.c @@ -318,6 +321,7 @@ if(LINUX) src/detection/cpu/cpu_linux.c src/detection/cpuUsage/cpuUsage_linux.c src/detection/cursor/cursor_linux.c + src/detection/bluetooth/bluetooth_linux.c src/detection/disk/disk_linux.c src/detection/displayserver/linux/displayserver_linux.c src/detection/displayserver/linux/wayland.c diff --git a/README.md b/README.md index c1d5dbb99..45a092552 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ The following libraries are used if present at runtime: * [`libmagickcore` (ImageMagick)](https://www.imagemagick.org/): Images in terminal using sixel or kitty graphics protocol. * [`libchafa`](https://github.com/hpjansson/chafa): Image output as ascii art. * [`libZ`](https://www.zlib.net/): Faster image output when using kitty graphics protocol. -* [`libDBus`](https://www.freedesktop.org/wiki/Software/dbus): Needed for detecting current media player and song. +* [`libDBus`](https://www.freedesktop.org/wiki/Software/dbus): Bluetooth, Player & Media detection. * [`libEGL`](https://www.khronos.org/registry/EGL/), [`libGLX`](https://dri.freedesktop.org/wiki/GLX/), [`libOSMesa`](https://docs.mesa3d.org/osmesa.html): At least one of them is needed by the OpenGL module for gl context creation. @@ -88,7 +88,7 @@ All categories not listed here should work without needing a specific implementa ##### Available Modules ``` -Battery, Bios, Board, Break, Brightness, Colors, Command, CPU, CPUUsage, Cursor, Custom, Date, DateTime, DE, Disk, Display, Font, GPU, Host, Icons, Kernel, Locale, LocalIP, Media, Memory, OpenCL, OpenGL, Packages, Player, Power Adapter, Processes, PublicIP, Separator OS, Shell, Swap, Terminal, Terminal Font, Theme, Time, Title, Uptime, Vulkan, Wifi, WM, WMTheme +Battery, Bios, Bluetooth, Board, Break, Brightness, Colors, Command, CPU, CPUUsage, Cursor, Custom, Date, DateTime, DE, Disk, Display, Font, GPU, Host, Icons, Kernel, Locale, LocalIP, Media, Memory, OpenCL, OpenGL, Packages, Player, Power Adapter, Processes, PublicIP, Separator OS, Shell, Swap, Terminal, Terminal Font, Theme, Time, Title, Uptime, Vulkan, Wifi, WM, WMTheme ``` ##### Builtin logos diff --git a/completions/bash b/completions/bash index 7c32c4cc3..246a4904a 100644 --- a/completions/bash +++ b/completions/bash @@ -42,6 +42,7 @@ __fastfetch_complete_help() "vulkan-format" "opengl-format" "opencl-format" + "bluetooth-format" ) COMPREPLY=($(compgen -W "${__ff_helps[*]}" -- "$CURRENT_WORD")) } @@ -349,6 +350,9 @@ __fastfetch_completion() "--users-key" "--users-format" "--users-error" + "--bluetooth-key" + "--bluetooth-format" + "--bluetooth-error" ) local FF_OPTIONS_PATH=( diff --git a/presets/all b/presets/all index 85be11eef..7a44f5bad 100644 --- a/presets/all +++ b/presets/all @@ -1 +1 @@ ---structure Title:Separator:OS:Host:Bios:Board:Chassis:Kernel:Uptime:Processes:Packages:Shell:Display:Brightness:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:CPUUsage:GPU:Memory:Swap:Disk:Battery:PowerAdapter:Player:Media:PublicIP:LocalIP:Wifi:DateTime:Locale:Vulkan:OpenGL:OpenCL:Users:Weather:Break:Colors +--structure Title:Separator:OS:Host:Bios:Board:Chassis:Kernel:Uptime:Processes:Packages:Shell:Display:Brightness:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:CPUUsage:GPU:Memory:Swap:Disk:Battery:PowerAdapter:Player:Media:PublicIP:LocalIP:Wifi:DateTime:Locale:Vulkan:OpenGL:OpenCL:Users:Bluetooth:Weather:Break:Colors diff --git a/presets/verbose b/presets/verbose index 57711c43d..991fecb7f 100644 --- a/presets/verbose +++ b/presets/verbose @@ -28,3 +28,4 @@ --vulkan-format driver: {}; Api Version: {}; Conformance Version: {} --opengl-format version: {}; renderer: {}; vendor: {} --opencl-format version: {}; device: {}; vendor: {} +--bluetooth-format Name: {}; Address: {}; Type: {}; Battery: {} diff --git a/src/common/dbus.c b/src/common/dbus.c new file mode 100644 index 000000000..e053be9a5 --- /dev/null +++ b/src/common/dbus.c @@ -0,0 +1,201 @@ +#include "dbus.h" + +#include "common/thread.h" + +static bool loadLibSymbols(const FFinstance* instance, FFDBusLibrary* lib) +{ + FF_LIBRARY_LOAD(dbus, &instance->config.libDBus, false, "libdbus-1" FF_LIBRARY_EXTENSION, 4); + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_bus_get, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_new_method_call, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_init, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_init_append, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_append_basic, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_get_arg_type, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_get_basic, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_recurse, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_has_next, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_iter_next, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_message_unref, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_connection_send_with_reply_and_block, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_connection_flush, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_pending_call_block, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_pending_call_steal_reply, false) + FF_LIBRARY_LOAD_SYMBOL_PTR(dbus, lib, dbus_pending_call_unref, false) + + return true; +} + +static const FFDBusLibrary* loadLib(const FFinstance* instance) +{ + static FFDBusLibrary lib; + static bool loaded = false; + static bool loadSuccess = false; + static FFThreadMutex mutex = FF_THREAD_MUTEX_INITIALIZER; + + ffThreadMutexLock(&mutex); + + if(!loaded) + { + loaded = true; + loadSuccess = loadLibSymbols(instance, &lib); + } + + ffThreadMutexUnlock(&mutex); + return loadSuccess ? &lib : NULL; +} + +const char* ffDBusLoadData(const FFinstance* instance, DBusBusType busType, FFDBusData* data) +{ + data->lib = loadLib(instance); + if(data->lib == NULL) + return "Failed to load DBus library"; + + data->connection = data->lib->ffdbus_bus_get(busType, NULL); + if(data->connection == NULL) + return "Failed to connect to DBus"; + + return NULL; +} + +bool ffDBusGetValue(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result) +{ + int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); + + if(argType == DBUS_TYPE_STRING) + { + const char* value = NULL; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + + if(!ffStrSet(value)) + return false; + + ffStrbufAppendS(result, value); + return true; + } + + if(argType != DBUS_TYPE_VARIANT && argType != DBUS_TYPE_ARRAY) + return false; + + DBusMessageIter subIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); + + if(argType == DBUS_TYPE_VARIANT) + return ffDBusGetValue(dbus, &subIter, result); + + //At this point we have an array + + bool foundAValue = false; + + while(true) + { + if(ffDBusGetValue(dbus, &subIter, result)) + { + foundAValue = true; + ffStrbufAppendS(result, ", "); + } + + FF_DBUS_ITER_CONTINUE(dbus, &subIter); + } + + if(foundAValue) + ffStrbufSubstrBefore(result, result->length - 2); + + return foundAValue; +} + +bool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result) +{ + int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); + + if(argType == DBUS_TYPE_BOOLEAN) + { + dbus_bool_t value = 0; + dbus->lib->ffdbus_message_iter_get_basic(iter, &value); + *result = value != 0; + return true; + } + + if(argType != DBUS_TYPE_VARIANT) + return false; + + DBusMessageIter subIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); + return ffDBusGetBool(dbus, &subIter, result); +} + +bool ffDBusGetByte(FFDBusData* dbus, DBusMessageIter* iter, uint8_t* result) +{ + int argType = dbus->lib->ffdbus_message_iter_get_arg_type(iter); + + if(argType == DBUS_TYPE_BYTE) + { + dbus->lib->ffdbus_message_iter_get_basic(iter, result); + return true; + } + + if(argType != DBUS_TYPE_VARIANT) + return false; + + DBusMessageIter subIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &subIter); + return ffDBusGetByte(dbus, &subIter, result); +} + +DBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method) +{ + DBusMessage* message = dbus->lib->ffdbus_message_new_method_call(busName, objectPath, interface, method); + if(message == NULL) + return NULL; + + DBusMessage* reply = dbus->lib->ffdbus_connection_send_with_reply_and_block(dbus->connection, message, FF_DBUS_TIMEOUT_MILLISECONDS, NULL); + + dbus->lib->ffdbus_message_unref(message); + + return reply; +} + +DBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property) +{ + DBusMessage* message = dbus->lib->ffdbus_message_new_method_call(busName, objectPath, "org.freedesktop.DBus.Properties", "Get"); + if(message == NULL) + return NULL; + + DBusMessageIter requestIterator; + dbus->lib->ffdbus_message_iter_init_append(message, &requestIterator); + + if(!dbus->lib->ffdbus_message_iter_append_basic(&requestIterator, DBUS_TYPE_STRING, &interface)) + { + dbus->lib->ffdbus_message_unref(message); + return NULL; + } + + if(!dbus->lib->ffdbus_message_iter_append_basic(&requestIterator, DBUS_TYPE_STRING, &property)) + { + dbus->lib->ffdbus_message_unref(message); + return NULL; + } + + DBusMessage* reply = dbus->lib->ffdbus_connection_send_with_reply_and_block(dbus->connection, message, FF_DBUS_TIMEOUT_MILLISECONDS, NULL); + + dbus->lib->ffdbus_message_unref(message); + + return reply; +} + +void ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result) +{ + DBusMessage* reply = ffDBusGetProperty(dbus, busName, objectPath, interface, property); + if(reply == NULL) + return; + + DBusMessageIter rootIterator; + if(!dbus->lib->ffdbus_message_iter_init(reply, &rootIterator)) + { + dbus->lib->ffdbus_message_unref(reply); + return; + } + + ffDBusGetValue(dbus, &rootIterator, result); + + dbus->lib->ffdbus_message_unref(reply); +} diff --git a/src/common/dbus.h b/src/common/dbus.h new file mode 100644 index 000000000..7f0fa2402 --- /dev/null +++ b/src/common/dbus.h @@ -0,0 +1,57 @@ +#pragma once + +#ifndef FF_INCLUDED_common_dbus +#define FF_INCLUDED_common_dbus + +#ifdef FF_HAVE_DBUS +#include + +#include "util/FFstrbuf.h" +#include "common/library.h" + +#define FF_DBUS_TIMEOUT_MILLISECONDS 35 + +#define FF_DBUS_ITER_CONTINUE(dbus, iterator) \ + { \ + if(!(dbus)->lib->ffdbus_message_iter_has_next(iterator)) \ + break; \ + (dbus)->lib->ffdbus_message_iter_next(iterator); \ + continue; \ + } + +typedef struct FFDBusLibrary +{ + FF_LIBRARY_SYMBOL(dbus_bus_get) + FF_LIBRARY_SYMBOL(dbus_message_new_method_call) + FF_LIBRARY_SYMBOL(dbus_message_iter_init) + FF_LIBRARY_SYMBOL(dbus_message_iter_init_append) + FF_LIBRARY_SYMBOL(dbus_message_iter_append_basic) + FF_LIBRARY_SYMBOL(dbus_message_iter_get_arg_type) + FF_LIBRARY_SYMBOL(dbus_message_iter_get_basic) + FF_LIBRARY_SYMBOL(dbus_message_iter_recurse) + FF_LIBRARY_SYMBOL(dbus_message_iter_has_next) + FF_LIBRARY_SYMBOL(dbus_message_iter_next) + FF_LIBRARY_SYMBOL(dbus_message_unref) + FF_LIBRARY_SYMBOL(dbus_connection_send_with_reply_and_block) + FF_LIBRARY_SYMBOL(dbus_connection_flush) + FF_LIBRARY_SYMBOL(dbus_pending_call_block) + FF_LIBRARY_SYMBOL(dbus_pending_call_steal_reply) + FF_LIBRARY_SYMBOL(dbus_pending_call_unref) +} FFDBusLibrary; + +typedef struct FFDBusData +{ + const FFDBusLibrary* lib; + DBusConnection* connection; +} FFDBusData; + +const char* ffDBusLoadData(const FFinstance* instance, DBusBusType busType, FFDBusData* data); //Returns an error message or NULL on success +bool ffDBusGetValue(FFDBusData* dbus, DBusMessageIter* iter, FFstrbuf* result); +bool ffDBusGetBool(FFDBusData* dbus, DBusMessageIter* iter, bool* result); +bool ffDBusGetByte(FFDBusData* dbus, DBusMessageIter* iter, uint8_t* result); +DBusMessage* ffDBusGetMethodReply(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* method); +DBusMessage* ffDBusGetProperty(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property); +void ffDBusGetPropertyString(FFDBusData* dbus, const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result); + +#endif // FF_HAVE_DBUS +#endif // FF_INCLUDED_common_dbus diff --git a/src/common/init.c b/src/common/init.c index e4f106e94..67cd43535 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -115,6 +115,7 @@ static void defaultConfig(FFinstance* instance) initModuleArg(&instance->config.openGL); initModuleArg(&instance->config.openCL); initModuleArg(&instance->config.users); + initModuleArg(&instance->config.bluetooth); ffStrbufInitA(&instance->config.libPCI, 0); ffStrbufInitA(&instance->config.libVulkan, 0); @@ -377,6 +378,7 @@ static void destroyConfig(FFinstance* instance) destroyModuleArg(&instance->config.openGL); destroyModuleArg(&instance->config.openCL); destroyModuleArg(&instance->config.users); + destroyModuleArg(&instance->config.bluetooth); ffStrbufDestroy(&instance->config.libPCI); ffStrbufDestroy(&instance->config.libVulkan); diff --git a/src/data/config_user.txt b/src/data/config_user.txt index 0e987509e..56b164ebf 100644 --- a/src/data/config_user.txt +++ b/src/data/config_user.txt @@ -295,6 +295,7 @@ #--opengl-key OpenGL #--opencl-key OpenCL #--users-key Users +#--bluetooth-key Bluetooth # Format options: # Sets the format string for module values. @@ -339,6 +340,7 @@ #--opengl-format #--opencl-format #--users-format +#--bluetooth-format # Error options: # Sets the format string to use if an error occured @@ -383,6 +385,7 @@ #--opengl-error #--opencl-error #--users-error +#--bluetooth-error # Library options: # Sets an user specific path to a library to load. diff --git a/src/data/modules.txt b/src/data/modules.txt index 586c2de1d..e83353ec9 100644 --- a/src/data/modules.txt +++ b/src/data/modules.txt @@ -1,5 +1,6 @@ Battery Bios +Bluetooth Board Break Chassis diff --git a/src/detection/bluetooth/bluetooth.c b/src/detection/bluetooth/bluetooth.c new file mode 100644 index 000000000..4fd3d55af --- /dev/null +++ b/src/detection/bluetooth/bluetooth.c @@ -0,0 +1,21 @@ +#include "bluetooth.h" + +#include "../internal.h" + +void ffDetectBluetoothImpl(const FFinstance* instance, FFBluetoothResult* bluetooth); + +const FFBluetoothResult* ffDetectBluetooth(const FFinstance* instance) +{ + FF_DETECTION_INTERNAL_GUARD(FFBluetoothResult, + ffStrbufInit(&result.error); + ffStrbufInit(&result.name); + ffStrbufInit(&result.address); + ffStrbufInit(&result.type); + result.battery = 0; + + ffDetectBluetoothImpl(instance, &result); + + if(result.error.length == 0 && result.name.length == 0) + ffStrbufAppendS(&result.error, "No bluetooth device found"); + ) +} diff --git a/src/detection/bluetooth/bluetooth.h b/src/detection/bluetooth/bluetooth.h new file mode 100644 index 000000000..6fb09415d --- /dev/null +++ b/src/detection/bluetooth/bluetooth.h @@ -0,0 +1,19 @@ +#pragma once + +#ifndef FF_INCLUDED_detection_bluetooth_bluetooth +#define FF_INCLUDED_detection_bluetooth_bluetooth + +#include "fastfetch.h" + +typedef struct FFBluetoothResult +{ + FFstrbuf error; + FFstrbuf name; + FFstrbuf address; + FFstrbuf type; + uint8_t battery; // 0-100% +} FFBluetoothResult; + +const FFBluetoothResult* ffDetectBluetooth(const FFinstance* instance); + +#endif diff --git a/src/detection/bluetooth/bluetooth_linux.c b/src/detection/bluetooth/bluetooth_linux.c new file mode 100644 index 000000000..6ed0ab7bc --- /dev/null +++ b/src/detection/bluetooth/bluetooth_linux.c @@ -0,0 +1,200 @@ +#include "bluetooth.h" + +#ifdef FF_HAVE_DBUS +#include "common/dbus.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( + string "Address" + variant string "03:21:8B:91:16:4D" + ) + dict entry( + string "Name" + variant string "JBL TUNE160BT" + ) + dict entry( + string "Connected" + variant boolean true + ) + ] + ) + dict entry( //property + string "org.bluez.Battery1" + array [ + dict entry( + string "Percentage" + variant byte 100 + ) + ] + ) + ] + ) +] +*/ + +static void detectBluetoothProperty(FFBluetoothResult* bluetooth, FFDBusData* dbus, DBusMessageIter* iter, bool* connected) +{ + 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(strstr(propertyType, "Device") == NULL && strstr(propertyType, "Battery") == NULL) + 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); + + while(true) + { + if(dbus->lib->ffdbus_message_iter_get_arg_type(&arrayIter) != DBUS_TYPE_DICT_ENTRY) + FF_DBUS_ITER_CONTINUE(dbus, &arrayIter) + + DBusMessageIter deviceIter; + dbus->lib->ffdbus_message_iter_recurse(&arrayIter, &deviceIter); + + if(dbus->lib->ffdbus_message_iter_get_arg_type(&deviceIter) != DBUS_TYPE_STRING) + FF_DBUS_ITER_CONTINUE(dbus, &arrayIter) + + const char* deviceProperty; + dbus->lib->ffdbus_message_iter_get_basic(&deviceIter, &deviceProperty); + + dbus->lib->ffdbus_message_iter_next(&deviceIter); + + if(strcmp(deviceProperty, "Address") == 0) + ffDBusGetValue(dbus, &deviceIter, &bluetooth->address); + else if(strcmp(deviceProperty, "Name") == 0) + ffDBusGetValue(dbus, &deviceIter, &bluetooth->name); + else if(strcmp(deviceProperty, "Icon") == 0) + ffDBusGetValue(dbus, &deviceIter, &bluetooth->type); + else if(strcmp(deviceProperty, "Percentage") == 0) + ffDBusGetByte(dbus, &deviceIter, &bluetooth->battery); + else if(strcmp(deviceProperty, "Connected") == 0) + { + ffDBusGetBool(dbus, &deviceIter, connected); + if(!*connected) + return; //Just for performance, we don't need to continue if we're not connected + } + + FF_DBUS_ITER_CONTINUE(dbus, &arrayIter); + } +} + +static bool detectBluetoothObject(FFBluetoothResult* bluetooth, FFDBusData* dbus, DBusMessageIter* iter) +{ + if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) + return false; + + 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 false; + + const char* objectPath; + dbus->lib->ffdbus_message_iter_get_basic(&dictIter, &objectPath); + + //We don't want adapter objects + if(strstr(objectPath, "dev_") == NULL) + return false; + + dbus->lib->ffdbus_message_iter_next(&dictIter); + + if(dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_ARRAY) + return false; + + DBusMessageIter arrayIter; + dbus->lib->ffdbus_message_iter_recurse(&dictIter, &arrayIter); + + bool connected = false; + + while(true) + { + detectBluetoothProperty(bluetooth, dbus, &arrayIter, &connected); + FF_DBUS_ITER_CONTINUE(dbus, &arrayIter); + } + + if(!connected || bluetooth->name.length == 0) + { + ffStrbufClear(&bluetooth->address); + ffStrbufClear(&bluetooth->name); + ffStrbufClear(&bluetooth->type); + bluetooth->battery = 0; + return false; + } + + return true; +} + +static void detectBluetoothRoot(FFBluetoothResult* bluetooth, FFDBusData* dbus, DBusMessageIter* iter) +{ + if(dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return; + + DBusMessageIter arrayIter; + dbus->lib->ffdbus_message_iter_recurse(iter, &arrayIter); + + while(true) + { + if(detectBluetoothObject(bluetooth, dbus, &arrayIter)) + return; + + FF_DBUS_ITER_CONTINUE(dbus, &arrayIter); + } + + return; +} + +static const char* detectBluetooth(const FFinstance* instance, FFBluetoothResult* bluetooth) +{ + FFDBusData dbus; + const char* error = ffDBusLoadData(instance, DBUS_BUS_SYSTEM, &dbus); + if(error) + return error; + + DBusMessage* managedObjects = ffDBusGetMethodReply(&dbus, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); + 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(bluetooth, &dbus, &rootIter); + + dbus.lib->ffdbus_message_unref(managedObjects); + return NULL; +} + +#endif + +void ffDetectBluetoothImpl(const FFinstance* instance, FFBluetoothResult* bluetooth) +{ + #ifdef FF_HAVE_DBUS + ffStrbufAppendS(&bluetooth->error, detectBluetooth(instance, bluetooth)); + #else + ffStrbufAppendS(&bluetooth->error, "Fastfetch was compiled without DBus support"); + #endif +} diff --git a/src/detection/media/media_linux.c b/src/detection/media/media_linux.c index f8d410593..d41ce1e4f 100644 --- a/src/detection/media/media_linux.c +++ b/src/detection/media/media_linux.c @@ -8,208 +8,75 @@ #define FF_DBUS_TIMEOUT_MILLISECONDS 35 #ifdef FF_HAVE_DBUS +#include "common/dbus.h" #include "common/library.h" #include "common/parsing.h" -#include -#define FF_DBUS_ITER_CONTINUE(iterator) \ - { \ - if(!data->ffdbus_message_iter_has_next(&iterator)) \ - break; \ - data->ffdbus_message_iter_next(&iterator); \ - continue; \ - } - -typedef struct DBusData +static bool getBusProperties(FFDBusData* data, const char* busName, FFMediaResult* result) { - FF_LIBRARY_SYMBOL(dbus_message_new_method_call) - FF_LIBRARY_SYMBOL(dbus_message_iter_init) - FF_LIBRARY_SYMBOL(dbus_message_iter_init_append) - FF_LIBRARY_SYMBOL(dbus_message_iter_append_basic) - FF_LIBRARY_SYMBOL(dbus_message_iter_get_arg_type) - FF_LIBRARY_SYMBOL(dbus_message_iter_get_basic) - FF_LIBRARY_SYMBOL(dbus_message_iter_recurse) - FF_LIBRARY_SYMBOL(dbus_message_iter_has_next) - FF_LIBRARY_SYMBOL(dbus_message_iter_next) - FF_LIBRARY_SYMBOL(dbus_message_unref) - FF_LIBRARY_SYMBOL(dbus_connection_send_with_reply) - FF_LIBRARY_SYMBOL(dbus_connection_flush) - FF_LIBRARY_SYMBOL(dbus_pending_call_block) - FF_LIBRARY_SYMBOL(dbus_pending_call_steal_reply) - FF_LIBRARY_SYMBOL(dbus_pending_call_unref) - - DBusConnection *connection; -} DBusData; - -static bool getValue(DBusMessageIter* iter, FFstrbuf* result, DBusData* data) -{ - int argType = data->ffdbus_message_iter_get_arg_type(iter); - - if(argType == DBUS_TYPE_STRING) - { - const char* value = NULL; - data->ffdbus_message_iter_get_basic(iter, &value); - - if(!ffStrSet(value)) - return false; - - ffStrbufAppendS(result, value); - return true; - } - - if(argType != DBUS_TYPE_VARIANT && argType != DBUS_TYPE_ARRAY) - return false; - - DBusMessageIter subIter; - data->ffdbus_message_iter_recurse(iter, &subIter); - - if(argType == DBUS_TYPE_VARIANT) - return getValue(&subIter, result, data); - - //At this point we have an array - - bool foundAValue = false; - - while(true) - { - if(getValue(&subIter, result, data)) - { - foundAValue = true; - ffStrbufAppendS(result, ", "); - } - - FF_DBUS_ITER_CONTINUE(subIter); - } - - if(foundAValue) - ffStrbufSubstrBefore(result, result->length - 2); - - return foundAValue; -} - -static DBusMessage* getReply(DBusMessage* request, DBusData* data) -{ - DBusPendingCall* pendingCall = NULL; - dbus_bool_t succesfull = data->ffdbus_connection_send_with_reply(data->connection, request, &pendingCall, FF_DBUS_TIMEOUT_MILLISECONDS); - data->ffdbus_message_unref(request); - if(!succesfull || pendingCall == NULL) - return NULL; - - data->ffdbus_connection_flush(data->connection); - data->ffdbus_pending_call_block(pendingCall); - - DBusMessage* reply = data->ffdbus_pending_call_steal_reply(pendingCall); - data->ffdbus_pending_call_unref(pendingCall); - return reply; -} - -static DBusMessage* getProperty(const char* busName, const char* objectPath, const char* interface, const char* property, DBusData* data) -{ - DBusMessage* message = data->ffdbus_message_new_method_call(busName, objectPath, "org.freedesktop.DBus.Properties", "Get"); - if(message == NULL) - return NULL; - - DBusMessageIter requestIterator; - data->ffdbus_message_iter_init_append(message, &requestIterator); - - if(!data->ffdbus_message_iter_append_basic(&requestIterator, DBUS_TYPE_STRING, &interface)) - { - data->ffdbus_message_unref(message); - return NULL; - } - - if(!data->ffdbus_message_iter_append_basic(&requestIterator, DBUS_TYPE_STRING, &property)) - { - data->ffdbus_message_unref(message); - return NULL; - } - - return getReply(message, data); -} - -static void getPropertyString(const char* busName, const char* objectPath, const char* interface, const char* property, FFstrbuf* result, DBusData* data) -{ - DBusMessage* reply = getProperty(busName, objectPath, interface, property, data); - if(reply == NULL) - return; - - DBusMessageIter rootIterator; - if(!data->ffdbus_message_iter_init(reply, &rootIterator)) - { - data->ffdbus_message_unref(reply); - return; - } - - getValue(&rootIterator, result, data); - - data->ffdbus_message_unref(reply); -} - -static bool getBusProperties(const char* busName, FFMediaResult* result, DBusData* data) -{ - DBusMessage* reply = getProperty(busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", "Metadata", data); + DBusMessage* reply = ffDBusGetProperty(data, busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player", "Metadata"); if(reply == NULL) return false; DBusMessageIter rootIterator; - if(!data->ffdbus_message_iter_init(reply, &rootIterator)) + if(!data->lib->ffdbus_message_iter_init(reply, &rootIterator)) { - data->ffdbus_message_unref(reply); + data->lib->ffdbus_message_unref(reply); return false; } - if(data->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_VARIANT) + if(data->lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_VARIANT) { - data->ffdbus_message_unref(reply); + data->lib->ffdbus_message_unref(reply); return false; } DBusMessageIter variantIterator; - data->ffdbus_message_iter_recurse(&rootIterator, &variantIterator); - if(data->ffdbus_message_iter_get_arg_type(&variantIterator) != DBUS_TYPE_ARRAY) + data->lib->ffdbus_message_iter_recurse(&rootIterator, &variantIterator); + if(data->lib->ffdbus_message_iter_get_arg_type(&variantIterator) != DBUS_TYPE_ARRAY) { - data->ffdbus_message_unref(reply); + data->lib->ffdbus_message_unref(reply); return false; } DBusMessageIter arrayIterator; - data->ffdbus_message_iter_recurse(&variantIterator, &arrayIterator); + data->lib->ffdbus_message_iter_recurse(&variantIterator, &arrayIterator); while(true) { - if(data->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) - FF_DBUS_ITER_CONTINUE(arrayIterator) + if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_DICT_ENTRY) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) DBusMessageIter dictIterator; - data->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator); + data->lib->ffdbus_message_iter_recurse(&arrayIterator, &dictIterator); - if(data->ffdbus_message_iter_get_arg_type(&dictIterator) != DBUS_TYPE_STRING) - FF_DBUS_ITER_CONTINUE(arrayIterator) + if(data->lib->ffdbus_message_iter_get_arg_type(&dictIterator) != DBUS_TYPE_STRING) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) - if(!data->ffdbus_message_iter_has_next(&dictIterator)) - FF_DBUS_ITER_CONTINUE(arrayIterator) + if(!data->lib->ffdbus_message_iter_has_next(&dictIterator)) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) const char* key; - data->ffdbus_message_iter_get_basic(&dictIterator, &key); + data->lib->ffdbus_message_iter_get_basic(&dictIterator, &key); - data->ffdbus_message_iter_next(&dictIterator); + data->lib->ffdbus_message_iter_next(&dictIterator); if(strcmp(key, "xesam:title") == 0) - getValue(&dictIterator, &result->song, data); + ffDBusGetValue(data, &dictIterator, &result->song); else if(strcmp(key, "xesam:album") == 0) - getValue(&dictIterator, &result->album, data); + ffDBusGetValue(data, &dictIterator, &result->album); else if(strcmp(key, "xesam:artist") == 0) - getValue(&dictIterator, &result->artist, data); + ffDBusGetValue(data, &dictIterator, &result->artist); else if(strcmp(key, "xesam:url") == 0) - getValue(&dictIterator, &result->url, data); + ffDBusGetValue(data, &dictIterator, &result->url); if(result->song.length > 0 && result->artist.length > 0 && result->album.length > 0 && result->url.length > 0) break; - FF_DBUS_ITER_CONTINUE(arrayIterator) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) } - data->ffdbus_message_unref(reply); + data->lib->ffdbus_message_unref(reply); if(result->song.length == 0) { @@ -223,20 +90,20 @@ static bool getBusProperties(const char* busName, FFMediaResult* result, DBusDat ffStrbufAppendS(&result->busNameShort, busName + sizeof(FF_DBUS_MPRIS_PREFIX) - 1); //We found a song, get the player name - getPropertyString(busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2", "Identity", &result->player, data); + ffDBusGetPropertyString(data, busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2", "Identity", &result->player); if(result->player.length == 0) - getPropertyString(busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2", "DesktopEntry", &result->player, data); + ffDBusGetPropertyString(data, busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2", "DesktopEntry", &result->player); if(result->player.length == 0) ffStrbufAppend(&result->player, &result->busNameShort); return true; } -static void getCustomBus(const FFinstance* instance, FFMediaResult* result, DBusData* data) +static void getCustomBus(FFDBusData* data, const FFinstance* instance, FFMediaResult* result) { if(ffStrbufStartsWithS(&instance->config.playerName, FF_DBUS_MPRIS_PREFIX)) { - getBusProperties(instance->config.playerName.chars, result, data); + getBusProperties(data, instance->config.playerName.chars, result); return; } @@ -244,88 +111,61 @@ static void getCustomBus(const FFinstance* instance, FFMediaResult* result, DBus ffStrbufInit(&busName); ffStrbufAppendS(&busName, FF_DBUS_MPRIS_PREFIX); ffStrbufAppend(&busName, &instance->config.playerName); - getBusProperties(busName.chars, result, data); + getBusProperties(data, busName.chars, result); ffStrbufDestroy(&busName); } -static void getBestBus(FFMediaResult* result, DBusData* data) +static void getBestBus(FFDBusData* data, FFMediaResult* result) { if( - getBusProperties(FF_DBUS_MPRIS_PREFIX"spotify", result, data) || - getBusProperties(FF_DBUS_MPRIS_PREFIX"vlc", result, data) || - getBusProperties(FF_DBUS_MPRIS_PREFIX"plasma-browser-integration", result, data) + getBusProperties(data, FF_DBUS_MPRIS_PREFIX"spotify", result) || + getBusProperties(data, FF_DBUS_MPRIS_PREFIX"vlc", result) || + getBusProperties(data, FF_DBUS_MPRIS_PREFIX"plasma-browser-integration", result) ) return; - DBusMessage* message = data->ffdbus_message_new_method_call("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); - if(message == NULL) - return; - - DBusMessage* reply = getReply(message, data); + DBusMessage* reply = ffDBusGetMethodReply(data, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ListNames"); if(reply == NULL) return; DBusMessageIter rootIterator; - if(!data->ffdbus_message_iter_init(reply, &rootIterator) || data->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY) + if(!data->lib->ffdbus_message_iter_init(reply, &rootIterator) || data->lib->ffdbus_message_iter_get_arg_type(&rootIterator) != DBUS_TYPE_ARRAY) return; DBusMessageIter arrayIterator; - data->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); + data->lib->ffdbus_message_iter_recurse(&rootIterator, &arrayIterator); while(true) { - if(data->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_STRING) - FF_DBUS_ITER_CONTINUE(arrayIterator) + if(data->lib->ffdbus_message_iter_get_arg_type(&arrayIterator) != DBUS_TYPE_STRING) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) const char* busName; - data->ffdbus_message_iter_get_basic(&arrayIterator, &busName); + data->lib->ffdbus_message_iter_get_basic(&arrayIterator, &busName); if(strncmp(busName, FF_DBUS_MPRIS_PREFIX, sizeof(FF_DBUS_MPRIS_PREFIX) - 1) != 0) - FF_DBUS_ITER_CONTINUE(arrayIterator) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) - if(getBusProperties(busName, result, data)) + if(getBusProperties(data, busName, result)) break; - FF_DBUS_ITER_CONTINUE(arrayIterator) + FF_DBUS_ITER_CONTINUE(data, &arrayIterator) } - data->ffdbus_message_unref(reply); + data->lib->ffdbus_message_unref(reply); } static const char* getMedia(const FFinstance* instance, FFMediaResult* result) { - DBusData data; - - FF_LIBRARY_LOAD(dbus, &instance->config.libDBus, "dlopen dbus failed", "libdbus-1" FF_LIBRARY_EXTENSION, 4); - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(dbus, dbus_bus_get) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_new_method_call) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_init) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_init_append) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_append_basic) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_get_arg_type) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_get_basic) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_recurse) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_has_next) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_iter_next) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_message_unref) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_connection_send_with_reply) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_connection_flush) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_pending_call_block) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_pending_call_steal_reply) - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(dbus, data, dbus_pending_call_unref) - - data.connection = ffdbus_bus_get(DBUS_BUS_SESSION, NULL); - if(data.connection == NULL) - { - dlclose(dbus); - return "Failed to connect to session dbus"; - } + FFDBusData data; + const char* error = ffDBusLoadData(instance, DBUS_BUS_SESSION, &data); + if(error != NULL) + return error; if(instance->config.playerName.length > 0) - getCustomBus(instance, result, &data); + getCustomBus(&data, instance, result); else - getBestBus(result, &data); + getBestBus(&data, result); - dlclose(dbus); return NULL; } diff --git a/src/fastfetch.c b/src/fastfetch.c index 18c130ef2..771071d94 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -448,6 +448,15 @@ static inline void printCommandHelp(const char* command) "vendor" ); } + else if(strcasecmp(command, "bluetooth-format") == 0) + { + constructAndPrintCommandHelpFormat("bluetooth", "{1} (4%)", 4, + "Name", + "Address", + "Type", + "Battery percentage" + ); + } else fprintf(stderr, "No specific help for command %s provided\n", command); } @@ -1208,6 +1217,7 @@ static void parseOption(FFinstance* instance, FFdata* data, const char* key, con else if(optionParseModuleArgs(key, value, "opengl", &instance->config.openGL)) {} else if(optionParseModuleArgs(key, value, "opencl", &instance->config.openCL)) {} else if(optionParseModuleArgs(key, value, "users", &instance->config.users)) {} + else if(optionParseModuleArgs(key, value, "bluetooth", &instance->config.bluetooth)) {} /////////////////// //Library options// @@ -1503,6 +1513,8 @@ static void parseStructureCommand(FFinstance* instance, FFdata* data, const char ffPrintUsers(instance); else if(strcasecmp(line, "command") == 0) ffPrintCommand(instance); + else if(strcasecmp(line, "bluetooth") == 0) + ffPrintBluetooth(instance); else ffPrintErrorString(instance, line, 0, NULL, NULL, ""); } diff --git a/src/fastfetch.h b/src/fastfetch.h index 69121e4c9..c72f3e19b 100644 --- a/src/fastfetch.h +++ b/src/fastfetch.h @@ -139,6 +139,7 @@ typedef struct FFconfig FFModuleArgs openGL; FFModuleArgs openCL; FFModuleArgs users; + FFModuleArgs bluetooth; FFstrbuf libPCI; FFstrbuf libVulkan; @@ -310,5 +311,6 @@ void ffPrintOpenGL(FFinstance* instance); void ffPrintOpenCL(FFinstance* instance); void ffPrintUsers(FFinstance* instance); void ffPrintCommand(FFinstance* instance); +void ffPrintBluetooth(FFinstance* instance); #endif diff --git a/src/modules/bluetooth.c b/src/modules/bluetooth.c new file mode 100644 index 000000000..c68e90380 --- /dev/null +++ b/src/modules/bluetooth.c @@ -0,0 +1,36 @@ +#include "common/printing.h" +#include "detection/bluetooth/bluetooth.h" + +#define FF_BLUETOOTH_MODULE_NAME "Bluetooth" +#define FF_BLUETOOTH_NUM_FORMAT_ARGS 4 + +void ffPrintBluetooth(FFinstance* instance) +{ + const FFBluetoothResult* bluetooth = ffDetectBluetooth(instance); + + if(bluetooth->error.length > 0) + { + ffPrintError(instance, FF_BLUETOOTH_MODULE_NAME, 0, &instance->config.bluetooth, "%s", bluetooth->error.chars); + return; + } + + if(instance->config.bluetooth.outputFormat.length == 0) + { + ffPrintLogoAndKey(instance, FF_BLUETOOTH_MODULE_NAME, 0, &instance->config.bluetooth.key); + ffStrbufWriteTo(&bluetooth->name, stdout); + + if(bluetooth->battery > 0) + printf(" (%d%%)", bluetooth->battery); + + putchar('\n'); + } + else + { + ffPrintFormat(instance, FF_BLUETOOTH_MODULE_NAME, 0, &instance->config.bluetooth, FF_BLUETOOTH_NUM_FORMAT_ARGS, (FFformatarg[]) { + {FF_FORMAT_ARG_TYPE_STRBUF, &bluetooth->name}, + {FF_FORMAT_ARG_TYPE_STRBUF, &bluetooth->address}, + {FF_FORMAT_ARG_TYPE_STRBUF, &bluetooth->type}, + {FF_FORMAT_ARG_TYPE_UINT8, &bluetooth->battery} + }); + } +}