diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 4063171..2baf0b1 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -6,21 +6,28 @@ namespace waybar { class ALabel : public IModule { - public: - ALabel(const Json::Value&, const std::string format); - virtual ~ALabel() = default; - virtual auto update() -> void; - virtual std::string getIcon(uint16_t, const std::string& alt = ""); - virtual operator Gtk::Widget &(); - protected: - Gtk::EventBox event_box_; - Gtk::Label label_; - const Json::Value& config_; - std::string format_; - private: - bool handleToggle(GdkEventButton* const& ev); - bool alt = false; - const std::string default_format_; + public: + ALabel(const Json::Value&, const std::string format); + virtual ~ALabel() = default; + virtual auto update() -> void; + virtual std::string getIcon(uint16_t, const std::string& alt = ""); + virtual operator Gtk::Widget&(); + + protected: + Gtk::EventBox event_box_; + Gtk::Label label_; + const Json::Value& config_; + std::string format_; + std::string button_press_cmd_ = ""; + std::string scroll_up_cmd_ = ""; + std::string scroll_down_cmd_ = ""; + std::mutex mutex_; + + private: + bool handleToggle(GdkEventButton* const& ev); + bool handleScroll(GdkEventScroll*); + bool alt = false; + const std::string default_format_; }; -} +} // namespace waybar diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp index 77f388a..c4100fb 100644 --- a/include/modules/pulseaudio.hpp +++ b/include/modules/pulseaudio.hpp @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include #include "ALabel.hpp" @@ -18,6 +19,8 @@ class Pulseaudio : public ALabel { static void contextStateCb(pa_context*, void*); static void sinkInfoCb(pa_context*, const pa_sink_info*, int, void*); static void serverInfoCb(pa_context*, const pa_server_info*, void*); + static void volumeModifyCb(pa_context*, int, void*); + bool handleScroll(GdkEventScroll* e); const std::string getPortIcon() const; @@ -26,9 +29,11 @@ class Pulseaudio : public ALabel { pa_context* context_; uint32_t sink_idx_{0}; uint16_t volume_; + pa_cvolume pa_volume_; bool muted_; std::string port_name_; std::string desc_; + bool scrolling_; }; -} +} // namespace waybar::modules diff --git a/include/util/command.hpp b/include/util/command.hpp index 1806958..8ff42be 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -29,7 +29,24 @@ inline struct res exec(const std::string cmd) output.erase(output.length()-1); } int exit_code = WEXITSTATUS(pclose(fp)); - return { exit_code, output }; + return {exit_code, output}; } +inline bool forkExec(std::string cmd) { + if (cmd == "") return true; + + printf("fork exec command %s\n", cmd.c_str()); + int32_t pid = fork(); + + if (pid < 0) { + printf("Unable to exec cmd %s, error %s", cmd.c_str(), strerror(errno)); + return false; + } + + // Child executes the command + if (!pid) execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); + + return true; } + +} // namespace waybar::util::command diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 70d1765..cb9448b 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -1,4 +1,5 @@ #include "ALabel.hpp" +#include #include @@ -14,30 +15,94 @@ waybar::ALabel::ALabel(const Json::Value& config, const std::string format) } if (config_["format-alt"].isString()) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); - event_box_.signal_button_press_event() - .connect(sigc::mem_fun(*this, &ALabel::handleToggle)); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &ALabel::handleToggle)); + } + + // configure events' user commands + if (config_["on-click"].isString()) { + std::string cmd = config_["on-click"].asString(); + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &ALabel::handleToggle)); + + button_press_cmd_ = cmd; + } + if (config_["on-scroll-up"].isString()) { + std::string cmd = config_["on-scroll-up"].asString(); + event_box_.add_events(Gdk::SCROLL_MASK); + event_box_.signal_scroll_event().connect( + sigc::mem_fun(*this, &ALabel::handleScroll)); + + scroll_up_cmd_ = cmd; + } + if (config_["on-scroll-down"].isString()) { + std::string cmd = config_["on-scroll-down"].asString(); + event_box_.add_events(Gdk::SCROLL_MASK); + event_box_.signal_scroll_event().connect( + sigc::mem_fun(*this, &ALabel::handleScroll)); + + scroll_down_cmd_ = cmd; } } -auto waybar::ALabel::update() -> void -{ +auto waybar::ALabel::update() -> void { // Nothing here } -bool waybar::ALabel::handleToggle(GdkEventButton* const& /*ev*/) -{ - alt = !alt; - if (alt) { - format_ = config_["format-alt"].asString(); +bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { + if (button_press_cmd_ != "" && e->button == 1) { + waybar::util::command::forkExec(button_press_cmd_); } else { - format_ = default_format_; + alt = !alt; + if (alt) { + format_ = config_["format-alt"].asString(); + } else { + format_ = default_format_; + } } + dp.emit(); return true; } -std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt) -{ +bool waybar::ALabel::handleScroll(GdkEventScroll* e) { + + // Avoid concurrent scroll event + { + std::lock_guard lock(mutex_); + bool direction_up = false; + + if (e->direction == GDK_SCROLL_UP) { + direction_up = true; + } + if (e->direction == GDK_SCROLL_DOWN) { + direction_up = false; + } + if (e->direction == GDK_SCROLL_SMOOTH) { + gdouble delta_x, delta_y; + gdk_event_get_scroll_deltas(reinterpret_cast(e), + &delta_x, &delta_y); + if (delta_y < 0) { + direction_up = true; + } else if (delta_y > 0) { + direction_up = false; + } + } + + if (direction_up) + waybar::util::command::forkExec(scroll_up_cmd_); + else + waybar::util::command::forkExec(scroll_down_cmd_); + + dp.emit(); + } + + return true; +} + +std::string waybar::ALabel::getIcon(uint16_t percentage, + const std::string& alt) { auto format_icons = config_["format-icons"]; if (format_icons.isObject()) { if (!alt.empty() && format_icons[alt].isString()) { @@ -57,6 +122,4 @@ std::string waybar::ALabel::getIcon(uint16_t percentage, const std::string& alt) return ""; } -waybar::ALabel::operator Gtk::Widget &() { - return event_box_; -} +waybar::ALabel::operator Gtk::Widget&() { return event_box_; } diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 3427992..3614c98 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -1,9 +1,14 @@ #include "modules/pulseaudio.hpp" -waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config) - : ALabel(config, "{volume}%"), mainloop_(nullptr), mainloop_api_(nullptr), - context_(nullptr), sink_idx_(0), volume_(0), muted_(false) -{ +waybar::modules::Pulseaudio::Pulseaudio(const Json::Value &config) + : ALabel(config, "{volume}%"), + mainloop_(nullptr), + mainloop_api_(nullptr), + context_(nullptr), + sink_idx_(0), + volume_(0), + muted_(false), + scrolling_(false) { label_.set_name("pulseaudio"); mainloop_ = pa_threaded_mainloop_new(); if (mainloop_ == nullptr) { @@ -26,10 +31,18 @@ waybar::modules::Pulseaudio::Pulseaudio(const Json::Value& config) throw std::runtime_error("pa_mainloop_run() failed."); } pa_threaded_mainloop_unlock(mainloop_); + + // define the pulse scroll events only when no user provided + // events are configured + if (!config["on-scroll-up"].isString() && + !config["on-scroll-down"].isString()) { + event_box_.add_events(Gdk::SCROLL_MASK); + event_box_.signal_scroll_event().connect( + sigc::mem_fun(*this, &Pulseaudio::handleScroll)); + } } -waybar::modules::Pulseaudio::~Pulseaudio() -{ +waybar::modules::Pulseaudio::~Pulseaudio() { mainloop_api_->quit(mainloop_api_, 0); pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_free(mainloop_); @@ -58,6 +71,47 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) } } +bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { + // Avoid concurrent scroll event + bool direction_up = false; + // XXX/TODO: Change of 100 corresponds to 1%, does that always hold true? + uint16_t change = 100; + pa_cvolume pa_volume = pa_volume_; + + if (scrolling_) { + return false; + } + scrolling_ = true; + if (e->direction == GDK_SCROLL_UP) { + direction_up = true; + } + if (e->direction == GDK_SCROLL_DOWN) { + direction_up = false; + } + + if (e->direction == GDK_SCROLL_SMOOTH) { + gdouble delta_x, delta_y; + gdk_event_get_scroll_deltas(reinterpret_cast(e), &delta_x, + &delta_y); + if (delta_y < 0) { + direction_up = true; + } else if (delta_y > 0) { + direction_up = false; + } + } + + if (direction_up) { + if (volume_ + 1 < 100) pa_cvolume_inc(&pa_volume, change); + } else { + if (volume_ - 1 > 0) pa_cvolume_dec(&pa_volume, change); + } + + pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, + volumeModifyCb, this); + + return true; +} + /* * Called when an event we subscribed to occurs. */ @@ -75,16 +129,29 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context* context, } } +/* + * Called in response to a volume change request + */ +void waybar::modules::Pulseaudio::volumeModifyCb(pa_context *c, int success, + void *data) { + auto pa = static_cast(data); + if (success) { + pa_context_get_sink_info_by_index(pa->context_, pa->sink_idx_, sinkInfoCb, + data); + } +} + /* * Called when the requested sink information is ready. */ -void waybar::modules::Pulseaudio::sinkInfoCb(pa_context* /*context*/, - const pa_sink_info* i, int /*eol*/, void* data) -{ +void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, + const pa_sink_info *i, int /*eol*/, + void *data) { if (i != nullptr) { auto pa = static_cast(data); - float volume = static_cast(pa_cvolume_avg(&(i->volume))) - / float{PA_VOLUME_NORM}; + pa->pa_volume_ = i->volume; + float volume = static_cast(pa_cvolume_avg(&(pa->pa_volume_))) / + float{PA_VOLUME_NORM}; pa->sink_idx_ = i->index; pa->volume_ = std::round(volume * 100.0f); pa->muted_ = i->mute != 0; @@ -141,8 +208,11 @@ auto waybar::modules::Pulseaudio::update() -> void label_.get_style_context()->remove_class("muted"); label_.get_style_context()->add_class("bluetooth"); } - label_.set_label(fmt::format(format, - fmt::arg("volume", volume_), - fmt::arg("icon", getIcon(volume_, getPortIcon())))); + label_.set_label( + fmt::format(format, fmt::arg("volume", volume_), + fmt::arg("icon", getIcon(volume_, getPortIcon())))); label_.set_tooltip_text(desc_); + if (scrolling_) { + scrolling_ = false; + } }