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())); thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
fp_ = util::command::open(cmd, pid_, output_name_); fp_ = util::command::open(cmd, pid_, output_name_);
if (!fp_) { 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 { } else {
thread_.stop(); thread_.stop();
@@ -139,9 +145,11 @@ void waybar::modules::CustomGraph::waitingWorker() {
} }
void waybar::modules::CustomGraph::refresh(int sig) { void waybar::modules::CustomGraph::refresh(int sig) {
#ifdef SIGRTMIN
if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up(); thread_.wake_up();
} }
#endif
} }
void waybar::modules::CustomGraph::handleEvent() { void waybar::modules::CustomGraph::handleEvent() {
@@ -268,22 +276,32 @@ void waybar::modules::CustomGraph::parseOutputJson() {
std::istringstream output(output_.out); std::istringstream output(output_.out);
std::string line; std::string line;
class_.clear(); 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)) { while (getline(output, line)) {
auto parsed = parser_.parse(line); auto parsed = parser_.parse(line);
if (config_["escape"].isBool() && config_["escape"].asBool()) { const bool escape = config_["escape"].isBool() && config_["escape"].asBool();
text_ = Glib::Markup::escape_text(parsed["text"].asString()); if (escape) {
text_ = Glib::Markup::escape_text(sanitize(parsed["text"].asString()));
} else { } else {
text_ = parsed["text"].asString(); text_ = sanitize(parsed["text"].asString());
} }
if (config_["escape"].isBool() && config_["escape"].asBool()) { if (escape) {
alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); alt_ = Glib::Markup::escape_text(sanitize(parsed["alt"].asString()));
} else { } else {
alt_ = parsed["alt"].asString(); alt_ = sanitize(parsed["alt"].asString());
} }
if (config_["escape"].isBool() && config_["escape"].asBool()) { if (escape) {
tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString()); tooltip_ = Glib::Markup::escape_text(sanitize(parsed["tooltip"].asString()));
} else { } else {
tooltip_ = parsed["tooltip"].asString(); tooltip_ = sanitize(parsed["tooltip"].asString());
} }
if (parsed["class"].isString()) { if (parsed["class"].isString()) {
class_.push_back(parsed["class"].asString()); class_.push_back(parsed["class"].asString());