From d7d1ebd736ea0c7e464480ea6dee483598c5afad Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Mon, 29 Oct 2018 22:34:09 +0530 Subject: [PATCH 1/2] ALabel: Add support for configurable mouse events This patch adds 3 new configuration options applicable for subclasses of ALabel. The options can be used to execute user defined code in response to the 3 mouse events: * on-click: The left mouse button click * on-scroll-up * on-scroll-down This patch also modifies the behaviour of the format-alt toggle such that when the on-click event is configured, format-alt is toggled on any mouse click other than left click. When on-click is not defined, any mouse button would toggle format-alt. Signed-off-by: Harish Krupo --- include/ALabel.hpp | 39 ++++++++++------- include/util/command.hpp | 19 +++++++- src/ALabel.cpp | 93 +++++++++++++++++++++++++++++++++------- 3 files changed, 119 insertions(+), 32 deletions(-) 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/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_; } From 3e34137ac775d9b4ea7394c9bb0c48a74bb1fcb2 Mon Sep 17 00:00:00 2001 From: Harish Krupo Date: Mon, 29 Oct 2018 21:48:35 +0530 Subject: [PATCH 2/2] pulseaudio: Change volume on scroll event Subscribe for mouse scroll events on the pulseaudio widget and change volume when event is received. Scroll up increments the volume and scroll down decrements it. These events are only subscibed when there are no user defined commands present for them. Signed-off-by: Harish Krupo --- include/modules/pulseaudio.hpp | 9 +++- src/modules/pulseaudio.cpp | 98 +++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 16 deletions(-) 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/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; + } }