Added tooltips

This commit is contained in:
Erik Reider 2023-10-29 00:17:53 +02:00
parent 46e36c0e68
commit d32da917e4
6 changed files with 121 additions and 42 deletions

View File

@ -12,12 +12,15 @@
#include "gtkmm/revealer.h" #include "gtkmm/revealer.h"
#include "util/pipewire/privacy_node_info.hpp" #include "util/pipewire/privacy_node_info.hpp"
using waybar::util::PipewireBackend::PrivacyNodeInfo;
using waybar::util::PipewireBackend::PrivacyNodeType;
namespace waybar::modules::privacy { namespace waybar::modules::privacy {
class PrivacyItem : public Gtk::Revealer { class PrivacyItem : public Gtk::Revealer {
public: public:
PrivacyItem(const Json::Value&, enum util::PipewireBackend::PrivacyNodeType privacy_type_, PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
const std::string& pos); std::list<PrivacyNodeInfo *> *nodes, const std::string &pos);
bool is_enabled(); bool is_enabled();
@ -26,9 +29,10 @@ class PrivacyItem : public Gtk::Revealer {
void set_icon_size(uint size); void set_icon_size(uint size);
private: private:
enum util::PipewireBackend::PrivacyNodeType privacy_type; enum PrivacyNodeType privacy_type;
std::list<PrivacyNodeInfo *> *nodes;
std::mutex mutex_; Gtk::Box tooltip_window;
bool init = false; bool init = false;
bool in_use = false; bool in_use = false;
@ -37,12 +41,15 @@ class PrivacyItem : public Gtk::Revealer {
// Config // Config
bool enabled = true; bool enabled = true;
std::string iconName = "image-missing-symbolic"; std::string iconName = "image-missing-symbolic";
bool tooltip = true;
uint tooltipIconSize = 24;
Gtk::Box box_; Gtk::Box box_;
Gtk::Image icon_; Gtk::Image icon_;
void on_child_revealed_changed(); void on_child_revealed_changed();
void on_map_changed(); void on_map_changed();
void update_tooltip();
}; };
} // namespace waybar::modules::privacy } // namespace waybar::modules::privacy

View File

@ -30,7 +30,7 @@ class PipewireBackend {
sigc::signal<void> privacy_nodes_changed_signal_event; sigc::signal<void> privacy_nodes_changed_signal_event;
std::unordered_map<uint32_t, PrivacyNodeInfo*> privacy_nodes; std::unordered_map<uint32_t, PrivacyNodeInfo> privacy_nodes;
static std::shared_ptr<PipewireBackend> getInstance(); static std::shared_ptr<PipewireBackend> getInstance();

View File

@ -4,6 +4,8 @@
#include <string> #include <string>
#include "util/gtk_icon.hpp"
namespace waybar::util::PipewireBackend { namespace waybar::util::PipewireBackend {
enum PrivacyNodeType { enum PrivacyNodeType {
@ -22,6 +24,10 @@ class PrivacyNodeInfo {
std::string media_class; std::string media_class;
std::string media_name; std::string media_name;
std::string node_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; struct spa_hook node_listener;
@ -32,5 +38,30 @@ class PrivacyNodeInfo {
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); } ~PrivacyNodeInfo() { spa_hook_remove(&node_listener); }
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};
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 } // namespace waybar::util::PipewireBackend

View File

@ -23,9 +23,12 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st
nodes_screenshare(), nodes_screenshare(),
nodes_audio_in(), nodes_audio_in(),
nodes_audio_out(), nodes_audio_out(),
privacy_item_screenshare(config["screenshare"], PRIVACY_NODE_TYPE_VIDEO_INPUT, pos), privacy_item_screenshare(config["screenshare"], PRIVACY_NODE_TYPE_VIDEO_INPUT,
privacy_item_audio_input(config["audio-in"], PRIVACY_NODE_TYPE_AUDIO_INPUT, pos), &nodes_screenshare, pos),
privacy_item_audio_output(config["audio-out"], PRIVACY_NODE_TYPE_AUDIO_OUTPUT, 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(), visibility_conn(),
box_(Gtk::ORIENTATION_HORIZONTAL, 0) { box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
box_.set_name(name_); box_.set_name(name_);
@ -74,25 +77,18 @@ void Privacy::onPrivacyNodesChanged() {
nodes_audio_in.clear(); nodes_audio_in.clear();
nodes_screenshare.clear(); nodes_screenshare.clear();
bool screenshare = false;
bool audio_in = false;
bool audio_out = false;
for (auto& node : backend->privacy_nodes) { 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: case PW_NODE_STATE_RUNNING:
switch (node.second->type) { switch (node.second.type) {
case PRIVACY_NODE_TYPE_VIDEO_INPUT: case PRIVACY_NODE_TYPE_VIDEO_INPUT:
screenshare = true; nodes_screenshare.push_back(&node.second);
nodes_screenshare.push_back(node.second);
break; break;
case PRIVACY_NODE_TYPE_AUDIO_INPUT: case PRIVACY_NODE_TYPE_AUDIO_INPUT:
audio_in = true; nodes_audio_in.push_back(&node.second);
nodes_audio_in.push_back(node.second);
break; break;
case PRIVACY_NODE_TYPE_AUDIO_OUTPUT: case PRIVACY_NODE_TYPE_AUDIO_OUTPUT:
audio_out = true; nodes_audio_out.push_back(&node.second);
nodes_audio_out.push_back(node.second);
break; break;
case PRIVACY_NODE_TYPE_NONE: case PRIVACY_NODE_TYPE_NONE:
continue; continue;
@ -108,6 +104,7 @@ void Privacy::onPrivacyNodesChanged() {
} }
auto Privacy::update() -> void { auto Privacy::update() -> void {
mutex_.lock();
bool screenshare = !nodes_screenshare.empty(); bool screenshare = !nodes_screenshare.empty();
bool audio_in = !nodes_audio_in.empty(); bool audio_in = !nodes_audio_in.empty();
bool audio_out = !nodes_audio_out.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_screenshare.set_in_use(screenshare);
privacy_item_audio_input.set_in_use(audio_in); privacy_item_audio_input.set_in_use(audio_in);
privacy_item_audio_output.set_in_use(audio_out); privacy_item_audio_output.set_in_use(audio_out);
mutex_.unlock();
// Hide the whole widget if none are in use // Hide the whole widget if none are in use
bool is_visible = screenshare || audio_in || audio_out; bool is_visible = screenshare || audio_in || audio_out;
@ -130,9 +128,11 @@ auto Privacy::update() -> void {
visibility_conn = Glib::signal_timeout().connect( visibility_conn = Glib::signal_timeout().connect(
sigc::track_obj( sigc::track_obj(
[this] { [this] {
mutex_.lock();
bool screenshare = !nodes_screenshare.empty(); bool screenshare = !nodes_screenshare.empty();
bool audio_in = !nodes_audio_in.empty(); bool audio_in = !nodes_audio_in.empty();
bool audio_out = !nodes_audio_out.empty(); bool audio_out = !nodes_audio_out.empty();
mutex_.unlock();
event_box_.set_visible(screenshare || audio_in || audio_out); event_box_.set_visible(screenshare || audio_in || audio_out);
return false; return false;
}, },

View File

@ -23,12 +23,12 @@
namespace waybar::modules::privacy { namespace waybar::modules::privacy {
PrivacyItem::PrivacyItem(const Json::Value& config_, PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_,
enum util::PipewireBackend::PrivacyNodeType privacy_type_, std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos)
const std::string& pos)
: Gtk::Revealer(), : Gtk::Revealer(),
privacy_type(privacy_type_), privacy_type(privacy_type_),
mutex_(), nodes(nodes_),
tooltip_window(Gtk::ORIENTATION_VERTICAL, 0),
box_(Gtk::ORIENTATION_HORIZONTAL, 0), box_(Gtk::ORIENTATION_HORIZONTAL, 0),
icon_() { icon_() {
switch (privacy_type) { switch (privacy_type) {
@ -74,6 +74,26 @@ PrivacyItem::PrivacyItem(const Json::Value& config_,
} }
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID); 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));
}
property_child_revealed().signal_changed().connect( property_child_revealed().signal_changed().connect(
sigc::mem_fun(*this, &PrivacyItem::on_child_revealed_changed)); sigc::mem_fun(*this, &PrivacyItem::on_child_revealed_changed));
signal_map().connect(sigc::mem_fun(*this, &PrivacyItem::on_map_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); 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; } bool PrivacyItem::is_enabled() { return enabled; }
void PrivacyItem::on_child_revealed_changed() { void PrivacyItem::on_child_revealed_changed() {
@ -98,12 +143,12 @@ void PrivacyItem::on_map_changed() {
} }
void PrivacyItem::set_in_use(bool in_use) { void PrivacyItem::set_in_use(bool in_use) {
mutex_.lock(); if (in_use) {
if (this->in_use == in_use && init) { update_tooltip();
mutex_.unlock();
return;
} }
if (this->in_use == in_use && init) return;
if (init) { if (init) {
this->in_use = in_use; this->in_use = in_use;
if (this->in_use) { if (this->in_use) {
@ -136,8 +181,6 @@ void PrivacyItem::set_in_use(bool in_use) {
get_style_context()->add_class(status); get_style_context()->add_class(status);
} }
lastStatus = status; lastStatus = status;
mutex_.unlock();
} }
void PrivacyItem::set_icon_size(uint size) { icon_.set_pixel_size(size); } void PrivacyItem::set_icon_size(uint size) { icon_.set_pixel_size(size); }

View File

@ -26,21 +26,25 @@ static void get_node_info(void *data_, const struct pw_node_info *info) {
p_node_info->media_name = item->value; p_node_info->media_name = item->value;
} else if (strcmp(item->key, PW_KEY_NODE_NAME) == 0) { } else if (strcmp(item->key, PW_KEY_NODE_NAME) == 0) {
p_node_info->node_name = item->value; 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) { if (p_node_info->type != PRIVACY_NODE_TYPE_NONE) {
backend->mutex_.lock(); backend->mutex_.lock();
p_node_info->changed = true; 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->mutex_.unlock();
backend->privacy_nodes_changed_signal_event.emit(); backend->privacy_nodes_changed_signal_event.emit();
} else { } else {
if (p_node_info->changed) { if (p_node_info->changed) {
backend->mutex_.lock(); backend->mutex_.lock();
PrivacyNodeInfo *node = backend->privacy_nodes.at(info->id);
delete node;
backend->privacy_nodes.erase(info->id); backend->privacy_nodes.erase(info->id);
backend->mutex_.unlock(); backend->mutex_.unlock();
@ -64,7 +68,7 @@ static void registry_event_global(void *_data, uint32_t id, uint32_t permissions
PrivacyNodeInfo *p_node_info; PrivacyNodeInfo *p_node_info;
backend->mutex_.lock(); backend->mutex_.lock();
if (backend->privacy_nodes.contains(id)) { if (backend->privacy_nodes.contains(id)) {
p_node_info = backend->privacy_nodes.at(id); p_node_info = &backend->privacy_nodes.at(id);
} else { } else {
p_node_info = new PrivacyNodeInfo(id, backend); 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(); backend->mutex_.lock();
if (backend->privacy_nodes.contains(id)) { if (backend->privacy_nodes.contains(id)) {
PrivacyNodeInfo *node_info = backend->privacy_nodes.at(id);
delete node_info;
backend->privacy_nodes.erase(id); backend->privacy_nodes.erase(id);
} }
backend->mutex_.unlock(); backend->mutex_.unlock();
@ -118,10 +120,6 @@ PipewireBackend::PipewireBackend(private_constructor_tag tag)
} }
PipewireBackend::~PipewireBackend() { PipewireBackend::~PipewireBackend() {
for (auto &node : privacy_nodes) {
delete node.second;
}
if (registry != nullptr) { if (registry != nullptr) {
pw_proxy_destroy((struct pw_proxy *)registry); pw_proxy_destroy((struct pw_proxy *)registry);
} }