From 5f4b96ad1d8a34a819581a117b733a6ea097e288 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 22:32:15 -0500 Subject: [PATCH] refactor(custom): move continuous exec onto GLib command stream Replace the custom module's continuous getline() worker with the new GLib-backed command stream helper. This moves line delivery, child exit handling, and restart scheduling onto the main loop so continuous commands no longer depend on a blocking FILE* read inside SleeperThread. The behavior is kept aligned with the old module semantics: stdout lines still emit updates, non-zero exits still surface as errors, and restart-interval still respawns the command. Signed-off-by: Austin Horstman --- include/modules/custom.hpp | 9 ++- src/modules/custom.cpp | 117 +++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 59 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index 442b1c37..dbdcb698 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -3,10 +3,12 @@ #include #include +#include #include #include "AIconLabel.hpp" #include "util/command.hpp" +#include "util/command_line_stream.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" @@ -22,6 +24,9 @@ class Custom : public AIconLabel { private: void delayWorker(); void continuousWorker(); + void startContinuousProcess(bool throw_on_failure); + void handleContinuousProcessExit(int exit_code); + void scheduleContinuousRestart(); void waitingWorker(); void parseOutputRaw(); void parseOutputJson(); @@ -42,10 +47,10 @@ class Custom : public AIconLabel { const bool tooltip_format_enabled_; std::vector class_; int percentage_; - FILE* fp_; - int pid_; util::command::res output_; util::JsonParser parser_; + std::unique_ptr continuous_stream_; + sigc::connection restart_connection_; util::SleeperThread thread_; }; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 83f36797..54fda760 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -2,10 +2,9 @@ #include +#include #include -#include "util/scope_guard.hpp" - waybar::modules::Custom::Custom(const std::string& name, const std::string& id, const Json::Value& config, const std::string& output_name) : AIconLabel(config, "custom-" + name, id, "{}"), @@ -13,9 +12,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, output_name_(output_name), id_(id), tooltip_format_enabled_{config_["tooltip-format"].isString()}, - percentage_(0), - fp_(nullptr), - pid_(-1) { + percentage_(0) { if (config.isNull()) { spdlog::warn("There is no configuration for 'custom/{}', element will be hidden", name); } @@ -40,10 +37,9 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, } waybar::modules::Custom::~Custom() { - if (pid_ != -1) { - killpg(pid_, SIGTERM); - waitpid(pid_, NULL, 0); - pid_ = -1; + restart_connection_.disconnect(); + if (continuous_stream_) { + continuous_stream_->stop(); } } @@ -80,57 +76,64 @@ void waybar::modules::Custom::delayWorker() { } void waybar::modules::Custom::continuousWorker() { - auto cmd = config_["exec"].asString(); - pid_ = -1; - fp_ = util::command::open(cmd, pid_, output_name_); - if (!fp_) { - throw std::runtime_error("Unable to open " + cmd); - } - thread_ = [this, cmd] { - char* buff = nullptr; - waybar::util::ScopeGuard buff_deleter([&buff]() { - if (buff) { - free(buff); - } - }); - 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, ""}; + continuous_stream_ = std::make_unique( + output_name_, + [this](const std::string& output) { + output_ = {0, output}; dp.emit(); - spdlog::error("{} stopped unexpectedly, is it endless?", name_); - } - if (config_["restart-interval"].isNumeric() && config_["restart-interval"].asDouble() > 0) { - pid_ = -1; - thread_.sleep_for(std::chrono::milliseconds( - std::max(1L, // Minimum 1ms due to millisecond precision - static_cast(config_["restart-interval"].asDouble() * 1000)))); - fp_ = util::command::open(cmd, pid_, output_name_); - if (!fp_) { - throw std::runtime_error("Unable to open " + cmd); - } - } else { - // A non-positive restart-interval must not busy-respawn the script - // (that starves the GTK main loop); treat it as "do not restart". - thread_.stop(); - return; - } - } else { - std::string output = buff; + }, + [this](int exit_code) { handleContinuousProcessExit(exit_code); }); + startContinuousProcess(true); +} - // Remove last newline - if (!output.empty() && output[output.length() - 1] == '\n') { - output.erase(output.length() - 1); - } - output_ = {0, output}; - dp.emit(); +void waybar::modules::Custom::startContinuousProcess(bool throw_on_failure) { + const auto cmd = config_["exec"].asString(); + + try { + continuous_stream_->start(cmd); + } catch (const Glib::SpawnError& e) { + if (throw_on_failure) { + throw std::runtime_error("Unable to open " + cmd + ": " + e.what().raw()); } - }; + output_ = {1, ""}; + dp.emit(); + spdlog::error("Unable to restart {}: {}", name_, e.what().raw()); + scheduleContinuousRestart(); + } catch (const std::exception& e) { + if (throw_on_failure) { + throw; + } + output_ = {1, ""}; + dp.emit(); + spdlog::error("Unable to restart {}: {}", name_, e.what()); + scheduleContinuousRestart(); + } +} + +void waybar::modules::Custom::handleContinuousProcessExit(int exit_code) { + if (exit_code != 0) { + output_ = {exit_code, ""}; + dp.emit(); + spdlog::error("{} stopped unexpectedly, is it endless?", name_); + } + + scheduleContinuousRestart(); +} + +void waybar::modules::Custom::scheduleContinuousRestart() { + restart_connection_.disconnect(); + if (!config_["restart-interval"].isNumeric() || config_["restart-interval"].asDouble() <= 0) { + // A non-positive restart-interval must not busy-respawn the script + // (that starves the GTK main loop); treat it as "do not restart". + return; + } + + restart_connection_ = Glib::signal_timeout().connect( + [this] { + startContinuousProcess(false); + return false; + }, + std::max(1U, static_cast(config_["restart-interval"].asDouble() * 1000))); } void waybar::modules::Custom::waitingWorker() {