From 77734e9b02856b9195ee55e38eca627e6bcf0bc0 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 Jul 2026 10:07:29 +0200 Subject: [PATCH] custom-graph: fix worker crash, JSON UTF-8 validation, SIGRTMIN guard Bring src/modules/custom_graph.cpp in line with the hardened custom.cpp: - continuousWorker: on the restart path, an open() failure threw std::runtime_error out of the SleeperThread lambda, which escaped the thread and called std::terminate, killing all of Waybar. Log the error and stop the worker gracefully instead of throwing. - parseOutputJson: validate/make_valid the text/alt/tooltip JSON string fields before they reach fmt markup / set_tooltip_markup. Invalid UTF-8 from a script otherwise aborts the bar in g_utf8_* (parseOutputRaw already validated the same way). - refresh: wrap the SIGRTMIN-based signal check in #ifdef SIGRTMIN so the module builds on platforms without SIGRTMIN (e.g. some BSDs). Fixes a std::terminate crash on continuous-exec restart failure, an invalid-UTF-8 bar abort via JSON output, and a build break on platforms lacking SIGRTMIN. --- src/modules/custom_graph.cpp | 38 ++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index 8b0c70af..06e45d06 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -99,7 +99,13 @@ void waybar::modules::CustomGraph::continuousWorker() { thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); fp_ = util::command::open(cmd, pid_, output_name_); if (!fp_) { - throw std::runtime_error("Unable to open " + cmd); + // Letting this exception escape the SleeperThread would call + // std::terminate and kill all of Waybar. Degrade gracefully instead. + output_ = {1, ""}; + dp.emit(); + spdlog::error("Unable to restart {}: unable to open {}", name_, cmd); + thread_.stop(); + return; } } else { thread_.stop(); @@ -139,9 +145,11 @@ void waybar::modules::CustomGraph::waitingWorker() { } void waybar::modules::CustomGraph::refresh(int sig) { +#ifdef SIGRTMIN if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { thread_.wake_up(); } +#endif } void waybar::modules::CustomGraph::handleEvent() { @@ -268,22 +276,32 @@ void waybar::modules::CustomGraph::parseOutputJson() { std::istringstream output(output_.out); std::string line; class_.clear(); + // A script can emit invalid UTF-8; passing it unchecked to Pango/GTK aborts + // the whole bar in g_utf8_* (see parseOutputRaw, which validates the same way). + auto sanitize = [](const std::string& s) -> Glib::ustring { + Glib::ustring value = s; + if (!value.validate()) { + value = value.make_valid(); + } + return value; + }; while (getline(output, line)) { auto parsed = parser_.parse(line); - if (config_["escape"].isBool() && config_["escape"].asBool()) { - text_ = Glib::Markup::escape_text(parsed["text"].asString()); + const bool escape = config_["escape"].isBool() && config_["escape"].asBool(); + if (escape) { + text_ = Glib::Markup::escape_text(sanitize(parsed["text"].asString())); } else { - text_ = parsed["text"].asString(); + text_ = sanitize(parsed["text"].asString()); } - if (config_["escape"].isBool() && config_["escape"].asBool()) { - alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); + if (escape) { + alt_ = Glib::Markup::escape_text(sanitize(parsed["alt"].asString())); } else { - alt_ = parsed["alt"].asString(); + alt_ = sanitize(parsed["alt"].asString()); } - if (config_["escape"].isBool() && config_["escape"].asBool()) { - tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString()); + if (escape) { + tooltip_ = Glib::Markup::escape_text(sanitize(parsed["tooltip"].asString())); } else { - tooltip_ = parsed["tooltip"].asString(); + tooltip_ = sanitize(parsed["tooltip"].asString()); } if (parsed["class"].isString()) { class_.push_back(parsed["class"].asString());