Merge pull request #2612 from ErikReider/privacy-module
Add Privacy Module
This commit is contained in:
commit
e24adbc3c2
|
@ -17,6 +17,7 @@
|
||||||
- Network
|
- Network
|
||||||
- Bluetooth
|
- Bluetooth
|
||||||
- Pulseaudio
|
- Pulseaudio
|
||||||
|
- Privacy Info
|
||||||
- Wireplumber
|
- Wireplumber
|
||||||
- Disk
|
- Disk
|
||||||
- Memory
|
- Memory
|
||||||
|
|
|
@ -68,6 +68,9 @@
|
||||||
#ifdef HAVE_UPOWER
|
#ifdef HAVE_UPOWER
|
||||||
#include "modules/upower/upower.hpp"
|
#include "modules/upower/upower.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_PIPEWIRE
|
||||||
|
#include "modules/privacy/privacy.hpp"
|
||||||
|
#endif
|
||||||
#ifdef HAVE_LIBPULSE
|
#ifdef HAVE_LIBPULSE
|
||||||
#include "modules/pulseaudio.hpp"
|
#include "modules/pulseaudio.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
@ -101,7 +104,7 @@ namespace waybar {
|
||||||
class Factory {
|
class Factory {
|
||||||
public:
|
public:
|
||||||
Factory(const Bar& bar, const Json::Value& config);
|
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:
|
private:
|
||||||
const Bar& bar_;
|
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'))
|
libnl = dependency('libnl-3.0', required: get_option('libnl'))
|
||||||
libnlgen = dependency('libnl-genl-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'))
|
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'))
|
playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))
|
||||||
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
||||||
libudev = dependency('libudev', required: get_option('libudev'))
|
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'
|
src_files += 'src/modules/upower/upower_tooltip.cpp'
|
||||||
endif
|
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())
|
if (playerctl.found() and giounix.found() and not get_option('logind').disabled())
|
||||||
add_project_arguments('-DHAVE_MPRIS', language: 'cpp')
|
add_project_arguments('-DHAVE_MPRIS', language: 'cpp')
|
||||||
src_files += 'src/modules/mpris/mpris.cpp'
|
src_files += 'src/modules/mpris/mpris.cpp'
|
||||||
|
@ -373,9 +382,12 @@ endif
|
||||||
|
|
||||||
subdir('protocol')
|
subdir('protocol')
|
||||||
|
|
||||||
|
app_resources = []
|
||||||
|
subdir('resources/icons')
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'waybar',
|
'waybar',
|
||||||
src_files,
|
[src_files, app_resources],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
thread_dep,
|
thread_dep,
|
||||||
client_protos,
|
client_protos,
|
||||||
|
@ -392,6 +404,7 @@ executable(
|
||||||
libnl,
|
libnl,
|
||||||
libnlgen,
|
libnlgen,
|
||||||
upower_glib,
|
upower_glib,
|
||||||
|
pipewire,
|
||||||
playerctl,
|
playerctl,
|
||||||
libpulse,
|
libpulse,
|
||||||
libjack,
|
libjack,
|
||||||
|
@ -454,6 +467,7 @@ if scdoc.found()
|
||||||
'waybar-network.5.scd',
|
'waybar-network.5.scd',
|
||||||
'waybar-pulseaudio.5.scd',
|
'waybar-pulseaudio.5.scd',
|
||||||
'waybar-pulseaudio-slider.5.scd',
|
'waybar-pulseaudio-slider.5.scd',
|
||||||
|
'waybar-privacy.5.scd',
|
||||||
'waybar-river-mode.5.scd',
|
'waybar-river-mode.5.scd',
|
||||||
'waybar-river-tags.5.scd',
|
'waybar-river-tags.5.scd',
|
||||||
'waybar-river-window.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('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('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
|
||||||
option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower')
|
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('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris')
|
||||||
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
|
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
|
||||||
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
|
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 {
|
#scratchpad.empty {
|
||||||
background-color: transparent;
|
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);
|
getModules(factory, ref, group_module);
|
||||||
module = group_module;
|
module = group_module;
|
||||||
} else {
|
} else {
|
||||||
module = factory.makeModule(ref);
|
module = factory.makeModule(ref, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AModule> module_sp(module);
|
std::shared_ptr<AModule> module_sp(module);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "gtkmm/icontheme.h"
|
||||||
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
#include "idle-inhibit-unstable-v1-client-protocol.h"
|
||||||
#include "util/clara.hpp"
|
#include "util/clara.hpp"
|
||||||
#include "util/format.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",
|
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar",
|
||||||
Gio::APPLICATION_HANDLES_COMMAND_LINE);
|
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();
|
gdk_display = Gdk::Display::get_default();
|
||||||
if (!gdk_display) {
|
if (!gdk_display) {
|
||||||
throw std::runtime_error("Can't find 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::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 {
|
try {
|
||||||
auto hash_pos = name.find('#');
|
auto hash_pos = name.find('#');
|
||||||
auto ref = name.substr(0, hash_pos);
|
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]);
|
return new waybar::modules::upower::UPower(id, config_[name]);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_PIPEWIRE
|
||||||
|
if (ref == "privacy") {
|
||||||
|
return new waybar::modules::privacy::Privacy(id, config_[name], pos);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#ifdef HAVE_MPRIS
|
#ifdef HAVE_MPRIS
|
||||||
if (ref == "mpris") {
|
if (ref == "mpris") {
|
||||||
return new waybar::modules::mpris::Mpris(id, config_[name]);
|
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