fix(custom): validate JSON output UTF-8 and stop restart-interval:0 busy loop

- parseOutputJson() passed script text/alt/tooltip straight to Pango/GTK; an
  invalid-UTF-8 byte aborted the bar in g_utf8_collate. Validate/make_valid
  like parseOutputRaw already does. Fixes #2829.
- restart-interval:0 was floored to 1ms, respawning the script ~1000x/s and
  starving the main loop; a non-positive restart-interval now stops instead.
  Part of #4842.
This commit is contained in:
Alex
2026-07-04 03:09:20 +02:00
parent b5842d4f53
commit bfe38bd1e2
+23 -10
View File
@@ -105,7 +105,8 @@ void waybar::modules::Custom::continuousWorker() {
dp.emit(); dp.emit();
spdlog::error("{} stopped unexpectedly, is it endless?", name_); spdlog::error("{} stopped unexpectedly, is it endless?", name_);
} }
if (config_["restart-interval"].isNumeric()) { if (config_["restart-interval"].isNumeric() &&
config_["restart-interval"].asDouble() > 0) {
pid_ = -1; pid_ = -1;
thread_.sleep_for(std::chrono::milliseconds( thread_.sleep_for(std::chrono::milliseconds(
std::max(1L, // Minimum 1ms due to millisecond precision std::max(1L, // Minimum 1ms due to millisecond precision
@@ -115,6 +116,8 @@ void waybar::modules::Custom::continuousWorker() {
throw std::runtime_error("Unable to open " + cmd); throw std::runtime_error("Unable to open " + cmd);
} }
} else { } 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(); thread_.stop();
return; return;
} }
@@ -316,22 +319,32 @@ void waybar::modules::Custom::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());