Merge pull request #2612 from ErikReider/privacy-module
Add Privacy Module
This commit is contained in:
		
						commit
						e24adbc3c2
					
				|  | @ -17,6 +17,7 @@ | |||
| - Network | ||||
| - Bluetooth | ||||
| - Pulseaudio | ||||
| - Privacy Info | ||||
| - Wireplumber | ||||
| - Disk | ||||
| - Memory | ||||
|  |  | |||
|  | @ -68,6 +68,9 @@ | |||
| #ifdef HAVE_UPOWER | ||||
| #include "modules/upower/upower.hpp" | ||||
| #endif | ||||
| #ifdef HAVE_PIPEWIRE | ||||
| #include "modules/privacy/privacy.hpp" | ||||
| #endif | ||||
| #ifdef HAVE_LIBPULSE | ||||
| #include "modules/pulseaudio.hpp" | ||||
| #endif | ||||
|  | @ -101,7 +104,7 @@ namespace waybar { | |||
| class Factory { | ||||
|  public: | ||||
|   Factory(const Bar& bar, const Json::Value& config); | ||||
|   AModule* makeModule(const std::string& name) const; | ||||
|   AModule* makeModule(const std::string& name, const std::string& pos) const; | ||||
| 
 | ||||
|  private: | ||||
|   const Bar& bar_; | ||||
|  |  | |||
|  | @ -0,0 +1,41 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "ALabel.hpp" | ||||
| #include "gtkmm/box.h" | ||||
| #include "modules/privacy/privacy_item.hpp" | ||||
| #include "util/pipewire/pipewire_backend.hpp" | ||||
| #include "util/pipewire/privacy_node_info.hpp" | ||||
| 
 | ||||
| using waybar::util::PipewireBackend::PrivacyNodeInfo; | ||||
| 
 | ||||
| namespace waybar::modules::privacy { | ||||
| 
 | ||||
| class Privacy : public AModule { | ||||
|  public: | ||||
|   Privacy(const std::string &, const Json::Value &, const std::string &pos); | ||||
|   auto update() -> void override; | ||||
| 
 | ||||
|   void onPrivacyNodesChanged(); | ||||
| 
 | ||||
|  private: | ||||
|   std::list<PrivacyNodeInfo *> nodes_screenshare;  // Screen is being shared
 | ||||
|   std::list<PrivacyNodeInfo *> nodes_audio_in;     // Application is using the microphone
 | ||||
|   std::list<PrivacyNodeInfo *> nodes_audio_out;    // Application is outputting audio
 | ||||
| 
 | ||||
|   std::mutex mutex_; | ||||
|   sigc::connection visibility_conn; | ||||
| 
 | ||||
|   // Config
 | ||||
|   Gtk::Box box_; | ||||
|   uint iconSpacing = 4; | ||||
|   uint iconSize = 20; | ||||
|   uint transition_duration = 250; | ||||
| 
 | ||||
|   std::shared_ptr<util::PipewireBackend::PipewireBackend> backend = nullptr; | ||||
| }; | ||||
| 
 | ||||
| }  // namespace waybar::modules::privacy
 | ||||
|  | @ -0,0 +1,51 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <json/value.h> | ||||
| 
 | ||||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <mutex> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "gtkmm/box.h" | ||||
| #include "gtkmm/image.h" | ||||
| #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 &config_, enum PrivacyNodeType privacy_type_, | ||||
|               std::list<PrivacyNodeInfo *> *nodes, const std::string &pos, const uint icon_size, | ||||
|               const uint transition_duration); | ||||
| 
 | ||||
|   enum PrivacyNodeType privacy_type; | ||||
| 
 | ||||
|   void set_in_use(bool in_use); | ||||
| 
 | ||||
|  private: | ||||
|   std::list<PrivacyNodeInfo *> *nodes; | ||||
| 
 | ||||
|   sigc::connection signal_conn; | ||||
| 
 | ||||
|   Gtk::Box tooltip_window; | ||||
| 
 | ||||
|   bool init = false; | ||||
|   bool in_use = false; | ||||
| 
 | ||||
|   // Config
 | ||||
|   std::string iconName = "image-missing-symbolic"; | ||||
|   bool tooltip = true; | ||||
|   uint tooltipIconSize = 24; | ||||
| 
 | ||||
|   Gtk::Box box_; | ||||
|   Gtk::Image icon_; | ||||
| 
 | ||||
|   void update_tooltip(); | ||||
| }; | ||||
| 
 | ||||
| }  // namespace waybar::modules::privacy
 | ||||
|  | @ -0,0 +1,40 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <pipewire/pipewire.h> | ||||
| 
 | ||||
| #include "util/backend_common.hpp" | ||||
| #include "util/pipewire/privacy_node_info.hpp" | ||||
| 
 | ||||
| namespace waybar::util::PipewireBackend { | ||||
| 
 | ||||
| class PipewireBackend { | ||||
|  private: | ||||
|   pw_thread_loop* mainloop_; | ||||
|   pw_context* context_; | ||||
|   pw_core* core_; | ||||
| 
 | ||||
|   spa_hook registry_listener; | ||||
| 
 | ||||
|   /* Hack to keep constructor inaccessible but still public.
 | ||||
|    * This is required to be able to use std::make_shared. | ||||
|    * It is important to keep this class only accessible via a reference-counted | ||||
|    * pointer because the destructor will manually free memory, and this could be | ||||
|    * a problem with C++20's copy and move semantics. | ||||
|    */ | ||||
|   struct private_constructor_tag {}; | ||||
| 
 | ||||
|  public: | ||||
|   std::mutex mutex_; | ||||
| 
 | ||||
|   pw_registry* registry; | ||||
| 
 | ||||
|   sigc::signal<void> privacy_nodes_changed_signal_event; | ||||
| 
 | ||||
|   std::unordered_map<uint32_t, PrivacyNodeInfo*> privacy_nodes; | ||||
| 
 | ||||
|   static std::shared_ptr<PipewireBackend> getInstance(); | ||||
| 
 | ||||
|   PipewireBackend(private_constructor_tag tag); | ||||
|   ~PipewireBackend(); | ||||
| }; | ||||
| }  // namespace waybar::util::PipewireBackend
 | ||||
|  | @ -0,0 +1,62 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <pipewire/pipewire.h> | ||||
| 
 | ||||
| #include <string> | ||||
| 
 | ||||
| #include "util/gtk_icon.hpp" | ||||
| 
 | ||||
| namespace waybar::util::PipewireBackend { | ||||
| 
 | ||||
| enum PrivacyNodeType { | ||||
|   PRIVACY_NODE_TYPE_NONE, | ||||
|   PRIVACY_NODE_TYPE_VIDEO_INPUT, | ||||
|   PRIVACY_NODE_TYPE_AUDIO_INPUT, | ||||
|   PRIVACY_NODE_TYPE_AUDIO_OUTPUT | ||||
| }; | ||||
| 
 | ||||
| class PrivacyNodeInfo { | ||||
|  public: | ||||
|   PrivacyNodeType type = PRIVACY_NODE_TYPE_NONE; | ||||
|   uint32_t id; | ||||
|   uint32_t client_id; | ||||
|   enum pw_node_state state = PW_NODE_STATE_IDLE; | ||||
|   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 object_listener; | ||||
|   struct spa_hook proxy_listener; | ||||
| 
 | ||||
|   void *data; | ||||
| 
 | ||||
|   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}; | ||||
|     const 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
 | ||||
|  | @ -0,0 +1,85 @@ | |||
| waybar-privacy(5) | ||||
| 
 | ||||
| # NAME | ||||
| 
 | ||||
| waybar - privacy module | ||||
| 
 | ||||
| # DESCRIPTION | ||||
| 
 | ||||
| The *privacy* module displays if any application is capturing audio, sharing ++ | ||||
| the screen or playing audio. | ||||
| 
 | ||||
| # CONFIGURATION | ||||
| 
 | ||||
| *icon-spacing*: ++ | ||||
| 	typeof: integer ++ | ||||
| 	default: 4 ++ | ||||
| 	The spacing between each privacy icon. | ||||
| 
 | ||||
| *icon-size*: ++ | ||||
| 	typeof: integer ++ | ||||
| 	default: 20 ++ | ||||
| 	The size of each privacy icon. | ||||
| 
 | ||||
| *transition-duration*: ++ | ||||
| 	typeof: integer ++ | ||||
| 	default: 250 ++ | ||||
| 	Option to disable tooltip on hover. | ||||
| 
 | ||||
| *modules* ++ | ||||
| 	typeof: array of objects ++ | ||||
| 	default: [{"type": "screenshare"}, {"type": "audio-in"}] ++ | ||||
| 	Which privacy modules to monitor. See *MODULES CONFIGURATION* for++ | ||||
| 	more information. | ||||
| 
 | ||||
| # MODULES CONFIGURATION | ||||
| 
 | ||||
| *type*: ++ | ||||
| 	typeof: string ++ | ||||
| 	values: "screenshare", "audio-in", "audio-out" ++ | ||||
| 	Specifies which module to use and configure. | ||||
| 
 | ||||
| *tooltip*: ++ | ||||
| 	typeof: bool ++ | ||||
| 	default: true ++ | ||||
| 	Option to disable tooltip on hover. | ||||
| 
 | ||||
| *tooltip-icon-size*: ++ | ||||
| 	typeof: integer ++ | ||||
| 	default: 24 ++ | ||||
| 	The size of each icon in the tooltip. | ||||
| 
 | ||||
| # EXAMPLES | ||||
| 
 | ||||
| ``` | ||||
| "privacy": { | ||||
| 	"icon-spacing": 4, | ||||
| 	"icon-size": 18, | ||||
| 	"transition-duration": 250, | ||||
| 	"modules": [ | ||||
| 		{ | ||||
| 			"type": "screenshare", | ||||
| 			"tooltip": true, | ||||
| 			"tooltip-icon-size": 24 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"type": "audio-out", | ||||
| 			"tooltip": true, | ||||
| 			"tooltip-icon-size": 24 | ||||
| 		}, | ||||
| 		{ | ||||
| 			"type": "audio-in", | ||||
| 			"tooltip": true, | ||||
| 			"tooltip-icon-size": 24 | ||||
| 		} | ||||
| 	] | ||||
| }, | ||||
| ``` | ||||
| 
 | ||||
| # STYLE | ||||
| 
 | ||||
| - *#privacy* | ||||
| - *#privacy-item* | ||||
| - *#privacy-item.screenshare* | ||||
| - *#privacy-item.audio-in* | ||||
| - *#privacy-item.audio-out* | ||||
							
								
								
									
										16
									
								
								meson.build
								
								
								
								
							
							
						
						
									
										16
									
								
								meson.build
								
								
								
								
							|  | @ -98,6 +98,7 @@ libinput = dependency('libinput', required: get_option('libinput')) | |||
| libnl = dependency('libnl-3.0', required: get_option('libnl')) | ||||
| libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) | ||||
| upower_glib = dependency('upower-glib', required: get_option('upower_glib')) | ||||
| pipewire = dependency('libpipewire-0.3', required: get_option('pipewire')) | ||||
| playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris')) | ||||
| libpulse = dependency('libpulse', required: get_option('pulseaudio')) | ||||
| libudev = dependency('libudev', required: get_option('libudev')) | ||||
|  | @ -273,6 +274,14 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable | |||
|     src_files += 'src/modules/upower/upower_tooltip.cpp' | ||||
| endif | ||||
| 
 | ||||
| 
 | ||||
| if (pipewire.found()) | ||||
|     add_project_arguments('-DHAVE_PIPEWIRE', language: 'cpp') | ||||
|     src_files += 'src/modules/privacy/privacy.cpp' | ||||
|     src_files += 'src/modules/privacy/privacy_item.cpp' | ||||
|     src_files += 'src/util/pipewire_backend.cpp' | ||||
| endif | ||||
| 
 | ||||
| if (playerctl.found() and giounix.found() and not get_option('logind').disabled()) | ||||
|     add_project_arguments('-DHAVE_MPRIS', language: 'cpp') | ||||
|     src_files += 'src/modules/mpris/mpris.cpp' | ||||
|  | @ -373,9 +382,12 @@ endif | |||
| 
 | ||||
| subdir('protocol') | ||||
| 
 | ||||
| app_resources = [] | ||||
| subdir('resources/icons') | ||||
| 
 | ||||
| executable( | ||||
|     'waybar', | ||||
|     src_files, | ||||
|     [src_files, app_resources], | ||||
|     dependencies: [ | ||||
|         thread_dep, | ||||
|         client_protos, | ||||
|  | @ -392,6 +404,7 @@ executable( | |||
|         libnl, | ||||
|         libnlgen, | ||||
|         upower_glib, | ||||
|         pipewire, | ||||
|         playerctl, | ||||
|         libpulse, | ||||
|         libjack, | ||||
|  | @ -454,6 +467,7 @@ if scdoc.found() | |||
|         'waybar-network.5.scd', | ||||
|         'waybar-pulseaudio.5.scd', | ||||
|         'waybar-pulseaudio-slider.5.scd', | ||||
|         'waybar-privacy.5.scd', | ||||
|         'waybar-river-mode.5.scd', | ||||
|         'waybar-river-tags.5.scd', | ||||
|         'waybar-river-window.5.scd', | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev s | |||
| option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') | ||||
| option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') | ||||
| option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower') | ||||
| option('pipewire', type: 'feature', value: 'auto', description: 'Enable support for pipewire') | ||||
| option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris') | ||||
| option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') | ||||
| option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') | ||||
|  |  | |||
|  | @ -0,0 +1,6 @@ | |||
| gnome = import('gnome') | ||||
| 
 | ||||
| app_resources += gnome.compile_resources('icon-resources', | ||||
|   'waybar_icons.gresource.xml', | ||||
|   c_name: 'waybar_icons' | ||||
| ) | ||||
|  | @ -0,0 +1,4 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> | ||||
|     <path d="m 8 0 c -1.660156 0 -3 1.339844 -3 3 v 5 c 0 1.660156 1.339844 3 3 3 s 3 -1.339844 3 -3 v -5 c 0 -1.660156 -1.339844 -3 -3 -3 z m -6 6 v 2.011719 c 0 2.964843 2.164062 5.429687 5 5.90625 v 2.082031 h 2 v -2.082031 c 2.835938 -0.476563 5 -2.941407 5 -5.90625 v -2.011719 h -1.5 v 2.011719 c 0 2.5 -1.992188 4.488281 -4.5 4.488281 s -4.5 -1.988281 -4.5 -4.488281 v -2.011719 z m 0 0" fill="#2e3436"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 546 B | 
|  | @ -0,0 +1,2 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" viewBox="0 0 16 16" width="16px"><filter id="a" height="100%" width="100%" x="0%" y="0%"><feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/></filter><mask id="b"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.5"/></g></mask><clipPath id="c"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="d"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.7"/></g></mask><clipPath id="e"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><mask id="f"><g filter="url(#a)"><path d="m -1.6 -1.6 h 19.2 v 19.2 h -19.2 z" fill-opacity="0.35"/></g></mask><clipPath id="g"><path d="m 0 0 h 1600 v 1200 h -1600 z"/></clipPath><path d="m 1.972656 3.773438 c -0.132812 0.007812 -0.257812 0.070312 -0.34375 0.167968 c -1.066406 1.210938 -1.628906 2.585938 -1.628906 3.984375 c 0 1.402344 0.558594 2.8125 1.617188 4.105469 c 0.175781 0.214844 0.488281 0.246094 0.703124 0.070312 c 0.214844 -0.175781 0.246094 -0.488281 0.070313 -0.703124 c -0.945313 -1.152344 -1.390625 -2.332032 -1.390625 -3.472657 c 0 -1.136719 0.441406 -2.261719 1.378906 -3.324219 c 0.183594 -0.207031 0.164063 -0.523437 -0.042968 -0.707031 c -0.101563 -0.085937 -0.230469 -0.128906 -0.363282 -0.121093 z m 11.984375 0 c -0.105469 0.011718 -0.207031 0.054687 -0.285156 0.121093 c -0.207031 0.183594 -0.226563 0.5 -0.042969 0.710938 c 0.933594 1.058593 1.375 2.183593 1.375 3.320312 c 0 1.140625 -0.445312 2.320313 -1.386718 3.472657 c -0.175782 0.214843 -0.144532 0.527343 0.070312 0.703124 c 0.210938 0.175782 0.527344 0.144532 0.703125 -0.070312 c 1.058594 -1.292969 1.613281 -2.703125 1.613281 -4.101562 c 0 -1.402344 -0.558594 -2.777344 -1.625 -3.988282 c -0.109375 -0.121094 -0.265625 -0.183594 -0.421875 -0.167968 z m -8.101562 0.164062 c -0.480469 0.023438 -0.855469 0.417969 -0.855469 0.898438 v 6.359374 c 0 0.675782 0.742188 1.085938 1.3125 0.730469 l 5.265625 -3.246093 c 0.507813 -0.3125 0.507813 -1.046876 0.003906 -1.363282 l -5.203125 -3.246094 c -0.15625 -0.097656 -0.339844 -0.144531 -0.523437 -0.132812 z m -2.359375 1.050781 c -0.160156 -0.003906 -0.3125 0.066407 -0.410156 0.195313 c -0.679688 0.886718 -1.070313 1.824218 -1.078126 2.792968 c -0.011718 0.964844 0.363282 1.921876 1.085938 2.835938 c 0.167969 0.214844 0.484375 0.25 0.699219 0.078125 c 0.214843 -0.167969 0.253906 -0.484375 0.082031 -0.699219 c -0.617188 -0.777344 -0.875 -1.5 -0.871094 -2.207031 c 0.007813 -0.703125 0.289063 -1.425781 0.875 -2.191406 c 0.167969 -0.21875 0.128906 -0.53125 -0.09375 -0.703125 c -0.082031 -0.0625 -0.183594 -0.097656 -0.289062 -0.101563 z m 9.015625 0 c -0.109375 0.003907 -0.210938 0.039063 -0.292969 0.105469 c -0.21875 0.164062 -0.261719 0.480469 -0.09375 0.699219 c 0.585938 0.765625 0.867188 1.488281 0.875 2.195312 c 0.007812 0.703125 -0.253906 1.425781 -0.867188 2.203125 c -0.171874 0.214844 -0.136718 0.53125 0.082032 0.703125 c 0.214844 0.167969 0.527344 0.132813 0.699218 -0.082031 c 0.722657 -0.914062 1.097657 -1.871094 1.085938 -2.835938 c -0.011719 -0.96875 -0.398438 -1.90625 -1.078125 -2.792968 c -0.097656 -0.128906 -0.253906 -0.199219 -0.410156 -0.195313 z m 0 0" fill="#222222"/><g mask="url(#b)"><g clip-path="url(#c)" transform="matrix(1 0 0 1 -600 -1044)"><path d="m 550 182 c -0.351562 0.003906 -0.695312 0.101562 -1 0.28125 v 3.4375 c 0.304688 0.179688 0.648438 0.277344 1 0.28125 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 c -0.339844 0 -0.679688 0.058594 -1 0.175781 v 6.824219 h 4 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#d)"><g clip-path="url(#e)" transform="matrix(1 0 0 1 -600 -1044)"><path d="m 569 182 v 4 c 1.105469 0 2 -0.894531 2 -2 s -0.894531 -2 -2 -2 z m 0 5 v 7 h 3 v -4 c 0 -1.65625 -1.34375 -3 -3 -3 z m 0 0"/></g></g><g mask="url(#f)"><g clip-path="url(#g)" transform="matrix(1 0 0 1 -600 -1044)"><path d="m 573 182.269531 v 3.449219 c 0.613281 -0.355469 0.996094 -1.007812 1 -1.71875 c 0 -0.714844 -0.382812 -1.375 -1 -1.730469 z m 0 4.90625 v 6.824219 h 2 v -4 c 0 -1.269531 -0.800781 -2.402344 -2 -2.824219 z m 0 0"/></g></g></svg> | ||||
| After Width: | Height: | Size: 4.2 KiB | 
|  | @ -0,0 +1,7 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg"> | ||||
|     <g fill="#2e3436"> | ||||
|         <path d="m 3 0 c -1.660156 0 -3 1.339844 -3 3 v 7 c 0 1.660156 1.339844 3 3 3 h 10 c 1.660156 0 3 -1.339844 3 -3 v -1 c 0 -0.550781 -0.449219 -1 -1 -1 s -1 0.449219 -1 1 v 1 c 0 0.554688 -0.445312 1 -1 1 h -10 c -0.554688 0 -1 -0.445312 -1 -1 v -7 c 0 -0.554688 0.445312 -1 1 -1 h 4 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 z m 7 0 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 2.585938 l -5.292969 5.289062 c -0.1875 0.191407 -0.292969 0.445313 -0.292969 0.710938 s 0.105469 0.519531 0.292969 0.707031 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 l 5.292969 -5.292969 v 2.585938 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -5 c 0 -0.085938 -0.011719 -0.171875 -0.035156 -0.257812 c -0.023438 -0.085938 -0.054688 -0.167969 -0.101563 -0.242188 c -0.042969 -0.074219 -0.09375 -0.144531 -0.15625 -0.207031 c 0 0 0 0 0 -0.003907 c -0.015625 -0.011718 -0.03125 -0.023437 -0.046875 -0.035156 c -0.054687 -0.046875 -0.117187 -0.089844 -0.183594 -0.128906 c -0.035156 -0.015625 -0.074218 -0.03125 -0.113281 -0.046875 c -0.050781 -0.0195312 -0.101562 -0.0351562 -0.15625 -0.0507812 c -0.039062 -0.0078126 -0.082031 -0.0117188 -0.121093 -0.015625 c -0.027344 -0.0039063 -0.058594 -0.00781255 -0.085938 -0.0117188 z m -5 14 c -1.105469 0 -2 0.894531 -2 2 h 10 c 0 -1.105469 -0.894531 -2 -2 -2 z m 0 0"/> | ||||
|         <path d="m 3 3 v 7 h 10 v -4.171875 l -3.585938 3.585937 c -0.773437 0.773438 -2.054687 0.773438 -2.828124 0 c -0.773438 -0.773437 -0.773438 -2.058593 0 -2.832031 l 3.585937 -3.582031 z m 0 0" fill-opacity="0.34902"/> | ||||
|     </g> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.7 KiB | 
|  | @ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <gresources> | ||||
|   <gresource prefix="/fr/arouillard/waybar/icons/scalable/actions"> | ||||
|     <file preprocess="xml-stripblanks">waybar-privacy-audio-input-symbolic.svg</file> | ||||
|     <file preprocess="xml-stripblanks">waybar-privacy-audio-output-symbolic.svg</file> | ||||
|     <file preprocess="xml-stripblanks">waybar-privacy-screen-share-symbolic.svg</file> | ||||
|   </gresource> | ||||
| </gresources> | ||||
|  | @ -278,3 +278,24 @@ label:focus { | |||
| #scratchpad.empty { | ||||
| 	background-color: transparent; | ||||
| } | ||||
| 
 | ||||
| #privacy { | ||||
|     padding: 0; | ||||
| } | ||||
| 
 | ||||
| #privacy-item { | ||||
|     padding: 0 5px; | ||||
|     color: white; | ||||
| } | ||||
| 
 | ||||
| #privacy-item.screenshare { | ||||
|     background-color: #cf5700; | ||||
| } | ||||
| 
 | ||||
| #privacy-item.audio-in { | ||||
|     background-color: #1ca000; | ||||
| } | ||||
| 
 | ||||
| #privacy-item.audio-out { | ||||
|     background-color: #0069d4; | ||||
| } | ||||
|  |  | |||
|  | @ -760,7 +760,7 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, | |||
|           getModules(factory, ref, group_module); | ||||
|           module = group_module; | ||||
|         } else { | ||||
|           module = factory.makeModule(ref); | ||||
|           module = factory.makeModule(ref, pos); | ||||
|         } | ||||
| 
 | ||||
|         std::shared_ptr<AModule> module_sp(module); | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| 
 | ||||
| #include <iostream> | ||||
| 
 | ||||
| #include "gtkmm/icontheme.h" | ||||
| #include "idle-inhibit-unstable-v1-client-protocol.h" | ||||
| #include "util/clara.hpp" | ||||
| #include "util/format.hpp" | ||||
|  | @ -244,6 +245,11 @@ int waybar::Client::main(int argc, char *argv[]) { | |||
|   } | ||||
|   gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar", | ||||
|                                      Gio::APPLICATION_HANDLES_COMMAND_LINE); | ||||
| 
 | ||||
|   // Initialize Waybars GTK resources with our custom icons
 | ||||
|   auto theme = Gtk::IconTheme::get_default(); | ||||
|   theme->add_resource_path("/fr/arouillard/waybar/icons"); | ||||
| 
 | ||||
|   gdk_display = Gdk::Display::get_default(); | ||||
|   if (!gdk_display) { | ||||
|     throw std::runtime_error("Can't find display"); | ||||
|  |  | |||
|  | @ -10,7 +10,8 @@ | |||
| 
 | ||||
| waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {} | ||||
| 
 | ||||
| waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | ||||
| waybar::AModule* waybar::Factory::makeModule(const std::string& name, | ||||
|                                              const std::string& pos) const { | ||||
|   try { | ||||
|     auto hash_pos = name.find('#'); | ||||
|     auto ref = name.substr(0, hash_pos); | ||||
|  | @ -30,6 +31,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { | |||
|       return new waybar::modules::upower::UPower(id, config_[name]); | ||||
|     } | ||||
| #endif | ||||
| #ifdef HAVE_PIPEWIRE | ||||
|     if (ref == "privacy") { | ||||
|       return new waybar::modules::privacy::Privacy(id, config_[name], pos); | ||||
|     } | ||||
| #endif | ||||
| #ifdef HAVE_MPRIS | ||||
|     if (ref == "mpris") { | ||||
|       return new waybar::modules::mpris::Mpris(id, config_[name]); | ||||
|  |  | |||
|  | @ -0,0 +1,178 @@ | |||
| #include "modules/privacy/privacy.hpp" | ||||
| 
 | ||||
| #include <fmt/core.h> | ||||
| #include <json/value.h> | ||||
| #include <pipewire/pipewire.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <cstring> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "AModule.hpp" | ||||
| #include "gtkmm/image.h" | ||||
| #include "modules/privacy/privacy_item.hpp" | ||||
| 
 | ||||
| namespace waybar::modules::privacy { | ||||
| 
 | ||||
| using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT; | ||||
| using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT; | ||||
| using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE; | ||||
| using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT; | ||||
| 
 | ||||
| Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos) | ||||
|     : AModule(config, "privacy", id), | ||||
|       nodes_screenshare(), | ||||
|       nodes_audio_in(), | ||||
|       nodes_audio_out(), | ||||
|       visibility_conn(), | ||||
|       box_(Gtk::ORIENTATION_HORIZONTAL, 0) { | ||||
|   box_.set_name(name_); | ||||
| 
 | ||||
|   event_box_.add(box_); | ||||
| 
 | ||||
|   // Icon Spacing
 | ||||
|   if (config_["icon-spacing"].isUInt()) { | ||||
|     iconSpacing = config_["icon-spacing"].asUInt(); | ||||
|   } | ||||
|   box_.set_spacing(iconSpacing); | ||||
| 
 | ||||
|   // Icon Size
 | ||||
|   if (config_["icon-size"].isUInt()) { | ||||
|     iconSize = config_["icon-size"].asUInt(); | ||||
|   } | ||||
| 
 | ||||
|   // Transition Duration
 | ||||
|   if (config_["transition-duration"].isUInt()) { | ||||
|     transition_duration = config_["transition-duration"].asUInt(); | ||||
|   } | ||||
| 
 | ||||
|   // Initialize each privacy module
 | ||||
|   Json::Value modules = config_["modules"]; | ||||
|   // Add Screenshare and Mic usage as default modules if none are specified
 | ||||
|   if (!modules.isArray() || modules.size() == 0) { | ||||
|     modules = Json::Value(Json::arrayValue); | ||||
|     for (auto& type : {"screenshare", "audio-in"}) { | ||||
|       Json::Value obj = Json::Value(Json::objectValue); | ||||
|       obj["type"] = type; | ||||
|       modules.append(obj); | ||||
|     } | ||||
|   } | ||||
|   for (uint i = 0; i < modules.size(); i++) { | ||||
|     const Json::Value& module_config = modules[i]; | ||||
|     if (!module_config.isObject() || !module_config["type"].isString()) continue; | ||||
|     const std::string type = module_config["type"].asString(); | ||||
|     if (type == "screenshare") { | ||||
|       auto item = | ||||
|           Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_VIDEO_INPUT, | ||||
|                                          &nodes_screenshare, pos, iconSize, transition_duration); | ||||
|       box_.add(*item); | ||||
|     } else if (type == "audio-in") { | ||||
|       auto item = | ||||
|           Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_AUDIO_INPUT, | ||||
|                                          &nodes_audio_in, pos, iconSize, transition_duration); | ||||
|       box_.add(*item); | ||||
|     } else if (type == "audio-out") { | ||||
|       auto item = | ||||
|           Gtk::make_managed<PrivacyItem>(module_config, PRIVACY_NODE_TYPE_AUDIO_OUTPUT, | ||||
|                                          &nodes_audio_out, pos, iconSize, transition_duration); | ||||
|       box_.add(*item); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   backend = util::PipewireBackend::PipewireBackend::getInstance(); | ||||
|   backend->privacy_nodes_changed_signal_event.connect( | ||||
|       sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged)); | ||||
| 
 | ||||
|   dp.emit(); | ||||
| } | ||||
| 
 | ||||
| void Privacy::onPrivacyNodesChanged() { | ||||
|   mutex_.lock(); | ||||
|   nodes_audio_out.clear(); | ||||
|   nodes_audio_in.clear(); | ||||
|   nodes_screenshare.clear(); | ||||
| 
 | ||||
|   for (auto& node : backend->privacy_nodes) { | ||||
|     switch (node.second->state) { | ||||
|       case PW_NODE_STATE_RUNNING: | ||||
|         switch (node.second->type) { | ||||
|           case PRIVACY_NODE_TYPE_VIDEO_INPUT: | ||||
|             nodes_screenshare.push_back(node.second); | ||||
|             break; | ||||
|           case PRIVACY_NODE_TYPE_AUDIO_INPUT: | ||||
|             nodes_audio_in.push_back(node.second); | ||||
|             break; | ||||
|           case PRIVACY_NODE_TYPE_AUDIO_OUTPUT: | ||||
|             nodes_audio_out.push_back(node.second); | ||||
|             break; | ||||
|           case PRIVACY_NODE_TYPE_NONE: | ||||
|             continue; | ||||
|         } | ||||
|         break; | ||||
|       default: | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   mutex_.unlock(); | ||||
|   dp.emit(); | ||||
| } | ||||
| 
 | ||||
| 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(); | ||||
| 
 | ||||
|   for (Gtk::Widget* widget : box_.get_children()) { | ||||
|     PrivacyItem* module = dynamic_cast<PrivacyItem*>(widget); | ||||
|     if (!module) continue; | ||||
|     switch (module->privacy_type) { | ||||
|       case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT: | ||||
|         module->set_in_use(screenshare); | ||||
|         break; | ||||
|       case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT: | ||||
|         module->set_in_use(audio_in); | ||||
|         break; | ||||
|       case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT: | ||||
|         module->set_in_use(audio_out); | ||||
|         break; | ||||
|       case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE: | ||||
|         break; | ||||
|     } | ||||
|   } | ||||
|   mutex_.unlock(); | ||||
| 
 | ||||
|   // Hide the whole widget if none are in use
 | ||||
|   bool is_visible = screenshare || audio_in || audio_out; | ||||
|   if (is_visible != event_box_.get_visible()) { | ||||
|     // Disconnect any previous connection so that it doesn't get activated in
 | ||||
|     // the future, hiding the module when it should be visible
 | ||||
|     visibility_conn.disconnect(); | ||||
|     if (is_visible) { | ||||
|       event_box_.set_visible(true); | ||||
|     } else { | ||||
|       // Hides the widget when all of the privacy_item revealers animations
 | ||||
|       // have finished animating
 | ||||
|       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; | ||||
|               }, | ||||
|               *this), | ||||
|           transition_duration); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Call parent update
 | ||||
|   AModule::update(); | ||||
| } | ||||
| 
 | ||||
| }  // namespace waybar::modules::privacy
 | ||||
|  | @ -0,0 +1,163 @@ | |||
| #include "modules/privacy/privacy_item.hpp" | ||||
| 
 | ||||
| #include <fmt/core.h> | ||||
| #include <pipewire/pipewire.h> | ||||
| #include <spdlog/spdlog.h> | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <cstring> | ||||
| #include <string> | ||||
| #include <thread> | ||||
| 
 | ||||
| #include "AModule.hpp" | ||||
| #include "glibmm/main.h" | ||||
| #include "glibmm/priorities.h" | ||||
| #include "gtkmm/enums.h" | ||||
| #include "gtkmm/label.h" | ||||
| #include "gtkmm/revealer.h" | ||||
| #include "gtkmm/tooltip.h" | ||||
| #include "sigc++/adaptors/bind.h" | ||||
| #include "util/gtk_icon.hpp" | ||||
| #include "util/pipewire/privacy_node_info.hpp" | ||||
| 
 | ||||
| namespace waybar::modules::privacy { | ||||
| 
 | ||||
| PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, | ||||
|                          std::list<PrivacyNodeInfo *> *nodes_, const std::string &pos, | ||||
|                          const uint icon_size, const uint transition_duration) | ||||
|     : Gtk::Revealer(), | ||||
|       privacy_type(privacy_type_), | ||||
|       nodes(nodes_), | ||||
|       signal_conn(), | ||||
|       tooltip_window(Gtk::ORIENTATION_VERTICAL, 0), | ||||
|       box_(Gtk::ORIENTATION_HORIZONTAL, 0), | ||||
|       icon_() { | ||||
|   switch (privacy_type) { | ||||
|     case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT: | ||||
|       box_.get_style_context()->add_class("audio-in"); | ||||
|       iconName = "waybar-privacy-audio-input-symbolic"; | ||||
|       break; | ||||
|     case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT: | ||||
|       box_.get_style_context()->add_class("audio-out"); | ||||
|       iconName = "waybar-privacy-audio-output-symbolic"; | ||||
|       break; | ||||
|     case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT: | ||||
|       box_.get_style_context()->add_class("screenshare"); | ||||
|       iconName = "waybar-privacy-screen-share-symbolic"; | ||||
|       break; | ||||
|     default: | ||||
|     case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE: | ||||
|       return; | ||||
|   } | ||||
| 
 | ||||
|   // Set the reveal transition to not look weird when sliding in
 | ||||
|   if (pos == "modules-left") { | ||||
|     set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT); | ||||
|   } else if (pos == "modules-center") { | ||||
|     set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE); | ||||
|   } else if (pos == "modules-right") { | ||||
|     set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT); | ||||
|   } | ||||
|   set_transition_duration(transition_duration); | ||||
| 
 | ||||
|   box_.set_name("privacy-item"); | ||||
|   box_.add(icon_); | ||||
|   icon_.set_pixel_size(icon_size); | ||||
|   add(box_); | ||||
| 
 | ||||
|   // Icon Name
 | ||||
|   if (config_["icon-name"].isString()) { | ||||
|     iconName = config_["icon-name"].asString(); | ||||
|   } | ||||
|   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)); | ||||
|   } | ||||
| 
 | ||||
|   // Don't show by default
 | ||||
|   set_reveal_child(true); | ||||
|   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(); | ||||
| } | ||||
| 
 | ||||
| void PrivacyItem::set_in_use(bool in_use) { | ||||
|   if (in_use) { | ||||
|     update_tooltip(); | ||||
|   } | ||||
| 
 | ||||
|   if (this->in_use == in_use && init) return; | ||||
| 
 | ||||
|   if (init) { | ||||
|     // Disconnect any previous connection so that it doesn't get activated in
 | ||||
|     // the future, hiding the module when it should be visible
 | ||||
|     signal_conn.disconnect(); | ||||
| 
 | ||||
|     this->in_use = in_use; | ||||
|     guint duration = 0; | ||||
|     if (this->in_use) { | ||||
|       set_visible(true); | ||||
|     } else { | ||||
|       set_reveal_child(false); | ||||
|       duration = get_transition_duration(); | ||||
|     } | ||||
| 
 | ||||
|     signal_conn = Glib::signal_timeout().connect(sigc::track_obj( | ||||
|                                                      [this] { | ||||
|                                                        if (this->in_use) { | ||||
|                                                          set_reveal_child(true); | ||||
|                                                        } else { | ||||
|                                                          set_visible(false); | ||||
|                                                        } | ||||
|                                                        return false; | ||||
|                                                      }, | ||||
|                                                      *this), | ||||
|                                                  duration); | ||||
|   } else { | ||||
|     set_visible(false); | ||||
|     set_reveal_child(false); | ||||
|   } | ||||
|   this->init = true; | ||||
| } | ||||
| 
 | ||||
| }  // namespace waybar::modules::privacy
 | ||||
|  | @ -0,0 +1,155 @@ | |||
| #include "util/pipewire/pipewire_backend.hpp" | ||||
| 
 | ||||
| #include "util/pipewire/privacy_node_info.hpp" | ||||
| 
 | ||||
| namespace waybar::util::PipewireBackend { | ||||
| 
 | ||||
| static void get_node_info(void *data_, const struct pw_node_info *info) { | ||||
|   PrivacyNodeInfo *p_node_info = static_cast<PrivacyNodeInfo *>(data_); | ||||
|   PipewireBackend *backend = (PipewireBackend *)p_node_info->data; | ||||
| 
 | ||||
|   p_node_info->state = info->state; | ||||
| 
 | ||||
|   const struct spa_dict_item *item; | ||||
|   spa_dict_for_each(item, info->props) { | ||||
|     if (strcmp(item->key, PW_KEY_CLIENT_ID) == 0) { | ||||
|       p_node_info->client_id = strtoul(item->value, NULL, 10); | ||||
|     } else if (strcmp(item->key, PW_KEY_MEDIA_NAME) == 0) { | ||||
|       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; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   backend->privacy_nodes_changed_signal_event.emit(); | ||||
| } | ||||
| 
 | ||||
| static const struct pw_node_events node_events = { | ||||
|     .version = PW_VERSION_NODE_EVENTS, | ||||
|     .info = get_node_info, | ||||
| }; | ||||
| 
 | ||||
| static void proxy_destroy(void *data) { | ||||
|   PrivacyNodeInfo *node = (PrivacyNodeInfo *)data; | ||||
| 
 | ||||
|   spa_hook_remove(&node->proxy_listener); | ||||
|   spa_hook_remove(&node->object_listener); | ||||
| } | ||||
| 
 | ||||
| static const struct pw_proxy_events proxy_events = { | ||||
|     .version = PW_VERSION_PROXY_EVENTS, | ||||
|     .destroy = proxy_destroy, | ||||
| }; | ||||
| 
 | ||||
| static void registry_event_global(void *_data, uint32_t id, uint32_t permissions, const char *type, | ||||
|                                   uint32_t version, const struct spa_dict *props) { | ||||
|   if (!props || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; | ||||
| 
 | ||||
|   const char *lookup_str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); | ||||
|   if (!lookup_str) return; | ||||
|   std::string media_class = lookup_str; | ||||
|   enum PrivacyNodeType media_type = PRIVACY_NODE_TYPE_NONE; | ||||
|   if (media_class == "Stream/Input/Video") { | ||||
|     media_type = PRIVACY_NODE_TYPE_VIDEO_INPUT; | ||||
|   } else if (media_class == "Stream/Input/Audio") { | ||||
|     media_type = PRIVACY_NODE_TYPE_AUDIO_INPUT; | ||||
|   } else if (media_class == "Stream/Output/Audio") { | ||||
|     media_type = PRIVACY_NODE_TYPE_AUDIO_OUTPUT; | ||||
|   } else { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   PipewireBackend *backend = static_cast<PipewireBackend *>(_data); | ||||
|   struct pw_proxy *proxy = | ||||
|       (pw_proxy *)pw_registry_bind(backend->registry, id, type, version, sizeof(PrivacyNodeInfo)); | ||||
| 
 | ||||
|   if (!proxy) return; | ||||
| 
 | ||||
|   PrivacyNodeInfo *p_node_info = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); | ||||
|   p_node_info->id = id; | ||||
|   p_node_info->data = backend; | ||||
|   p_node_info->type = media_type; | ||||
|   p_node_info->media_class = media_class; | ||||
| 
 | ||||
|   pw_proxy_add_listener(proxy, &p_node_info->proxy_listener, &proxy_events, p_node_info); | ||||
| 
 | ||||
|   pw_proxy_add_object_listener(proxy, &p_node_info->object_listener, &node_events, p_node_info); | ||||
| 
 | ||||
|   backend->privacy_nodes.insert_or_assign(id, p_node_info); | ||||
| } | ||||
| 
 | ||||
| static void registry_event_global_remove(void *_data, uint32_t id) { | ||||
|   auto backend = static_cast<PipewireBackend *>(_data); | ||||
| 
 | ||||
|   backend->mutex_.lock(); | ||||
|   auto iter = backend->privacy_nodes.find(id); | ||||
|   if (iter != backend->privacy_nodes.end()) { | ||||
|     backend->privacy_nodes.erase(id); | ||||
|   } | ||||
|   backend->mutex_.unlock(); | ||||
| 
 | ||||
|   backend->privacy_nodes_changed_signal_event.emit(); | ||||
| } | ||||
| 
 | ||||
| static const struct pw_registry_events registry_events = { | ||||
|     .version = PW_VERSION_REGISTRY_EVENTS, | ||||
|     .global = registry_event_global, | ||||
|     .global_remove = registry_event_global_remove, | ||||
| }; | ||||
| 
 | ||||
| PipewireBackend::PipewireBackend(private_constructor_tag tag) | ||||
|     : mainloop_(nullptr), context_(nullptr), core_(nullptr) { | ||||
|   pw_init(nullptr, nullptr); | ||||
|   mainloop_ = pw_thread_loop_new("waybar", nullptr); | ||||
|   if (mainloop_ == nullptr) { | ||||
|     throw std::runtime_error("pw_thread_loop_new() failed."); | ||||
|   } | ||||
|   context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); | ||||
|   if (context_ == nullptr) { | ||||
|     throw std::runtime_error("pa_context_new() failed."); | ||||
|   } | ||||
|   core_ = pw_context_connect(context_, nullptr, 0); | ||||
|   if (core_ == nullptr) { | ||||
|     throw std::runtime_error("pw_context_connect() failed"); | ||||
|   } | ||||
|   registry = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0); | ||||
| 
 | ||||
|   spa_zero(registry_listener); | ||||
|   pw_registry_add_listener(registry, ®istry_listener, ®istry_events, this); | ||||
|   if (pw_thread_loop_start(mainloop_) < 0) { | ||||
|     throw std::runtime_error("pw_thread_loop_start() failed."); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| PipewireBackend::~PipewireBackend() { | ||||
|   if (registry != nullptr) { | ||||
|     pw_proxy_destroy((struct pw_proxy *)registry); | ||||
|   } | ||||
| 
 | ||||
|   spa_zero(registry_listener); | ||||
| 
 | ||||
|   if (core_ != nullptr) { | ||||
|     pw_core_disconnect(core_); | ||||
|   } | ||||
| 
 | ||||
|   if (context_ != nullptr) { | ||||
|     pw_context_destroy(context_); | ||||
|   } | ||||
| 
 | ||||
|   if (mainloop_ != nullptr) { | ||||
|     pw_thread_loop_stop(mainloop_); | ||||
|     pw_thread_loop_destroy(mainloop_); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<PipewireBackend> PipewireBackend::getInstance() { | ||||
|   private_constructor_tag tag; | ||||
|   return std::make_shared<PipewireBackend>(tag); | ||||
| } | ||||
| }  // namespace waybar::util::PipewireBackend
 | ||||
		Loading…
	
		Reference in New Issue