Merge pull request #4588 from Gazer/feat/basic-cpu-graph
feat: add customgraph module
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <glibmm/markup.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <json/json.h>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
#include "AModule.hpp"
|
||||
|
||||
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);
|
||||
virtual ~AGraph() = default;
|
||||
auto update() -> void override;
|
||||
|
||||
protected:
|
||||
Gtk::DrawingArea graph_;
|
||||
std::deque<int> values_;
|
||||
uint16_t datapoints_ = 20;
|
||||
GraphType graph_type_ = GraphType::LINE;
|
||||
|
||||
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);
|
||||
|
||||
void drawBars(const Cairo::RefPtr<Cairo::Context>& cr, double width, double height,
|
||||
int current_value, const Gdk::RGBA& fg_color);
|
||||
|
||||
void drawGauge(const Cairo::RefPtr<Cairo::Context>& cr, double width, double height,
|
||||
int current_value, const Gdk::RGBA& fg_color);
|
||||
};
|
||||
|
||||
} // 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,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
|
||||
#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<std::string> class_;
|
||||
int percentage_;
|
||||
FILE* fp_;
|
||||
int pid_;
|
||||
util::command::res output_;
|
||||
util::JsonParser parser_;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
||||
@@ -0,0 +1,80 @@
|
||||
waybar-cpu-graph(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.
|
||||
|
||||
*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
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#cpu_graph*
|
||||
- *.cpu-intensive*
|
||||
- *.cpu-high*
|
||||
- *.cpu-moderate*
|
||||
@@ -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/<name>*
|
||||
|
||||
*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-<name>*
|
||||
- *#custom-graph-<name>.<class>*
|
||||
- *<class>* can be set by the script. For more information see *return-type*
|
||||
@@ -159,11 +159,13 @@ endif
|
||||
|
||||
src_files = files(
|
||||
'src/factory.cpp',
|
||||
'src/AGraph.cpp',
|
||||
'src/AModule.cpp',
|
||||
'src/ALabel.cpp',
|
||||
'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',
|
||||
@@ -212,6 +214,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',
|
||||
@@ -236,6 +239,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',
|
||||
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
#include "AGraph.hpp"
|
||||
|
||||
#include <cairomm/context.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cmath>
|
||||
#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() && config_["datapoints"].asUInt() > 0) {
|
||||
datapoints_ = config_["datapoints"].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
|
||||
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 (datapoints_ > 0 && 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;
|
||||
|
||||
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()) {
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AGraph::drawBars(const Cairo::RefPtr<Cairo::Context>& 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->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->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);
|
||||
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(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,
|
||||
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<Cairo::Context>& 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(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->arc(center_x, center_y, radius, angle1, angle2);
|
||||
cr->stroke();
|
||||
|
||||
// Red section (66-100%)
|
||||
angle1 = angle2;
|
||||
angle2 = 0.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();
|
||||
|
||||
// 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
|
||||
+10
-2
@@ -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"
|
||||
@@ -59,6 +59,7 @@
|
||||
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
|
||||
#include "modules/cpu.hpp"
|
||||
#include "modules/cpu_frequency.hpp"
|
||||
#include "modules/cpu_graph.hpp"
|
||||
#include "modules/cpu_usage.hpp"
|
||||
#include "modules/load.hpp"
|
||||
#endif
|
||||
@@ -124,6 +125,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"
|
||||
@@ -275,6 +277,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]);
|
||||
@@ -382,6 +387,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() > 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]);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
#include "modules/custom_graph.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user