mirror of
https://github.com/rad4day/Waybar.git
synced 2025-07-13 22:52:30 +02:00
Merge pull request #2612 from ErikReider/privacy-module
Add Privacy Module
This commit is contained in:
@ -760,7 +760,7 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
|
||||
getModules(factory, ref, group_module);
|
||||
module = group_module;
|
||||
} else {
|
||||
module = factory.makeModule(ref);
|
||||
module = factory.makeModule(ref, pos);
|
||||
}
|
||||
|
||||
std::shared_ptr<AModule> module_sp(module);
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "gtkmm/icontheme.h"
|
||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "util/clara.hpp"
|
||||
#include "util/format.hpp"
|
||||
@ -244,6 +245,11 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||
}
|
||||
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar",
|
||||
Gio::APPLICATION_HANDLES_COMMAND_LINE);
|
||||
|
||||
// Initialize Waybars GTK resources with our custom icons
|
||||
auto theme = Gtk::IconTheme::get_default();
|
||||
theme->add_resource_path("/fr/arouillard/waybar/icons");
|
||||
|
||||
gdk_display = Gdk::Display::get_default();
|
||||
if (!gdk_display) {
|
||||
throw std::runtime_error("Can't find display");
|
||||
|
@ -10,7 +10,8 @@
|
||||
|
||||
waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {}
|
||||
|
||||
waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
||||
const std::string& pos) const {
|
||||
try {
|
||||
auto hash_pos = name.find('#');
|
||||
auto ref = name.substr(0, hash_pos);
|
||||
@ -30,6 +31,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
return new waybar::modules::upower::UPower(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_PIPEWIRE
|
||||
if (ref == "privacy") {
|
||||
return new waybar::modules::privacy::Privacy(id, config_[name], pos);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_MPRIS
|
||||
if (ref == "mpris") {
|
||||
return new waybar::modules::mpris::Mpris(id, config_[name]);
|
||||
|
178
src/modules/privacy/privacy.cpp
Normal file
178
src/modules/privacy/privacy.cpp
Normal file
@ -0,0 +1,178 @@
|
||||
#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"
|
||||
#include "gtkmm/image.h"
|
||||
#include "modules/privacy/privacy_item.hpp"
|
||||
|
||||
namespace waybar::modules::privacy {
|
||||
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE;
|
||||
using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT;
|
||||
|
||||
Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos)
|
||||
: AModule(config, "privacy", id),
|
||||
nodes_screenshare(),
|
||||
nodes_audio_in(),
|
||||
nodes_audio_out(),
|
||||
visibility_conn(),
|
||||
box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
|
||||
box_.set_name(name_);
|
||||
|
||||
event_box_.add(box_);
|
||||
|
||||
// Icon Spacing
|
||||
if (config_["icon-spacing"].isUInt()) {
|
||||
iconSpacing = config_["icon-spacing"].asUInt();
|
||||
}
|
||||
box_.set_spacing(iconSpacing);
|
||||
|
||||
// Icon Size
|
||||
if (config_["icon-size"].isUInt()) {
|
||||
iconSize = config_["icon-size"].asUInt();
|
||||
}
|
||||
|
||||
// Transition Duration
|
||||
if (config_["transition-duration"].isUInt()) {
|
||||
transition_duration = config_["transition-duration"].asUInt();
|
||||
}
|
||||
|
||||
// Initialize each privacy module
|
||||
Json::Value modules = config_["modules"];
|
||||
// Add Screenshare and Mic usage as default modules if none are specified
|
||||
if (!modules.isArray() || modules.size() == 0) {
|
||||
modules = Json::Value(Json::arrayValue);
|
||||
for (auto& type : {"screenshare", "audio-in"}) {
|
||||
Json::Value obj = Json::Value(Json::objectValue);
|
||||
obj["type"] = type;
|
||||
modules.append(obj);
|
||||
}
|
||||
}
|
||||
for (uint i = 0; i < modules.size(); i++) {
|
||||
const Json::Value& module_config = modules[i];
|
||||
if (!module_config.isObject() || !module_config["type"].isString()) continue;
|
||||
const std::string type = module_config["type"].asString();
|
||||
if (type == "screenshare") {
|
||||
auto item =
|
||||
Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_VIDEO_INPUT,
|
||||
&nodes_screenshare, pos, iconSize, transition_duration);
|
||||
box_.add(*item);
|
||||
} else if (type == "audio-in") {
|
||||
auto item =
|
||||
Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_AUDIO_INPUT,
|
||||
&nodes_audio_in, pos, iconSize, transition_duration);
|
||||
box_.add(*item);
|
||||
} else if (type == "audio-out") {
|
||||
auto item =
|
||||
Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_AUDIO_OUTPUT,
|
||||
&nodes_audio_out, pos, iconSize, transition_duration);
|
||||
box_.add(*item);
|
||||
}
|
||||
}
|
||||
|
||||
backend = util::PipewireBackend::PipewireBackend::getInstance();
|
||||
backend->privacy_nodes_changed_signal_event.connect(
|
||||
sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged));
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
void Privacy::onPrivacyNodesChanged() {
|
||||
mutex_.lock();
|
||||
nodes_audio_out.clear();
|
||||
nodes_audio_in.clear();
|
||||
nodes_screenshare.clear();
|
||||
|
||||
for (auto& node : backend->privacy_nodes) {
|
||||
switch (node.second->state) {
|
||||
case PW_NODE_STATE_RUNNING:
|
||||
switch (node.second->type) {
|
||||
case PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
nodes_screenshare.push_back(node.second);
|
||||
break;
|
||||
case PRIVACY_NODE_TYPE_AUDIO_INPUT:
|
||||
nodes_audio_in.push_back(node.second);
|
||||
break;
|
||||
case PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
|
||||
nodes_audio_out.push_back(node.second);
|
||||
break;
|
||||
case PRIVACY_NODE_TYPE_NONE:
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_.unlock();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
auto Privacy::update() -> void {
|
||||
mutex_.lock();
|
||||
bool screenshare = !nodes_screenshare.empty();
|
||||
bool audio_in = !nodes_audio_in.empty();
|
||||
bool audio_out = !nodes_audio_out.empty();
|
||||
|
||||
for (Gtk::Widget* widget : box_.get_children()) {
|
||||
PrivacyItem* module = dynamic_cast<PrivacyItem*>(widget);
|
||||
if (!module) continue;
|
||||
switch (module->privacy_type) {
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
module->set_in_use(screenshare);
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:
|
||||
module->set_in_use(audio_in);
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
|
||||
module->set_in_use(audio_out);
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.unlock();
|
||||
|
||||
// Hide the whole widget if none are in use
|
||||
bool is_visible = screenshare || audio_in || audio_out;
|
||||
if (is_visible != event_box_.get_visible()) {
|
||||
// Disconnect any previous connection so that it doesn't get activated in
|
||||
// the future, hiding the module when it should be visible
|
||||
visibility_conn.disconnect();
|
||||
if (is_visible) {
|
||||
event_box_.set_visible(true);
|
||||
} else {
|
||||
// Hides the widget when all of the privacy_item revealers animations
|
||||
// have finished animating
|
||||
visibility_conn = Glib::signal_timeout().connect(
|
||||
sigc::track_obj(
|
||||
[this] {
|
||||
mutex_.lock();
|
||||
bool screenshare = !nodes_screenshare.empty();
|
||||
bool audio_in = !nodes_audio_in.empty();
|
||||
bool audio_out = !nodes_audio_out.empty();
|
||||
mutex_.unlock();
|
||||
event_box_.set_visible(screenshare || audio_in || audio_out);
|
||||
return false;
|
||||
},
|
||||
*this),
|
||||
transition_duration);
|
||||
}
|
||||
}
|
||||
|
||||
// Call parent update
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::privacy
|
163
src/modules/privacy/privacy_item.cpp
Normal file
163
src/modules/privacy/privacy_item.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
#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 {
|
||||
|
||||
PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
|
||||
std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos,
|
||||
const uint icon_size, const uint transition_duration)
|
||||
: Gtk::Revealer(),
|
||||
privacy_type(privacy_type_),
|
||||
nodes(nodes_),
|
||||
signal_conn(),
|
||||
tooltip_window(Gtk::ORIENTATION_VERTICAL, 0),
|
||||
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
icon_() {
|
||||
switch (privacy_type) {
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT:
|
||||
box_.get_style_context()->add_class("audio-in");
|
||||
iconName = "waybar-privacy-audio-input-symbolic";
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
|
||||
box_.get_style_context()->add_class("audio-out");
|
||||
iconName = "waybar-privacy-audio-output-symbolic";
|
||||
break;
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT:
|
||||
box_.get_style_context()->add_class("screenshare");
|
||||
iconName = "waybar-privacy-screen-share-symbolic";
|
||||
break;
|
||||
default:
|
||||
case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE:
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the reveal transition to not look weird when sliding in
|
||||
if (pos == "modules-left") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT);
|
||||
} else if (pos == "modules-center") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE);
|
||||
} else if (pos == "modules-right") {
|
||||
set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT);
|
||||
}
|
||||
set_transition_duration(transition_duration);
|
||||
|
||||
box_.set_name("privacy-item");
|
||||
box_.add(icon_);
|
||||
icon_.set_pixel_size(icon_size);
|
||||
add(box_);
|
||||
|
||||
// Icon Name
|
||||
if (config_["icon-name"].isString()) {
|
||||
iconName = config_["icon-name"].asString();
|
||||
}
|
||||
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);
|
||||
|
||||
// Tooltip Icon Size
|
||||
if (config_["tooltip-icon-size"].isUInt()) {
|
||||
tooltipIconSize = config_["tooltip-icon-size"].asUInt();
|
||||
}
|
||||
// Tooltip
|
||||
if (config_["tooltip"].isString()) {
|
||||
tooltip = config_["tooltip"].asBool();
|
||||
}
|
||||
set_has_tooltip(tooltip);
|
||||
if (tooltip) {
|
||||
// Sets the window to use when showing the tooltip
|
||||
update_tooltip();
|
||||
this->signal_query_tooltip().connect(sigc::track_obj(
|
||||
[this](int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip> &tooltip) {
|
||||
tooltip->set_custom(tooltip_window);
|
||||
return true;
|
||||
},
|
||||
*this));
|
||||
}
|
||||
|
||||
// Don't show by default
|
||||
set_reveal_child(true);
|
||||
set_visible(false);
|
||||
}
|
||||
|
||||
void PrivacyItem::update_tooltip() {
|
||||
// Removes all old nodes
|
||||
for (auto child : tooltip_window.get_children()) {
|
||||
delete child;
|
||||
}
|
||||
|
||||
for (auto *node : *nodes) {
|
||||
Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4);
|
||||
|
||||
// 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);
|
||||
box->add(*node_icon);
|
||||
|
||||
// Set model
|
||||
Gtk::Label *node_name = new Gtk::Label(node->get_name());
|
||||
box->add(*node_name);
|
||||
|
||||
tooltip_window.add(*box);
|
||||
}
|
||||
|
||||
tooltip_window.show_all();
|
||||
}
|
||||
|
||||
void PrivacyItem::set_in_use(bool in_use) {
|
||||
if (in_use) {
|
||||
update_tooltip();
|
||||
}
|
||||
|
||||
if (this->in_use == in_use && init) return;
|
||||
|
||||
if (init) {
|
||||
// Disconnect any previous connection so that it doesn't get activated in
|
||||
// the future, hiding the module when it should be visible
|
||||
signal_conn.disconnect();
|
||||
|
||||
this->in_use = in_use;
|
||||
guint duration = 0;
|
||||
if (this->in_use) {
|
||||
set_visible(true);
|
||||
} else {
|
||||
set_reveal_child(false);
|
||||
duration = get_transition_duration();
|
||||
}
|
||||
|
||||
signal_conn = Glib::signal_timeout().connect(sigc::track_obj(
|
||||
[this] {
|
||||
if (this->in_use) {
|
||||
set_reveal_child(true);
|
||||
} else {
|
||||
set_visible(false);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
*this),
|
||||
duration);
|
||||
} else {
|
||||
set_visible(false);
|
||||
set_reveal_child(false);
|
||||
}
|
||||
this->init = true;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::privacy
|
155
src/util/pipewire_backend.cpp
Normal file
155
src/util/pipewire_backend.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
#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<PrivacyNodeInfo *>(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<PipewireBackend *>(_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<PipewireBackend *>(_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> PipewireBackend::getInstance() {
|
||||
private_constructor_tag tag;
|
||||
return std::make_shared<PipewireBackend>(tag);
|
||||
}
|
||||
} // namespace waybar::util::PipewireBackend
|
Reference in New Issue
Block a user