Skip to content

Commit

Permalink
Merge pull request #3082 from Kuruyia/refactor/privacy-module
Browse files Browse the repository at this point in the history
refactor(privacy): clean up the module
  • Loading branch information
Alexays authored Mar 28, 2024
2 parents b652b42 + fe15530 commit ddc767c
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 203 deletions.
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

0 comments on commit ddc767c

Please sign in to comment.