diff --git a/include/util/pipewire/pipewire_backend.hpp b/include/util/pipewire/pipewire_backend.hpp index 4e23b282..ac70a139 100644 --- a/include/util/pipewire/pipewire_backend.hpp +++ b/include/util/pipewire/pipewire_backend.hpp @@ -13,7 +13,8 @@ class PipewireBackend { pw_context* context_; pw_core* core_; - spa_hook registry_listener; + pw_registry* registry_; + spa_hook registryListener_; /* Hack to keep constructor inaccessible but still public. * This is required to be able to use std::make_shared. @@ -21,20 +22,22 @@ class PipewireBackend { * pointer because the destructor will manually free memory, and this could be * a problem with C++20's copy and move semantics. */ - struct private_constructor_tag {}; + struct PrivateConstructorTag {}; public: - std::mutex mutex_; - - pw_registry* registry; - sigc::signal privacy_nodes_changed_signal_event; std::unordered_map privacy_nodes; + std::mutex mutex_; static std::shared_ptr getInstance(); - PipewireBackend(private_constructor_tag tag); + // Handlers for PipeWire events + void handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props); + void handleRegistryEventGlobalRemove(uint32_t id); + + PipewireBackend(PrivateConstructorTag tag); ~PipewireBackend(); }; } // namespace waybar::util::PipewireBackend diff --git a/include/util/pipewire/privacy_node_info.hpp b/include/util/pipewire/privacy_node_info.hpp index 3b7f446d..7b8df018 100644 --- a/include/util/pipewire/privacy_node_info.hpp +++ b/include/util/pipewire/privacy_node_info.hpp @@ -34,29 +34,12 @@ class PrivacyNodeInfo { void *data; - std::string get_name() { - const std::vector names{&application_name, &node_name}; - std::string name = "Unknown Application"; - for (auto &name_ : names) { - if (name_ != nullptr && name_->length() > 0) { - name = *name_; - name[0] = toupper(name[0]); - break; - } - } - return name; - } + std::string getName(); + std::string getIconName(); - std::string get_icon_name() { - const std::vector names{&application_icon_name, &pipewire_access_portal_app_id, - &application_name, &node_name}; - const std::string name = "application-x-executable-symbolic"; - for (auto &name_ : names) { - if (name_ != nullptr && name_->length() > 0 && DefaultGtkIconThemeWrapper::has_icon(*name_)) { - return *name_; - } - } - return name; - } + // Handlers for PipeWire events + void handleProxyEventDestroy(); + void handleNodeEventInfo(const struct pw_node_info *info); }; + } // namespace waybar::util::PipewireBackend diff --git a/meson.build b/meson.build index bbcde7f0..dfdf08a8 100644 --- a/meson.build +++ b/meson.build @@ -348,7 +348,8 @@ if pipewire.found() src_files += files( 'src/modules/privacy/privacy.cpp', 'src/modules/privacy/privacy_item.cpp', - 'src/util/pipewire_backend.cpp', + 'src/util/pipewire/pipewire_backend.cpp', + 'src/util/pipewire/privacy_node_info.cpp', ) man_files += files('man/waybar-privacy.5.scd') endif diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 64a1572b..b7eede75 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -1,12 +1,8 @@ #include "modules/privacy/privacy.hpp" -#include #include -#include #include -#include -#include #include #include "AModule.hpp" diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index a0a2da57..c5b617d5 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -1,23 +1,14 @@ #include "modules/privacy/privacy_item.hpp" -#include -#include -#include - -#include -#include #include #include #include "AModule.hpp" #include "glibmm/main.h" -#include "glibmm/priorities.h" -#include "gtkmm/enums.h" #include "gtkmm/label.h" #include "gtkmm/revealer.h" #include "gtkmm/tooltip.h" #include "sigc++/adaptors/bind.h" -#include "util/gtk_icon.hpp" #include "util/pipewire/privacy_node_info.hpp" namespace waybar::modules::privacy { @@ -108,12 +99,12 @@ void PrivacyItem::update_tooltip() { // Set device icon Gtk::Image *node_icon = new Gtk::Image(); node_icon->set_pixel_size(tooltipIconSize); - node_icon->set_from_icon_name(node->get_icon_name(), Gtk::ICON_SIZE_INVALID); + node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); box->add(*node_icon); // Set model - Gtk::Label *node_name = new Gtk::Label(node->get_name()); - box->add(*node_name); + auto *nodeName = new Gtk::Label(node->getName()); + box->add(*nodeName); tooltip_window.add(*box); } diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp new file mode 100644 index 00000000..044b926f --- /dev/null +++ b/src/util/pipewire/pipewire_backend.cpp @@ -0,0 +1,140 @@ +#include "util/pipewire/pipewire_backend.hpp" + +#include "util/pipewire/privacy_node_info.hpp" + +namespace waybar::util::PipewireBackend { + +static void getNodeInfo(void *data_, const struct pw_node_info *info) { + auto *pNodeInfo = static_cast(data_); + pNodeInfo->handleNodeEventInfo(info); + + static_cast(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit(); +} + +static const struct pw_node_events NODE_EVENTS = { + .version = PW_VERSION_NODE_EVENTS, + .info = getNodeInfo, +}; + +static void proxyDestroy(void *data) { + static_cast(data)->handleProxyEventDestroy(); +} + +static const struct pw_proxy_events PROXY_EVENTS = { + .version = PW_VERSION_PROXY_EVENTS, + .destroy = proxyDestroy, +}; + +static void registryEventGlobal(void *_data, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const struct spa_dict *props) { + static_cast(_data)->handleRegistryEventGlobal(id, permissions, type, version, + props); +} + +static void registryEventGlobalRemove(void *_data, uint32_t id) { + static_cast(_data)->handleRegistryEventGlobalRemove(id); +} + +static const struct pw_registry_events REGISTRY_EVENTS = { + .version = PW_VERSION_REGISTRY_EVENTS, + .global = registryEventGlobal, + .global_remove = registryEventGlobalRemove, +}; + +PipewireBackend::PipewireBackend(PrivateConstructorTag tag) + : mainloop_(nullptr), context_(nullptr), core_(nullptr) { + pw_init(nullptr, nullptr); + mainloop_ = pw_thread_loop_new("waybar", nullptr); + if (mainloop_ == nullptr) { + throw std::runtime_error("pw_thread_loop_new() failed."); + } + context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); + if (context_ == nullptr) { + throw std::runtime_error("pa_context_new() failed."); + } + core_ = pw_context_connect(context_, nullptr, 0); + if (core_ == nullptr) { + throw std::runtime_error("pw_context_connect() failed"); + } + registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0); + + spa_zero(registryListener_); + pw_registry_add_listener(registry_, ®istryListener_, ®ISTRY_EVENTS, this); + if (pw_thread_loop_start(mainloop_) < 0) { + throw std::runtime_error("pw_thread_loop_start() failed."); + } +} + +PipewireBackend::~PipewireBackend() { + if (registry_ != nullptr) { + pw_proxy_destroy((struct pw_proxy *)registry_); + } + + spa_zero(registryListener_); + + if (core_ != nullptr) { + pw_core_disconnect(core_); + } + + if (context_ != nullptr) { + pw_context_destroy(context_); + } + + if (mainloop_ != nullptr) { + pw_thread_loop_stop(mainloop_); + pw_thread_loop_destroy(mainloop_); + } +} + +std::shared_ptr PipewireBackend::getInstance() { + PrivateConstructorTag tag; + return std::make_shared(tag); +} + +void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const struct spa_dict *props) { + if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; + + const char *lookupStr = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + if (lookupStr == nullptr) return; + std::string mediaClass = lookupStr; + enum PrivacyNodeType mediaType = PRIVACY_NODE_TYPE_NONE; + if (mediaClass == "Stream/Input/Video") { + mediaType = PRIVACY_NODE_TYPE_VIDEO_INPUT; + } else if (mediaClass == "Stream/Input/Audio") { + mediaType = PRIVACY_NODE_TYPE_AUDIO_INPUT; + } else if (mediaClass == "Stream/Output/Audio") { + mediaType = PRIVACY_NODE_TYPE_AUDIO_OUTPUT; + } else { + return; + } + + auto *proxy = (pw_proxy *)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo)); + + if (proxy == nullptr) return; + + auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); + pNodeInfo->id = id; + pNodeInfo->data = this; + pNodeInfo->type = mediaType; + pNodeInfo->media_class = mediaClass; + + pw_proxy_add_listener(proxy, &pNodeInfo->proxy_listener, &PROXY_EVENTS, pNodeInfo); + + pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo); + + privacy_nodes.insert_or_assign(id, pNodeInfo); +} + +void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) { + mutex_.lock(); + auto iter = privacy_nodes.find(id); + if (iter != privacy_nodes.end()) { + privacy_nodes.erase(id); + } + mutex_.unlock(); + + privacy_nodes_changed_signal_event.emit(); +} + +} // namespace waybar::util::PipewireBackend diff --git a/src/util/pipewire/privacy_node_info.cpp b/src/util/pipewire/privacy_node_info.cpp new file mode 100644 index 00000000..739dc528 --- /dev/null +++ b/src/util/pipewire/privacy_node_info.cpp @@ -0,0 +1,56 @@ +#include "util/pipewire/privacy_node_info.hpp" + +namespace waybar::util::PipewireBackend { + +std::string PrivacyNodeInfo::getName() { + const std::vector names{&application_name, &node_name}; + std::string name = "Unknown Application"; + for (const auto &item : names) { + if (item != nullptr && !item->empty()) { + name = *item; + name[0] = toupper(name[0]); + break; + } + } + return name; +} + +std::string PrivacyNodeInfo::getIconName() { + const std::vector names{&application_icon_name, &pipewire_access_portal_app_id, + &application_name, &node_name}; + std::string name = "application-x-executable-symbolic"; + for (const auto &item : names) { + if (item != nullptr && !item->empty() && DefaultGtkIconThemeWrapper::has_icon(*item)) { + return *item; + } + } + return name; +} + +void PrivacyNodeInfo::handleProxyEventDestroy() { + spa_hook_remove(&proxy_listener); + spa_hook_remove(&object_listener); +} + +void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info *info) { + state = info->state; + + const struct spa_dict_item *item; + spa_dict_for_each(item, info->props) { + if (strcmp(item->key, PW_KEY_CLIENT_ID) == 0) { + client_id = strtoul(item->value, nullptr, 10); + } else if (strcmp(item->key, PW_KEY_MEDIA_NAME) == 0) { + media_name = item->value; + } else if (strcmp(item->key, PW_KEY_NODE_NAME) == 0) { + node_name = item->value; + } else if (strcmp(item->key, PW_KEY_APP_NAME) == 0) { + application_name = item->value; + } else if (strcmp(item->key, "pipewire.access.portal.app_id") == 0) { + pipewire_access_portal_app_id = item->value; + } else if (strcmp(item->key, PW_KEY_APP_ICON_NAME) == 0) { + application_icon_name = item->value; + } + } +} + +} // namespace waybar::util::PipewireBackend diff --git a/src/util/pipewire_backend.cpp b/src/util/pipewire_backend.cpp deleted file mode 100644 index 5fe3ba62..00000000 --- a/src/util/pipewire_backend.cpp +++ /dev/null @@ -1,155 +0,0 @@ -#include "util/pipewire/pipewire_backend.hpp" - -#include "util/pipewire/privacy_node_info.hpp" - -namespace waybar::util::PipewireBackend { - -static void get_node_info(void *data_, const struct pw_node_info *info) { - PrivacyNodeInfo *p_node_info = static_cast(data_); - PipewireBackend *backend = (PipewireBackend *)p_node_info->data; - - p_node_info->state = info->state; - - const struct spa_dict_item *item; - spa_dict_for_each(item, info->props) { - if (strcmp(item->key, PW_KEY_CLIENT_ID) == 0) { - p_node_info->client_id = strtoul(item->value, NULL, 10); - } else if (strcmp(item->key, PW_KEY_MEDIA_NAME) == 0) { - p_node_info->media_name = item->value; - } else if (strcmp(item->key, PW_KEY_NODE_NAME) == 0) { - p_node_info->node_name = item->value; - } else if (strcmp(item->key, PW_KEY_APP_NAME) == 0) { - p_node_info->application_name = item->value; - } else if (strcmp(item->key, "pipewire.access.portal.app_id") == 0) { - p_node_info->pipewire_access_portal_app_id = item->value; - } else if (strcmp(item->key, PW_KEY_APP_ICON_NAME) == 0) { - p_node_info->application_icon_name = item->value; - } - } - - backend->privacy_nodes_changed_signal_event.emit(); -} - -static const struct pw_node_events node_events = { - .version = PW_VERSION_NODE_EVENTS, - .info = get_node_info, -}; - -static void proxy_destroy(void *data) { - PrivacyNodeInfo *node = (PrivacyNodeInfo *)data; - - spa_hook_remove(&node->proxy_listener); - spa_hook_remove(&node->object_listener); -} - -static const struct pw_proxy_events proxy_events = { - .version = PW_VERSION_PROXY_EVENTS, - .destroy = proxy_destroy, -}; - -static void registry_event_global(void *_data, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props) { - if (!props || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; - - const char *lookup_str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); - if (!lookup_str) return; - std::string media_class = lookup_str; - enum PrivacyNodeType media_type = PRIVACY_NODE_TYPE_NONE; - if (media_class == "Stream/Input/Video") { - media_type = PRIVACY_NODE_TYPE_VIDEO_INPUT; - } else if (media_class == "Stream/Input/Audio") { - media_type = PRIVACY_NODE_TYPE_AUDIO_INPUT; - } else if (media_class == "Stream/Output/Audio") { - media_type = PRIVACY_NODE_TYPE_AUDIO_OUTPUT; - } else { - return; - } - - PipewireBackend *backend = static_cast(_data); - struct pw_proxy *proxy = - (pw_proxy *)pw_registry_bind(backend->registry, id, type, version, sizeof(PrivacyNodeInfo)); - - if (!proxy) return; - - PrivacyNodeInfo *p_node_info = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); - p_node_info->id = id; - p_node_info->data = backend; - p_node_info->type = media_type; - p_node_info->media_class = media_class; - - pw_proxy_add_listener(proxy, &p_node_info->proxy_listener, &proxy_events, p_node_info); - - pw_proxy_add_object_listener(proxy, &p_node_info->object_listener, &node_events, p_node_info); - - backend->privacy_nodes.insert_or_assign(id, p_node_info); -} - -static void registry_event_global_remove(void *_data, uint32_t id) { - auto backend = static_cast(_data); - - backend->mutex_.lock(); - auto iter = backend->privacy_nodes.find(id); - if (iter != backend->privacy_nodes.end()) { - backend->privacy_nodes.erase(id); - } - backend->mutex_.unlock(); - - backend->privacy_nodes_changed_signal_event.emit(); -} - -static const struct pw_registry_events registry_events = { - .version = PW_VERSION_REGISTRY_EVENTS, - .global = registry_event_global, - .global_remove = registry_event_global_remove, -}; - -PipewireBackend::PipewireBackend(private_constructor_tag tag) - : mainloop_(nullptr), context_(nullptr), core_(nullptr) { - pw_init(nullptr, nullptr); - mainloop_ = pw_thread_loop_new("waybar", nullptr); - if (mainloop_ == nullptr) { - throw std::runtime_error("pw_thread_loop_new() failed."); - } - context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); - if (context_ == nullptr) { - throw std::runtime_error("pa_context_new() failed."); - } - core_ = pw_context_connect(context_, nullptr, 0); - if (core_ == nullptr) { - throw std::runtime_error("pw_context_connect() failed"); - } - registry = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0); - - spa_zero(registry_listener); - pw_registry_add_listener(registry, ®istry_listener, ®istry_events, this); - if (pw_thread_loop_start(mainloop_) < 0) { - throw std::runtime_error("pw_thread_loop_start() failed."); - } -} - -PipewireBackend::~PipewireBackend() { - if (registry != nullptr) { - pw_proxy_destroy((struct pw_proxy *)registry); - } - - spa_zero(registry_listener); - - if (core_ != nullptr) { - pw_core_disconnect(core_); - } - - if (context_ != nullptr) { - pw_context_destroy(context_); - } - - if (mainloop_ != nullptr) { - pw_thread_loop_stop(mainloop_); - pw_thread_loop_destroy(mainloop_); - } -} - -std::shared_ptr PipewireBackend::getInstance() { - private_constructor_tag tag; - return std::make_shared(tag); -} -} // namespace waybar::util::PipewireBackend