From eb1775d5f8a7192ab3509ff6762ea7f65070e023 Mon Sep 17 00:00:00 2001 From: Risto Toijala Date: Sun, 3 Oct 2021 13:33:08 +0300 Subject: [PATCH] Implement shared execs for custom modules For more complicated configurations with several actions activated through clicks on various connected modules, it is currently necessary to have each of those connected modules exec their own subprocess to produce the wanted output. This is problematic because it greatly increases CPU usage. Implement a new configuration option for bars, "custom-execs", which contains an array of subprocess specifications ("exec", "exec_if", "interval", "restart-interval"). The bar runs each of these executables and reads their output. The output is in the form of a JSON object with module names as keys and either strings (for "raw" type output) or objects (for "json" type output) as values. These values are forwarded to the custom modules named by the keys. --- include/bar.hpp | 8 +++ include/modules/custom.hpp | 13 +++- src/bar.cpp | 31 ++++++++++ src/modules/custom.cpp | 120 ++++++++++++++++++++++++++----------- 4 files changed, 135 insertions(+), 37 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 6f3dfcf9..1cc3787a 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -9,6 +9,9 @@ #include #include "AModule.hpp" +#include "modules/custom.hpp" +#include "util/json.hpp" +#include "util/worker_thread.hpp" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar { @@ -76,6 +79,7 @@ class Bar { void getModules(const Factory &, const std::string &); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); + void customExecOutputCallback(std::string output); std::unique_ptr surface_impl_; bar_layer layer_; @@ -86,6 +90,10 @@ class Bar { std::vector> modules_left_; std::vector> modules_center_; std::vector> modules_right_; + waybar::util::JsonParser parser_; + // Contains pointers to modules in modules_left_, etc. + std::map custom_modules_; + std::vector> custom_threads_; }; } // namespace waybar diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index b689d4cb..03c53c2f 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include "ALabel.hpp" #include "util/command.hpp" @@ -15,6 +17,7 @@ namespace waybar::modules { class Custom : public ALabel { public: Custom(const std::string&, const std::string&, const Json::Value&); + void injectOutput(Json::Value); auto update() -> void; void refresh(int /*signal*/); @@ -23,6 +26,7 @@ class Custom : public ALabel { void workerOutputCallback(std::string); void parseOutputRaw(const std::string&); void parseOutputJson(const std::string&); + void handleOutputJson(const Json::Value&); void handleEvent(); bool handleScroll(GdkEventScroll* e); bool handleToggle(GdkEventButton* const& e); @@ -33,9 +37,12 @@ class Custom : public ALabel { std::string tooltip_; std::vector class_; int percentage_; - util::command::res output_; - util::JsonParser parser_; - + // Worker exit code, worker raw output, or injected JSON. + // Injected JSON is string for raw output, object for JSON output. + std::variant output_; + util::JsonParser parser_; + // Protects output_ since it is accessed from many threads. + std::mutex output_mutex_; waybar::util::WorkerThread thread_; }; diff --git a/src/bar.cpp b/src/bar.cpp index 7d763599..9cf85b41 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -508,6 +508,20 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); setupWidgets(); + + if (config["custom-execs"].isArray()) { + Json::ArrayIndex size = config["custom-execs"].size(); + for (Json::ArrayIndex i = 0; i < size; i++) { + custom_threads_.push_back(std::make_unique( + config["custom-execs"][i], + [this](std::string output) { customExecOutputCallback(output); }, + [this](int exit_code) { + // Do nothing. We don't know which modules this was meant for. + spdlog::debug("Custom exec exited with code {}", exit_code); + })); + } + } + window.show_all(); if (spdlog::should_log(spdlog::level::debug)) { @@ -521,6 +535,19 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } } +void waybar::Bar::customExecOutputCallback(std::string output) { + // parsed is an object with module names as keys and output (string or object) as values. + auto parsed = parser_.parse(output); + auto end = parsed.end(); + for (Json::Value::const_iterator module_it = parsed.begin(); module_it != end; ++module_it) { + auto it = custom_modules_.find(module_it.name()); + if (it != custom_modules_.end()) { + it->second->injectOutput(*module_it); + } else { + } + } +} + void waybar::Bar::onMap(GdkEventAny*) { /* * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). @@ -612,6 +639,10 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { for (const auto& name : config[pos]) { try { auto module = factory.makeModule(name.asString()); + auto custom = dynamic_cast(module); + if (custom != nullptr) { + custom_modules_[name.asString()] = custom; + } if (pos == "modules-left") { modules_left_.emplace_back(module); } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 0addba64..58695477 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -13,12 +13,26 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, } void waybar::modules::Custom::workerExitCallback(int exit_code) { - output_ = {exit_code, ""}; + { + std::lock_guard guard(output_mutex_); + output_ = exit_code; + } dp.emit(); } void waybar::modules::Custom::workerOutputCallback(std::string output) { - output_ = {0, std::move(output)}; + { + std::lock_guard guard(output_mutex_); + output_ = std::move(output); + } + dp.emit(); +} + +void waybar::modules::Custom::injectOutput(Json::Value output) { + { + std::lock_guard guard(output_mutex_); + output_ = std::move(output); + } dp.emit(); } @@ -47,23 +61,52 @@ bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { } auto waybar::modules::Custom::update() -> void { + std::variant output; + { + std::lock_guard guard(output_mutex_); + output = std::move(output_); + output_ = std::monostate(); + } + // 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 { - if (config_["return-type"].asString() == "json") { - parseOutputJson(output_.out); - } else { - parseOutputRaw(output_.out); + bool hide = config_["exec"].isString() || config_["exec-if"].isString(); + + if (std::holds_alternative(output)) { + if (hide) { + // No changes since the last update, do nothing. + ALabel::update(); + return; } + } else if (std::holds_alternative(output)) { + // The exit code is non-zero if we get here, so do nothing to hide the label. + } else if (std::holds_alternative(output)) { + const std::string& s = std::get(output); + if (!s.empty()) { + hide = false; + if (config_["return-type"].asString() == "json") { + parseOutputJson(s); + } else { + parseOutputRaw(s); + } + } + } else { + hide = false; + const Json::Value& value = std::get(output); + if (value.isString()) { + parseOutputRaw(value.asString()); + } else { + handleOutputJson(value); + } + } + + if (!hide) { 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(); + hide = true; } else { label_.set_markup(str); if (tooltipEnabled()) { @@ -87,6 +130,11 @@ auto waybar::modules::Custom::update() -> void { event_box_.show(); } } + + if (hide) { + event_box_.hide(); + } + // Call parent update ALabel::update(); } @@ -121,29 +169,33 @@ void waybar::modules::Custom::parseOutputJson(const std::string& output_str) { 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()); - } - } - if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { - percentage_ = parsed["percentage"].asUInt(); - } else { - percentage_ = 0; - } + handleOutputJson(parsed); break; } } + +void waybar::modules::Custom::handleOutputJson(const Json::Value& value) { + if (config_["escape"].isBool() && config_["escape"].asBool()) { + text_ = Glib::Markup::escape_text(value["text"].asString()); + } else { + text_ = value["text"].asString(); + } + if (config_["escape"].isBool() && config_["escape"].asBool()) { + alt_ = Glib::Markup::escape_text(value["alt"].asString()); + } else { + alt_ = value["alt"].asString(); + } + tooltip_ = value["tooltip"].asString(); + if (value["class"].isString()) { + class_.push_back(value["class"].asString()); + } else if (value["class"].isArray()) { + for (auto const& c : value["class"]) { + class_.push_back(c.asString()); + } + } + if (!value["percentage"].asString().empty() && value["percentage"].isUInt()) { + percentage_ = value["percentage"].asUInt(); + } else { + percentage_ = 0; + } +}