Added tooltips
parent
46e36c0e68
commit
d32da917e4
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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,15 +24,44 @@ 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;
|
||||||
|
|
||||||
bool changed = false;
|
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); }
|
~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
|
||||||
|
|
|
@ -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;
|
||||||
},
|
},
|
||||||
|
|
|
@ -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); }
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue