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.
This commit is contained in:
Alex
2026-07-05 10:13:24 +02:00
parent 48db4aa36e
commit 77734e9b02
+28 -10
View File
@@ -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());