diff --git a/include/modules/privacy/privacy_item.hpp b/include/modules/privacy/privacy_item.hpp index 321cae5c..802ca08e 100644 --- a/include/modules/privacy/privacy_item.hpp +++ b/include/modules/privacy/privacy_item.hpp @@ -12,12 +12,15 @@ #include "gtkmm/revealer.h" #include "util/pipewire/privacy_node_info.hpp" +using waybar::util::PipewireBackend::PrivacyNodeInfo; +using waybar::util::PipewireBackend::PrivacyNodeType; + namespace waybar::modules::privacy { class PrivacyItem : public Gtk::Revealer { public: - PrivacyItem(const Json::Value&, enum util::PipewireBackend::PrivacyNodeType privacy_type_, - const std::string& pos); + PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, + std::list *nodes, const std::string &pos); bool is_enabled(); @@ -26,9 +29,10 @@ class PrivacyItem : public Gtk::Revealer { void set_icon_size(uint size); private: - enum util::PipewireBackend::PrivacyNodeType privacy_type; + enum PrivacyNodeType privacy_type; + std::list *nodes; - std::mutex mutex_; + Gtk::Box tooltip_window; bool init = false; bool in_use = false; @@ -37,12 +41,15 @@ class PrivacyItem : public Gtk::Revealer { // Config bool enabled = true; std::string iconName = "image-missing-symbolic"; + bool tooltip = true; + uint tooltipIconSize = 24; Gtk::Box box_; Gtk::Image icon_; void on_child_revealed_changed(); void on_map_changed(); + void update_tooltip(); }; } // namespace waybar::modules::privacy diff --git a/include/util/pipewire/pipewire_backend.hpp b/include/util/pipewire/pipewire_backend.hpp index 4e23b282..8eb0184a 100644 --- a/include/util/pipewire/pipewire_backend.hpp +++ b/include/util/pipewire/pipewire_backend.hpp @@ -30,7 +30,7 @@ class PipewireBackend { sigc::signal privacy_nodes_changed_signal_event; - std::unordered_map privacy_nodes; + std::unordered_map privacy_nodes; static std::shared_ptr getInstance(); diff --git a/include/util/pipewire/privacy_node_info.hpp b/include/util/pipewire/privacy_node_info.hpp index b370cbb2..1c523f97 100644 --- a/include/util/pipewire/privacy_node_info.hpp +++ b/include/util/pipewire/privacy_node_info.hpp @@ -4,6 +4,8 @@ #include +#include "util/gtk_icon.hpp" + namespace waybar::util::PipewireBackend { enum PrivacyNodeType { @@ -22,15 +24,44 @@ class PrivacyNodeInfo { std::string media_class; std::string media_name; std::string node_name; + std::string application_name; + + std::string pipewire_access_portal_app_id; + std::string application_icon_name; struct spa_hook node_listener; bool changed = false; - void* data; + void *data; - PrivacyNodeInfo(uint32_t id_, void* data_) : id(id_), data(data_) {} + PrivacyNodeInfo(uint32_t id_, void *data_) : id(id_), data(data_) {} ~PrivacyNodeInfo() { spa_hook_remove(&node_listener); } + + 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 get_icon_name() { + const std::vector names{&application_icon_name, &pipewire_access_portal_app_id, + &application_name, &node_name}; + 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; + } }; } // namespace waybar::util::PipewireBackend diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 06fb8259..2f7e4235 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -23,9 +23,12 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st nodes_screenshare(), nodes_audio_in(), nodes_audio_out(), - privacy_item_screenshare(config["screenshare"], PRIVACY_NODE_TYPE_VIDEO_INPUT, pos), - privacy_item_audio_input(config["audio-in"], PRIVACY_NODE_TYPE_AUDIO_INPUT, pos), - privacy_item_audio_output(config["audio-out"], PRIVACY_NODE_TYPE_AUDIO_OUTPUT, pos), + privacy_item_screenshare(config["screenshare"], PRIVACY_NODE_TYPE_VIDEO_INPUT, + &nodes_screenshare, pos), + privacy_item_audio_input(config["audio-in"], PRIVACY_NODE_TYPE_AUDIO_INPUT, &nodes_audio_in, + pos), + privacy_item_audio_output(config["audio-out"], PRIVACY_NODE_TYPE_AUDIO_OUTPUT, + &nodes_audio_out, pos), visibility_conn(), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { box_.set_name(name_); @@ -74,25 +77,18 @@ void Privacy::onPrivacyNodesChanged() { nodes_audio_in.clear(); nodes_screenshare.clear(); - bool screenshare = false; - bool audio_in = false; - bool audio_out = false; for (auto& node : backend->privacy_nodes) { - if (screenshare && audio_in && audio_out) break; - switch (node.second->state) { + switch (node.second.state) { case PW_NODE_STATE_RUNNING: - switch (node.second->type) { + switch (node.second.type) { case PRIVACY_NODE_TYPE_VIDEO_INPUT: - screenshare = true; - nodes_screenshare.push_back(node.second); + nodes_screenshare.push_back(&node.second); break; case PRIVACY_NODE_TYPE_AUDIO_INPUT: - audio_in = true; - nodes_audio_in.push_back(node.second); + nodes_audio_in.push_back(&node.second); break; case PRIVACY_NODE_TYPE_AUDIO_OUTPUT: - audio_out = true; - nodes_audio_out.push_back(node.second); + nodes_audio_out.push_back(&node.second); break; case PRIVACY_NODE_TYPE_NONE: continue; @@ -108,6 +104,7 @@ void Privacy::onPrivacyNodesChanged() { } 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(); @@ -115,6 +112,7 @@ auto Privacy::update() -> void { privacy_item_screenshare.set_in_use(screenshare); privacy_item_audio_input.set_in_use(audio_in); privacy_item_audio_output.set_in_use(audio_out); + mutex_.unlock(); // Hide the whole widget if none are in use bool is_visible = screenshare || audio_in || audio_out; @@ -130,9 +128,11 @@ auto Privacy::update() -> void { 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; }, diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index 7ed8efe1..f4c04ede 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -23,12 +23,12 @@ namespace waybar::modules::privacy { -PrivacyItem::PrivacyItem(const Json::Value& config_, - enum util::PipewireBackend::PrivacyNodeType privacy_type_, - const std::string& pos) +PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, + std::list *nodes_, const std::string &pos) : Gtk::Revealer(), privacy_type(privacy_type_), - mutex_(), + nodes(nodes_), + tooltip_window(Gtk::ORIENTATION_VERTICAL, 0), box_(Gtk::ORIENTATION_HORIZONTAL, 0), icon_() { switch (privacy_type) { @@ -74,6 +74,26 @@ PrivacyItem::PrivacyItem(const Json::Value& config_, } 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 &tooltip) { + tooltip->set_custom(tooltip_window); + return true; + }, + *this)); + } + property_child_revealed().signal_changed().connect( sigc::mem_fun(*this, &PrivacyItem::on_child_revealed_changed)); signal_map().connect(sigc::mem_fun(*this, &PrivacyItem::on_map_changed)); @@ -83,6 +103,31 @@ PrivacyItem::PrivacyItem(const Json::Value& config_, 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(); +} + bool PrivacyItem::is_enabled() { return enabled; } void PrivacyItem::on_child_revealed_changed() { @@ -98,12 +143,12 @@ void PrivacyItem::on_map_changed() { } void PrivacyItem::set_in_use(bool in_use) { - mutex_.lock(); - if (this->in_use == in_use && init) { - mutex_.unlock(); - return; + if (in_use) { + update_tooltip(); } + if (this->in_use == in_use && init) return; + if (init) { this->in_use = in_use; if (this->in_use) { @@ -136,8 +181,6 @@ void PrivacyItem::set_in_use(bool in_use) { get_style_context()->add_class(status); } lastStatus = status; - - mutex_.unlock(); } void PrivacyItem::set_icon_size(uint size) { icon_.set_pixel_size(size); } diff --git a/src/util/pipewire_backend.cpp b/src/util/pipewire_backend.cpp index a2ac64a1..47f4dc4f 100644 --- a/src/util/pipewire_backend.cpp +++ b/src/util/pipewire_backend.cpp @@ -26,21 +26,25 @@ static void get_node_info(void *data_, const struct pw_node_info *info) { 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; } } if (p_node_info->type != PRIVACY_NODE_TYPE_NONE) { backend->mutex_.lock(); p_node_info->changed = true; - backend->privacy_nodes.insert_or_assign(info->id, p_node_info); + backend->privacy_nodes.insert_or_assign(info->id, *p_node_info); backend->mutex_.unlock(); backend->privacy_nodes_changed_signal_event.emit(); } else { if (p_node_info->changed) { backend->mutex_.lock(); - PrivacyNodeInfo *node = backend->privacy_nodes.at(info->id); - delete node; backend->privacy_nodes.erase(info->id); backend->mutex_.unlock(); @@ -64,7 +68,7 @@ static void registry_event_global(void *_data, uint32_t id, uint32_t permissions PrivacyNodeInfo *p_node_info; backend->mutex_.lock(); if (backend->privacy_nodes.contains(id)) { - p_node_info = backend->privacy_nodes.at(id); + p_node_info = &backend->privacy_nodes.at(id); } else { p_node_info = new PrivacyNodeInfo(id, backend); } @@ -78,8 +82,6 @@ static void registry_event_global_remove(void *_data, uint32_t id) { backend->mutex_.lock(); if (backend->privacy_nodes.contains(id)) { - PrivacyNodeInfo *node_info = backend->privacy_nodes.at(id); - delete node_info; backend->privacy_nodes.erase(id); } backend->mutex_.unlock(); @@ -118,10 +120,6 @@ PipewireBackend::PipewireBackend(private_constructor_tag tag) } PipewireBackend::~PipewireBackend() { - for (auto &node : privacy_nodes) { - delete node.second; - } - if (registry != nullptr) { pw_proxy_destroy((struct pw_proxy *)registry); }