From efadb82c56b16699c01b029913af7474b61464f3 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 13 Sep 2025 00:00:20 +0200 Subject: [PATCH 1/9] feat: basic line graph component and cpu graph module --- include/AGraph.hpp | 50 +++++++++ include/modules/cpu_graph.hpp | 32 ++++++ man/waybar-cpu-graph.5.scd | 85 +++++++++++++++ meson.build | 3 + src/AGraph.cpp | 197 ++++++++++++++++++++++++++++++++++ src/factory.cpp | 4 + src/modules/cpu_graph.cpp | 47 ++++++++ 7 files changed, 418 insertions(+) create mode 100644 include/AGraph.hpp create mode 100644 include/modules/cpu_graph.hpp create mode 100644 man/waybar-cpu-graph.5.scd create mode 100644 src/AGraph.cpp create mode 100644 src/modules/cpu_graph.cpp diff --git a/include/AGraph.hpp b/include/AGraph.hpp new file mode 100644 index 00000000..3466e577 --- /dev/null +++ b/include/AGraph.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "AModule.hpp" + +namespace waybar { + +class AGraph : public AModule { + public: + AGraph(const Json::Value &, const std::string &, const std::string &, + uint16_t interval = 0, bool enable_click = false, + bool enable_scroll = false); + virtual ~AGraph() = default; + auto update() -> void override; + + protected: + Gtk::DrawingArea graph_; + std::deque values_; + uint16_t datapoints_ = 20; + uint16_t y_offset_ = 0; + + void addValue(const int n); + + const std::chrono::seconds interval_; + + bool onDraw(const Cairo::RefPtr &cr); + + std::map submenus_; + std::map menuActionsMap_; + static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data); + + private: + void drawFilledArea(const Cairo::RefPtr &cr, + const std::vector> &points, + double height, const Gdk::RGBA &bg_color); + + void drawLine(const Cairo::RefPtr &cr, + const std::vector> &points, const Gdk::RGBA &fg_color); + + void drawPath(const Cairo::RefPtr &cr, + const std::vector> &points); +}; + +} // namespace waybar diff --git a/include/modules/cpu_graph.hpp b/include/modules/cpu_graph.hpp new file mode 100644 index 00000000..df5f6ea3 --- /dev/null +++ b/include/modules/cpu_graph.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "AGraph.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class CpuGraph : public AGraph { + public: + CpuGraph(const std::string&, const Json::Value&); + virtual ~CpuGraph() = default; + auto update() -> void override; + + private: + static constexpr const char *MODERATE_CLASS = "cpu-moderate"; + static constexpr const char *HIGH_CLASS = "cpu-high"; + static constexpr const char *INTENSIVE_CLASS = "cpu-intensive"; + + std::vector> prev_times_; + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/man/waybar-cpu-graph.5.scd b/man/waybar-cpu-graph.5.scd new file mode 100644 index 00000000..bfe9de42 --- /dev/null +++ b/man/waybar-cpu-graph.5.scd @@ -0,0 +1,85 @@ +waybar-cpu(5) + +# NAME + +waybar - cpu graph module + +# DESCRIPTION + +The *cpu graph* module displays a line graph with the CPU utilization. + +# CONFIGURATION + +*interval*: ++ + typeof: integer ++ + default: 10 ++ + The interval in which the information gets polled. + +*width*: ++ + typeof: integer ++ + The length in pixels the module should display. + +*y_offset*: ++ + typeof: integer ++ + The margin in pixels at the bottom of the module. + +*datapoints*: ++ + typeof: integer ++ + How many data points to show. + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right-click on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +# EXAMPLES + +Basic configuration: + +``` +"cpu_graph": { + "interval": 2, + "width": 10, + "y_offset": 4 +} +``` + +# STYLE + +- *#cpu_graph* +- *.cpu-intensive* +- *.cpu-high* +- *.cpu-moderate* diff --git a/meson.build b/meson.build index 0c494eb2..713a723f 100644 --- a/meson.build +++ b/meson.build @@ -159,6 +159,7 @@ endif src_files = files( 'src/factory.cpp', + 'src/AGraph.cpp', 'src/AModule.cpp', 'src/ALabel.cpp', 'src/AIconLabel.cpp', @@ -210,6 +211,7 @@ if is_linux 'src/modules/bluetooth.cpp', 'src/modules/cffi.cpp', 'src/modules/cpu.cpp', + 'src/modules/cpu_graph.cpp', 'src/modules/cpu_frequency/common.cpp', 'src/modules/cpu_frequency/linux.cpp', 'src/modules/cpu_usage/common.cpp', @@ -234,6 +236,7 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd src_files += files( 'src/modules/cffi.cpp', 'src/modules/cpu.cpp', + 'src/modules/cpu_graph.cpp', 'src/modules/cpu_frequency/bsd.cpp', 'src/modules/cpu_frequency/common.cpp', 'src/modules/cpu_usage/bsd.cpp', diff --git a/src/AGraph.cpp b/src/AGraph.cpp new file mode 100644 index 00000000..529cdb66 --- /dev/null +++ b/src/AGraph.cpp @@ -0,0 +1,197 @@ +#include "AGraph.hpp" + +#include +#include + +#include +#include +#include + +#include "config.hpp" + +namespace waybar { + +AGraph::AGraph(const Json::Value& config, const std::string& name, const std::string& id, + uint16_t interval, bool enable_click, bool enable_scroll) + : AModule(config, name, id, + config["format-alt"].isString() || config["menu"].isString() || enable_click, + enable_scroll), + interval_(config_["interval"] == "once" + ? std::chrono::seconds::max() + : std::chrono::seconds( + config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)) { + graph_.signal_draw().connect(sigc::mem_fun(*this, &AGraph::onDraw)); + graph_.set_name(name); + if (!id.empty()) { + graph_.get_style_context()->add_class(id); + } + graph_.get_style_context()->add_class(MODULE_CLASS); + if (config_["width"].isUInt()) { + graph_.set_size_request(config_["width"].asUInt(), -1); + } else { + graph_.set_size_request(100, -1); + } + + event_box_.add(graph_); + + if (config_["datapoints"].isUInt()) { + datapoints_ = config_["datapoints_"].asUInt(); + } + + if (config_["y_offset"].isUInt()) { + y_offset_ = config_["y_offset"].asUInt(); + } + + // If a GTKMenu is requested in the config + if (config_["menu"].isString()) { + // Create the GTKMenu widget + try { + // Check that the file exists + std::string menuFile = config_["menu-file"].asString(); + + // there might be "~" or "$HOME" in original path, try to expand it. + auto result = Config::tryExpandPath(menuFile, ""); + if (result.empty()) { + throw std::runtime_error("Failed to expand file: " + menuFile); + } + + menuFile = result.front(); + // Read the menu descriptor file + std::ifstream file(menuFile); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + menuFile); + } + std::stringstream fileContent; + fileContent << file.rdbuf(); + GtkBuilder* builder = gtk_builder_new(); + + // Make the GtkBuilder and check for errors in his parsing + if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { + throw std::runtime_error("Error found in the file " + menuFile); + } + + menu_ = gtk_builder_get_object(builder, "menu"); + if (menu_ == nullptr) { + throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); + } + submenus_ = std::map(); + menuActionsMap_ = std::map(); + + // Linking actions to the GTKMenu based on + for (Json::Value::const_iterator it = config_["menu-actions"].begin(); + it != config_["menu-actions"].end(); ++it) { + std::string key = it.key().asString(); + submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str())); + menuActionsMap_[key] = it->asString(); + g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), + (gpointer)menuActionsMap_[key].c_str()); + } + } catch (std::runtime_error& e) { + spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); + } + } +} + +auto AGraph::update() -> void { + graph_.queue_draw(); + AModule::update(); +} + +void AGraph::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { + waybar::util::command::res res = waybar::util::command::exec((char*)data, "GtkMenu"); +} + +void AGraph::addValue(const int n) { + if (values_.size() >= datapoints_) { + values_.pop_front(); + } + values_.push_back(n); +} + +bool AGraph::onDraw(const Cairo::RefPtr& cr) { + const int width = graph_.get_allocated_width(); + const int height = graph_.get_allocated_height() - 1 - y_offset_; + + if (values_.empty() || width <= 0 || height <= 0) { + return false; + } + + auto style_context = graph_.get_style_context(); + Gdk::RGBA fg_color = style_context->get_color(Gtk::STATE_FLAG_NORMAL); + Gdk::RGBA bg_color = fg_color; + bg_color.set_alpha(0.3); + + cr->set_line_width(1.0); + + const double step_width = static_cast(width) / datapoints_; + const int values_count = values_.size(); + const int empty_space = datapoints_ - values_count; + + std::vector> points; + points.reserve(values_count); + + for (int i = empty_space; i < datapoints_; ++i) { + double x = i * step_width; + int value_index = i - empty_space; + int value = values_[value_index]; + double y = height - (static_cast(value) / 100.0 * height); + points.emplace_back(x, y); + } + + if (!points.empty()) { + + drawFilledArea(cr, points, height, bg_color); + + drawLine(cr, points, fg_color); + } + + return false; +} +void AGraph::drawFilledArea(const Cairo::RefPtr& cr, + const std::vector>& points, + double height, const Gdk::RGBA& bg_color) { + if (points.empty()) return; + + double first_x = points.front().first; + double last_x = points.back().first; + + drawPath(cr, points); + + cr->line_to(last_x, height); + cr->line_to(first_x, height); + cr->close_path(); + + cr->set_source_rgba(bg_color.get_red(), bg_color.get_green(), bg_color.get_blue(), + bg_color.get_alpha()); + cr->fill(); +} + +void AGraph::drawLine(const Cairo::RefPtr& cr, + const std::vector>& points, + const Gdk::RGBA& fg_color) { + if (points.empty()) return; + + cr->begin_new_path(); + drawPath(cr, points); + + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + fg_color.get_alpha()); + cr->stroke(); +} + +void AGraph::drawPath(const Cairo::RefPtr& cr, + const std::vector>& points) { + if (points.empty()) return; + + bool first_point = true; + for (const auto& point : points) { + if (first_point) { + cr->move_to(point.first, point.second); + first_point = false; + } else { + cr->line_to(point.first, point.second); + } + } +} + +} // namespace waybar diff --git a/src/factory.cpp b/src/factory.cpp index 2fd3e3b8..e82711b5 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -51,6 +51,7 @@ #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #include "modules/cpu.hpp" +#include "modules/cpu_graph.hpp" #include "modules/cpu_frequency.hpp" #include "modules/cpu_usage.hpp" #include "modules/load.hpp" @@ -251,6 +252,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "cpu") { return new waybar::modules::Cpu(id, config_[name]); } + if (ref == "cpu_graph") { + return new waybar::modules::CpuGraph(id, config_[name]); + } #if defined(HAVE_CPU_LINUX) if (ref == "cpu_frequency") { return new waybar::modules::CpuFrequency(id, config_[name]); diff --git a/src/modules/cpu_graph.cpp b/src/modules/cpu_graph.cpp new file mode 100644 index 00000000..a9a29dd2 --- /dev/null +++ b/src/modules/cpu_graph.cpp @@ -0,0 +1,47 @@ +#include "modules/cpu_graph.hpp" + +#include "modules/cpu_frequency.hpp" +#include "modules/cpu_usage.hpp" +#include "modules/load.hpp" + +// In the 80000 version of fmt library authors decided to optimize imports +// and moved declarations required for fmt::dynamic_format_arg_store in new +// header fmt/args.h +#if (FMT_VERSION >= 80000) +#include +#else +#include +#endif + +waybar::modules::CpuGraph::CpuGraph(const std::string& id, const Json::Value& config) + : AGraph(config, "cpu_graph", id, 5) { + thread_ = [this] { + dp.emit(); + thread_.sleep_for(interval_); + }; +} + +auto waybar::modules::CpuGraph::update() -> void { + // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both + auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); + if (tooltipEnabled()) { + graph_.set_tooltip_text(tooltip); + } + auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; + addValue(total_usage); + + graph_.get_style_context()->remove_class(MODERATE_CLASS); + graph_.get_style_context()->remove_class(HIGH_CLASS); + graph_.get_style_context()->remove_class(INTENSIVE_CLASS); + + if (total_usage > 90) { + graph_.get_style_context()->add_class(INTENSIVE_CLASS); + } else if (total_usage > 70) { + graph_.get_style_context()->add_class(HIGH_CLASS); + } else if (total_usage > 30) { + graph_.get_style_context()->add_class(MODERATE_CLASS); + } + + // Call parent update + AGraph::update(); +} From 4d6354af48e5fba70d522ce75d8a0eb20cd661c6 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 11 Oct 2025 01:52:10 +0200 Subject: [PATCH 2/9] feat: add gauge and stacked bar graph type --- include/AGraph.hpp | 19 ++- include/modules/custom_graph.hpp | 49 ++++++ meson.build | 1 + src/AGraph.cpp | 122 ++++++++++++- src/factory.cpp | 4 + src/modules/custom_graph.cpp | 283 +++++++++++++++++++++++++++++++ 6 files changed, 466 insertions(+), 12 deletions(-) create mode 100644 include/modules/custom_graph.hpp create mode 100644 src/modules/custom_graph.cpp diff --git a/include/AGraph.hpp b/include/AGraph.hpp index 3466e577..a49b215d 100644 --- a/include/AGraph.hpp +++ b/include/AGraph.hpp @@ -11,11 +11,12 @@ namespace waybar { +enum class GraphType { LINE, BAR, GAUGE }; + class AGraph : public AModule { public: - AGraph(const Json::Value &, const std::string &, const std::string &, - uint16_t interval = 0, bool enable_click = false, - bool enable_scroll = false); + AGraph(const Json::Value &, const std::string &, const std::string &, uint16_t interval = 0, + bool enable_click = false, bool enable_scroll = false); virtual ~AGraph() = default; auto update() -> void override; @@ -24,6 +25,7 @@ class AGraph : public AModule { std::deque values_; uint16_t datapoints_ = 20; uint16_t y_offset_ = 0; + GraphType graph_type_ = GraphType::LINE; void addValue(const int n); @@ -37,14 +39,21 @@ class AGraph : public AModule { private: void drawFilledArea(const Cairo::RefPtr &cr, - const std::vector> &points, - double height, const Gdk::RGBA &bg_color); + const std::vector> &points, double height, + const Gdk::RGBA &bg_color); void drawLine(const Cairo::RefPtr &cr, const std::vector> &points, const Gdk::RGBA &fg_color); void drawPath(const Cairo::RefPtr &cr, const std::vector> &points); + + void drawBars(const Cairo::RefPtr &cr, + double width, double height, int current_value, + const Gdk::RGBA &fg_color); + + void drawGauge(const Cairo::RefPtr &cr, double width, double height, + int current_value, const Gdk::RGBA &fg_color); }; } // namespace waybar diff --git a/include/modules/custom_graph.hpp b/include/modules/custom_graph.hpp new file mode 100644 index 00000000..a081f376 --- /dev/null +++ b/include/modules/custom_graph.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include + +#include "AGraph.hpp" +#include "util/command.hpp" +#include "util/json.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class CustomGraph : public AGraph { + public: + CustomGraph(const std::string&, const std::string&, const Json::Value&, const std::string&); + virtual ~CustomGraph(); + auto update() -> void override; + void refresh(int /*signal*/) override; + + private: + void delayWorker(); + void continuousWorker(); + void waitingWorker(); + void parseOutputRaw(); + void parseOutputJson(); + void handleEvent(); + bool handleScroll(GdkEventScroll* e) override; + bool handleToggle(GdkEventButton* const& e) override; + + const std::string name_; + const std::string output_name_; + std::string text_; + std::string id_; + std::string alt_; + std::string tooltip_; + const bool tooltip_format_enabled_; + std::vector class_; + int percentage_; + FILE* fp_; + int pid_; + util::command::res output_; + util::JsonParser parser_; + + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index 713a723f..8413dbe1 100644 --- a/meson.build +++ b/meson.build @@ -165,6 +165,7 @@ src_files = files( 'src/AIconLabel.cpp', 'src/AAppIconLabel.cpp', 'src/modules/custom.cpp', + 'src/modules/custom_graph.cpp', 'src/modules/disk.cpp', 'src/modules/idle_inhibitor.cpp', 'src/modules/image.cpp', diff --git a/src/AGraph.cpp b/src/AGraph.cpp index 529cdb66..b8076ecc 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,17 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st y_offset_ = config_["y_offset"].asUInt(); } + if (config_["graph_type"].isString()) { + std::string type = config_["graph_type"].asString(); + if (type == "line") { + graph_type_ = GraphType::LINE; + } else if (type == "bar") { + graph_type_ = GraphType::BAR; + } else if (type == "gauge") { + graph_type_ = GraphType::GAUGE; + } + } + // If a GTKMenu is requested in the config if (config_["menu"].isString()) { // Create the GTKMenu widget @@ -137,19 +149,26 @@ bool AGraph::onDraw(const Cairo::RefPtr& cr) { double y = height - (static_cast(value) / 100.0 * height); points.emplace_back(x, y); } - if (!points.empty()) { - - drawFilledArea(cr, points, height, bg_color); - - drawLine(cr, points, fg_color); + switch (graph_type_) { + case GraphType::LINE: + drawFilledArea(cr, points, height, bg_color); + drawLine(cr, points, fg_color); + break; + case GraphType::BAR: + drawBars(cr, width, height, values_.empty() ? 0 : values_.back(), fg_color); + break; + case GraphType::GAUGE: + drawGauge(cr, width, height, values_.empty() ? 0 : values_.back(), fg_color); + break; + } } return false; } void AGraph::drawFilledArea(const Cairo::RefPtr& cr, - const std::vector>& points, - double height, const Gdk::RGBA& bg_color) { + const std::vector>& points, double height, + const Gdk::RGBA& bg_color) { if (points.empty()) return; double first_x = points.front().first; @@ -194,4 +213,93 @@ void AGraph::drawPath(const Cairo::RefPtr& cr, } } +void AGraph::drawBars(const Cairo::RefPtr& cr, + double width, double height, int current_value, + const Gdk::RGBA& fg_color) { + + current_value = std::min(100, std::max(0, current_value)); + + double green_height = height * (std::min(current_value, 40) / 100.0); + cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->rectangle(0, height - green_height, width, green_height); + cr->fill(); + + if (current_value > 40) { + double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; + cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->rectangle(0, height - green_height - yellow_height, width, yellow_height); + cr->fill(); + } + + if (current_value > 75) { + double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; + cr->set_source_rgba(1.0, 0.5, 0.0, 1.0); + double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; + cr->rectangle(0, height - green_height - yellow_height - orange_height, width, + orange_height); + cr->fill(); + } + + if (current_value > 85) { + double red_height = height * (current_value - 85) / 100.0; + cr->set_source_rgba(1.0, 0.0, 0.0, 1.0); + double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; + double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; + cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width, + red_height); + cr->fill(); + } + + double value_height = height * (current_value / 100.0); + cr->set_source_rgba(0.2, 0.2, 0.2, 0.8); + cr->rectangle(0, height - value_height, width, 2); + cr->fill(); +} + +void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color) { + double center_x = width / 2.0; + double center_y = height; + double radius = height / 2.0; + + cr->set_line_width(10.0); + + double angle1 = M_PI; + double angle2 = angle1 + 0.3 * angle1; + + // Green section (0-33%) + cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->arc(center_x, center_y, radius, angle1, angle2); + cr->stroke(); + + // Yellow section (33-66%) + angle1 = angle2; + angle2 = angle1 + 0.3 * angle1; + cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->arc(center_x, center_y, radius, angle1, angle2); + cr->stroke(); + + // Red section (66-100%) + angle1 = angle2; + angle2 = 0.0; + cr->set_source_rgba(1.0, 0.0, 0.0, 0.8); + cr->arc(center_x, center_y, radius, angle1, angle2); + cr->stroke(); + + // Draw needle + double percentage = std::min(100, std::max(0, current_value)) / 100.0; + double needle_angle = M_PI * percentage; + double needle_length = radius; + + double needle_x = center_x - needle_length * cos(needle_angle); + double needle_y = center_y - needle_length * sin(needle_angle); + + cr->set_source_rgba(1.0, 1.0, 1.0, 1.0); + cr->set_line_width(2.0); + cr->begin_new_path(); + cr->move_to(center_x, center_y); + cr->line_to(needle_x, needle_y); + cr->stroke(); +} + } // namespace waybar diff --git a/src/factory.cpp b/src/factory.cpp index e82711b5..dfb5caf4 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -118,6 +118,7 @@ #include "modules/cava/cava_frontend.hpp" #include "modules/cffi.hpp" #include "modules/custom.hpp" +#include "modules/custom_graph.hpp" #include "modules/image.hpp" #include "modules/temperature.hpp" #include "modules/user.hpp" @@ -362,6 +363,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name); } + if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 7) { + return new waybar::modules::CustomGraph(ref.substr(7), id, config_[name], bar_.output->name); + } if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) { return new waybar::modules::CFFI(ref.substr(5), id, config_[name]); } diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp new file mode 100644 index 00000000..288d97f2 --- /dev/null +++ b/src/modules/custom_graph.cpp @@ -0,0 +1,283 @@ +#include "modules/custom_graph.hpp" + +#include + +#include "util/scope_guard.hpp" + +waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id, + const Json::Value& config, const std::string& output_name) + : AGraph(config, "custom-graph" + name, id), + name_(name), + output_name_(output_name), + id_(id), + tooltip_format_enabled_{config_["tooltip-format"].isString()}, + percentage_(0), + fp_(nullptr), + pid_(-1) { + if (config.isNull()) { + spdlog::warn("There is no configuration for 'custom-graph/{}', element will be hidden", name); + } + dp.emit(); + if (!config_["signal"].empty() && config_["interval"].empty() && + config_["restart-interval"].empty()) { + waitingWorker(); + } else if (interval_.count() > 0) { + delayWorker(); + } else if (config_["exec"].isString()) { + continuousWorker(); + } +} + +waybar::modules::CustomGraph::~CustomGraph() { + if (pid_ != -1) { + killpg(pid_, SIGTERM); + waitpid(pid_, NULL, 0); + pid_ = -1; + } +} + +void waybar::modules::CustomGraph::delayWorker() { + thread_ = [this] { + for (int i : this->pid_children_) { + int status; + waitpid(i, &status, 0); + } + + this->pid_children_.clear(); + + bool can_update = true; + if (config_["exec-if"].isString()) { + output_ = util::command::execNoRead(config_["exec-if"].asString()); + if (output_.exit_code != 0) { + can_update = false; + dp.emit(); + } + } + if (can_update) { + if (config_["exec"].isString()) { + output_ = util::command::exec(config_["exec"].asString(), output_name_); + } + dp.emit(); + } + thread_.sleep_for(interval_); + }; +} + +void waybar::modules::CustomGraph::continuousWorker() { + auto cmd = config_["exec"].asString(); + pid_ = -1; + fp_ = util::command::open(cmd, pid_, output_name_); + if (!fp_) { + throw std::runtime_error("Unable to open " + cmd); + } + thread_ = [this, cmd] { + char* buff = nullptr; + waybar::util::ScopeGuard buff_deleter([&buff]() { + if (buff) { + free(buff); + } + }); + size_t len = 0; + if (getline(&buff, &len, fp_) == -1) { + int exit_code = 1; + if (fp_) { + exit_code = WEXITSTATUS(util::command::close(fp_, pid_)); + fp_ = nullptr; + } + if (exit_code != 0) { + output_ = {exit_code, ""}; + dp.emit(); + spdlog::error("{} stopped unexpectedly, is it endless?", name_); + } + if (config_["restart-interval"].isUInt()) { + pid_ = -1; + 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); + } + } else { + thread_.stop(); + return; + } + } else { + std::string output = buff; + + // Remove last newline + if (!output.empty() && output[output.length() - 1] == '\n') { + output.erase(output.length() - 1); + } + output_ = {0, output}; + dp.emit(); + } + }; +} + +void waybar::modules::CustomGraph::waitingWorker() { + thread_ = [this] { + bool can_update = true; + if (config_["exec-if"].isString()) { + output_ = util::command::execNoRead(config_["exec-if"].asString()); + if (output_.exit_code != 0) { + can_update = false; + dp.emit(); + } + } + if (can_update) { + if (config_["exec"].isString()) { + output_ = util::command::exec(config_["exec"].asString(), output_name_); + } + dp.emit(); + } + thread_.sleep(); + }; +} + +void waybar::modules::CustomGraph::refresh(int sig) { + if (sig == SIGRTMIN + config_["signal"].asInt()) { + thread_.wake_up(); + } +} + +void waybar::modules::CustomGraph::handleEvent() { + if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) { + thread_.wake_up(); + } +} + +bool waybar::modules::CustomGraph::handleScroll(GdkEventScroll* e) { + auto ret = AGraph::handleScroll(e); + handleEvent(); + return ret; +} + +bool waybar::modules::CustomGraph::handleToggle(GdkEventButton* const& e) { + auto ret = AGraph::handleToggle(e); + handleEvent(); + return ret; +} + +auto waybar::modules::CustomGraph::update() -> void { + // Hide label if output is empty + if ((config_["exec"].isString() || config_["exec-if"].isString()) && + (output_.out.empty() || output_.exit_code != 0)) { + event_box_.hide(); + } else { + if (config_["return-type"].asString() == "json") { + parseOutputJson(); + } else { + parseOutputRaw(); + } + + try { + addValue(percentage_); + + if (tooltipEnabled()) { + if (tooltip_format_enabled_) { + auto tooltip = config_["tooltip-format"].asString(); + tooltip = fmt::format( + fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), + fmt::arg("percentage", percentage_)); + graph_.set_tooltip_markup(tooltip); + } else { + if (graph_.get_tooltip_markup() != tooltip_) { + graph_.set_tooltip_markup(tooltip_); + } + } + } + auto style = graph_.get_style_context(); + auto classes = style->list_classes(); + for (auto const& c : classes) { + if (c == id_) continue; + style->remove_class(c); + } + for (auto const& c : class_) { + style->add_class(c); + } + style->add_class("flat"); + style->add_class(MODULE_CLASS); + event_box_.show(); + } catch (const fmt::format_error& e) { + if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0) + throw; + + throw fmt::format_error( + "mixing manual and automatic argument indexing is no longer supported; " + "try replacing \"{}\" with \"{text}\" in your format specifier"); + } + } + // Call parent update + AGraph::update(); +} + +void waybar::modules::CustomGraph::parseOutputRaw() { + std::istringstream output(output_.out); + std::string line; + int i = 0; + while (getline(output, line)) { + Glib::ustring validated_line = line; + if (!validated_line.validate()) { + validated_line = validated_line.make_valid(); + } + + if (i == 0) { + if (config_["escape"].isBool() && config_["escape"].asBool()) { + text_ = Glib::Markup::escape_text(validated_line); + tooltip_ = Glib::Markup::escape_text(validated_line); + } else { + text_ = validated_line; + tooltip_ = validated_line; + } + tooltip_ = validated_line; + class_.clear(); + } else if (i == 1) { + if (config_["escape"].isBool() && config_["escape"].asBool()) { + tooltip_ = Glib::Markup::escape_text(validated_line); + } else { + tooltip_ = validated_line; + } + } else if (i == 2) { + class_.push_back(validated_line); + } else { + break; + } + i++; + } +} + +void waybar::modules::CustomGraph::parseOutputJson() { + std::istringstream output(output_.out); + std::string line; + class_.clear(); + while (getline(output, line)) { + auto parsed = parser_.parse(line); + if (config_["escape"].isBool() && config_["escape"].asBool()) { + text_ = Glib::Markup::escape_text(parsed["text"].asString()); + } else { + text_ = parsed["text"].asString(); + } + if (config_["escape"].isBool() && config_["escape"].asBool()) { + alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); + } else { + alt_ = parsed["alt"].asString(); + } + if (config_["escape"].isBool() && config_["escape"].asBool()) { + tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString()); + } else { + tooltip_ = parsed["tooltip"].asString(); + } + if (parsed["class"].isString()) { + class_.push_back(parsed["class"].asString()); + } else if (parsed["class"].isArray()) { + for (auto const& c : parsed["class"]) { + class_.push_back(c.asString()); + } + } + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { + percentage_ = (int)lround(parsed["percentage"].asFloat()); + } else { + percentage_ = 0; + } + break; + } +} From dfc26364e15dfe202990e598426e9f5535f52fde Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 24 Oct 2025 19:47:43 +0200 Subject: [PATCH 3/9] feat: use the foreground color for cleaner look --- include/AGraph.hpp | 1 - man/waybar-cpu-graph.5.scd | 7 +------ src/AGraph.cpp | 26 +++++++++++++++----------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/include/AGraph.hpp b/include/AGraph.hpp index a49b215d..69045473 100644 --- a/include/AGraph.hpp +++ b/include/AGraph.hpp @@ -24,7 +24,6 @@ class AGraph : public AModule { Gtk::DrawingArea graph_; std::deque values_; uint16_t datapoints_ = 20; - uint16_t y_offset_ = 0; GraphType graph_type_ = GraphType::LINE; void addValue(const int n); diff --git a/man/waybar-cpu-graph.5.scd b/man/waybar-cpu-graph.5.scd index bfe9de42..c93aaf06 100644 --- a/man/waybar-cpu-graph.5.scd +++ b/man/waybar-cpu-graph.5.scd @@ -19,10 +19,6 @@ The *cpu graph* module displays a line graph with the CPU utilization. typeof: integer ++ The length in pixels the module should display. -*y_offset*: ++ - typeof: integer ++ - The margin in pixels at the bottom of the module. - *datapoints*: ++ typeof: integer ++ How many data points to show. @@ -72,8 +68,7 @@ Basic configuration: ``` "cpu_graph": { "interval": 2, - "width": 10, - "y_offset": 4 + "width": 10 } ``` diff --git a/src/AGraph.cpp b/src/AGraph.cpp index b8076ecc..1899b1b2 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -39,10 +39,6 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st datapoints_ = config_["datapoints_"].asUInt(); } - if (config_["y_offset"].isUInt()) { - y_offset_ = config_["y_offset"].asUInt(); - } - if (config_["graph_type"].isString()) { std::string type = config_["graph_type"].asString(); if (type == "line") { @@ -122,7 +118,7 @@ void AGraph::addValue(const int n) { bool AGraph::onDraw(const Cairo::RefPtr& cr) { const int width = graph_.get_allocated_width(); - const int height = graph_.get_allocated_height() - 1 - y_offset_; + const int height = graph_.get_allocated_height() - 1; if (values_.empty() || width <= 0 || height <= 0) { return false; @@ -220,20 +216,23 @@ void AGraph::drawBars(const Cairo::RefPtr& cr, current_value = std::min(100, std::max(0, current_value)); double green_height = height * (std::min(current_value, 40) / 100.0); - cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.5); cr->rectangle(0, height - green_height, width, green_height); cr->fill(); if (current_value > 40) { double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; - cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.7); cr->rectangle(0, height - green_height - yellow_height, width, yellow_height); cr->fill(); } if (current_value > 75) { double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; - cr->set_source_rgba(1.0, 0.5, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.85); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; cr->rectangle(0, height - green_height - yellow_height - orange_height, width, orange_height); @@ -242,7 +241,8 @@ void AGraph::drawBars(const Cairo::RefPtr& cr, if (current_value > 85) { double red_height = height * (current_value - 85) / 100.0; - cr->set_source_rgba(1.0, 0.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 1.0); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width, @@ -268,14 +268,16 @@ void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, do double angle2 = angle1 + 0.3 * angle1; // Green section (0-33%) - cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.5); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); // Yellow section (33-66%) angle1 = angle2; angle2 = angle1 + 0.3 * angle1; - cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.75); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); @@ -283,6 +285,8 @@ void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, do angle1 = angle2; angle2 = 0.0; cr->set_source_rgba(1.0, 0.0, 0.0, 0.8); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 1.0); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); From 01c6ebdf9e56b943821dbbccbe8599976589dd04 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 25 Oct 2025 00:13:15 +0200 Subject: [PATCH 4/9] feat: update man pages --- man/waybar-cpu-graph.5.scd | 2 +- man/waybar-custom-graph.5.scd | 189 ++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 man/waybar-custom-graph.5.scd diff --git a/man/waybar-cpu-graph.5.scd b/man/waybar-cpu-graph.5.scd index c93aaf06..1877aeeb 100644 --- a/man/waybar-cpu-graph.5.scd +++ b/man/waybar-cpu-graph.5.scd @@ -1,4 +1,4 @@ -waybar-cpu(5) +waybar-cpu-graph(5) # NAME diff --git a/man/waybar-custom-graph.5.scd b/man/waybar-custom-graph.5.scd new file mode 100644 index 00000000..4f020a6c --- /dev/null +++ b/man/waybar-custom-graph.5.scd @@ -0,0 +1,189 @@ +waybar-custom-graph(5) +# NAME + +waybar - custom graph module + +# DESCRIPTION + +The *custom-graph* module displays a graph with the percentage output of a script. + +# CONFIGURATION + +Addressed by *custom-graph/* + +*exec*: ++ + typeof: string ++ + The path to the script, which should be executed. + +*exec-if*: ++ + typeof: string ++ + The path to a script, which determines if the script in *exec* should be executed. ++ + *exec* will be executed if the exit code of *exec-if* equals 0. + +*exec-on-event*: ++ + typeof: bool ++ + default: true ++ + If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after executing the event command. + +*return-type*: ++ + typeof: string ++ + See *return-type* + +*interval*: ++ + typeof: integer or float ++ + The interval (in seconds) in which the information gets polled. ++ + Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ + Use *once* if you want to execute the module only on startup. ++ + You can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++ + If a *signal* is defined then the script will run once on startup and will only update with a signal. + +*restart-interval*: ++ + typeof: integer or float ++ + The restart interval (in seconds). ++ + Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ + Can't be used with the *interval* option, so only with continuous scripts. ++ + Once the script exits, it'll be re-executed after the *restart-interval*. + +*signal*: ++ + typeof: integer ++ + The signal number used to update the module. ++ + The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++ + If no interval is defined then a signal will be the only way to update the module. + +*format*: ++ + typeof: string ++ + default: {text} ++ + The format, how information should be displayed. On {text} data gets inserted. + +*format-icons*: ++ + typeof: array ++ + Based on the set percentage, the corresponding icon gets selected. The order is *low* to *high*. + +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label (in 90 degree increments). + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right-click on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + The tooltip format. If specified, overrides any tooltip output from the script in *exec*. ++ + Uses the same format replacements as *format*. + +*escape*: ++ + typeof: bool ++ + default: false ++ + Option to enable escaping of script output. + +*menu*: ++ + typeof: string ++ + Action that popups the menu. + +*menu-file*: ++ + typeof: string ++ + Location of the menu descriptor file. There need to be an element of type + GtkMenu with id *menu* + +*menu-actions*: ++ + typeof: array ++ + The actions corresponding to the buttons of the menu. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +# RETURN-TYPE + +When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format. +This should look like this: + +``` +{"text": "$text", "tooltip": "$tooltip", "class": "$class", "percentage": $percentage } +``` + +The *class* parameter also accepts an array of strings. + +If nothing or an invalid option is specified, Waybar expects i3blocks style output. Values are *newline* separated. +This should look like this: + +``` +$text\\n$tooltip\\n$class* +``` + +*class* is a CSS class, to apply different styles in *style.css* + +# FORMAT REPLACEMENTS + +*{text}*: Output of the script. + +*{percentage}* Percentage which can be set via a json return type. + +*{icon}*: An icon from 'format-icons' according to percentage. + +# EXAMPLES + +## Memory: + +``` +"custom-graph/memory": { + "interval": 60, + "graph_type": "gauge", + "width": 52, + "exec": "/path/mem.sh", + "signal": 8, + "return-type": "json" +}, +``` + +mem.sh: + +``` +#!/bin/bash + +mem_info=$(cat /proc/meminfo) +mem_total=$(echo "$mem_info" | grep '^MemTotal:' | awk '{print $2}') +mem_available=$(echo "$mem_info" | grep '^MemAvailable:' | awk '{print $2}') + +mem_used=$((mem_total - mem_available)) +mem_percent=$((mem_used * 100 / mem_total)) + +echo "{\"text\": \"${mem_percent}%\", \"percentage\": ${mem_percent},\"tooltip\": \"Memory: ${mem_used}KB used / ${mem_total}KB total\"}'" +``` + +# STYLE + +- *#custom-graph-* +- *#custom-graph-.* +- ** can be set by the script. For more information see *return-type* From 283515901e1b48a11d1e6af58a1776ea6e95fb64 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 8 Nov 2025 01:46:31 +0100 Subject: [PATCH 5/9] fix: id read and name set --- src/factory.cpp | 4 ++-- src/modules/custom_graph.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/factory.cpp b/src/factory.cpp index dfb5caf4..b0ac2e8c 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -363,8 +363,8 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name); } - if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 7) { - return new waybar::modules::CustomGraph(ref.substr(7), id, config_[name], bar_.output->name); + if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 13) { + return new waybar::modules::CustomGraph(ref.substr(13), id, config_[name], bar_.output->name); } if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) { return new waybar::modules::CFFI(ref.substr(5), id, config_[name]); diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index 288d97f2..f8660c5f 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -6,7 +6,7 @@ waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id, const Json::Value& config, const std::string& output_name) - : AGraph(config, "custom-graph" + name, id), + : AGraph(config, "custom-graph-" + name, id), name_(name), output_name_(output_name), id_(id), From dbf1cfb0f11b687cbaafa65f73acaf5ce2eeb282 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Mon, 23 Feb 2026 16:20:58 +0100 Subject: [PATCH 6/9] fix: typo reading datapoints --- src/AGraph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AGraph.cpp b/src/AGraph.cpp index 1899b1b2..139bddb2 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -36,7 +36,7 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st event_box_.add(graph_); if (config_["datapoints"].isUInt()) { - datapoints_ = config_["datapoints_"].asUInt(); + datapoints_ = config_["datapoints"].asUInt(); } if (config_["graph_type"].isString()) { @@ -110,7 +110,7 @@ void AGraph::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { } void AGraph::addValue(const int n) { - if (values_.size() >= datapoints_) { + if (datapoints_ > 0 && values_.size() >= datapoints_) { values_.pop_front(); } values_.push_back(n); From 132ca3ac456c374afba1a2d913d6bcf5aae2d418 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 6 Mar 2026 23:48:12 +0100 Subject: [PATCH 7/9] fix: datapoints should be positive --- src/AGraph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AGraph.cpp b/src/AGraph.cpp index 139bddb2..bd64410f 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -35,7 +35,7 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st event_box_.add(graph_); - if (config_["datapoints"].isUInt()) { + if (config_["datapoints"].isUInt() && config_["datapoints"].asUInt() > 0) { datapoints_ = config_["datapoints"].asUInt(); } From 7d965a874f736854516f18320787ede9c6c47ef4 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 6 Mar 2026 23:57:22 +0100 Subject: [PATCH 8/9] pr fixes --- src/AGraph.cpp | 31 ++++++++++--------------------- src/modules/custom_graph.cpp | 1 - 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/AGraph.cpp b/src/AGraph.cpp index bd64410f..f79e7218 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -209,40 +209,33 @@ void AGraph::drawPath(const Cairo::RefPtr& cr, } } -void AGraph::drawBars(const Cairo::RefPtr& cr, - double width, double height, int current_value, - const Gdk::RGBA& fg_color) { - +void AGraph::drawBars(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color) { current_value = std::min(100, std::max(0, current_value)); double green_height = height * (std::min(current_value, 40) / 100.0); - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.5); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.5); cr->rectangle(0, height - green_height, width, green_height); cr->fill(); if (current_value > 40) { double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.7); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.7); cr->rectangle(0, height - green_height - yellow_height, width, yellow_height); cr->fill(); } if (current_value > 75) { double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.85); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.85); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; - cr->rectangle(0, height - green_height - yellow_height - orange_height, width, - orange_height); + cr->rectangle(0, height - green_height - yellow_height - orange_height, width, orange_height); cr->fill(); } if (current_value > 85) { double red_height = height * (current_value - 85) / 100.0; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 1.0); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width, @@ -268,25 +261,21 @@ void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, do double angle2 = angle1 + 0.3 * angle1; // Green section (0-33%) - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.5); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.5); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); // Yellow section (33-66%) angle1 = angle2; angle2 = angle1 + 0.3 * angle1; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.75); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.75); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); // Red section (66-100%) angle1 = angle2; angle2 = 0.0; - cr->set_source_rgba(1.0, 0.0, 0.0, 0.8); - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 1.0); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index f8660c5f..c23ed0d6 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -228,7 +228,6 @@ void waybar::modules::CustomGraph::parseOutputRaw() { text_ = validated_line; tooltip_ = validated_line; } - tooltip_ = validated_line; class_.clear(); } else if (i == 1) { if (config_["escape"].isBool() && config_["escape"].asBool()) { From c4c9345fef30a5a29f5ab44ad16b1d3c6413ad86 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:52:02 +0200 Subject: [PATCH 9/9] Fix clang-format --- include/AGraph.hpp | 31 +++++++++++++++---------------- include/modules/cpu_graph.hpp | 6 +++--- src/factory.cpp | 6 +++--- src/modules/custom_graph.cpp | 7 +++---- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/include/AGraph.hpp b/include/AGraph.hpp index 69045473..e2ada835 100644 --- a/include/AGraph.hpp +++ b/include/AGraph.hpp @@ -15,7 +15,7 @@ enum class GraphType { LINE, BAR, GAUGE }; class AGraph : public AModule { public: - AGraph(const Json::Value &, const std::string &, const std::string &, uint16_t interval = 0, + AGraph(const Json::Value&, const std::string&, const std::string&, uint16_t interval = 0, bool enable_click = false, bool enable_scroll = false); virtual ~AGraph() = default; auto update() -> void override; @@ -30,29 +30,28 @@ class AGraph : public AModule { const std::chrono::seconds interval_; - bool onDraw(const Cairo::RefPtr &cr); + bool onDraw(const Cairo::RefPtr& cr); - std::map submenus_; + std::map submenus_; std::map menuActionsMap_; - static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data); + static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data); private: - void drawFilledArea(const Cairo::RefPtr &cr, - const std::vector> &points, double height, - const Gdk::RGBA &bg_color); + void drawFilledArea(const Cairo::RefPtr& cr, + const std::vector>& points, double height, + const Gdk::RGBA& bg_color); - void drawLine(const Cairo::RefPtr &cr, - const std::vector> &points, const Gdk::RGBA &fg_color); + void drawLine(const Cairo::RefPtr& cr, + const std::vector>& points, const Gdk::RGBA& fg_color); - void drawPath(const Cairo::RefPtr &cr, - const std::vector> &points); + void drawPath(const Cairo::RefPtr& cr, + const std::vector>& points); - void drawBars(const Cairo::RefPtr &cr, - double width, double height, int current_value, - const Gdk::RGBA &fg_color); + void drawBars(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color); - void drawGauge(const Cairo::RefPtr &cr, double width, double height, - int current_value, const Gdk::RGBA &fg_color); + void drawGauge(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color); }; } // namespace waybar diff --git a/include/modules/cpu_graph.hpp b/include/modules/cpu_graph.hpp index df5f6ea3..cf74a3fe 100644 --- a/include/modules/cpu_graph.hpp +++ b/include/modules/cpu_graph.hpp @@ -21,9 +21,9 @@ class CpuGraph : public AGraph { auto update() -> void override; private: - static constexpr const char *MODERATE_CLASS = "cpu-moderate"; - static constexpr const char *HIGH_CLASS = "cpu-high"; - static constexpr const char *INTENSIVE_CLASS = "cpu-intensive"; + static constexpr const char* MODERATE_CLASS = "cpu-moderate"; + static constexpr const char* HIGH_CLASS = "cpu-high"; + static constexpr const char* INTENSIVE_CLASS = "cpu-intensive"; std::vector> prev_times_; util::SleeperThread thread_; diff --git a/src/factory.cpp b/src/factory.cpp index cd29554c..6969da6b 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -43,11 +43,11 @@ #include "modules/niri/workspaces.hpp" #endif #ifdef HAVE_MANGO -#include "modules/mango/language.hpp" #include "modules/mango/keymode.hpp" +#include "modules/mango/language.hpp" +#include "modules/mango/layout.hpp" #include "modules/mango/window.hpp" #include "modules/mango/workspaces.hpp" -#include "modules/mango/layout.hpp" #endif #ifdef HAVE_WAYFIRE #include "modules/wayfire/window.hpp" @@ -58,8 +58,8 @@ #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #include "modules/cpu.hpp" -#include "modules/cpu_graph.hpp" #include "modules/cpu_frequency.hpp" +#include "modules/cpu_graph.hpp" #include "modules/cpu_usage.hpp" #include "modules/load.hpp" #endif diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index c23ed0d6..2a12f0c1 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -5,7 +5,7 @@ #include "util/scope_guard.hpp" waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id, - const Json::Value& config, const std::string& output_name) + const Json::Value& config, const std::string& output_name) : AGraph(config, "custom-graph-" + name, id), name_(name), output_name_(output_name), @@ -175,9 +175,8 @@ auto waybar::modules::CustomGraph::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format( - fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), - fmt::arg("percentage", percentage_)); + tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("alt", alt_), fmt::arg("percentage", percentage_)); graph_.set_tooltip_markup(tooltip); } else { if (graph_.get_tooltip_markup() != tooltip_) {