Merge pull request #1540 from daangoossens22/bluetooth
feat: extend bluetooth module
This commit is contained in:
		
						commit
						3d023a0421
					
				|  | @ -55,16 +55,12 @@ | |||
| #include "modules/sndio.hpp" | ||||
| #endif | ||||
| #ifdef HAVE_GIO_UNIX | ||||
| #include "modules/bluetooth.hpp" | ||||
| #include "modules/inhibitor.hpp" | ||||
| #endif | ||||
| #include "bar.hpp" | ||||
| #include "modules/custom.hpp" | ||||
| #include "modules/temperature.hpp" | ||||
| #if defined(__linux__) | ||||
| #ifdef WANT_RFKILL | ||||
| #include "modules/bluetooth.hpp" | ||||
| #endif | ||||
| #endif | ||||
| 
 | ||||
| namespace waybar { | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,18 +1,79 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include "ALabel.hpp" | ||||
| #ifdef WANT_RFKILL | ||||
| #include "util/rfkill.hpp" | ||||
| #endif | ||||
| #include <gio/gio.h> | ||||
| 
 | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace waybar::modules { | ||||
| 
 | ||||
| class Bluetooth : public ALabel { | ||||
|   struct ControllerInfo { | ||||
|     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_controller; | ||||
|     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; | ||||
|     // NOTE: experimental feature in bluez
 | ||||
|     std::optional<unsigned char> battery_percentage; | ||||
|   }; | ||||
| 
 | ||||
|  public: | ||||
|   Bluetooth(const std::string&, const Json::Value&); | ||||
|   ~Bluetooth() = default; | ||||
|   auto update() -> void; | ||||
| 
 | ||||
|  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 getControllerProperties(GDBusObject*, ControllerInfo&) -> bool; | ||||
| 
 | ||||
|   auto findCurController(ControllerInfo&) -> bool; | ||||
|   auto findConnectedDevices(const std::string&, std::vector<DeviceInfo>&) -> void; | ||||
| 
 | ||||
| #ifdef WANT_RFKILL | ||||
|   util::Rfkill rfkill_; | ||||
| #endif | ||||
|   const std::unique_ptr<GDBusObjectManager, void (*)(GDBusObjectManager*)> manager_; | ||||
| 
 | ||||
|   std::string state_; | ||||
|   ControllerInfo cur_controller_; | ||||
|   std::vector<DeviceInfo> connected_devices_; | ||||
|   DeviceInfo cur_focussed_device_; | ||||
|   std::string device_enumerate_; | ||||
| 
 | ||||
|   std::vector<std::string> device_preference_; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace waybar::modules
 | ||||
|  |  | |||
|  | @ -6,21 +6,41 @@ waybar - bluetooth module | |||
| 
 | ||||
| # DESCRIPTION | ||||
| 
 | ||||
| The *bluetooth* module displays information about the status of the device's bluetooth device. | ||||
| The *bluetooth* module displays information about a bluetooth controller and its connections. | ||||
| 
 | ||||
| # CONFIGURATION | ||||
| 
 | ||||
| Addressed by *bluetooth* | ||||
| 
 | ||||
| *controller*: ++ | ||||
| 	typeof: string ++ | ||||
| 	Use the controller with the defined alias. Otherwise a random controller is used. Recommended to define when there is more than 1 controller available to the system. | ||||
| 
 | ||||
| *format-device-preference*: ++ | ||||
| 	typeof: array ++ | ||||
| 	A ranking of bluetooth devices, addressed by their alias. The order is from *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. | ||||
| 
 | ||||
| *format*: ++ | ||||
| 	typeof: string  ++ | ||||
| 	default: *{icon}* ++ | ||||
| 	default: * {status}* ++ | ||||
| 	The format, how information should be displayed. This format is used when other formats aren't specified. | ||||
| 
 | ||||
| *format-icons*: ++ | ||||
| 	typeof: array/object ++ | ||||
| 	Based on the device status, the corresponding icon gets selected. ++ | ||||
| 	The order is *low* to *high*. Or by the state if it is an object. | ||||
| *format-disabled*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is disabled. | ||||
| 
 | ||||
| *format-off*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is turned off. | ||||
| 
 | ||||
| *format-on*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is turned on with no devices connected. | ||||
| 
 | ||||
| *format-connected*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is connected to at least 1 device. | ||||
| 
 | ||||
| *rotate*: ++ | ||||
| 	typeof: integer ++ | ||||
|  | @ -71,23 +91,94 @@ Addressed by *bluetooth* | |||
| 	typeof: string ++ | ||||
| 	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 controller is disabled. | ||||
| 
 | ||||
| *tooltip-format-off*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is turned off. | ||||
| 
 | ||||
| *tooltip-format-on*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is turned on with no devices connected. | ||||
| 
 | ||||
| *tooltip-format-connected*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed controller is connected to at least 1 device. | ||||
| 
 | ||||
| *tooltip-format-enumerate-connected*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used to define how each connected device should be displayed within the *device_enumerate* format replacement in the tooltip menu. | ||||
| 
 | ||||
| # FORMAT REPLACEMENTS | ||||
| 
 | ||||
| *{status}*: Status of the bluetooth device. | ||||
| 
 | ||||
| *{icon}*: Icon, as defined in *format-icons*. | ||||
| *{num_connections}*: Number of connections the displayed controller has. | ||||
| 
 | ||||
| *{controller_address}*: Address of the displayed controller. | ||||
| 
 | ||||
| *{controller_address_type}*: Address type of the displayed controller. | ||||
| 
 | ||||
| *{controller_alias}*: Alias of the displayed controller. | ||||
| 
 | ||||
| *{device_address}*: Address of the displayed device. | ||||
| 
 | ||||
| *{device_address_type}*: Address type of the displayed device. | ||||
| 
 | ||||
| *{device_alias}*: Alias of the displayed device. | ||||
| 
 | ||||
| *{device_enumerate}*: Show a list of all connected devices, each on a seperate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++ | ||||
| and/or *tooltip-format-enumerate-connected-battery* config options. Can only be used in the tooltip related format options. | ||||
| 
 | ||||
| # EXPERIMENTAL BATTERY PERCENTAGE FEATURE | ||||
| 
 | ||||
| At the time of writing, the experimental features of BlueZ need to be turned on, for the battery percentage options listed below to work. | ||||
| 
 | ||||
| ## FORMAT REPLACEMENT | ||||
| 
 | ||||
| *{device_battery_percentage}*: Battery percentage of the displayed device if available. Use only in the config options defined below. | ||||
| 
 | ||||
| ## CONFIGURATION | ||||
| 
 | ||||
| *format-connected-battery*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed device provides its battery percentage. | ||||
| 
 | ||||
| *tooltip-format-connected-battery*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used when the displayed device provides its battery percentage. | ||||
| 
 | ||||
| *tooltip-format-enumerate-connected-battery*: ++ | ||||
| 	typeof: string ++ | ||||
| 	This format is used to define how each connected device with a battery should be displayed within the *device_enumerate* format replacement option. ++ | ||||
| 	When this config option is not defined, it will fall back on the *tooltip-format-enumerate-connected* config option. | ||||
| 
 | ||||
| # EXAMPLES | ||||
| 
 | ||||
| ``` | ||||
| "bluetooth": { | ||||
| 	"format": "{icon}", | ||||
| 	"format-alt": "bluetooth: {status}", | ||||
| 	"format-icons": { | ||||
| 		"enabled": "", | ||||
| 		"disabled": "" | ||||
| 	}, | ||||
| 	"tooltip-format": "{}" | ||||
| 	// "controller": "controller1", // specify the alias of the controller if there are more than 1 on the system | ||||
| 	"format": " {status}", | ||||
| 	"format-disabled": "", // an empty format will hide the module | ||||
| 	"format-connected": " {num_connections} connected", | ||||
| 	"tooltip-format": "{controller_alias}\\t{controller_address}", | ||||
| 	"tooltip-format-connected": "{controller_alias}\\t{controller_address}\\n\\n{device_enumerate}", | ||||
| 	"tooltip-format-enumerate-connected": "{device_alias}\\t{device_address}" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ``` | ||||
| "bluetooth": { | ||||
| 	"format": " {status}", | ||||
| 	"format-connected": " {device_alias}", | ||||
| 	"format-connected-battery": " {device_alias} {device_battery_percentage}%", | ||||
| 	// "format-device-preference": [ "device1", "device2" ], // preference list deciding the displayed device | ||||
| 	"tooltip-format": "{controller_alias}\\t{controller_address}\\n\\n{num_connections} connected", | ||||
| 	"tooltip-format-connected": "{controller_alias}\\t{controller_address}\\n\\n{num_connections} connected\\n\\n{device_enumerate}", | ||||
| 	"tooltip-format-enumerate-connected": "{device_alias}\\t{device_address}", | ||||
| 	"tooltip-format-enumerate-connected-battery": "{device_alias}\\t{device_address}\\t{device_battery_percentage}%" | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
|  | @ -95,3 +186,9 @@ Addressed by *bluetooth* | |||
| 
 | ||||
| - *#bluetooth* | ||||
| - *#bluetooth.disabled* | ||||
| - *#bluetooth.off* | ||||
| - *#bluetooth.on* | ||||
| - *#bluetooth.connected* | ||||
| - *#bluetooth.discoverable* | ||||
| - *#bluetooth.discovering* | ||||
| - *#bluetooth.pairable* | ||||
|  |  | |||
							
								
								
									
										14
									
								
								meson.build
								
								
								
								
							
							
						
						
									
										14
									
								
								meson.build
								
								
								
								
							|  | @ -253,16 +253,14 @@ endif | |||
| if (giounix.found() and not get_option('logind').disabled()) | ||||
|     add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp') | ||||
|     src_files += 'src/modules/inhibitor.cpp' | ||||
|     src_files += 'src/modules/bluetooth.cpp' | ||||
| endif | ||||
| 
 | ||||
| if get_option('rfkill').enabled() | ||||
|     if is_linux | ||||
|         add_project_arguments('-DWANT_RFKILL', language: 'cpp') | ||||
|         src_files += files( | ||||
|             'src/modules/bluetooth.cpp', | ||||
|             'src/util/rfkill.cpp' | ||||
|         ) | ||||
|     endif | ||||
| if get_option('rfkill').enabled() and is_linux | ||||
|     add_project_arguments('-DWANT_RFKILL', language: 'cpp') | ||||
|     src_files += files( | ||||
|         'src/util/rfkill.cpp' | ||||
|     ) | ||||
| endif | ||||
| 
 | ||||
| if tz_dep.found() | ||||
|  |  | |||
|  | @ -101,6 +101,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | |||
|     } | ||||
| #endif | ||||
| #ifdef HAVE_GIO_UNIX | ||||
|     if (ref == "bluetooth") { | ||||
|       return new waybar::modules::Bluetooth(id, config_[name]); | ||||
|     } | ||||
|     if (ref == "inhibitor") { | ||||
|       return new waybar::modules::Inhibitor(id, bar_, config_[name]); | ||||
|     } | ||||
|  | @ -108,13 +111,6 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | |||
|     if (ref == "temperature") { | ||||
|       return new waybar::modules::Temperature(id, config_[name]); | ||||
|     } | ||||
| #if defined(__linux__) | ||||
| #ifdef WANT_RFKILL | ||||
|     if (ref == "bluetooth") { | ||||
|       return new waybar::modules::Bluetooth(id, config_[name]); | ||||
|     } | ||||
| #endif | ||||
| #endif | ||||
|     if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { | ||||
|       return new waybar::modules::Custom(ref.substr(7), id, config_[name]); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,30 +1,391 @@ | |||
| #include "modules/bluetooth.hpp" | ||||
| 
 | ||||
| #include <fmt/format.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| #include <algorithm> | ||||
| #include <sstream> | ||||
| 
 | ||||
| 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) | ||||
|     : 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 controller that is selcected stays unchanged
 | ||||
|   // for duration of the module
 | ||||
|   if (!findCurController(cur_controller_)) { | ||||
|     if (config_["controller-alias"].isString()) { | ||||
|       spdlog::error("findCurController() failed: no bluetooth controller found with alias '{}'", | ||||
|                     config_["controller-alias"].asString()); | ||||
|     } else { | ||||
|       spdlog::error("findCurController() failed: no bluetooth controller found"); | ||||
|     } | ||||
|     return; | ||||
|   } | ||||
|   findConnectedDevices(cur_controller_.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))); | ||||
| #endif | ||||
| 
 | ||||
|   dp.emit(); | ||||
| } | ||||
| 
 | ||||
| auto waybar::modules::Bluetooth::update() -> void { | ||||
|   std::string status = rfkill_.getState() ? "disabled" : "enabled"; | ||||
| 
 | ||||
|   label_.set_markup( | ||||
|       fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status)))); | ||||
|   if (status == "disabled") { | ||||
|     label_.get_style_context()->add_class("disabled"); | ||||
|   } else { | ||||
|     label_.get_style_context()->remove_class("disabled"); | ||||
|   // focussed device is either:
 | ||||
|   // - the first device in the device_preference_ list that is connected to the
 | ||||
|   //   current controller (if none fallback to last connected device)
 | ||||
|   // - it is the last device that connected to the current controller
 | ||||
|   if (!connected_devices_.empty()) { | ||||
|     bool preferred_device_connected = false; | ||||
|     if (!device_preference_.empty()) { | ||||
|       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(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (tooltipEnabled()) { | ||||
|     if (config_["tooltip-format"].isString()) { | ||||
|       auto tooltip_format = config_["tooltip-format"].asString(); | ||||
|       auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status)); | ||||
|       label_.set_tooltip_text(tooltip_text); | ||||
|   std::string state; | ||||
|   std::string tooltip_format; | ||||
|   if (!cur_controller_.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 { | ||||
|       label_.set_tooltip_text(status); | ||||
|       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(); | ||||
|   } | ||||
| 
 | ||||
|   format_.empty() ? event_box_.hide() : event_box_.show(); | ||||
| 
 | ||||
|   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_controller_.discoverable); | ||||
|   update_style_context("discovering", cur_controller_.discovering); | ||||
|   update_style_context("pairable", cur_controller_.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("controller_address", cur_controller_.address), | ||||
|       fmt::arg("controller_address_type", cur_controller_.address_type), | ||||
|       fmt::arg("controller_alias", cur_controller_.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)))); | ||||
| 
 | ||||
|   if (tooltipEnabled()) { | ||||
|     bool tooltip_enumerate_connections_ = config_["tooltip-format-enumerate-connected"].isString(); | ||||
|     bool tooltip_enumerate_connections_battery_ = | ||||
|         config_["tooltip-format-enumerate-connected-battery"].isString(); | ||||
|     if (tooltip_enumerate_connections_ || tooltip_enumerate_connections_battery_) { | ||||
|       std::stringstream ss; | ||||
|       for (DeviceInfo dev : connected_devices_) { | ||||
|         if ((tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) || | ||||
|             tooltip_enumerate_connections_) { | ||||
|           ss << "\n"; | ||||
|           std::string enumerate_format = | ||||
|               (tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) | ||||
|                   ? config_["tooltip-format-enumerate-connected-battery"].asString() | ||||
|                   : config_["tooltip-format-enumerate-connected"].asString(); | ||||
|           ss << fmt::format( | ||||
|               enumerate_format, fmt::arg("device_address", dev.address), | ||||
|               fmt::arg("device_address_type", dev.address_type), | ||||
|               fmt::arg("device_alias", dev.alias), | ||||
|               fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); | ||||
|         } | ||||
|       } | ||||
|       device_enumerate_ = ss.str(); | ||||
|       // don't start the connected devices text with a new line
 | ||||
|       if (!device_enumerate_.empty()) { | ||||
|         device_enumerate_.erase(0, 1); | ||||
|       } | ||||
|     } | ||||
|     label_.set_tooltip_text(fmt::format( | ||||
|         tooltip_format, fmt::arg("status", state_), | ||||
|         fmt::arg("num_connections", connected_devices_.size()), | ||||
|         fmt::arg("controller_address", cur_controller_.address), | ||||
|         fmt::arg("controller_address_type", cur_controller_.address_type), | ||||
|         fmt::arg("controller_alias", cur_controller_.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)), | ||||
|         fmt::arg("device_enumerate", device_enumerate_))); | ||||
|   } | ||||
| 
 | ||||
|   // 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_controller_.path) { | ||||
|       bt->getControllerProperties(G_DBUS_OBJECT(object_proxy), bt->cur_controller_); | ||||
|       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 { | ||||
|       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_controller = 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::getControllerProperties(GDBusObject* object, | ||||
|                                                          ControllerInfo& controller_info) -> bool { | ||||
|   GDBusProxy* proxy_controller = | ||||
|       G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Adapter1")); | ||||
| 
 | ||||
|   if (proxy_controller != NULL) { | ||||
|     controller_info.path = g_dbus_object_get_object_path(object); | ||||
|     controller_info.address = getStringProperty(proxy_controller, "Address"); | ||||
|     controller_info.address_type = getStringProperty(proxy_controller, "AddressType"); | ||||
|     controller_info.alias = getStringProperty(proxy_controller, "Alias"); | ||||
|     controller_info.powered = getBoolProperty(proxy_controller, "Powered"); | ||||
|     controller_info.discoverable = getBoolProperty(proxy_controller, "Discoverable"); | ||||
|     controller_info.pairable = getBoolProperty(proxy_controller, "Pairable"); | ||||
|     controller_info.discovering = getBoolProperty(proxy_controller, "Discovering"); | ||||
| 
 | ||||
|     g_object_unref(proxy_controller); | ||||
| 
 | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
| 
 | ||||
| auto waybar::modules::Bluetooth::findCurController(ControllerInfo& controller_info) -> bool { | ||||
|   bool found_controller = 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 (getControllerProperties(object, controller_info) && | ||||
|         (!config_["controller-alias"].isString() || | ||||
|          config_["controller-alias"].asString() == controller_info.alias)) { | ||||
|       found_controller = true; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   g_list_free_full(objects, g_object_unref); | ||||
| 
 | ||||
|   return found_controller; | ||||
| } | ||||
| 
 | ||||
| auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_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_controller == cur_controller_.path) { | ||||
|       connected_devices.push_back(device); | ||||
|     } | ||||
|   } | ||||
|   g_list_free_full(objects, g_object_unref); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue