Added tooltips
This commit is contained in:
		
							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