diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index e4a81bb4..0e13d076 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -5,21 +5,50 @@ #include #include -#include "AButton.hpp" +#include "AModule.hpp" +#include "bar.hpp" #include "util/command.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" +#include +#include +#include + +#include + namespace waybar::modules { -class Custom : public AButton { +class Custom : public AModule { public: - Custom(const std::string&, const std::string&, const Json::Value&); + Custom(const std::string&, const waybar::Bar&, const std::string&, const Json::Value&); ~Custom(); auto update() -> void; void refresh(int /*signal*/); private: + + struct Node { + std::string name_; + std::string text_; + std::string alt_; + std::string tooltip_; + std::string format_; + std::string onclick_; + bool hide_; + std::vector class_; + int percentage_; + Node() : name_("unnamed"), + text_(""), + alt_(""), + tooltip_(""), + format_(""), + onclick_(""), + hide_(false), + class_(std::vector()), + percentage_(0) {} + }; + void delayWorker(); void continuousWorker(); void parseOutputRaw(); @@ -27,17 +56,22 @@ class Custom : public AButton { void handleEvent(); bool handleScroll(GdkEventScroll* e); bool handleToggle(GdkEventButton* const& e); + void handleClick(std::string name); + Node parseItem(Json::Value &parsed); + Gtk::Button &addButton(const Node &node); + + const std::chrono::seconds interval_; + util::command::res output_; + + std::vector results_; + std::vector prev_; + + std::unordered_map buttons_; + Gtk::Box box_; - const std::string name_; - std::string text_; - std::string id_; - std::string alt_; - std::string tooltip_; - std::vector class_; - int percentage_; FILE* fp_; int pid_; - util::command::res output_; + util::JsonParser parser_; util::SleeperThread thread_; diff --git a/include/modules/custom_list.hpp b/include/modules/custom_list.hpp deleted file mode 100644 index ee5be87e..00000000 --- a/include/modules/custom_list.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include -#include -#include -#include "bar.hpp" -#include "AModule.hpp" -#include "util/command.hpp" -#include "util/json.hpp" -#include "util/sleeper_thread.hpp" - -#include - -namespace waybar::modules { - -class CustomList : public AModule { - public: - CustomList(const std::string&, const waybar::Bar&, const std::string&,const Json::Value&); - ~CustomList(); - auto update() -> void; - void refresh(int /*signal*/); - - private: - void delayWorker(); - void continuousWorker(); - void parseOutputRaw(); - void parseOutputJson(); - void handleEvent(); - bool handleScroll(GdkEventScroll* e); - bool handleToggle(GdkEventButton* const& e); - - Gtk::Button &addButton(const Json::Value &node); - void handleClick(std::string); - - std::unordered_map buttons_; - std::vector results_; - std::vector prev_; - const std::string name_; - const Bar& bar_; - Gtk::Box box_; - std::chrono::seconds interval_; - - std::string id_; - std::vector class_; - FILE* fp_; - int pid_; - util::command::res output_; - util::JsonParser parser_; - - util::SleeperThread thread_; -}; - -} // namespace waybar::modules diff --git a/src/factory.cpp b/src/factory.cpp index 6518ebee..d911872c 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -147,10 +147,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { return new waybar::modules::Temperature(id, config_[name]); } if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { - return new waybar::modules::Custom(ref.substr(7), id, config_[name]); - } - if(ref.compare(0, 11, "customlist/") == 0 && ref.size() > 11) { - return new waybar::modules::CustomList(ref.substr(12), bar_, id, config_[name]); + return new waybar::modules::Custom(ref.substr(7), bar_, id, config_[name]); } } catch (const std::exception& e) { auto err = fmt::format("Disabling module \"{}\", {}", name, e.what()); diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 8d7a7638..fc6480f7 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -2,14 +2,20 @@ #include -waybar::modules::Custom::Custom(const std::string& name, const std::string& id, +waybar::modules::Custom::Custom(const std::string& name, + const Bar &bar, + const std::string& id, const Json::Value& config) - : AButton(config, "custom-" + name, id, "{}"), - name_(name), - id_(id), - percentage_(0), + : AModule(config, "custom-" + name, id, false, !config["disable-scroll"].asBool()), + interval_(config["interval"].isUInt() ? config_["interval"].asUInt() : 0), + box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), fp_(nullptr), pid_(-1) { + box_.set_name("custom-" + name); + event_box_.add(box_); + if(!id.empty()) { + box_.get_style_context()->add_class(id); + } dp.emit(); if (interval_.count() > 0) { delayWorker(); @@ -80,7 +86,6 @@ void waybar::modules::Custom::continuousWorker() { } } else { std::string output = buff; - // Remove last newline if (!output.empty() && output[output.length() - 1] == '\n') { output.erase(output.length() - 1); @@ -104,13 +109,13 @@ void waybar::modules::Custom::handleEvent() { } bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) { - auto ret = AButton::handleScroll(e); + auto ret = AModule::handleScroll(e); handleEvent(); return ret; } bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { - auto ret = AButton::handleToggle(e); + auto ret = AModule::handleToggle(e); handleEvent(); return ret; } @@ -121,99 +126,188 @@ auto waybar::modules::Custom::update() -> void { (output_.out.empty() || output_.exit_code != 0)) { event_box_.hide(); } else { + event_box_.show(); if (config_["return-type"].asString() == "json") { parseOutputJson(); } else { parseOutputRaw(); } - auto str = fmt::format(format_, text_, fmt::arg("alt", alt_), - fmt::arg("icon", getIcon(percentage_, alt_)), - fmt::arg("percentage", percentage_)); - if (str.empty()) { - event_box_.hide(); - } else { - label_->set_markup(str); - if (tooltipEnabled()) { - if (text_ == tooltip_) { - if (button_.get_tooltip_markup() != str) { - button_.set_tooltip_markup(str); - } - } else { - if (button_.get_tooltip_markup() != tooltip_) { - button_.set_tooltip_markup(tooltip_); + bool needReorder = false; + for(auto res = results_.begin(); res != results_.end(); res++) { + std::string name = res->name_; + auto bit = buttons_.find(name); + if(bit == buttons_.end()) { + needReorder = true; + } + + auto &button = bit == buttons_.end() ? addButton(*res) : bit->second; + + auto str = res->text_; + //auto str = fmt::format(res->format_, res->text_, fmt::arg("alt", res->alt_), + //fmt::arg("icon", getIcon(res->percentage_, res->alt_)), TODO + // fmt::arg("percentage", res->percentage_)); + + if(str.empty() || res->hide_) { + button.hide(); + } else { + Gtk::Label *label_ = static_cast(button.get_children()[0]); + label_->set_markup(str); + button.show(); + auto prev = std::find_if(prev_.begin(), prev_.end(), [name](auto p) { + return p.name_ == name; + }); + if(prev != prev_.end()) { + for(auto it : prev->class_) { + if(std::find(res->class_.begin(), res->class_.end(), it) == res->class_.end()) { + button.get_style_context()->remove_class(it); + } } } + for(auto it : res->class_) { + if(prev != prev_.end()) { + if(std::find(prev->class_.begin(), prev->class_.end(), it) == prev->class_.end()) { + button.get_style_context()->add_class(it); + } + } else { + button.get_style_context()->add_class(it); + } + } + + if(needReorder) { + box_.reorder_child(button, res - results_.begin()); + } + + if(button.get_tooltip_markup() != res->tooltip_) { + button.set_tooltip_markup(res->tooltip_); + } } - auto classes = button_.get_style_context()->list_classes(); - for (auto const& c : classes) { - if (c == id_) continue; - button_.get_style_context()->remove_class(c); + } + + for(auto prev : prev_) { + auto res_it = std::find_if(results_.begin(), results_.end(), [prev](auto it) { + return it.name_ == prev.name_; + }); + if(res_it == results_.end()) { + auto bit = buttons_.find(prev.name_); + if(bit != buttons_.end()) { + auto &button = bit->second; + box_.remove(button); + buttons_.erase(bit); + } } - for (auto const& c : class_) { - button_.get_style_context()->add_class(c); - } - button_.get_style_context()->add_class("flat"); - button_.get_style_context()->add_class("text-button"); - event_box_.show(); } } + prev_ = results_; // Call parent update - AButton::update(); + AModule::update(); } void waybar::modules::Custom::parseOutputRaw() { + Node n = Node(); + // Retain name if there is only one node + n.name_ = name_; std::istringstream output(output_.out); std::string line; int i = 0; while (getline(output, line)) { if (i == 0) { if (config_["escape"].isBool() && config_["escape"].asBool()) { - text_ = Glib::Markup::escape_text(line); + n.text_ = Glib::Markup::escape_text(line); } else { - text_ = line; + n.text_ = line; } - tooltip_ = line; - class_.clear(); + n.tooltip_ = line; + n.class_.clear(); } else if (i == 1) { - tooltip_ = line; + n.tooltip_ = line; } else if (i == 2) { - class_.push_back(line); + n.class_.push_back(line); } else { break; } i++; } + results_.clear(); + results_.push_back(n); +} + +waybar::modules::Custom::Node waybar::modules::Custom::parseItem(Json::Value &parsed) { + Node n; + if (config_["escape"].isBool() && config_["escape"].asBool()) { + n.text_ = Glib::Markup::escape_text(parsed["text"].asString()); + } else { + n.text_ = parsed["text"].asString(); + } + if (config_["escape"].isBool() && config_["escape"].asBool()) { + n.alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); + } else { + n.alt_ = parsed["alt"].asString(); + } + n.tooltip_ = parsed["tooltip"].asString(); + if (parsed["class"].isString()) { + n.class_.push_back(parsed["class"].asString()); + } else if (parsed["class"].isArray()) { + for (auto const& c : parsed["class"]) { + n.class_.push_back(c.asString()); + } + } + if(!parsed["name"].asString().empty()) { + n.name_ = name_ + parsed["name"].asString(); + } + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { + n.percentage_ = parsed["percentage"].asUInt(); + } else { + n.percentage_ = 0; + } + if (!parsed["onclick"].asString().empty() && parsed["onclick"].isString()) { + n.onclick_ = parsed["onclick"].asString(); + } + return n; } void waybar::modules::Custom::parseOutputJson() { std::istringstream output(output_.out); std::string line; - class_.clear(); while (getline(output, line)) { auto parsed = parser_.parse(line); - if (config_["escape"].isBool() && config_["escape"].asBool()) { - text_ = Glib::Markup::escape_text(parsed["text"].asString()); - } else { - text_ = parsed["text"].asString(); - } - if (config_["escape"].isBool() && config_["escape"].asBool()) { - alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); - } else { - alt_ = parsed["alt"].asString(); - } - tooltip_ = parsed["tooltip"].asString(); - if (parsed["class"].isString()) { - class_.push_back(parsed["class"].asString()); - } else if (parsed["class"].isArray()) { - for (auto const& c : parsed["class"]) { - class_.push_back(c.asString()); + results_.clear(); + if(parsed["data"].isArray()) { + for(auto it : parsed["data"]) { + results_.push_back(parseItem(it)); } - } - if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { - percentage_ = parsed["percentage"].asUInt(); } else { - percentage_ = 0; + Node n = parseItem(parsed); + n.name_ = name_ + "-node"; + results_.push_back(n); } - break; + } +} + +Gtk::Button &waybar::modules::Custom::addButton(const waybar::modules::Custom::Node &node) { + auto pair = buttons_.emplace(node.name_, node.name_); + auto &&button = pair.first->second; + box_.pack_start(button, false, false, 0); + button.set_name(name_ + node.name_); + button.set_relief(Gtk::RELIEF_NONE); + if(!config_["disable-click"].asBool()) { + button.signal_pressed().connect([this, node] { + handleClick(node.name_); + }); + } + return button; +} + + +void waybar::modules::Custom::handleClick(std::string name) { + auto node = std::find_if(results_.begin(), results_.end(), [name](auto it) { + return it.name_ == name; + }); + + if(node == results_.end()) + return; + auto cmd = node->onclick_; + + if(!cmd.empty()) { + util::command::execNoRead(cmd); } } diff --git a/src/modules/custom_list.cpp b/src/modules/custom_list.cpp deleted file mode 100644 index c137fc2f..00000000 --- a/src/modules/custom_list.cpp +++ /dev/null @@ -1,261 +0,0 @@ -#include "modules/custom_list.hpp" - -#include - -waybar::modules::CustomList::CustomList(const std::string& name, - const Bar &bar, - const std::string& id, - const Json::Value& config) - : AModule(config, "custom-list-" + name, id, false, !config["disable-scroll"].asBool()), - name_(name), - bar_(bar), - box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), - interval_(config["interval"].isUInt() ? config_["interval"].asUInt() : 0), - fp_(nullptr), - pid_(-1) { - box_.set_name("custom-list-" + name); - event_box_.add(box_); - if(!id.empty()) - box_.get_style_context()->add_class(id); - if (interval_.count() > 0) { - delayWorker(); - } else if (config_["exec"].isString()) { - continuousWorker(); - } -} - -waybar::modules::CustomList::~CustomList() { - if (pid_ != -1) { - killpg(pid_, SIGTERM); - pid_ = -1; - } -} - -void waybar::modules::CustomList::delayWorker() { - thread_ = [this] { - bool can_update = true; - if (config_["exec-if"].isString()) { - output_ = util::command::execNoRead(config_["exec-if"].asString()); - if (output_.exit_code != 0) { - can_update = false; - dp.emit(); - } - } - if (can_update) { - if (config_["exec"].isString()) { - output_ = util::command::exec(config_["exec"].asString()); - } - dp.emit(); - } - thread_.sleep_for(interval_); - }; -} - -void waybar::modules::CustomList::continuousWorker() { - auto cmd = config_["exec"].asString(); - pid_ = -1; - fp_ = util::command::open(cmd, pid_); - if (!fp_) { - throw std::runtime_error("Unable to open " + cmd); - } - thread_ = [this, cmd] { - char* buff = nullptr; - size_t len = 0; - if (getline(&buff, &len, fp_) == -1) { - int exit_code = 1; - if (fp_) { - exit_code = WEXITSTATUS(util::command::close(fp_, pid_)); - fp_ = nullptr; - } - if (exit_code != 0) { - output_ = {exit_code, ""}; - dp.emit(); - spdlog::error("{} stopped unexpectedly, is it endless?", name_); - } - if (config_["restart-interval"].isUInt()) { - pid_ = -1; - thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); - fp_ = util::command::open(cmd, pid_); - if (!fp_) { - throw std::runtime_error("Unable to open " + cmd); - } - } else { - thread_.stop(); - return; - } - } else { - std::string output = buff; - - // Remove last newline - if (!output.empty() && output[output.length() - 1] == '\n') { - output.erase(output.length() - 1); - } - output_ = {0, output}; - dp.emit(); - } - }; -} - -void waybar::modules::CustomList::refresh(int sig) { - if (sig == SIGRTMIN + config_["signal"].asInt()) { - thread_.wake_up(); - } -} - -void waybar::modules::CustomList::handleEvent() { - if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) { - thread_.wake_up(); - } -} - -bool waybar::modules::CustomList::handleScroll(GdkEventScroll* e) { - //auto ret = ALabel::handleScroll(e); - ///* TODO */ - handleEvent(); - return false; -} - -bool waybar::modules::CustomList::handleToggle(GdkEventButton* const& e) { - /* TODO */ - //auto ret = ALabel::handleToggle(e); - handleEvent(); - return false; -} - -auto waybar::modules::CustomList::update() -> void { - // Hide label if output is empty - if ((config_["exec"].isString() || config_["exec-if"].isString()) && - (output_.out.empty() || output_.exit_code != 0)) { - event_box_.hide(); - } else { - parseOutputJson(); - } - bool needReorder = false; - for(auto res = results_.begin(); res != results_.end(); res++) { - std::string name = (*res)["name"].asString(); - auto bit = buttons_.find(name); - if(bit == buttons_.end()) { - needReorder = true; - } - auto &button = bit == buttons_.end() ? addButton(*res) : bit->second; - - auto prev = std::find_if(prev_.begin(), prev_.end(), [name](auto p) { - return p["name"] == name; - }); - - // Classes - std::vector prev_classes; - if(prev != prev_.end()) { - if ((*prev)["class"].isString()) { - prev_classes.push_back((*prev)["class"].asString()); - } else if ((*prev)["class"].isArray()) { - for (auto const& c : (*prev)["class"]) { - prev_classes.push_back(c.asString()); - } - } - } - std::vector new_classes; - if ((*res)["class"].isString()) { - new_classes.push_back((*res)["class"].asString()); - } else if ((*res)["class"].isArray()) { - for (auto const& c : (*res)["class"]) { - new_classes.push_back(c.asString()); - } - } - - for(auto it : prev_classes) { - if(std::find(new_classes.begin(), new_classes.end(), it) == new_classes.end()) - button.get_style_context()->remove_class(it); - } - for(auto it : new_classes) { - if(std::find(prev_classes.begin(), prev_classes.end(), it) == prev_classes.end()) - button.get_style_context()->add_class(it); - } - - // Reorder to match results order - if(needReorder) { - box_.reorder_child(button, res - results_.begin()); - } - - if(!(*res)["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup((*res)["text"].asString()); - } else { - button.set_label((*res)["text"].asString()); - } - - if((*res)["hide"].asBool()) { - button.hide(); - } else { - button.show(); - } - - if(button.get_tooltip_markup() != (*res)["tooltip"].asString()) { - button.set_tooltip_markup((*res)["tooltip"].asString()); - } - - } - for(auto prev : prev_) { - auto res_it = std::find_if(results_.begin(), results_.end(), [prev](auto it) { - return it["name"] == prev["name"]; - }); - if(res_it == results_.end()) { - // Node has been removed - auto bit = buttons_.find(prev["name"].asString()); - if(bit != buttons_.end()) { - auto &button = bit->second; - box_.remove(button); - buttons_.erase(bit); - } - } - - } - prev_ = results_; - - // Call parent update - AModule::update(); -} - -void waybar::modules::CustomList::handleClick(std::string name) { - auto node = std::find_if(results_.begin(), results_.end(), [name](auto it) { - return it["name"] == name; - }); - - if(node == results_.end()) - return; - auto cmd = (*node)["onclick"]; - - if(cmd.isString()) { - util::command::execNoRead(cmd.asString()); - } -} - -Gtk::Button &waybar::modules::CustomList::addButton(const Json::Value &node) { - auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString()); - auto &&button = pair.first->second; - box_.pack_start(button, false, false, 0); - button.set_name(name_ + node["name"].asString()); - button.set_relief(Gtk::RELIEF_NONE); - if(!config_["disable-click"].asBool()) { - button.signal_pressed().connect([this, node] { - handleClick(node["name"].asString()); - }); - } - return button; -} - -void waybar::modules::CustomList::parseOutputJson() { - std::istringstream output(output_.out); - std::string line; - class_.clear(); - while (getline(output, line)) { - auto parsed = parser_.parse(line); - if(!parsed["data"].isArray()) { - throw std::runtime_error("Output should be a list"); - } - results_.clear(); - for(auto it : parsed["data"]) { - results_.push_back(it); - } - break; - } -}