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; + } +}