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.
pull/1276/head
Risto Toijala 2021-10-03 13:33:08 +03:00
parent ccdc9b8e0b
commit eb1775d5f8
4 changed files with 135 additions and 37 deletions

View File

@ -9,6 +9,9 @@
#include <json/json.h> #include <json/json.h>
#include "AModule.hpp" #include "AModule.hpp"
#include "modules/custom.hpp"
#include "util/json.hpp"
#include "util/worker_thread.hpp"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar { namespace waybar {
@ -76,6 +79,7 @@ class Bar {
void getModules(const Factory &, const std::string &); void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name); void setupAltFormatKeyForModuleList(const char *module_list_name);
void customExecOutputCallback(std::string output);
std::unique_ptr<BarSurface> surface_impl_; std::unique_ptr<BarSurface> surface_impl_;
bar_layer layer_; bar_layer layer_;
@ -86,6 +90,10 @@ class Bar {
std::vector<std::unique_ptr<waybar::AModule>> modules_left_; std::vector<std::unique_ptr<waybar::AModule>> modules_left_;
std::vector<std::unique_ptr<waybar::AModule>> modules_center_; std::vector<std::unique_ptr<waybar::AModule>> modules_center_;
std::vector<std::unique_ptr<waybar::AModule>> modules_right_; std::vector<std::unique_ptr<waybar::AModule>> modules_right_;
waybar::util::JsonParser parser_;
// Contains pointers to modules in modules_left_, etc.
std::map<std::string, waybar::modules::Custom *> custom_modules_;
std::vector<std::unique_ptr<waybar::util::WorkerThread>> custom_threads_;
}; };
} // namespace waybar } // namespace waybar

View File

@ -3,7 +3,9 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <csignal> #include <csignal>
#include <mutex>
#include <string> #include <string>
#include <variant>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/command.hpp" #include "util/command.hpp"
@ -15,6 +17,7 @@ namespace waybar::modules {
class Custom : public ALabel { class Custom : public ALabel {
public: public:
Custom(const std::string&, const std::string&, const Json::Value&); Custom(const std::string&, const std::string&, const Json::Value&);
void injectOutput(Json::Value);
auto update() -> void; auto update() -> void;
void refresh(int /*signal*/); void refresh(int /*signal*/);
@ -23,6 +26,7 @@ class Custom : public ALabel {
void workerOutputCallback(std::string); void workerOutputCallback(std::string);
void parseOutputRaw(const std::string&); void parseOutputRaw(const std::string&);
void parseOutputJson(const std::string&); void parseOutputJson(const std::string&);
void handleOutputJson(const Json::Value&);
void handleEvent(); void handleEvent();
bool handleScroll(GdkEventScroll* e); bool handleScroll(GdkEventScroll* e);
bool handleToggle(GdkEventButton* const& e); bool handleToggle(GdkEventButton* const& e);
@ -33,9 +37,12 @@ class Custom : public ALabel {
std::string tooltip_; std::string tooltip_;
std::vector<std::string> class_; std::vector<std::string> class_;
int percentage_; int percentage_;
util::command::res output_; // Worker exit code, worker raw output, or injected JSON.
// Injected JSON is string for raw output, object for JSON output.
std::variant<std::monostate, int, std::string, Json::Value> output_;
util::JsonParser parser_; util::JsonParser parser_;
// Protects output_ since it is accessed from many threads.
std::mutex output_mutex_;
waybar::util::WorkerThread thread_; waybar::util::WorkerThread thread_;
}; };

View File

@ -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)); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
setupWidgets(); 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<waybar::util::WorkerThread>(
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(); window.show_all();
if (spdlog::should_log(spdlog::level::debug)) { 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*) { void waybar::Bar::onMap(GdkEventAny*) {
/* /*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). * 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]) { for (const auto& name : config[pos]) {
try { try {
auto module = factory.makeModule(name.asString()); auto module = factory.makeModule(name.asString());
auto custom = dynamic_cast<waybar::modules::Custom*>(module);
if (custom != nullptr) {
custom_modules_[name.asString()] = custom;
}
if (pos == "modules-left") { if (pos == "modules-left") {
modules_left_.emplace_back(module); modules_left_.emplace_back(module);
} }

View File

@ -13,12 +13,26 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
} }
void waybar::modules::Custom::workerExitCallback(int exit_code) { void waybar::modules::Custom::workerExitCallback(int exit_code) {
output_ = {exit_code, ""}; {
std::lock_guard<std::mutex> guard(output_mutex_);
output_ = exit_code;
}
dp.emit(); dp.emit();
} }
void waybar::modules::Custom::workerOutputCallback(std::string output) { void waybar::modules::Custom::workerOutputCallback(std::string output) {
output_ = {0, std::move(output)}; {
std::lock_guard<std::mutex> guard(output_mutex_);
output_ = std::move(output);
}
dp.emit();
}
void waybar::modules::Custom::injectOutput(Json::Value output) {
{
std::lock_guard<std::mutex> guard(output_mutex_);
output_ = std::move(output);
}
dp.emit(); dp.emit();
} }
@ -47,23 +61,52 @@ bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
} }
auto waybar::modules::Custom::update() -> void { auto waybar::modules::Custom::update() -> void {
// Hide label if output is empty std::variant<std::monostate, int, std::string, Json::Value> output;
if ((config_["exec"].isString() || config_["exec-if"].isString()) && {
(output_.out.empty() || output_.exit_code != 0)) { std::lock_guard<std::mutex> guard(output_mutex_);
event_box_.hide(); output = std::move(output_);
} else { output_ = std::monostate();
if (config_["return-type"].asString() == "json") {
parseOutputJson(output_.out);
} else {
parseOutputRaw(output_.out);
} }
// Hide label if output is empty
bool hide = config_["exec"].isString() || config_["exec-if"].isString();
if (std::holds_alternative<std::monostate>(output)) {
if (hide) {
// No changes since the last update, do nothing.
ALabel::update();
return;
}
} else if (std::holds_alternative<int>(output)) {
// The exit code is non-zero if we get here, so do nothing to hide the label.
} else if (std::holds_alternative<std::string>(output)) {
const std::string& s = std::get<std::string>(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<Json::Value>(output);
if (value.isString()) {
parseOutputRaw(value.asString());
} else {
handleOutputJson(value);
}
}
if (!hide) {
auto str = fmt::format(format_, auto str = fmt::format(format_,
text_, text_,
fmt::arg("alt", alt_), fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_)); fmt::arg("percentage", percentage_));
if (str.empty()) { if (str.empty()) {
event_box_.hide(); hide = true;
} else { } else {
label_.set_markup(str); label_.set_markup(str);
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -87,6 +130,11 @@ auto waybar::modules::Custom::update() -> void {
event_box_.show(); event_box_.show();
} }
} }
if (hide) {
event_box_.hide();
}
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }
@ -121,29 +169,33 @@ void waybar::modules::Custom::parseOutputJson(const std::string& output_str) {
class_.clear(); class_.clear();
while (getline(output, line)) { while (getline(output, line)) {
auto parsed = parser_.parse(line); auto parsed = parser_.parse(line);
if (config_["escape"].isBool() && config_["escape"].asBool()) { handleOutputJson(parsed);
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;
}
break; 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;
}
}