#include "modules/systemd_failed_units.hpp" #include #include #include #include #include #include #include #include #include static const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000; namespace waybar::modules { SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config) : ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1), hide_on_ok_(true), tooltip_format_( "System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n" "{failed_units_list}"), tooltip_format_ok_("System: {system_state}\nUser: {user_state}"), tooltip_unit_format_("{name}: {description}"), update_pending_(false), nr_failed_system_(0), nr_failed_user_(0), nr_failed_(0), last_status_() { if (config["hide-on-ok"].isBool()) { hide_on_ok_ = config["hide-on-ok"].asBool(); } if (config["format-ok"].isString()) { format_ok_ = config["format-ok"].asString(); } else { format_ok_ = format_; } if (config["tooltip-format"].isString()) { tooltip_format_ = config["tooltip-format"].asString(); } if (config["tooltip-format-ok"].isString()) { tooltip_format_ok_ = config["tooltip-format-ok"].asString(); } if (config["tooltip-unit-format"].isString()) { tooltip_unit_format_ = config["tooltip-unit-format"].asString(); } /* Default to enable both "system" and "user". */ if (!config["system"].isBool() || config["system"].asBool()) { system_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties"); if (!system_props_proxy_) { throw std::runtime_error("Unable to connect to systemwide systemd DBus!"); } system_props_proxy_->signal_signal().connect( sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); try { system_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); } catch (const Glib::Error& e) { spdlog::warn("Unable to connect to systemwide systemd Manager interface: {}", e.what().c_str()); } } if (!config["user"].isBool() || config["user"].asBool()) { user_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties"); if (!user_props_proxy_) { throw std::runtime_error("Unable to connect to user systemd DBus!"); } user_props_proxy_->signal_signal().connect( sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); try { user_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); } catch (const Glib::Error& e) { spdlog::warn("Unable to connect to user systemd Manager interface: {}", e.what().c_str()); } } if (!user_props_proxy_ && !system_props_proxy_) throw std::runtime_error("Neither system nor user status is requested."); updateData(); /* Always update for the first time. */ dp.emit(); } auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) -> void { if (signal_name == "PropertiesChanged" && !update_pending_) { update_pending_ = true; /* The fail count may fluctuate due to restarting. */ Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData), UPDATE_DEBOUNCE_TIME_MS); } } void SystemdFailedUnits::RequestSystemState() { auto load = [](const char* kind, Glib::RefPtr& proxy) -> std::string { try { if (!proxy) return "ignored"; auto parameters = Glib::VariantContainerBase( g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState")); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; g_variant_get(const_cast(data.gobj()), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) { return g_variant_get_string(const_cast(variant.gobj()), NULL); } } } catch (Glib::Error& e) { spdlog::error("Failed to get {} state: {}", kind, e.what().c_str()); } return "unknown"; }; system_state_ = load("systemwide", system_props_proxy_); user_state_ = load("user", user_props_proxy_); if ((system_state_ == "running" || system_state_ == "ignored") && (user_state_ == "running" || user_state_ == "ignored")) overall_state_ = "ok"; else overall_state_ = "degraded"; } void SystemdFailedUnits::RequestFailedUnits() { auto load = [](const char* kind, Glib::RefPtr& proxy) -> uint32_t { try { if (!proxy) return 0; auto parameters = Glib::VariantContainerBase( g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits")); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; g_variant_get(const_cast(data.gobj()), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) { return g_variant_get_uint32(const_cast(variant.gobj())); } } } catch (Glib::Error& e) { spdlog::error("Failed to get {} failed units: {}", kind, e.what().c_str()); } return 0; }; nr_failed_system_ = load("systemwide", system_props_proxy_); nr_failed_user_ = load("user", user_props_proxy_); nr_failed_ = nr_failed_system_ + nr_failed_user_; } void SystemdFailedUnits::RequestFailedUnitsList() { failed_units_.clear(); if (!tooltipEnabled() || nr_failed_ == 0) { return; } if (system_manager_proxy_) { auto units = LoadFailedUnitsList("systemwide", system_manager_proxy_, "system"); failed_units_.insert(failed_units_.end(), units.begin(), units.end()); } if (user_manager_proxy_) { auto units = LoadFailedUnitsList("user", user_manager_proxy_, "user"); failed_units_.insert(failed_units_.end(), units.begin(), units.end()); } } auto SystemdFailedUnits::LoadFailedUnitsList(const char* kind, Glib::RefPtr& proxy, const std::string& scope) -> std::vector { // org.freedesktop.systemd1.Manager.ListUnits returns // (name, description, load_state, active_state, sub_state, followed, unit_path, job_id, // job_type, job_path). using UnitRow = std::tuple; using ListUnitsReply = Glib::Variant>>; std::vector units; if (!proxy) { return units; } try { auto data = proxy->call_sync("ListUnits"); if (!data) return units; if (!data.is_of_type(ListUnitsReply::variant_type())) { spdlog::warn("Unexpected DBus signature for ListUnits: {}", data.get_type_string()); return units; } auto [rows] = Glib::VariantBase::cast_dynamic(data).get(); for (const auto& row : rows) { const auto& name = std::get<0>(row); const auto& description = std::get<1>(row); const auto& load_state = std::get<2>(row); const auto& active_state = std::get<3>(row); const auto& sub_state = std::get<4>(row); if (active_state == "failed" || sub_state == "failed") { units.push_back({name, description, load_state, active_state, sub_state, scope}); } } } catch (const Glib::Error& e) { spdlog::error("Failed to list {} units: {}", kind, e.what().c_str()); } return units; } std::string SystemdFailedUnits::BuildTooltipFailedList() const { if (failed_units_.empty()) { return ""; } std::string list; list.reserve(failed_units_.size() * 16); bool first = true; for (const auto& unit : failed_units_) { try { auto line = fmt::format( fmt::runtime(tooltip_unit_format_), fmt::arg("name", Glib::Markup::escape_text(unit.name).raw()), fmt::arg("description", Glib::Markup::escape_text(unit.description).raw()), fmt::arg("load_state", unit.load_state), fmt::arg("active_state", unit.active_state), fmt::arg("sub_state", unit.sub_state), fmt::arg("scope", unit.scope)); if (!first) { list += "\n"; } first = false; list += "- "; list += line; } catch (const std::exception& e) { spdlog::warn("Failed to format tooltip for unit {}: {}", unit.name, e.what()); } } return list; } void SystemdFailedUnits::updateData() { update_pending_ = false; RequestSystemState(); if (overall_state_ == "degraded") { RequestFailedUnits(); RequestFailedUnitsList(); } else { nr_failed_system_ = nr_failed_user_ = nr_failed_ = 0; failed_units_.clear(); } dp.emit(); } auto SystemdFailedUnits::update() -> void { // Hide if needed. if (overall_state_ == "ok" && hide_on_ok_) { event_box_.set_visible(false); last_status_ = overall_state_; return; } event_box_.set_visible(true); // Set state class. if (!last_status_.empty() && label_.get_style_context()->has_class(last_status_)) { label_.get_style_context()->remove_class(last_status_); } if (!label_.get_style_context()->has_class(overall_state_)) { label_.get_style_context()->add_class(overall_state_); } last_status_ = overall_state_; // A malformed user format/tooltip-format (e.g. an unknown {placeholder}) makes fmt throw a // fmt::format_error; catch it so a bad config warns once instead of taking update() down. try { setLabelMarkup(fmt::format( fmt::runtime(nr_failed_ == 0 ? format_ok_ : format_), fmt::arg("nr_failed", nr_failed_), fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_))); } catch (const std::exception& e) { static bool labelWarned = false; if (!labelWarned) { spdlog::warn("systemd-failed-units: invalid format, using fallback: {}", e.what()); labelWarned = true; } setLabelMarkup(fmt::format("{} failed", nr_failed_)); } if (tooltipEnabled()) { std::string failed_list = BuildTooltipFailedList(); auto tooltip_template = overall_state_ == "ok" ? tooltip_format_ok_ : tooltip_format_; if (!tooltip_template.empty()) { try { setTooltipMarkup(fmt::format( fmt::runtime(tooltip_template), fmt::arg("nr_failed", nr_failed_), fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_), fmt::arg("failed_units_list", failed_list))); } catch (const std::exception& e) { static bool tooltipWarned = false; if (!tooltipWarned) { spdlog::warn("systemd-failed-units: invalid tooltip-format, skipping tooltip: {}", e.what()); tooltipWarned = true; } setTooltipMarkup(""); } } else { setTooltipMarkup(""); } } ALabel::update(); } } // namespace waybar::modules