diff --git a/include/ASlider.hpp b/include/ASlider.hpp new file mode 100644 index 00000000..44cde507 --- /dev/null +++ b/include/ASlider.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "AModule.hpp" +#include "gtkmm/scale.h" + +namespace waybar { + +class ASlider : public AModule { + public: + ASlider(const Json::Value& config, const std::string& name, const std::string& id); + virtual void onValueChanged(); + + protected: + bool vertical_ = false; + int min_ = 0, max_ = 100, curr_ = 50; + Gtk::Scale scale_; +}; + +} // namespace waybar \ No newline at end of file diff --git a/include/modules/pulseaudio_slider.hpp b/include/modules/pulseaudio_slider.hpp new file mode 100644 index 00000000..3ef44684 --- /dev/null +++ b/include/modules/pulseaudio_slider.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "ASlider.hpp" +#include "util/audio_backend.hpp" +namespace waybar::modules { + +enum class PulseaudioSliderTarget { + Sink, + Source, +}; + +class PulseaudioSlider : public ASlider { + public: + PulseaudioSlider(const std::string&, const Json::Value&); + virtual ~PulseaudioSlider() = default; + + void update() override; + void onValueChanged() override; + + private: + std::shared_ptr backend = nullptr; + PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink; +}; + +} // namespace waybar::modules \ No newline at end of file diff --git a/include/util/audio_backend.hpp b/include/util/audio_backend.hpp index a0c36ea1..1a882cc5 100644 --- a/include/util/audio_backend.hpp +++ b/include/util/audio_backend.hpp @@ -66,7 +66,7 @@ class AudioBackend { AudioBackend(std::function on_updated_cb, private_constructor_tag tag); ~AudioBackend(); - void changeVolume(uint16_t volume, uint16_t max_volume = 100); + void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100); void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100); void setIgnoredSinks(const Json::Value& config); diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd new file mode 100644 index 00000000..8ecc040e --- /dev/null +++ b/man/waybar-pulseaudio-slider.5.scd @@ -0,0 +1,83 @@ +waybar-pulseaudio-slider(5) + +# NAME + +waybar - pulseaudio slider module + +# DESCRIPTION + +The *pulseaudio slider* module displays and controls the current volume of the default sink or source as a bar. + +The volume can be controlled by dragging the slider accross the bar, or clicking on a specific position. + +# CONFIGURATION + +*min*: ++ + typeof: int ++ + default: 0 ++ + The minimum volume value the slider should display and set. + +*max*: ++ + typeof: int ++ + default: 100 ++ + The maximum volume value the slider should display and set. + +*orientation*: ++ + typeof: string ++ + default: horizontal ++ + The orientation of the slider. Can be either `horizontal` or `vertical`. + +# EXAMPLES + +``` +"modules-right": [ + "pulseaudio-slider", +], +"pulseaudio/slider": { + "min": 0, + "max": 100, + "orientation": "horizontal" +} +``` + +# STYLE + +The slider is a component with multiple CSS Nodes, of which the following are exposed: + +*#pulseaudio-slider*: ++ + Controls the style of the box *around* the slider and bar. + +*#pulseaudio-slider slider*: ++ + Controls the style of the slider handle. + +*#pulseaudio-slider trough*: ++ + Controls the style of the part of the bar that has not been filled. + +*#pulseaudio-slider highlight*: ++ + Controls the style of the part of the bar that has been filled. + +## STYLE EXAMPLE + +``` +#pulseaudio-slider slider { + min-height: 0px; + min-width: 0px; + opacity: 0; + background-image: none; + border: none; + box-shadow: none; +} + +#pulseaudio-slider trough { + min-height: 80px; + min-width: 10px; + border-radius: 5px; + background-color: black; +} + +#pulseaudio-slider highlight { + min-width: 10px; + border-radius: 5px; + background-color: green; +} +``` diff --git a/meson.build b/meson.build index e85785ad..d2d3e17a 100644 --- a/meson.build +++ b/meson.build @@ -166,6 +166,7 @@ src_files = files( 'src/modules/image.cpp', 'src/modules/temperature.cpp', 'src/modules/user.cpp', + 'src/ASlider.cpp', 'src/main.cpp', 'src/bar.cpp', 'src/client.cpp', @@ -274,6 +275,7 @@ endif if libpulse.found() add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') src_files += 'src/modules/pulseaudio.cpp' + src_files += 'src/modules/pulseaudio_slider.cpp' endif if libjack.found() @@ -441,6 +443,7 @@ if scdoc.found() 'waybar-mpris.5.scd', 'waybar-network.5.scd', 'waybar-pulseaudio.5.scd', + 'waybar-pulseaudio-slider.5.scd', 'waybar-river-mode.5.scd', 'waybar-river-tags.5.scd', 'waybar-river-window.5.scd', diff --git a/src/ASlider.cpp b/src/ASlider.cpp new file mode 100644 index 00000000..a5e3889c --- /dev/null +++ b/src/ASlider.cpp @@ -0,0 +1,34 @@ +#include "ASlider.hpp" + +#include "gtkmm/adjustment.h" +#include "gtkmm/enums.h" + +namespace waybar { + +ASlider::ASlider(const Json::Value& config, const std::string& name, const std::string& id) + : AModule(config, name, id, false, false), + vertical_(config_["orientation"].asString() == "vertical"), + scale_(vertical_ ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL) { + scale_.set_name(name); + if (!id.empty()) { + scale_.get_style_context()->add_class(id); + } + event_box_.add(scale_); + scale_.signal_value_changed().connect(sigc::mem_fun(*this, &ASlider::onValueChanged)); + + if (config_["min"].isUInt()) { + min_ = config_["min"].asUInt(); + } + + if (config_["max"].isUInt()) { + max_ = config_["max"].asUInt(); + } + + scale_.set_inverted(vertical_); + scale_.set_draw_value(false); + scale_.set_adjustment(Gtk::Adjustment::create(curr_, min_, max_ + 1, 1, 1, 1)); +} + +void ASlider::onValueChanged() {} + +} // namespace waybar \ No newline at end of file diff --git a/src/factory.cpp b/src/factory.cpp index 1d7a00b5..0358b9db 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -1,5 +1,9 @@ #include "factory.hpp" +#ifdef HAVE_LIBPULSE +#include "modules/pulseaudio_slider.hpp" +#endif + waybar::Factory::Factory(const Bar& bar, const Json::Value& config) : bar_(bar), config_(config) {} waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { @@ -136,6 +140,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "pulseaudio") { return new waybar::modules::Pulseaudio(id, config_[name]); } + if (ref == "pulseaudio/slider") { + return new waybar::modules::PulseaudioSlider(id, config_[name]); + } #endif #ifdef HAVE_LIBMPDCLIENT if (ref == "mpd") { diff --git a/src/modules/pulseaudio_slider.cpp b/src/modules/pulseaudio_slider.cpp new file mode 100644 index 00000000..edd92670 --- /dev/null +++ b/src/modules/pulseaudio_slider.cpp @@ -0,0 +1,45 @@ +#include "modules/pulseaudio_slider.hpp" + +namespace waybar::modules { + +PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& config) + : ASlider(config, "pulseaudio-slider", id) { + backend = util::AudioBackend::getInstance([this] { this->dp.emit(); }); + backend->setIgnoredSinks(config_["ignored-sinks"]); + + if (config_["target"].isString()) { + std::string target = config_["target"].asString(); + if (target == "sink") { + this->target = PulseaudioSliderTarget::Sink; + } else if (target == "source") { + this->target = PulseaudioSliderTarget::Source; + } + } +} + +void PulseaudioSlider::update() { + switch (target) { + case PulseaudioSliderTarget::Sink: + if (backend->getSinkMuted()) { + scale_.set_value(min_); + } else { + scale_.set_value(backend->getSinkVolume()); + } + break; + + case PulseaudioSliderTarget::Source: + if (backend->getSourceMuted()) { + scale_.set_value(min_); + } else { + scale_.set_value(backend->getSourceVolume()); + } + break; + } +} + +void PulseaudioSlider::onValueChanged() { + uint16_t volume = scale_.get_value(); + backend->changeVolume(volume, min_, max_); +} + +} // namespace waybar::modules \ No newline at end of file diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index ddb2ab6f..4600d80c 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -204,11 +204,12 @@ void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, vo pa_context_get_source_info_list(context, sourceInfoCb, data); } -void AudioBackend::changeVolume(uint16_t volume, uint16_t max_volume) { +void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { double volume_tick = static_cast(PA_VOLUME_NORM) / 100; pa_cvolume pa_volume = pa_volume_; volume = std::min(volume, max_volume); + volume = std::max(volume, min_volume); pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this);