Added tooltips
parent
46e36c0e68
commit
d32da917e4
|
@ -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<PrivacyNodeInfo *> *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<PrivacyNodeInfo *> *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
|
||||
|
|
|
@ -30,7 +30,7 @@ class PipewireBackend {
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "util/gtk_icon.hpp"
|
||||
|
||||
namespace waybar::util::PipewireBackend {
|
||||
|
||||
enum PrivacyNodeType {
|
||||
|
@ -22,6 +24,10 @@ 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;
|
||||
|
||||
|
@ -32,5 +38,30 @@ class PrivacyNodeInfo {
|
|||
PrivacyNodeInfo(uint32_t id_, void *data_) : id(id_), data(data_) {}
|
||||
|
||||
~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
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
|
|
|
@ -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<PrivacyNodeInfo *> *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<Gtk::Tooltip> &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); }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue