#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."); } pw_thread_loop_lock(mainloop_); context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); if (context_ == nullptr) { pw_thread_loop_unlock(mainloop_); throw std::runtime_error("pa_context_new() failed."); } core_ = pw_context_connect(context_, nullptr, 0); if (core_ == nullptr) { pw_thread_loop_unlock(mainloop_); 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) { pw_thread_loop_unlock(mainloop_); throw std::runtime_error("pw_thread_loop_start() failed."); } pw_thread_loop_unlock(mainloop_); } PipewireBackend::~PipewireBackend() { if (mainloop_ != nullptr) { pw_thread_loop_lock(mainloop_); } 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_unlock(mainloop_); 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