feat: basic line graph component and cpu graph module
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <glibmm/markup.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#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<int> 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<Cairo::Context> &cr);
|
||||
|
||||
std::map<std::string, GtkMenuItem *> submenus_;
|
||||
std::map<std::string, std::string> menuActionsMap_;
|
||||
static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data);
|
||||
|
||||
private:
|
||||
void drawFilledArea(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
const std::vector<std::pair<double, double>> &points,
|
||||
double height, const Gdk::RGBA &bg_color);
|
||||
|
||||
void drawLine(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
const std::vector<std::pair<double, double>> &points, const Gdk::RGBA &fg_color);
|
||||
|
||||
void drawPath(const Cairo::RefPtr<Cairo::Context> &cr,
|
||||
const std::vector<std::pair<double, double>> &points);
|
||||
};
|
||||
|
||||
} // namespace waybar
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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<std::tuple<size_t, size_t>> prev_times_;
|
||||
util::SleeperThread thread_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
@@ -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*
|
||||
@@ -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',
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
#include "AGraph.hpp"
|
||||
|
||||
#include <cairomm/context.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <util/command.hpp>
|
||||
|
||||
#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<std::string, GtkMenuItem*>();
|
||||
menuActionsMap_ = std::map<std::string, std::string>();
|
||||
|
||||
// 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<Cairo::Context>& 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<double>(width) / datapoints_;
|
||||
const int values_count = values_.size();
|
||||
const int empty_space = datapoints_ - values_count;
|
||||
|
||||
std::vector<std::pair<double, double>> 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<double>(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<Cairo::Context>& cr,
|
||||
const std::vector<std::pair<double, double>>& 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<Cairo::Context>& cr,
|
||||
const std::vector<std::pair<double, double>>& 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<Cairo::Context>& cr,
|
||||
const std::vector<std::pair<double, double>>& 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
|
||||
@@ -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]);
|
||||
|
||||
@@ -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 <fmt/args.h>
|
||||
#else
|
||||
#include <fmt/core.h>
|
||||
#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();
|
||||
}
|
||||
Reference in New Issue
Block a user