From eeb7bc702e8e20ec097e30f4fe0703b153b7e9dd Mon Sep 17 00:00:00 2001 From: Erik Reider <35975961+ErikReider@users.noreply.github.com> Date: Sat, 4 Jul 2026 01:37:06 +0200 Subject: [PATCH] Added GeoClue2 privacy item Rebased onto current master and fixed the privacy module build: - privacy_item.hpp no longer includes privacy.hpp (it does not use the Privacy class); this broke the circular include that left PrivacyItem undeclared when privacy.hpp was reached first from privacy_item.cpp. - privacy_item.cpp now includes gtkmm/label.h explicitly, since Gtk::Label was previously only pulled in transitively via privacy.hpp. --- include/modules/privacy/privacy.hpp | 22 +++-- include/modules/privacy/privacy_item.hpp | 45 +++++++--- include/util/geoclue_backend.hpp | 43 +++++++++ include/util/pipewire/pipewire_backend.hpp | 2 +- include/util/pipewire/privacy_node_info.hpp | 5 +- man/waybar-privacy.5.scd | 13 ++- meson.build | 1 + .../waybar-privacy-location-symbolic.svg | 2 + resources/icons/waybar_icons.gresource.xml | 1 + resources/style.css | 4 + src/modules/privacy/privacy.cpp | 89 ++++++++++++++----- src/modules/privacy/privacy_item.cpp | 51 ++++++----- src/util/geoclue_backend.cpp | 88 ++++++++++++++++++ src/util/pipewire/pipewire_backend.cpp | 13 +-- src/util/pipewire/privacy_node_info.cpp | 8 +- 15 files changed, 314 insertions(+), 73 deletions(-) create mode 100644 include/util/geoclue_backend.hpp create mode 100644 resources/icons/waybar-privacy-location-symbolic.svg create mode 100644 src/util/geoclue_backend.cpp diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index 729fcc6a..7307afc9 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -1,13 +1,16 @@ #pragma once +#include #include #include "gtkmm/box.h" #include "modules/privacy/privacy_item.hpp" +#include "util/geoclue_backend.hpp" #include "util/pipewire/pipewire_backend.hpp" #include "util/pipewire/privacy_node_info.hpp" -using waybar::util::PipewireBackend::PrivacyNodeInfo; +using waybar::util::PipewireBackend::PrivacyNodeType; +using waybar::util::PipewireBackend::PWPrivacyNodeInfo; namespace waybar::modules::privacy { @@ -16,15 +19,15 @@ class Privacy : public AModule { Privacy(const std::string&, const Json::Value&, Gtk::Orientation, const std::string& pos); auto update() -> void override; - void onPrivacyNodesChanged(); - private: - std::list nodes_screenshare; // Screen is being shared - std::list nodes_audio_in; // Application is using the microphone - std::list nodes_audio_out; // Application is outputting audio + std::list nodes_screenshare; // Screen is being shared + std::list nodes_audio_in; // Application is using the microphone + std::list nodes_audio_out; // Application is outputting audio + std::atomic location_in_use; // GeoClue is being used std::mutex mutex_; sigc::connection visibility_conn; + sigc::connection geoclue_timeout_conn; // Config Gtk::Box box_; @@ -35,7 +38,12 @@ class Privacy : public AModule { std::set> ignore; bool ignore_monitor = true; - std::shared_ptr backend = nullptr; + std::shared_ptr pw_backend = nullptr; + std::shared_ptr geoclue_backend = nullptr; + + void onPWPrivacyNodesChanged(); + bool locationTimeout(bool in_use); + void onGeoCluePrivacyNodesChanged(); }; } // namespace waybar::modules::privacy diff --git a/include/modules/privacy/privacy_item.hpp b/include/modules/privacy/privacy_item.hpp index 31e200e3..2a9cafcd 100644 --- a/include/modules/privacy/privacy_item.hpp +++ b/include/modules/privacy/privacy_item.hpp @@ -9,35 +9,36 @@ #include "gtkmm/revealer.h" #include "util/pipewire/privacy_node_info.hpp" -using waybar::util::PipewireBackend::PrivacyNodeInfo; using waybar::util::PipewireBackend::PrivacyNodeType; +using waybar::util::PipewireBackend::PWPrivacyNodeInfo; namespace waybar::modules::privacy { class PrivacyItem : public Gtk::Revealer { - public: + protected: PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_, - std::list* nodes, Gtk::Orientation orientation, - const std::string& pos, const uint icon_size, const uint transition_duration); + Gtk::Orientation orientation, const std::string& pos, const uint icon_size, + const uint transition_duration); + + public: + virtual void set_tooltip() = 0; enum PrivacyNodeType privacy_type; void set_in_use(bool in_use); - private: - std::list* nodes; - - sigc::connection signal_conn; - + uint tooltipIconSize = 24; Gtk::Box tooltip_window; + private: + sigc::connection signal_conn; + 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_; @@ -45,4 +46,28 @@ class PrivacyItem : public Gtk::Revealer { void update_tooltip(); }; +class GeoCluePrivacyItem : public PrivacyItem { + public: + GeoCluePrivacyItem(const Json::Value& config_, Gtk::Orientation orientation, + const std::string& pos, const uint icon_size, const uint transition_duration) + : PrivacyItem(config_, util::PipewireBackend::PRIVACY_NODE_TYPE_LOCATION, orientation, pos, + icon_size, transition_duration) {} + + void set_tooltip() override; +}; + +class PWPrivacyItem : public PrivacyItem { + public: + PWPrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_, + std::list* nodes_, Gtk::Orientation orientation, + const std::string& pos, const uint icon_size, const uint transition_duration) + : PrivacyItem(config_, privacy_type_, orientation, pos, icon_size, transition_duration), + nodes(nodes_) {} + + void set_tooltip() override; + + private: + std::list* nodes; +}; + } // namespace waybar::modules::privacy diff --git a/include/util/geoclue_backend.hpp b/include/util/geoclue_backend.hpp new file mode 100644 index 00000000..4bf19091 --- /dev/null +++ b/include/util/geoclue_backend.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +#include "giomm/dbusproxy.h" + +namespace waybar::util::GeoClueBackend { + +class GeoClueBackend { + private: + guint watcherID_; + sigc::connection signal_conn; + Glib::RefPtr proxy; + bool connected; + + /* 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 PrivateConstructorTag {}; + + public: + sigc::signal in_use_changed_signal_event; + + std::atomic location_in_use; // GeoClue is being used + + static std::shared_ptr getInstance(); + + // DBus callbacks + void onAppear(const Glib::RefPtr&, const Glib::ustring&, + const Glib::ustring&); + void onVanished(const Glib::RefPtr&, const Glib::ustring&); + void propertyChanged(const Gio::DBus::Proxy::MapChangedProperties& changedProperties, + const std::vector& invalidatedProperties); + + GeoClueBackend(PrivateConstructorTag tag); + ~GeoClueBackend(); +}; +} // namespace waybar::util::GeoClueBackend diff --git a/include/util/pipewire/pipewire_backend.hpp b/include/util/pipewire/pipewire_backend.hpp index 90fb2bb2..7ea0ffee 100644 --- a/include/util/pipewire/pipewire_backend.hpp +++ b/include/util/pipewire/pipewire_backend.hpp @@ -29,7 +29,7 @@ class PipewireBackend { public: sigc::signal privacy_nodes_changed_signal_event; - std::unordered_map privacy_nodes; + std::unordered_map privacy_nodes; std::mutex mutex_; static std::shared_ptr getInstance(); diff --git a/include/util/pipewire/privacy_node_info.hpp b/include/util/pipewire/privacy_node_info.hpp index 54acad42..cd45313b 100644 --- a/include/util/pipewire/privacy_node_info.hpp +++ b/include/util/pipewire/privacy_node_info.hpp @@ -12,10 +12,11 @@ enum PrivacyNodeType { PRIVACY_NODE_TYPE_NONE, PRIVACY_NODE_TYPE_VIDEO_INPUT, PRIVACY_NODE_TYPE_AUDIO_INPUT, - PRIVACY_NODE_TYPE_AUDIO_OUTPUT + PRIVACY_NODE_TYPE_AUDIO_OUTPUT, + PRIVACY_NODE_TYPE_LOCATION }; -class PrivacyNodeInfo { +class PWPrivacyNodeInfo { public: PrivacyNodeType type = PRIVACY_NODE_TYPE_NONE; uint32_t id; diff --git a/man/waybar-privacy.5.scd b/man/waybar-privacy.5.scd index 1c4ef59d..72191dfe 100644 --- a/man/waybar-privacy.5.scd +++ b/man/waybar-privacy.5.scd @@ -28,7 +28,7 @@ the screen or playing audio. *modules* ++ typeof: array of objects ++ - default: [{"type": "screenshare"}, {"type": "audio-in"}] ++ + default: [{"type": "screenshare"}, {"type": "audio-in"}, {"type": "location"}] ++ Which privacy modules to monitor. See *MODULES CONFIGURATION* for++ more information. @@ -52,9 +52,13 @@ the screen or playing audio. *type*: ++ typeof: string ++ - values: "screenshare", "audio-in", "audio-out" ++ + values: "screenshare", "audio-in", "audio-out", "location" ++ Specifies which module to use and configure. +*icon-name*: ++ + typeof: string ++ + If set, changes the modules icon. + *tooltip*: ++ typeof: bool ++ default: true ++ @@ -95,6 +99,10 @@ the screen or playing audio. "type": "audio-in", "tooltip": true, "tooltip-icon-size": 24 + }, + { + "type": "location", + "icon-name": "location-services-active-symbolic" } ], "ignore-monitor": true, @@ -118,3 +126,4 @@ the screen or playing audio. - *#privacy-item.screenshare* - *#privacy-item.audio-in* - *#privacy-item.audio-out* +- *#privacy-item.location* diff --git a/meson.build b/meson.build index 5563d610..c4abfaa4 100644 --- a/meson.build +++ b/meson.build @@ -426,6 +426,7 @@ if pipewire.found() 'src/modules/privacy/privacy_item.cpp', 'src/util/pipewire/pipewire_backend.cpp', 'src/util/pipewire/privacy_node_info.cpp', + 'src/util/geoclue_backend.cpp', ) man_files += files('man/waybar-privacy.5.scd') endif diff --git a/resources/icons/waybar-privacy-location-symbolic.svg b/resources/icons/waybar-privacy-location-symbolic.svg new file mode 100644 index 00000000..3b843e79 --- /dev/null +++ b/resources/icons/waybar-privacy-location-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/resources/icons/waybar_icons.gresource.xml b/resources/icons/waybar_icons.gresource.xml index 077049bf..a8584eef 100644 --- a/resources/icons/waybar_icons.gresource.xml +++ b/resources/icons/waybar_icons.gresource.xml @@ -4,5 +4,6 @@ waybar-privacy-audio-input-symbolic.svg waybar-privacy-audio-output-symbolic.svg waybar-privacy-screen-share-symbolic.svg + waybar-privacy-location-symbolic.svg diff --git a/resources/style.css b/resources/style.css index bcfcb5c8..fe29f8d5 100644 --- a/resources/style.css +++ b/resources/style.css @@ -325,3 +325,7 @@ label:focus { #privacy-item.audio-out { background-color: #0069d4; } + +#privacy-item.location { + background-color: #00afff; +} diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 4e613971..6b0a019c 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -10,10 +10,11 @@ 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; +using waybar::util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT; +using waybar::util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT; +using waybar::util::PipewireBackend::PRIVACY_NODE_TYPE_LOCATION; +using waybar::util::PipewireBackend::PRIVACY_NODE_TYPE_NONE; +using waybar::util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT; Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation, const std::string& pos) @@ -21,7 +22,9 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat nodes_screenshare(), nodes_audio_in(), nodes_audio_out(), + location_in_use(false), visibility_conn(), + geoclue_timeout_conn(), box_(orientation, 0) { box_.set_name(name_); @@ -45,20 +48,21 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat // Initialize each privacy module Json::Value modules = config_["modules"]; - // Add Screenshare and Mic usage as default modules if none are specified + // Add Screenshare, Mic, and Location usage as default modules if none are specified if (!modules.isArray() || modules.empty()) { modules = Json::Value(Json::arrayValue); - for (const auto& type : {"screenshare", "audio-in"}) { + for (const auto& type : {"screenshare", "audio-in", "location"}) { Json::Value obj = Json::Value(Json::objectValue); obj["type"] = type; modules.append(obj); } } - std::map > typeMap = { + std::map*, PrivacyNodeType>> typeMap = { {"screenshare", {&nodes_screenshare, PRIVACY_NODE_TYPE_VIDEO_INPUT}}, {"audio-in", {&nodes_audio_in, PRIVACY_NODE_TYPE_AUDIO_INPUT}}, {"audio-out", {&nodes_audio_out, PRIVACY_NODE_TYPE_AUDIO_OUTPUT}}, + {"location", {nullptr, PRIVACY_NODE_TYPE_LOCATION}}, }; for (const auto& module : modules) { @@ -68,8 +72,21 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat auto iter = typeMap.find(type); if (iter != typeMap.end()) { auto& [nodePtr, nodeType] = iter->second; - auto* item = Gtk::make_managed(module, nodeType, nodePtr, orientation, pos, + PrivacyItem* item = nullptr; + switch (nodeType) { + case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT: + case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT: + case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT: + item = Gtk::make_managed(module, nodeType, nodePtr, orientation, pos, iconSize, transition_duration); + break; + case util::PipewireBackend::PRIVACY_NODE_TYPE_LOCATION: + item = Gtk::make_managed(module, orientation, pos, iconSize, + transition_duration); + break; + case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE: + continue; + } box_.add(*item); modules_.push_back(item); } @@ -93,20 +110,24 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat ignore_monitor = config_["ignore-monitor"].asBool(); } - backend = util::PipewireBackend::PipewireBackend::getInstance(); - backend->privacy_nodes_changed_signal_event.connect( - sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged)); + pw_backend = util::PipewireBackend::PipewireBackend::getInstance(); + pw_backend->privacy_nodes_changed_signal_event.connect( + sigc::mem_fun(*this, &Privacy::onPWPrivacyNodesChanged)); + + geoclue_backend = util::GeoClueBackend::GeoClueBackend::getInstance(); + geoclue_backend->in_use_changed_signal_event.connect( + sigc::mem_fun(*this, &Privacy::onGeoCluePrivacyNodesChanged)); dp.emit(); } -void Privacy::onPrivacyNodesChanged() { +void Privacy::onPWPrivacyNodesChanged() { mutex_.lock(); nodes_audio_out.clear(); nodes_audio_in.clear(); nodes_screenshare.clear(); - for (auto& node : backend->privacy_nodes) { + for (auto& node : pw_backend->privacy_nodes) { if (ignore_monitor && node.second->is_monitor) continue; auto iter = ignore.find(std::pair(node.second->type, node.second->node_name)); @@ -124,7 +145,7 @@ void Privacy::onPrivacyNodesChanged() { case PRIVACY_NODE_TYPE_AUDIO_OUTPUT: nodes_audio_out.push_back(node.second); break; - case PRIVACY_NODE_TYPE_NONE: + default: continue; } break; @@ -137,36 +158,63 @@ void Privacy::onPrivacyNodesChanged() { dp.emit(); } +bool Privacy::locationTimeout(bool in_use) { + location_in_use.store(geoclue_backend->location_in_use); + dp.emit(); + + return false; +} +void Privacy::onGeoCluePrivacyNodesChanged() { + geoclue_timeout_conn.disconnect(); + + const bool in_use = geoclue_backend->location_in_use; + if (in_use) { + locationTimeout(in_use); + } else { + // The GeoClue service is really fast, so wait before hiding, so that the + // widget is at least visible for 3 seconds. + geoclue_timeout_conn = Glib::signal_timeout().connect_seconds( + sigc::bind(sigc::mem_fun(*this, &Privacy::locationTimeout), in_use), 3); + } +} + auto Privacy::update() -> void { // set in modules or not bool setScreenshare = false; bool setAudioIn = false; bool setAudioOut = false; + bool setLocation = false; // used or not bool useScreenshare = false; bool useAudioIn = false; bool useAudioOut = false; + bool useLocation = false; mutex_.lock(); for (PrivacyItem* module : modules_) { switch (module->privacy_type) { - case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT: + case PRIVACY_NODE_TYPE_VIDEO_INPUT: setScreenshare = true; useScreenshare = !nodes_screenshare.empty(); module->set_in_use(useScreenshare); break; - case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_INPUT: + case PRIVACY_NODE_TYPE_AUDIO_INPUT: setAudioIn = true; useAudioIn = !nodes_audio_in.empty(); module->set_in_use(useAudioIn); break; - case util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT: + case PRIVACY_NODE_TYPE_AUDIO_OUTPUT: setAudioOut = true; useAudioOut = !nodes_audio_out.empty(); module->set_in_use(useAudioOut); break; - case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE: + case PRIVACY_NODE_TYPE_LOCATION: + setLocation = true; + useLocation = location_in_use; + module->set_in_use(useLocation); + break; + case PRIVACY_NODE_TYPE_NONE: break; } } @@ -174,7 +222,7 @@ auto Privacy::update() -> void { // Hide the whole widget if none are in use bool isVisible = (setScreenshare && useScreenshare) || (setAudioIn && useAudioIn) || - (setAudioOut && useAudioOut); + (setAudioOut && useAudioOut) || (setLocation && useLocation); if (isVisible != event_box_.get_visible()) { // Disconnect any previous connection so that it doesn't get activated in @@ -187,12 +235,13 @@ auto Privacy::update() -> void { // have finished animating visibility_conn = Glib::signal_timeout().connect( sigc::track_obj( - [this, setScreenshare, setAudioOut, setAudioIn]() { + [this, setScreenshare, setAudioOut, setAudioIn, setLocation]() { mutex_.lock(); bool visible = false; visible |= setScreenshare && !nodes_screenshare.empty(); visible |= setAudioIn && !nodes_audio_in.empty(); visible |= setAudioOut && !nodes_audio_out.empty(); + visible |= setLocation && location_in_use; mutex_.unlock(); event_box_.set_visible(visible); return false; diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index 3a55eee7..880d026e 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -8,19 +8,16 @@ #include "gtkmm/label.h" #include "gtkmm/revealer.h" #include "gtkmm/tooltip.h" -#include "util/pipewire/privacy_node_info.hpp" namespace waybar::modules::privacy { PrivacyItem::PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_, - std::list* nodes_, Gtk::Orientation orientation, - const std::string& pos, const uint icon_size, + Gtk::Orientation orientation, 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), + signal_conn(), box_(Gtk::ORIENTATION_HORIZONTAL, 0), icon_() { switch (privacy_type) { @@ -36,7 +33,10 @@ PrivacyItem::PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privac box_.get_style_context()->add_class("screenshare"); iconName = "waybar-privacy-screen-share-symbolic"; break; - default: + case util::PipewireBackend::PRIVACY_NODE_TYPE_LOCATION: + box_.get_style_context()->add_class("location"); + iconName = "waybar-privacy-location-symbolic"; + break; case util::PipewireBackend::PRIVACY_NODE_TYPE_NONE: return; } @@ -81,7 +81,6 @@ PrivacyItem::PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privac 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& tooltip) { tooltip->set_custom(tooltip_window); @@ -103,21 +102,8 @@ void PrivacyItem::update_tooltip() { // work differently in GTK4. delete child; } - for (auto* node : *nodes) { - auto* box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL, 4); - // Set device icon - auto* node_icon = Gtk::make_managed(); - node_icon->set_pixel_size(tooltipIconSize); - node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); - box->add(*node_icon); - - // Set model - auto* nodeName = Gtk::make_managed(node->getName()); - box->add(*nodeName); - - tooltip_window.add(*box); - } + set_tooltip(); tooltip_window.show_all(); } @@ -161,4 +147,27 @@ void PrivacyItem::set_in_use(bool in_use) { this->init = true; } +void GeoCluePrivacyItem::set_tooltip() { + auto* nodeName = Gtk::make_managed("Location in use"); + tooltip_window.add(*nodeName); +} + +void PWPrivacyItem::set_tooltip() { + for (auto* node : *nodes) { + auto* box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL, 4); + + // Set device icon + auto* node_icon = Gtk::make_managed(); + node_icon->set_pixel_size(tooltipIconSize); + node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); + box->add(*node_icon); + + // Set model + auto* nodeName = Gtk::make_managed(node->getName()); + box->add(*nodeName); + + tooltip_window.add(*box); + } +} + } // namespace waybar::modules::privacy diff --git a/src/util/geoclue_backend.cpp b/src/util/geoclue_backend.cpp new file mode 100644 index 00000000..dbe09074 --- /dev/null +++ b/src/util/geoclue_backend.cpp @@ -0,0 +1,88 @@ +#include "util/geoclue_backend.hpp" + +#include + +#include + +namespace waybar::util::GeoClueBackend { + +GeoClueBackend::GeoClueBackend(PrivateConstructorTag tag) + : watcherID_(0), signal_conn(), connected(false), location_in_use(false) { + // Start watching DBUS + watcherID_ = + Gio::DBus::watch_name(Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.GeoClue2", + sigc::mem_fun(*this, &GeoClueBackend::onAppear), + sigc::mem_fun(*this, &GeoClueBackend::onVanished), + Gio::DBus::BusNameWatcherFlags::BUS_NAME_WATCHER_FLAGS_AUTO_START); +} + +GeoClueBackend::~GeoClueBackend() { + if (watcherID_ > 0) { + Gio::DBus::unwatch_name(watcherID_); + watcherID_ = 0; + } + signal_conn.disconnect(); +} + +std::shared_ptr GeoClueBackend::getInstance() { + PrivateConstructorTag tag; + return std::make_shared(tag); +} + +void GeoClueBackend::propertyChanged( + const Gio::DBus::Proxy::MapChangedProperties& changedProperties, + const std::vector& invalidatedProperties) { + if (!connected) { + return; + } + + if (auto in_use_variant = changedProperties.find("InUse"); + in_use_variant != changedProperties.end()) { + bool in_use = + Glib::VariantBase::cast_dynamic>(in_use_variant->second).get(); + location_in_use.store(in_use); + in_use_changed_signal_event.emit(); + } +} + +void GeoClueBackend::onAppear(const Glib::RefPtr& conn, + const Glib::ustring& name, const Glib::ustring& name_owner) { + location_in_use.store(false); + signal_conn.disconnect(); + connected = true; + + proxy = Gio::DBus::Proxy::create_sync(conn, "org.freedesktop.GeoClue2", + "/org/freedesktop/GeoClue2/Manager", + "org.freedesktop.GeoClue2.Manager"); + + // Subscribe DBus events + signal_conn = proxy->signal_properties_changed().connect( + sigc::mem_fun(*this, &GeoClueBackend::propertyChanged)); + + // Get the initial value + auto callArgs = Glib::Variant>::create( + std::make_tuple("org.freedesktop.GeoClue2.Manager", "InUse")); + Glib::VariantContainerBase base = + proxy->call_sync("org.freedesktop.DBus.Properties.Get", callArgs); + if (base.is_of_type(Glib::VariantType("(v)"))) { + Glib::Variant> variant_value; + base.get_child(variant_value, 0); + auto [value] = variant_value.get(); + if (location_in_use != value) { + location_in_use.store(value); + } + } + + in_use_changed_signal_event.emit(); +} + +void GeoClueBackend::onVanished(const Glib::RefPtr& conn, + const Glib::ustring& name) { + signal_conn.disconnect(); + connected = false; + + location_in_use.store(false); + in_use_changed_signal_event.emit(); +} + +} // namespace waybar::util::GeoClueBackend diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp index 82ec0c0f..72528fb4 100644 --- a/src/util/pipewire/pipewire_backend.cpp +++ b/src/util/pipewire/pipewire_backend.cpp @@ -5,7 +5,7 @@ namespace waybar::util::PipewireBackend { static void getNodeInfo(void* data_, const struct pw_node_info* info) { - auto* pNodeInfo = static_cast(data_); + auto* pNodeInfo = static_cast(data_); pNodeInfo->handleNodeEventInfo(info); static_cast(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit(); @@ -17,7 +17,7 @@ static const struct pw_node_events NODE_EVENTS = { }; static void proxyDestroy(void* data) { - static_cast(data)->handleProxyEventDestroy(); + static_cast(data)->handleProxyEventDestroy(); } static const struct pw_proxy_events PROXY_EVENTS = { @@ -127,12 +127,13 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission return; } - auto* proxy = (pw_proxy*)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo)); + auto* proxy = + (pw_proxy*)pw_registry_bind(registry_, id, type, version, sizeof(PWPrivacyNodeInfo)); if (proxy == nullptr) return; - auto* pNodeInfo = (PrivacyNodeInfo*)pw_proxy_get_user_data(proxy); - new (pNodeInfo) PrivacyNodeInfo{}; + auto* pNodeInfo = (PWPrivacyNodeInfo*)pw_proxy_get_user_data(proxy); + new (pNodeInfo) PWPrivacyNodeInfo{}; pNodeInfo->id = id; pNodeInfo->data = this; pNodeInfo->type = mediaType; @@ -152,7 +153,7 @@ void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) { mutex_.lock(); auto iter = privacy_nodes.find(id); if (iter != privacy_nodes.end()) { - privacy_nodes[id]->~PrivacyNodeInfo(); + privacy_nodes[id]->~PWPrivacyNodeInfo(); privacy_nodes.erase(id); } mutex_.unlock(); diff --git a/src/util/pipewire/privacy_node_info.cpp b/src/util/pipewire/privacy_node_info.cpp index dbd6ed85..0da4b25b 100644 --- a/src/util/pipewire/privacy_node_info.cpp +++ b/src/util/pipewire/privacy_node_info.cpp @@ -2,7 +2,7 @@ namespace waybar::util::PipewireBackend { -std::string PrivacyNodeInfo::getName() { +std::string PWPrivacyNodeInfo::getName() { const std::vector names{&application_name, &node_name}; std::string name = "Unknown Application"; for (const auto& item : names) { @@ -15,7 +15,7 @@ std::string PrivacyNodeInfo::getName() { return name; } -std::string PrivacyNodeInfo::getIconName() { +std::string PWPrivacyNodeInfo::getIconName() { const std::vector names{&application_icon_name, &pipewire_access_portal_app_id, &application_name, &node_name}; std::string name = "application-x-executable-symbolic"; @@ -27,12 +27,12 @@ std::string PrivacyNodeInfo::getIconName() { return name; } -void PrivacyNodeInfo::handleProxyEventDestroy() { +void PWPrivacyNodeInfo::handleProxyEventDestroy() { spa_hook_remove(&proxy_listener); spa_hook_remove(&object_listener); } -void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info* info) { +void PWPrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info* info) { state = info->state; const struct spa_dict_item* item;