feat: extend bluetooth module

This commit is contained in:
Daan Goossens 2022-05-02 18:11:21 +02:00
parent caee2e611f
commit 638b4e6573
6 changed files with 497 additions and 49 deletions

View File

@ -56,15 +56,11 @@
#endif #endif
#ifdef HAVE_GIO_UNIX #ifdef HAVE_GIO_UNIX
#include "modules/inhibitor.hpp" #include "modules/inhibitor.hpp"
#include "modules/bluetooth.hpp"
#endif #endif
#include "bar.hpp" #include "bar.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/temperature.hpp" #include "modules/temperature.hpp"
#if defined(__linux__)
#ifdef WANT_RFKILL
#include "modules/bluetooth.hpp"
#endif
#endif
namespace waybar { namespace waybar {

View File

@ -1,18 +1,77 @@
#pragma once #pragma once
#include "ALabel.hpp" #include "ALabel.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp" #include "util/rfkill.hpp"
#endif
#include <gio/gio.h>
#include <vector>
#include <string>
#include <optional>
namespace waybar::modules { namespace waybar::modules {
class Bluetooth : public ALabel { class Bluetooth : public ALabel {
struct AdapterInfo
{
std::string path;
std::string address;
std::string address_type;
// std::string name; // just use alias instead
std::string alias;
bool powered;
bool discoverable;
bool pairable;
bool discovering;
};
// NOTE: there are some properties that not all devices provide
struct DeviceInfo
{
std::string path;
std::string paired_adapter;
std::string address;
std::string address_type;
// std::optional<std::string> name; // just use alias instead
std::string alias;
std::optional<std::string> icon;
bool paired;
bool trusted;
bool blocked;
bool connected;
bool services_resolved;
// TODO: make experimental in waybar as it is also a experimental feature in bluez
std::optional<unsigned char> battery_percentage;
};
public: public:
Bluetooth(const std::string&, const Json::Value&); Bluetooth(const std::string&, const Json::Value&);
~Bluetooth() = default; ~Bluetooth() = default;
auto update() -> void; auto update() -> void;
private: private:
static auto onInterfaceAddedOrRemoved(GDBusObjectManager*, GDBusObject*, GDBusInterface*, gpointer) -> void;
static auto onInterfaceProxyPropertiesChanged(GDBusObjectManagerClient*, GDBusObjectProxy*, GDBusProxy*, GVariant*, const gchar* const*, gpointer) -> void;
auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional<unsigned char>;
auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool;
auto getAdapterProperties(GDBusObject*, AdapterInfo&) -> bool;
auto findCurAdapter(AdapterInfo&) -> bool;
auto findConnectedDevices(const std::string&, std::vector<DeviceInfo>&) -> void;
#ifdef WANT_RFKILL
util::Rfkill rfkill_; util::Rfkill rfkill_;
#endif
const std::unique_ptr<GDBusObjectManager, void (*)(GDBusObjectManager*)> manager_;
std::string state_;
AdapterInfo cur_adapter_;
std::vector<DeviceInfo> connected_devices_;
DeviceInfo cur_focussed_device_;
std::vector<std::string> device_preference_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -6,21 +6,44 @@ waybar - bluetooth module
# DESCRIPTION # DESCRIPTION
The *bluetooth* module displays information about the status of the device's bluetooth device. The *bluetooth* module displays information about the bluetooth adapter and its connections.
# CONFIGURATION # CONFIGURATION
Addressed by *bluetooth* Addressed by *bluetooth*
*adapter-alias*: ++
typeof: string ++
Use the adapter with the defined alias (name). Otherwise select a random adapter to display. Recommended to define when there is more than 1 adapter available to the system (use ```bluetoothctl show``` to show all available adapters).
*format-device-preference*: ++
typeof: array ++
A ranking of bluetooth devices, addressed by their alias (name). The order is *first displayed* to *last displayed*. If this config option is not defined or none of the devices in the list are connected, it will fall back to showing the last connected device. A devices alias can be manually changed using the ```bluetoothctl set-alias``` command.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: *{icon}* ++ default: * {status}* ++
The format, how information should be displayed. This format is used when other formats aren't specified. The format, how information should be displayed. This format is used when other formats aren't specified.
*format-icons*: ++ *format-disabled*: ++
typeof: array/object ++ typeof: string ++
Based on the device status, the corresponding icon gets selected. ++ This format is used when the displayed adapter is disabled.
The order is *low* to *high*. Or by the state if it is an object.
*format-off*: ++
typeof: string ++
This format is used when the displayed adapter is turned off.
*format-on*: ++
typeof: string ++
This format is used when the displayed adapter is turned on with no devices connected.
*format-connected*: ++
typeof: string ++
This format is used when the displayed adapter is connected to at least 1 device.
*format-connected-battery*: ++
typeof: string ++
This format is used when the selected connected device, defined by the config option *format-device-preference*, provides is battery percentage. This needs the experimental features of bluez to be enabled to work.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
@ -71,23 +94,65 @@ Addressed by *bluetooth*
typeof: string ++ typeof: string ++
The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified. The format, how information should be displayed in the tooltip. This format is used when other formats aren't specified.
*tooltip-format-disabled*: ++
typeof: string ++
This format is used when the displayed adapter is disabled.
*tooltip-format-off*: ++
typeof: string ++
This format is used when the displayed adapter is turned off.
*tooltip-format-on*: ++
typeof: string ++
This format is used when the displayed adapter is turned on with no devices connected.
*tooltip-format-connected*: ++
typeof: string ++
This format is used when the displayed adapter is connected to at least 1 device.
*tooltip-format-connected-battery*: ++
typeof: string ++
This format is used when the selected connected device, defined by the config option *format-device-preference*, provides is battery percentage. This needs the experimental features of bluez to be enabled to work.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{status}*: Status of the bluetooth device. *{status}*: Status of the bluetooth device.
*{icon}*: Icon, as defined in *format-icons*. *{num_connections}*: Number of connections the selected adapter has.
*{adapter_address}*: Address of the selected adapter.
*{adapter_address_type}*: Address type of the selected adapter.
*{adapter_alias}*: Alias of the selected adapter. By default equal to the *adapter_name* but can be changed by the user when there are conflicts.
*{device_address}*: Address of the current selected selected connected device.
*{device_address_type}*: Address type of the current selected selected connected device.
*{device_alias}*: Alias of the current selected connected device. By default equal to the *device_name* but can be changed by the user when there are conflicts.
*{device_battery_percentage}*: Battery percentage of the current selected device if available. Only use in the *format-connected-battery* and *tooltip-format-connected-battery*.
# EXAMPLES # EXAMPLES
``` ```
"bluetooth": { "bluetooth": {
"format": "{icon}", "format": " {status}",
"format-alt": "bluetooth: {status}", "format-connected": " {device_alias}",
"format-icons": { "format-connected-battery": " {device_battery_percentage}%",
"enabled": "", "tooltip-format": "{adapter_alias} {adapter_address}"
"disabled": "" }
}, ```
"tooltip-format": "{}"
```
"bluetooth": {
// "adapter-alias": "adapter1", // specify the adapter alias (name) if there are more than 1 on the system
"format": " {status}",
"format-connected": " {num_connections} connected",
// TODO: make it so that it shows all connected devices in the tooltip
// "tooltip-format-connected": "{device_alias}",
// "tooltip-format-connected-battery": "{device_alias} {device_battery_percentage}%",
} }
``` ```
@ -95,3 +160,9 @@ Addressed by *bluetooth*
- *#bluetooth* - *#bluetooth*
- *#bluetooth.disabled* - *#bluetooth.disabled*
- *#bluetooth.off*
- *#bluetooth.on*
- *#bluetooth.connected*
- *#bluetooth.discoverable*
- *#bluetooth.discovering*
- *#bluetooth.pairable*

View File

@ -253,17 +253,15 @@ endif
if (giounix.found() and not get_option('logind').disabled()) if (giounix.found() and not get_option('logind').disabled())
add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp')
src_files += 'src/modules/inhibitor.cpp' src_files += 'src/modules/inhibitor.cpp'
src_files += 'src/modules/bluetooth.cpp'
endif endif
if get_option('rfkill').enabled() if get_option('rfkill').enabled() and is_linux
if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp') add_project_arguments('-DWANT_RFKILL', language: 'cpp')
src_files += files( src_files += files(
'src/modules/bluetooth.cpp',
'src/util/rfkill.cpp' 'src/util/rfkill.cpp'
) )
endif endif
endif
if tz_dep.found() if tz_dep.found()
add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')

View File

@ -104,17 +104,13 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "inhibitor") { if (ref == "inhibitor") {
return new waybar::modules::Inhibitor(id, bar_, config_[name]); return new waybar::modules::Inhibitor(id, bar_, config_[name]);
} }
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
#if defined(__linux__)
#ifdef WANT_RFKILL
if (ref == "bluetooth") { if (ref == "bluetooth") {
return new waybar::modules::Bluetooth(id, config_[name]); return new waybar::modules::Bluetooth(id, config_[name]);
} }
#endif #endif
#endif if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]); return new waybar::modules::Custom(ref.substr(7), id, config_[name]);
} }

View File

@ -1,30 +1,358 @@
#include "modules/bluetooth.hpp" #include "modules/bluetooth.hpp"
#include <algorithm>
#include <spdlog/spdlog.h>
#include <fmt/format.h> #include <fmt/format.h>
namespace {
using GDBusManager = std::unique_ptr<GDBusObjectManager, void (*)(GDBusObjectManager*)>;
auto generateManager() -> GDBusManager {
GError* error = nullptr;
GDBusObjectManager* manager = g_dbus_object_manager_client_new_for_bus_sync(
G_BUS_TYPE_SYSTEM,
GDBusObjectManagerClientFlags::G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
"org.bluez",
"/",
NULL,
NULL,
NULL,
NULL,
&error
);
if (error) {
spdlog::error("g_dbus_object_manager_client_new_for_bus_sync() failed: {}", error->message);
g_error_free(error);
}
auto destructor = [](GDBusObjectManager* manager) {
if (manager) {
g_object_unref(manager);
}
};
return GDBusManager{manager, destructor};
}
auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool {
auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);
if (gvar) {
bool property_value = g_variant_get_boolean(gvar);
g_variant_unref(gvar);
return property_value;
}
spdlog::error("getBoolProperty() failed: doesn't have property {}", property_name);
return false;
}
auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name) -> std::optional<std::string> {
auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);
if (gvar) {
std::string property_value = g_variant_get_string(gvar, NULL);
g_variant_unref(gvar);
return property_value;
}
return std::nullopt;
}
auto getStringProperty(GDBusProxy* proxy, const char* property_name) -> std::string {
auto property_value = getOptionalStringProperty(proxy, property_name);
if (!property_value.has_value()) {
spdlog::error("getStringProperty() failed: doesn't have property {}", property_name);
}
return property_value.value_or("");
}
auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned char {
auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name);
if (gvar) {
unsigned char property_value;
g_variant_get(gvar, "y", &property_value);
g_variant_unref(gvar);
return property_value;
}
spdlog::error("getUcharProperty() failed: doesn't have property {}", property_name);
return 0;
}
} // namespace
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config) waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, "{icon}", 10), rfkill_{RFKILL_TYPE_BLUETOOTH} { : ALabel(config, "bluetooth", id, " {status}", 10),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_BLUETOOTH},
#endif
manager_(generateManager()) {
if (config_["format-device-preference"].isArray()) {
std::transform(config_["format-device-preference"].begin(),
config_["format-device-preference"].end(),
std::back_inserter(device_preference_),
[](auto x){ return x.asString(); });
}
// NOTE: assumption made that the adapter that is selcected stays unchanged
// for duration of the module
if (!findCurAdapter(cur_adapter_)) {
if (config_["adapter-alias"].isString()) {
spdlog::error("find_cur_adapter() failed: no bluetooth adapter found with alias '{}'", config_["adapter-alias"].asString());
} else {
spdlog::error("find_cur_adapter() failed: no bluetooth adapter found");
}
return;
}
findConnectedDevices(cur_adapter_.path, connected_devices_);
g_signal_connect(manager_.get(), "interface-proxy-properties-changed", G_CALLBACK(onInterfaceProxyPropertiesChanged), this);
g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this);
g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), this);
#ifdef WANT_RFKILL
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update))); rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
#endif
dp.emit();
} }
auto waybar::modules::Bluetooth::update() -> void { auto waybar::modules::Bluetooth::update() -> void {
std::string status = rfkill_.getState() ? "disabled" : "enabled"; // focussed device is either:
// - the first device in the device_preference_ list that is connected to the
label_.set_markup( // current adapter (if none fallback to last connected device)
fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status)))); // - it is the last device that connected to the current adapter
if (status == "disabled") { if (!connected_devices_.empty()) {
label_.get_style_context()->add_class("disabled"); bool preferred_device_connected = false;
} else { if (!device_preference_.empty()) {
label_.get_style_context()->remove_class("disabled"); for (const std::string& device_alias : device_preference_) {
auto it = std::find_if(connected_devices_.begin(), connected_devices_.end(), [device_alias](auto device){ return device_alias == device.alias; });
if (it != connected_devices_.end()) {
preferred_device_connected = true;
cur_focussed_device_ = *it;
break;
}
}
}
if (!preferred_device_connected) {
cur_focussed_device_ = connected_devices_.back();
}
} }
std::string state;
std::string tooltip_format;
if (!cur_adapter_.powered)
state = "off";
else if (!connected_devices_.empty())
state = "connected";
else
state = "on";
#ifdef WANT_RFKILL
if (rfkill_.getState())
state = "disabled";
#endif
if (!alt_) {
if (state == "connected" && cur_focussed_device_.battery_percentage.has_value() && config_["format-connected-battery"].isString()) {
format_ = config_["format-connected-battery"].asString();
} else if (config_["format-" + state].isString()) {
format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
format_ = config_["format"].asString();
} else {
format_ = default_format_;
}
}
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
auto update_style_context = [this](const std::string& style_class, bool in_next_state) {
if (in_next_state && !label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->add_class(style_class);
} else if (!in_next_state && label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->remove_class(style_class);
}
};
update_style_context("discoverable", cur_adapter_.discoverable);
update_style_context("discovering", cur_adapter_.discovering);
update_style_context("pairable", cur_adapter_.pairable);
if (!state_.empty()) {
update_style_context(state_, false);
}
update_style_context(state, true);
state_ = state;
label_.set_markup(fmt::format(format_,
fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("adapter_address", cur_adapter_.address),
fmt::arg("adapter_address_type", cur_adapter_.address_type),
fmt::arg("adapter_alias", cur_adapter_.alias),
fmt::arg("device_address", cur_focussed_device_.address),
fmt::arg("device_address_type", cur_focussed_device_.address_type),
fmt::arg("device_alias", cur_focussed_device_.alias),
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0))
));
// TODO: make possible to show information about all connected devices in the tooltip
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) { label_.set_tooltip_text(fmt::format(tooltip_format,
auto tooltip_format = config_["tooltip-format"].asString(); fmt::arg("status", state_),
auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status)); fmt::arg("num_connections", connected_devices_.size()),
label_.set_tooltip_text(tooltip_text); fmt::arg("adapter_address", cur_adapter_.address),
fmt::arg("adapter_address_type", cur_adapter_.address_type),
fmt::arg("adapter_alias", cur_adapter_.alias),
fmt::arg("device_address", cur_focussed_device_.address),
fmt::arg("device_address_type", cur_focussed_device_.address_type),
fmt::arg("device_alias", cur_focussed_device_.alias),
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0))
));
}
// Call parent update
ALabel::update();
}
// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is connected/disconnected
auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager *manager, GDBusObject *object,
GDBusInterface *interface,
gpointer user_data) -> void
{
std::string interface_name = g_dbus_proxy_get_interface_name(G_DBUS_PROXY(interface));
std::string object_path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(interface));
if (interface_name == "org.bluez.Battery1") {
Bluetooth* bt = static_cast<Bluetooth*>(user_data);
auto device = std::find_if(bt->connected_devices_.begin(), bt->connected_devices_.end(), [object_path](auto d){ return d.path == object_path; });
if (device != bt->connected_devices_.end()) {
device->battery_percentage = bt->getDeviceBatteryPercentage(object);
bt->dp.emit();
}
}
}
auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged(GDBusObjectManagerClient *manager,
GDBusObjectProxy *object_proxy,
GDBusProxy *interface_proxy,
GVariant *changed_properties,
const gchar *const *invalidated_properties,
gpointer user_data) -> void
{
std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy);
std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy));
Bluetooth* bt = static_cast<Bluetooth*>(user_data);
if (interface_name == "org.bluez.Adapter1") {
if (object_path == bt->cur_adapter_.path) {
bt->getAdapterProperties(G_DBUS_OBJECT(object_proxy), bt->cur_adapter_);
bt->dp.emit();
}
} else if (interface_name == "org.bluez.Device1" ||
interface_name == "org.bluez.Battery1") {
DeviceInfo device;
bt->getDeviceProperties(G_DBUS_OBJECT(object_proxy), device);
auto cur_device = std::find_if(bt->connected_devices_.begin(), bt->connected_devices_.end(), [device](auto d){ return d.path == device.path; });
if (cur_device == bt->connected_devices_.end()) {
if (device.connected) {
bt->connected_devices_.push_back(device);
bt->dp.emit();
}
} else { } else {
label_.set_tooltip_text(status); if (!device.connected) {
bt->connected_devices_.erase(cur_device);
} else {
*cur_device = device;
}
bt->dp.emit();
} }
} }
} }
auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) -> std::optional<unsigned char> {
GDBusProxy* proxy_device_bat = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Battery1"));
if (proxy_device_bat != NULL) {
unsigned char battery_percentage = getUcharProperty(proxy_device_bat, "Percentage");
g_object_unref(proxy_device_bat);
return battery_percentage;
}
return std::nullopt;
}
auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info) -> bool {
GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1"));
if (proxy_device != NULL) {
device_info.path = g_dbus_object_get_object_path(object);
device_info.paired_adapter = getStringProperty(proxy_device, "Adapter");
device_info.address = getStringProperty(proxy_device, "Address");
device_info.address_type = getStringProperty(proxy_device, "AddressType");
device_info.alias = getStringProperty(proxy_device, "Alias");
device_info.icon = getOptionalStringProperty(proxy_device, "Icon");
device_info.paired = getBoolProperty(proxy_device, "Paired");
device_info.trusted = getBoolProperty(proxy_device, "Trusted");
device_info.blocked = getBoolProperty(proxy_device, "Blocked");
device_info.connected = getBoolProperty(proxy_device, "Connected");
device_info.services_resolved = getBoolProperty(proxy_device, "ServicesResolved");
g_object_unref(proxy_device);
device_info.battery_percentage = getDeviceBatteryPercentage(object);
return true;
}
return false;
}
auto waybar::modules::Bluetooth::getAdapterProperties(GDBusObject* object, AdapterInfo& adapter_info) -> bool {
GDBusProxy* proxy_adapter = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Adapter1"));
if (proxy_adapter != NULL) {
adapter_info.path = g_dbus_object_get_object_path(object);
adapter_info.address = getStringProperty(proxy_adapter, "Address");
adapter_info.address_type = getStringProperty(proxy_adapter, "AddressType");
adapter_info.alias = getStringProperty(proxy_adapter, "Alias");
adapter_info.powered = getBoolProperty(proxy_adapter, "Powered");
adapter_info.discoverable = getBoolProperty(proxy_adapter, "Discoverable");
adapter_info.pairable = getBoolProperty(proxy_adapter, "Pairable");
adapter_info.discovering = getBoolProperty(proxy_adapter, "Discovering");
g_object_unref(proxy_adapter);
return true;
}
return false;
}
auto waybar::modules::Bluetooth::findCurAdapter(AdapterInfo& adapter_info) -> bool {
bool found_adapter = false;
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next) {
GDBusObject* object = G_DBUS_OBJECT(l->data);
if (getAdapterProperties(object, adapter_info) && (!config_["adapter-alias"].isString() || config_["adapter-alias"].asString() == adapter_info.alias)) {
found_adapter = true;
break;
}
}
g_list_free_full(objects, g_object_unref);
return found_adapter;
}
auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_adapter_path, std::vector<DeviceInfo>& connected_devices) -> void {
GList* objects = g_dbus_object_manager_get_objects(manager_.get());
for (GList* l = objects; l != NULL; l = l->next)
{
GDBusObject* object = G_DBUS_OBJECT(l->data);
DeviceInfo device;
if (getDeviceProperties(object, device) && device.connected && device.paired_adapter == cur_adapter_.path) {
connected_devices.push_back(device);
}
}
g_list_free_full(objects, g_object_unref);
}