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_files = files(
|
||||||
'src/factory.cpp',
|
'src/factory.cpp',
|
||||||
|
'src/AGraph.cpp',
|
||||||
'src/AModule.cpp',
|
'src/AModule.cpp',
|
||||||
'src/ALabel.cpp',
|
'src/ALabel.cpp',
|
||||||
'src/AIconLabel.cpp',
|
'src/AIconLabel.cpp',
|
||||||
'src/AAppIconLabel.cpp',
|
'src/AAppIconLabel.cpp',
|
||||||
'src/modules/custom.cpp',
|
'src/modules/custom.cpp',
|
||||||
|
'src/modules/custom_graph.cpp',
|
||||||
'src/modules/disk.cpp',
|
'src/modules/disk.cpp',
|
||||||
'src/modules/idle_inhibitor.cpp',
|
'src/modules/idle_inhibitor.cpp',
|
||||||
'src/modules/image.cpp',
|
'src/modules/image.cpp',
|
||||||
@@ -212,6 +214,7 @@ if is_linux
|
|||||||
'src/modules/bluetooth.cpp',
|
'src/modules/bluetooth.cpp',
|
||||||
'src/modules/cffi.cpp',
|
'src/modules/cffi.cpp',
|
||||||
'src/modules/cpu.cpp',
|
'src/modules/cpu.cpp',
|
||||||
|
'src/modules/cpu_graph.cpp',
|
||||||
'src/modules/cpu_frequency/common.cpp',
|
'src/modules/cpu_frequency/common.cpp',
|
||||||
'src/modules/cpu_frequency/linux.cpp',
|
'src/modules/cpu_frequency/linux.cpp',
|
||||||
'src/modules/cpu_usage/common.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_files += files(
|
||||||
'src/modules/cffi.cpp',
|
'src/modules/cffi.cpp',
|
||||||
'src/modules/cpu.cpp',
|
'src/modules/cpu.cpp',
|
||||||
|
'src/modules/cpu_graph.cpp',
|
||||||
'src/modules/cpu_frequency/bsd.cpp',
|
'src/modules/cpu_frequency/bsd.cpp',
|
||||||
'src/modules/cpu_frequency/common.cpp',
|
'src/modules/cpu_frequency/common.cpp',
|
||||||
'src/modules/cpu_usage/bsd.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"
|
#include "modules/niri/workspaces.hpp"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_MANGO
|
#ifdef HAVE_MANGO
|
||||||
#include "modules/mango/language.hpp"
|
|
||||||
#include "modules/mango/keymode.hpp"
|
#include "modules/mango/keymode.hpp"
|
||||||
|
#include "modules/mango/language.hpp"
|
||||||
|
#include "modules/mango/layout.hpp"
|
||||||
#include "modules/mango/window.hpp"
|
#include "modules/mango/window.hpp"
|
||||||
#include "modules/mango/workspaces.hpp"
|
#include "modules/mango/workspaces.hpp"
|
||||||
#include "modules/mango/layout.hpp"
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_WAYFIRE
|
#ifdef HAVE_WAYFIRE
|
||||||
#include "modules/wayfire/window.hpp"
|
#include "modules/wayfire/window.hpp"
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
|
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
|
||||||
#include "modules/cpu.hpp"
|
#include "modules/cpu.hpp"
|
||||||
#include "modules/cpu_frequency.hpp"
|
#include "modules/cpu_frequency.hpp"
|
||||||
|
#include "modules/cpu_graph.hpp"
|
||||||
#include "modules/cpu_usage.hpp"
|
#include "modules/cpu_usage.hpp"
|
||||||
#include "modules/load.hpp"
|
#include "modules/load.hpp"
|
||||||
#endif
|
#endif
|
||||||
@@ -124,6 +125,7 @@
|
|||||||
#include "modules/cava/cava_frontend.hpp"
|
#include "modules/cava/cava_frontend.hpp"
|
||||||
#include "modules/cffi.hpp"
|
#include "modules/cffi.hpp"
|
||||||
#include "modules/custom.hpp"
|
#include "modules/custom.hpp"
|
||||||
|
#include "modules/custom_graph.hpp"
|
||||||
#include "modules/image.hpp"
|
#include "modules/image.hpp"
|
||||||
#include "modules/temperature.hpp"
|
#include "modules/temperature.hpp"
|
||||||
#include "modules/user.hpp"
|
#include "modules/user.hpp"
|
||||||
@@ -275,6 +277,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name,
|
|||||||
if (ref == "cpu") {
|
if (ref == "cpu") {
|
||||||
return new waybar::modules::Cpu(id, config_[name]);
|
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 defined(HAVE_CPU_LINUX)
|
||||||
if (ref == "cpu_frequency") {
|
if (ref == "cpu_frequency") {
|
||||||
return new waybar::modules::CpuFrequency(id, config_[name]);
|
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) {
|
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
|
||||||
return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name);
|
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) {
|
if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) {
|
||||||
return new waybar::modules::CFFI(ref.substr(5), id, config_[name]);
|
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