Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(privacy): clean up the module #3082

Merged
merged 1 commit into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions include/util/pipewire/pipewire_backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,31 @@ 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.
* It is important to keep this class only accessible via a reference-counted
* 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<void> privacy_nodes_changed_signal_event;

std::unordered_map<uint32_t, PrivacyNodeInfo*> privacy_nodes;
std::mutex mutex_;

static std::shared_ptr<PipewireBackend> 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
31 changes: 7 additions & 24 deletions include/util/pipewire/privacy_node_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,12 @@ class PrivacyNodeInfo {

void *data;

std::string get_name() {
const std::vector<std::string *> 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 get_icon_name() {
const std::vector<std::string *> 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;
}
std::string getName();
std::string getIconName();

// Handlers for PipeWire events
void handleProxyEventDestroy();
void handleNodeEventInfo(const struct pw_node_info *info);
};

} // namespace waybar::util::PipewireBackend
3 changes: 2 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions src/modules/privacy/privacy.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
#include "modules/privacy/privacy.hpp"

#include <fmt/core.h>
#include <json/value.h>
#include <pipewire/pipewire.h>
#include <spdlog/spdlog.h>

#include <cstdio>
#include <cstring>
#include <string>

#include "AModule.hpp"
Expand Down
15 changes: 3 additions & 12 deletions src/modules/privacy/privacy_item.cpp
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
#include "modules/privacy/privacy_item.hpp"

#include <fmt/core.h>
#include <pipewire/pipewire.h>
#include <spdlog/spdlog.h>

#include <cstdio>
#include <cstring>
#include <string>
#include <thread>

#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 {
Expand Down Expand Up @@ -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);
}
Expand Down
140 changes: 140 additions & 0 deletions src/util/pipewire/pipewire_backend.cpp
Original file line number Diff line number Diff line change
@@ -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<PrivacyNodeInfo *>(data_);
pNodeInfo->handleNodeEventInfo(info);

static_cast<PipewireBackend *>(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<PrivacyNodeInfo *>(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<PipewireBackend *>(_data)->handleRegistryEventGlobal(id, permissions, type, version,
props);
}

static void registryEventGlobalRemove(void *_data, uint32_t id) {
static_cast<PipewireBackend *>(_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_, &registryListener_, &REGISTRY_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> PipewireBackend::getInstance() {
PrivateConstructorTag tag;
return std::make_shared<PipewireBackend>(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
56 changes: 56 additions & 0 deletions src/util/pipewire/privacy_node_info.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "util/pipewire/privacy_node_info.hpp"

namespace waybar::util::PipewireBackend {

std::string PrivacyNodeInfo::getName() {
const std::vector<std::string *> 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<std::string *> 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
Loading
Loading