mirror of
https://github.com/aristocratos/btop.git
synced 2024-09-28 06:11:28 +02:00
File reorganization and more efficient build
This commit is contained in:
parent
ba481d042c
commit
d459d088a0
68
Makefile
68
Makefile
@ -1,14 +1,46 @@
|
||||
PREFIX ?= /usr/local
|
||||
DOCDIR ?= $(PREFIX)/share/btop/doc
|
||||
CPP = g++
|
||||
override CPPFLAGS += -std=c++20 -pthread
|
||||
OPTFLAG = -O3
|
||||
INFOFLAGS += -Wall -Wextra -Wno-stringop-overread -pedantic
|
||||
INCLUDES = -Isrc -Iinclude
|
||||
|
||||
btop: btop.cpp
|
||||
@mkdir -p bin
|
||||
$(CPP) $(CPPFLAGS) $(INCLUDES) $(OPTFLAG) $(INFOFLAGS) -o bin/btop btop.cpp
|
||||
#Compiler and Linker
|
||||
CXX := g++
|
||||
|
||||
#The Target Binary Program
|
||||
TARGET := btop
|
||||
|
||||
#The Directories, Source, Includes, Objects and Binary
|
||||
SRCDIR := src
|
||||
INCDIR := include
|
||||
BUILDDIR := obj
|
||||
TARGETDIR := bin
|
||||
SRCEXT := cpp
|
||||
DEPEXT := d
|
||||
OBJEXT := o
|
||||
|
||||
#Flags, Libraries and Includes
|
||||
CXXFLAGS := -std=c++20 -pthread -O3 -Wall -Wextra -Wno-stringop-overread -pedantic
|
||||
INC := -I$(INCDIR) -I$(SRCDIR)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
#DO NOT EDIT BELOW THIS LINE
|
||||
#---------------------------------------------------------------------------------
|
||||
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
|
||||
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))
|
||||
|
||||
#Default Make
|
||||
all: directories $(TARGET)
|
||||
|
||||
#Make the Directories
|
||||
directories:
|
||||
@mkdir -p $(TARGETDIR)
|
||||
@mkdir -p $(BUILDDIR)
|
||||
|
||||
#Clean only Objecst
|
||||
clean:
|
||||
@rm -rf $(BUILDDIR)
|
||||
|
||||
#Full Clean, Objects and Binaries
|
||||
dist-clean: clean
|
||||
@rm -rf $(TARGETDIR)
|
||||
|
||||
install:
|
||||
@mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
@ -23,5 +55,21 @@ uninstall:
|
||||
@rm -rf $(DESTDIR)$(DOCDIR)
|
||||
@rm -rf $(DESTDIR)$(PREFIX)/share/btop
|
||||
|
||||
clean:
|
||||
rm -rf bin
|
||||
#Pull in dependency info for *existing* .o files
|
||||
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))
|
||||
|
||||
#Link
|
||||
$(TARGET): $(OBJECTS)
|
||||
$(CXX) -o $(TARGETDIR)/$(TARGET) $^
|
||||
|
||||
#Compile
|
||||
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
|
||||
$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $<
|
||||
@$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT)
|
||||
@cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp
|
||||
@sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT)
|
||||
@sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT)
|
||||
@rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp
|
||||
|
||||
#Non-File Targets
|
||||
.PHONY: all clean dist-clean uninstall
|
||||
|
@ -30,19 +30,7 @@ tab-size = 4
|
||||
#include <filesystem>
|
||||
#include <unistd.h>
|
||||
#include <robin_hood.h>
|
||||
|
||||
namespace Global {
|
||||
const std::vector<std::array<std::string, 2>> Banner_src = {
|
||||
{"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
|
||||
{"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
|
||||
{"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
|
||||
{"#9A1919", "██╔══██╗ ██║ ██║ ██║██╔═══╝ ╚═██╔═╝╚═██╔═╝"},
|
||||
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
|
||||
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
|
||||
};
|
||||
const std::string Version = "0.0.20";
|
||||
int coreCount;
|
||||
}
|
||||
#include <cmath>
|
||||
|
||||
#include <btop_tools.h>
|
||||
#include <btop_config.h>
|
||||
@ -71,9 +59,23 @@ namespace Global {
|
||||
#error Platform not supported!
|
||||
#endif
|
||||
|
||||
namespace Global {
|
||||
const std::vector<std::array<std::string, 2>> Banner_src = {
|
||||
{"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
|
||||
{"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
|
||||
{"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
|
||||
{"#9A1919", "██╔══██╗ ██║ ██║ ██║██╔═══╝ ╚═██╔═╝╚═██╔═╝"},
|
||||
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
|
||||
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
|
||||
};
|
||||
std::string Version = "0.0.21";
|
||||
int coreCount;
|
||||
}
|
||||
|
||||
using std::string, std::vector, std::array, robin_hood::unordered_flat_map, std::atomic, std::endl, std::cout, std::views::iota, std::list, std::accumulate;
|
||||
using std::flush, std::endl, std::future, std::string_literals::operator""s, std::future_status;
|
||||
using std::flush, std::endl, std::future, std::string_literals::operator""s, std::future_status, std::to_string, std::round;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
||||
|
||||
@ -89,6 +91,8 @@ namespace Global {
|
||||
uint64_t start_time;
|
||||
|
||||
bool quitting = false;
|
||||
|
||||
bool arg_tty = false;
|
||||
}
|
||||
|
||||
|
||||
@ -102,10 +106,26 @@ void argumentParser(int argc, char **argv){
|
||||
exit(0);
|
||||
}
|
||||
else if (argument == "-h" || argument == "--help") {
|
||||
cout << "help here" << endl;
|
||||
cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n"
|
||||
<< "optional arguments:\n"
|
||||
<< " -h, --help show this help message and exit\n"
|
||||
<< " -v, --version show version info and exit\n"
|
||||
<< " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
|
||||
<< " +t, --tty_off force (OFF) tty mode\n"
|
||||
<< " --debug start with loglevel set to DEBUG, overriding value set in config\n"
|
||||
<< endl;
|
||||
exit(0);
|
||||
}
|
||||
else if (argument == "--debug") Global::debug = true;
|
||||
else if (argument == "--debug")
|
||||
Global::debug = true;
|
||||
else if (argument == "-t" || argument == "--tty_on") {
|
||||
Config::set("tty_mode", true);
|
||||
Global::arg_tty = true;
|
||||
}
|
||||
else if (argument == "+t" || argument == "--tty_off") {
|
||||
Config::set("tty_mode", false);
|
||||
Global::arg_tty = true;
|
||||
}
|
||||
else {
|
||||
cout << " Unknown argument: " << argument << "\n" <<
|
||||
" Use -h or --help for help." << endl;
|
||||
@ -163,18 +183,21 @@ void banner_gen() {
|
||||
int bg_i;
|
||||
Global::banner.clear();
|
||||
Global::banner_width = 0;
|
||||
auto tty_mode = (Config::getB("tty_mode"));
|
||||
for (auto line: Global::Banner_src) {
|
||||
if (auto w = ulen(line[1]); w > Global::banner_width) Global::banner_width = w;
|
||||
fg = Theme::hex_to_color(line[0], !truecolor);
|
||||
bg_i = 120-z*12;
|
||||
bg = Theme::dec_to_color(bg_i, bg_i, bg_i, !truecolor);
|
||||
for (size_t i = 0; i < line[1].size(); i += 3) {
|
||||
if (line[1][i] == ' '){
|
||||
if (line[1][i] == ' ') {
|
||||
letter = ' ';
|
||||
i -= 2;
|
||||
} else{
|
||||
letter = line[1].substr(i, 3);
|
||||
}
|
||||
else
|
||||
letter = line[1].substr(i, 3);
|
||||
|
||||
if (tty_mode && letter != "█" && letter != " ") letter = "░";
|
||||
b_color = (letter == "█") ? fg : bg;
|
||||
if (b_color != oc) Global::banner += b_color;
|
||||
Global::banner += letter;
|
||||
@ -260,10 +283,10 @@ int main(int argc, char **argv){
|
||||
{ vector<string> load_errors;
|
||||
Config::load(Config::conf_file, load_errors);
|
||||
|
||||
if (Global::debug) Logger::loglevel = 4;
|
||||
else Logger::loglevel = v_index(Logger::log_levels, Config::getS("log_level"));
|
||||
if (Global::debug) Logger::set("DEBUG");
|
||||
else Logger::set(Config::getS("log_level"));
|
||||
|
||||
Logger::info("Log level set to " + Config::getS("log_level") + ".");
|
||||
Logger::debug("Logger set to DEBUG");
|
||||
|
||||
for (auto& err_str : load_errors) Logger::warning(err_str);
|
||||
}
|
||||
@ -283,6 +306,17 @@ int main(int argc, char **argv){
|
||||
clean_quit(1);
|
||||
}
|
||||
|
||||
Logger::debug("Running on " + Term::current_tty);
|
||||
if (!Global::arg_tty && Config::getB("force_tty")) {
|
||||
Config::set("tty_mode", true);
|
||||
Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols");
|
||||
}
|
||||
else if (!Global::arg_tty && Term::current_tty.starts_with("/dev/tty")) {
|
||||
Config::set("tty_mode", true);
|
||||
Logger::info("Real tty detected, setting 16 color mode and using tty friendly graph symbols");
|
||||
}
|
||||
|
||||
|
||||
#if defined(LINUX)
|
||||
//? Linux init
|
||||
Proc::init();
|
||||
@ -329,7 +363,7 @@ int main(int argc, char **argv){
|
||||
|
||||
cout << "Colors:" << endl;
|
||||
uint i = 0;
|
||||
for(auto& item : Theme::colors) {
|
||||
for(auto& item : Theme::test_colors()) {
|
||||
cout << rjust(item.first, 15) << ":" << item.second << "■"s * 10 << Fx::reset << " ";
|
||||
// << Theme::dec(item.first)[0] << ":" << Theme::dec(item.first)[1] << ":" << Theme::dec(item.first)[2] << ;
|
||||
if (++i == 4) {
|
||||
@ -341,7 +375,7 @@ int main(int argc, char **argv){
|
||||
|
||||
|
||||
cout << "Gradients:";
|
||||
for (auto& [name, cvec] : Theme::gradients) {
|
||||
for (auto& [name, cvec] : Theme::test_gradients()) {
|
||||
cout << endl << rjust(name + ":", 10);
|
||||
for (auto& color : cvec) {
|
||||
cout << color << "■";
|
||||
@ -388,13 +422,21 @@ int main(int argc, char **argv){
|
||||
// for (long long i = 100; i >= 0; i--) mydata.push_back(i);
|
||||
|
||||
Draw::Graph kgraph {};
|
||||
cout << Draw::createBox({.x = 5, .y = 10, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "graph", .fill = false, .num = 7}) << Mv::save << flush;
|
||||
Draw::Graph kgraph2 {};
|
||||
Draw::Graph kgraph3 {};
|
||||
|
||||
cout << Draw::createBox({.x = 5, .y = 10, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "braille", .fill = false, .num = 1}) << Mv::save;
|
||||
cout << Draw::createBox({.x = 5, .y = 23, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "block", .fill = false, .num = 2});
|
||||
cout << Draw::createBox({.x = 5, .y = 36, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "tty", .fill = false, .num = 3}) << flush;
|
||||
// Draw::Meter kmeter {};
|
||||
// Draw::Graph kgraph2 {};
|
||||
// Draw::Graph kgraph3 {};
|
||||
|
||||
auto kts = time_micros();
|
||||
kgraph(Term::width - 12, 10, "cpu", mydata, false, false);
|
||||
kgraph(Term::width - 12, 10, "cpu", mydata, "braille", false, false);
|
||||
kgraph2(Term::width - 12, 10, "cpu", mydata, "block", false, false);
|
||||
kgraph3(Term::width - 12, 10, "cpu", mydata, "tty", false, false);
|
||||
|
||||
// kmeter(Term::width - 12, "process");
|
||||
// cout << Mv::save << kgraph(mydata) << "\n\nInit took " << time_micros() - kts << " μs. " << endl;
|
||||
|
||||
@ -405,7 +447,10 @@ int main(int argc, char **argv){
|
||||
// cout << kgraph2() << endl;
|
||||
// exit(0);
|
||||
|
||||
cout << Mv::restore << kgraph(mydata, true) << "\n\n" << Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
|
||||
cout << Mv::restore << kgraph(mydata, true)
|
||||
<< Mv::restore << Mv::d(13) << kgraph2(mydata, true)
|
||||
<< Mv::restore << Mv::d(26) << kgraph3(mydata, true) << endl
|
||||
<< Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
|
||||
// cout << Mv::save << kgraph(mydata, true) << "\n" << kgraph2(mydata, true) << "\n" << kgraph3(mydata, true) << "\n" << kmeter(mydata.back()) << "\n\nInit took " << time_micros() - kts << " μs. " << endl;
|
||||
// sleep_ms(1000);
|
||||
// mydata.push_back(50);
|
||||
@ -420,7 +465,10 @@ int main(int argc, char **argv){
|
||||
// mydata.back() = y;
|
||||
kts = time_micros();
|
||||
// cout << Mv::restore << " "s * Term::width << "\n" << " "s * Term::width << endl;
|
||||
cout << Mv::restore << kgraph(mydata) << endl;
|
||||
cout << Mv::restore << kgraph(mydata)
|
||||
<< Mv::restore << Mv::d(13) << kgraph2(mydata)
|
||||
<< Mv::restore << Mv::d(26) << kgraph3(mydata)
|
||||
<< endl;
|
||||
// cout << Mv::restore << kgraph(mydata) << "\n" << kgraph2(mydata) << "\n" << " "s * Term::width << Mv::l(Term::width) << kgraph3(mydata) << "\n" << kmeter(mydata.back()) << endl;
|
||||
ktavg.push_front(time_micros() - kts);
|
||||
if (ktavg.size() > 100) ktavg.pop_back();
|
337
src/btop_config.cpp
Normal file
337
src/btop_config.cpp
Normal file
@ -0,0 +1,337 @@
|
||||
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <robin_hood.h>
|
||||
#include <ranges>
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
|
||||
#include <btop_config.h>
|
||||
#include <btop_tools.h>
|
||||
|
||||
using robin_hood::unordered_flat_map, std::map, std::array, std::atomic;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
||||
//* Functions and variables for reading and writing the btop config file
|
||||
namespace Config {
|
||||
namespace {
|
||||
atomic<bool> locked (false);
|
||||
atomic<bool> writelock (false);
|
||||
bool write_new;
|
||||
|
||||
vector<array<string, 2>> descriptions = {
|
||||
{"color_theme", "#* Color theme, looks for a .theme file in \"/usr/[local/]share/bpytop/themes\" and \"~/.config/bpytop/themes\", \"Default\" for builtin default theme.\n"
|
||||
"#* Prefix name by a plus sign (+) for a theme located in user themes folder, i.e. color_theme=\"+monokai\"." },
|
||||
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
|
||||
{"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
|
||||
{"graph_symbol", "#* Default symbols to use for graph creation, \"braille\", \"block\" or \"tty\".\n"
|
||||
"#* \"braille\" offers the highest resolution but might not be included in all fonts.\n"
|
||||
"#* \"block\" has half the resolution of braille but uses more common characters.\n"
|
||||
"#* \"tty\" uses only 3 different symbols but will work with most fonts and should work in a real TTY.\n"
|
||||
"#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."},
|
||||
{"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
{"graph_symbol_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
{"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
{"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
{"force_tty", "#* Set to true to true to force tty mode regardless if a real tty has been detected or not."},
|
||||
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
|
||||
{"update_ms", "#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs."},
|
||||
{"proc_update_mult", "#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n"
|
||||
"#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)."},
|
||||
{"proc_sorting", "#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n"
|
||||
"#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly."},
|
||||
{"proc_reversed", "#* Reverse sorting order, True or False."},
|
||||
{"proc_tree", "#* Show processes as a tree."},
|
||||
{"proc_colors", "#* Use the cpu graph colors in the process list."},
|
||||
{"proc_gradient", "#* Use a darkening gradient in the process list."},
|
||||
{"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."},
|
||||
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
|
||||
{"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n"
|
||||
"#* Select from a list of detected attributes from the options menu."},
|
||||
{"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n"
|
||||
"#* Select from a list of detected attributes from the options menu."},
|
||||
{"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
|
||||
{"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
|
||||
{"show_uptime", "#* Shows the system uptime in the CPU box."},
|
||||
{"check_temp", "#* Show cpu temperature."},
|
||||
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
|
||||
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
|
||||
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
|
||||
{"show_cpu_freq", "#* Show CPU frequency."},
|
||||
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
|
||||
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
|
||||
{"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
|
||||
{"disks_filter", "#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with a comma \",\".\n"
|
||||
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"."},
|
||||
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
|
||||
{"show_swap", "#* If swap memory should be shown in memory box."},
|
||||
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
|
||||
{"show_disks", "#* If mem box should be split to also show disks info."},
|
||||
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
|
||||
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
|
||||
{"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
|
||||
{"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
|
||||
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
|
||||
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"device:speed\" separate disks with a comma \",\".\n"
|
||||
"#* Example: \"/dev/sda:100, /dev/sdb:20\"."},
|
||||
{"net_download", "#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"."},
|
||||
{"net_upload", ""},
|
||||
{"net_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
|
||||
{"net_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
|
||||
{"net_color_fixed", "#* If the network graphs color gradient should scale to bandwidth usage or auto scale, bandwidth usage is based on \"net_download\" and \"net_upload\" values."},
|
||||
{"net_iface", "#* Starts with the Network Interface specified here."},
|
||||
{"show_battery", "#* Show battery stats in top right if battery is present."},
|
||||
{"log_level", "#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n"
|
||||
"#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}
|
||||
};
|
||||
|
||||
unordered_flat_map<string, string> strings = {
|
||||
{"color_theme", "Default"},
|
||||
{"shown_boxes", "cpu mem net proc"},
|
||||
{"graph_symbol", "braille"},
|
||||
{"graph_symbol_cpu", "default"},
|
||||
{"graph_symbol_mem", "default"},
|
||||
{"graph_symbol_net", "default"},
|
||||
{"graph_symbol_proc", "default"},
|
||||
{"proc_sorting", "cpu lazy"},
|
||||
{"cpu_graph_upper", "total"},
|
||||
{"cpu_graph_lower", "total"},
|
||||
{"cpu_sensor", "Auto"},
|
||||
{"temp_scale", "celsius"},
|
||||
{"draw_clock", "%X"},
|
||||
{"custom_cpu_name", ""},
|
||||
{"disks_filter", ""},
|
||||
{"io_graph_speeds", ""},
|
||||
{"net_download", "10M"},
|
||||
{"net_upload", "10M"},
|
||||
{"net_iface", ""},
|
||||
{"log_level", "WARNING"},
|
||||
{"proc_filter", ""}
|
||||
};
|
||||
unordered_flat_map<string, string> stringsTmp;
|
||||
|
||||
unordered_flat_map<string, bool> bools = {
|
||||
{"theme_background", true},
|
||||
{"truecolor", true},
|
||||
{"proc_reversed", false},
|
||||
{"proc_tree", false},
|
||||
{"proc_colors", true},
|
||||
{"proc_gradient", true},
|
||||
{"proc_per_core", false},
|
||||
{"proc_mem_bytes", true},
|
||||
{"cpu_invert_lower", true},
|
||||
{"cpu_single_graph", false},
|
||||
{"show_uptime", true},
|
||||
{"check_temp", true},
|
||||
{"show_coretemp", true},
|
||||
{"show_cpu_freq", true},
|
||||
{"background_update", true},
|
||||
{"mem_graphs", true},
|
||||
{"show_swap", true},
|
||||
{"swap_disk", true},
|
||||
{"show_disks", true},
|
||||
{"only_physical", true},
|
||||
{"use_fstab", false},
|
||||
{"show_io_stat", true},
|
||||
{"io_mode", false},
|
||||
{"io_graph_combined", false},
|
||||
{"net_color_fixed", false},
|
||||
{"net_auto", true},
|
||||
{"net_sync", false},
|
||||
{"show_battery", true},
|
||||
{"tty_mode", false},
|
||||
{"force_tty", false},
|
||||
};
|
||||
unordered_flat_map<string, bool> boolsTmp;
|
||||
|
||||
unordered_flat_map<string, int> ints = {
|
||||
{"update_ms", 2000},
|
||||
{"proc_update_mult", 2},
|
||||
};
|
||||
unordered_flat_map<string, int> intsTmp;
|
||||
|
||||
bool _locked(const string& name){
|
||||
atomic_wait(writelock);
|
||||
if (!write_new && rng::find_if(descriptions, [&name](const auto& a){ return a.at(0) == name; }) != descriptions.end())
|
||||
write_new = true;
|
||||
return locked.load();
|
||||
}
|
||||
}
|
||||
|
||||
fs::path conf_dir;
|
||||
fs::path conf_file;
|
||||
|
||||
vector<string> valid_graph_symbols = { "braille", "block", "tty" };
|
||||
|
||||
//* Return bool config value <name>
|
||||
const bool& getB(string name){
|
||||
return bools.at(name);
|
||||
}
|
||||
|
||||
//* Return integer config value <name>
|
||||
const int& getI(string name){
|
||||
return ints.at(name);
|
||||
}
|
||||
|
||||
//* Return string config value <name>
|
||||
const string& getS(string name){
|
||||
return strings.at(name);
|
||||
}
|
||||
|
||||
//* Set config value <name> to bool <value>
|
||||
void set(string name, bool value){
|
||||
if (_locked(name)) boolsTmp.insert_or_assign(name, value);
|
||||
else bools.at(name) = value;
|
||||
}
|
||||
|
||||
//* Set config value <name> to int <value>
|
||||
void set(string name, int value){
|
||||
if (_locked(name)) intsTmp.insert_or_assign(name, value);
|
||||
ints.at(name) = value;
|
||||
}
|
||||
|
||||
//* Set config value <name> to string <value>
|
||||
void set(string name, string value){
|
||||
if (_locked(name)) stringsTmp.insert_or_assign(name, value);
|
||||
else strings.at(name) = value;
|
||||
}
|
||||
|
||||
//* Flip config bool <name>
|
||||
void flip(string name){
|
||||
if (_locked(name)) {
|
||||
if (boolsTmp.contains(name)) boolsTmp.at(name) = !boolsTmp.at(name);
|
||||
else boolsTmp.insert_or_assign(name, (!bools.at(name)));
|
||||
}
|
||||
else bools.at(name) = !bools.at(name);
|
||||
}
|
||||
|
||||
//* Wait if locked then lock config and cache changes until unlock
|
||||
void lock(){
|
||||
atomic_wait_set(locked);
|
||||
}
|
||||
|
||||
//* Unlock config and write any cached values to config
|
||||
void unlock(){
|
||||
atomic_wait_set(writelock);
|
||||
|
||||
for (auto& item : stringsTmp){
|
||||
strings.at(item.first) = item.second;
|
||||
}
|
||||
stringsTmp.clear();
|
||||
|
||||
for (auto& item : intsTmp){
|
||||
ints.at(item.first) = item.second;
|
||||
}
|
||||
intsTmp.clear();
|
||||
|
||||
for (auto& item : boolsTmp){
|
||||
bools.at(item.first) = item.second;
|
||||
}
|
||||
boolsTmp.clear();
|
||||
|
||||
locked = false;
|
||||
writelock = false;
|
||||
}
|
||||
|
||||
//* Load the config file from disk
|
||||
void load(fs::path conf_file, vector<string>& load_errors){
|
||||
if (conf_file.empty())
|
||||
return;
|
||||
else if (!fs::exists(conf_file)) {
|
||||
write_new = true;
|
||||
return;
|
||||
}
|
||||
std::ifstream cread(conf_file);
|
||||
if (cread.good()) {
|
||||
vector<string> valid_names;
|
||||
for (auto &n : descriptions)
|
||||
valid_names.push_back(n[0]);
|
||||
string v_string;
|
||||
getline(cread, v_string, '\n');
|
||||
if (!v_string.ends_with(Global::Version))
|
||||
write_new = true;
|
||||
while (!cread.eof()) {
|
||||
cread >> std::ws;
|
||||
if (cread.peek() == '#') {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
string name, value;
|
||||
getline(cread, name, '=');
|
||||
if (!v_contains(valid_names, name)) {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bools.contains(name)) {
|
||||
cread >> value;
|
||||
if (!isbool(value))
|
||||
load_errors.push_back("Got an invalid bool value for config name: " + name);
|
||||
else
|
||||
bools.at(name) = stobool(value);
|
||||
}
|
||||
else if (ints.contains(name)) {
|
||||
cread >> value;
|
||||
if (!isint(value))
|
||||
load_errors.push_back("Got an invalid integer value for config name: " + name);
|
||||
else
|
||||
ints.at(name) = stoi(value);
|
||||
}
|
||||
else if (strings.contains(name)) {
|
||||
cread >> std::ws;
|
||||
if (cread.peek() == '"') {
|
||||
cread.ignore(1);
|
||||
getline(cread, value, '"');
|
||||
}
|
||||
else cread >> value;
|
||||
|
||||
if (name == "log_level" && !v_contains(Logger::log_levels, value))
|
||||
load_errors.push_back("Invalid log_level: " + value);
|
||||
else if (name == "graph_symbol" && !v_contains(valid_graph_symbols, value))
|
||||
load_errors.push_back("Invalid graph symbol identifier: " + value);
|
||||
else
|
||||
strings.at(name) = value;
|
||||
}
|
||||
|
||||
cread.ignore(SSmax, '\n');
|
||||
}
|
||||
cread.close();
|
||||
if (!load_errors.empty()) write_new = true;
|
||||
}
|
||||
}
|
||||
|
||||
//* Write the config file to disk
|
||||
void write(){
|
||||
if (conf_file.empty() || !write_new) return;
|
||||
Logger::debug("Writing new config file");
|
||||
std::ofstream cwrite(conf_file, std::ios::trunc);
|
||||
if (cwrite.good()) {
|
||||
cwrite << "#? Config file for btop v. " << Global::Version;
|
||||
for (auto [name, description] : descriptions) {
|
||||
cwrite << "\n\n" << (description.empty() ? "" : description + "\n") << name << "=";
|
||||
if (strings.contains(name)) cwrite << "\"" << strings.at(name) << "\"";
|
||||
else if (ints.contains(name)) cwrite << ints.at(name);
|
||||
else if (bools.contains(name)) cwrite << (bools.at(name) ? "True" : "False");
|
||||
}
|
||||
cwrite.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -16,303 +16,52 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#ifndef _btop_config_included_
|
||||
#define _btop_config_included_
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <robin_hood.h>
|
||||
#include <filesystem>
|
||||
|
||||
#include <btop_tools.h>
|
||||
|
||||
using std::string, std::vector, robin_hood::unordered_flat_map, std::map;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Tools;
|
||||
|
||||
using std::string, std::vector;
|
||||
|
||||
//* Functions and variables for reading and writing the btop config file
|
||||
namespace Config {
|
||||
namespace {
|
||||
|
||||
fs::path conf_dir;
|
||||
fs::path conf_file;
|
||||
extern std::filesystem::path conf_dir;
|
||||
extern std::filesystem::path conf_file;
|
||||
|
||||
atomic<bool> locked (false);
|
||||
atomic<bool> writelock (false);
|
||||
bool write_new;
|
||||
|
||||
vector<array<string, 2>> descriptions = {
|
||||
{"color_theme", "#* Color theme, looks for a .theme file in \"/usr/[local/]share/bpytop/themes\" and \"~/.config/bpytop/themes\", \"Default\" for builtin default theme.\n"
|
||||
"#* Prefix name by a plus sign (+) for a theme located in user themes folder, i.e. color_theme=\"+monokai\"." },
|
||||
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
|
||||
{"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
|
||||
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
|
||||
{"update_ms", "#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs."},
|
||||
{"proc_update_mult", "#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n"
|
||||
"#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)."},
|
||||
{"proc_sorting", "#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n"
|
||||
"#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly."},
|
||||
{"proc_reversed", "#* Reverse sorting order, True or False."},
|
||||
{"proc_tree", "#* Show processes as a tree."},
|
||||
{"proc_colors", "#* Use the cpu graph colors in the process list."},
|
||||
{"proc_gradient", "#* Use a darkening gradient in the process list."},
|
||||
{"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."},
|
||||
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
|
||||
{"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n"
|
||||
"#* Select from a list of detected attributes from the options menu."},
|
||||
{"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n"
|
||||
"#* Select from a list of detected attributes from the options menu."},
|
||||
{"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
|
||||
{"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
|
||||
{"show_uptime", "#* Shows the system uptime in the CPU box."},
|
||||
{"check_temp", "#* Show cpu temperature."},
|
||||
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
|
||||
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
|
||||
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
|
||||
{"show_cpu_freq", "#* Show CPU frequency."},
|
||||
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
|
||||
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
|
||||
{"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
|
||||
{"disks_filter", "#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with a comma \",\".\n"
|
||||
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"."},
|
||||
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
|
||||
{"show_swap", "#* If swap memory should be shown in memory box."},
|
||||
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
|
||||
{"show_disks", "#* If mem box should be split to also show disks info."},
|
||||
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
|
||||
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
|
||||
{"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
|
||||
{"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
|
||||
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
|
||||
{"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"device:speed\" separate disks with a comma \",\".\n"
|
||||
"#* Example: \"/dev/sda:100, /dev/sdb:20\"."},
|
||||
{"net_download", "#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"."},
|
||||
{"net_upload", ""},
|
||||
{"net_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
|
||||
{"net_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
|
||||
{"net_color_fixed", "#* If the network graphs color gradient should scale to bandwidth usage or auto scale, bandwidth usage is based on \"net_download\" and \"net_upload\" values."},
|
||||
{"net_iface", "#* Starts with the Network Interface specified here."},
|
||||
{"show_battery", "#* Show battery stats in top right if battery is present."},
|
||||
{"log_level", "#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n"
|
||||
"#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}
|
||||
};
|
||||
|
||||
unordered_flat_map<string, string> strings = {
|
||||
{"color_theme", "Default"},
|
||||
{"shown_boxes", "cpu mem net proc"},
|
||||
{"proc_sorting", "cpu lazy"},
|
||||
{"cpu_graph_upper", "total"},
|
||||
{"cpu_graph_lower", "total"},
|
||||
{"cpu_sensor", "Auto"},
|
||||
{"temp_scale", "celsius"},
|
||||
{"draw_clock", "%X"},
|
||||
{"custom_cpu_name", ""},
|
||||
{"disks_filter", ""},
|
||||
{"io_graph_speeds", ""},
|
||||
{"net_download", "10M"},
|
||||
{"net_upload", "10M"},
|
||||
{"net_iface", ""},
|
||||
{"log_level", "WARNING"},
|
||||
{"proc_filter", ""}
|
||||
};
|
||||
unordered_flat_map<string, string> stringsTmp;
|
||||
|
||||
unordered_flat_map<string, bool> bools = {
|
||||
{"theme_background", true},
|
||||
{"truecolor", true},
|
||||
{"proc_reversed", false},
|
||||
{"proc_tree", false},
|
||||
{"proc_colors", true},
|
||||
{"proc_gradient", true},
|
||||
{"proc_per_core", false},
|
||||
{"proc_mem_bytes", true},
|
||||
{"cpu_invert_lower", true},
|
||||
{"cpu_single_graph", false},
|
||||
{"show_uptime", true},
|
||||
{"check_temp", true},
|
||||
{"show_coretemp", true},
|
||||
{"show_cpu_freq", true},
|
||||
{"background_update", true},
|
||||
{"mem_graphs", true},
|
||||
{"show_swap", true},
|
||||
{"swap_disk", true},
|
||||
{"show_disks", true},
|
||||
{"only_physical", true},
|
||||
{"use_fstab", false},
|
||||
{"show_io_stat", true},
|
||||
{"io_mode", false},
|
||||
{"io_graph_combined", false},
|
||||
{"net_color_fixed", false},
|
||||
{"net_auto", true},
|
||||
{"net_sync", false},
|
||||
{"show_battery", true},
|
||||
};
|
||||
unordered_flat_map<string, bool> boolsTmp;
|
||||
|
||||
unordered_flat_map<string, int> ints = {
|
||||
{"update_ms", 2000},
|
||||
{"proc_update_mult", 2},
|
||||
};
|
||||
unordered_flat_map<string, int> intsTmp;
|
||||
|
||||
bool _locked(){
|
||||
atomic_wait(writelock);
|
||||
if (!write_new) write_new = true;
|
||||
return locked.load();
|
||||
}
|
||||
}
|
||||
extern vector<string> valid_graph_symbols;
|
||||
|
||||
//* Return bool config value <name>
|
||||
const bool& getB(string name){
|
||||
return bools.at(name);
|
||||
}
|
||||
const bool& getB(string name);
|
||||
|
||||
//* Return integer config value <name>
|
||||
const int& getI(string name){
|
||||
return ints.at(name);
|
||||
}
|
||||
const int& getI(string name);
|
||||
|
||||
//* Return string config value <name>
|
||||
const string& getS(string name){
|
||||
return strings.at(name);
|
||||
}
|
||||
const string& getS(string name);
|
||||
|
||||
//* Set config value <name> to bool <value>
|
||||
void set(string name, bool value){
|
||||
if (_locked()) boolsTmp.insert_or_assign(name, value);
|
||||
else bools.at(name) = value;
|
||||
}
|
||||
void set(string name, bool value);
|
||||
|
||||
//* Set config value <name> to int <value>
|
||||
void set(string name, int value){
|
||||
if (_locked()) intsTmp.insert_or_assign(name, value);
|
||||
ints.at(name) = value;
|
||||
}
|
||||
void set(string name, int value);
|
||||
|
||||
//* Set config value <name> to string <value>
|
||||
void set(string name, string value){
|
||||
if (_locked()) stringsTmp.insert_or_assign(name, value);
|
||||
else strings.at(name) = value;
|
||||
}
|
||||
void set(string name, string value);
|
||||
|
||||
//* Flip config bool <name>
|
||||
void flip(string name){
|
||||
if (_locked()) {
|
||||
if (boolsTmp.contains(name)) boolsTmp.at(name) = !boolsTmp.at(name);
|
||||
else boolsTmp.insert_or_assign(name, (!bools.at(name)));
|
||||
}
|
||||
else bools.at(name) = !bools.at(name);
|
||||
}
|
||||
void flip(string name);
|
||||
|
||||
//* Wait if locked then lock config and cache changes until unlock
|
||||
void lock(){
|
||||
atomic_wait_set(locked);
|
||||
}
|
||||
void lock();
|
||||
|
||||
//* Unlock config and write any cached values to config
|
||||
void unlock(){
|
||||
atomic_wait_set(writelock);
|
||||
|
||||
for (auto& item : stringsTmp){
|
||||
strings.at(item.first) = item.second;
|
||||
}
|
||||
stringsTmp.clear();
|
||||
|
||||
for (auto& item : intsTmp){
|
||||
ints.at(item.first) = item.second;
|
||||
}
|
||||
intsTmp.clear();
|
||||
|
||||
for (auto& item : boolsTmp){
|
||||
bools.at(item.first) = item.second;
|
||||
}
|
||||
boolsTmp.clear();
|
||||
|
||||
locked = false;
|
||||
writelock = false;
|
||||
}
|
||||
void unlock();
|
||||
|
||||
//* Load the config file from disk
|
||||
void load(fs::path conf_file, vector<string>& load_errors){
|
||||
if (conf_file.empty())
|
||||
return;
|
||||
else if (!fs::exists(conf_file)) {
|
||||
write_new = true;
|
||||
return;
|
||||
}
|
||||
std::ifstream cread(conf_file);
|
||||
if (cread.good()) {
|
||||
vector<string> valid_names;
|
||||
for (auto &n : descriptions)
|
||||
valid_names.push_back(n[0]);
|
||||
string v_string;
|
||||
getline(cread, v_string, '\n');
|
||||
if (!v_string.ends_with(Global::Version))
|
||||
write_new = true;
|
||||
while (!cread.eof()) {
|
||||
cread >> std::ws;
|
||||
if (cread.peek() == '#') {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
string name, value;
|
||||
getline(cread, name, '=');
|
||||
if (!v_contains(valid_names, name)) {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bools.contains(name)) {
|
||||
cread >> value;
|
||||
if (!isbool(value))
|
||||
load_errors.push_back("Got an invalid bool value for config name: " + name);
|
||||
else
|
||||
bools.at(name) = stobool(value);
|
||||
}
|
||||
else if (ints.contains(name)) {
|
||||
cread >> value;
|
||||
if (!isint(value))
|
||||
load_errors.push_back("Got an invalid integer value for config name: " + name);
|
||||
else
|
||||
ints.at(name) = stoi(value);
|
||||
}
|
||||
else if (strings.contains(name)) {
|
||||
cread >> std::ws;
|
||||
if (cread.peek() == '"') {
|
||||
cread.ignore(1);
|
||||
getline(cread, value, '"');
|
||||
}
|
||||
else cread >> value;
|
||||
|
||||
if (name == "log_level" && !v_contains(Logger::log_levels, value)) load_errors.push_back("Invalid log_level: " + value);
|
||||
else strings.at(name) = value;
|
||||
}
|
||||
|
||||
cread.ignore(SSmax, '\n');
|
||||
}
|
||||
cread.close();
|
||||
if (!load_errors.empty()) write_new = true;
|
||||
}
|
||||
}
|
||||
void load(std::filesystem::path conf_file, vector<string>& load_errors);
|
||||
|
||||
//* Write the config file to disk
|
||||
void write(){
|
||||
if (conf_file.empty() || !write_new) return;
|
||||
Logger::debug("Writing new config file");
|
||||
std::ofstream cwrite(conf_file, std::ios::trunc);
|
||||
if (cwrite.good()) {
|
||||
cwrite << "#? Config file for btop v. " << Global::Version;
|
||||
for (auto [name, description] : descriptions) {
|
||||
cwrite << "\n\n" << (description.empty() ? "" : description + "\n") << name << "=";
|
||||
if (strings.contains(name)) cwrite << "\"" << strings.at(name) << "\"";
|
||||
else if (ints.contains(name)) cwrite << ints.at(name);
|
||||
else if (bools.contains(name)) cwrite << (bools.at(name) ? "True" : "False");
|
||||
}
|
||||
cwrite.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
void write();
|
||||
}
|
281
src/btop_draw.cpp
Normal file
281
src/btop_draw.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <btop_draw.h>
|
||||
#include <btop_config.h>
|
||||
#include <btop_theme.h>
|
||||
#include <btop_tools.h>
|
||||
|
||||
using robin_hood::unordered_flat_map, std::round, std::views::iota,
|
||||
std::string_literals::operator""s, std::clamp, std::array, std::floor;
|
||||
|
||||
namespace rng = std::ranges;
|
||||
|
||||
namespace Symbols {
|
||||
const string h_line = "─";
|
||||
const string v_line = "│";
|
||||
const string left_up = "┌";
|
||||
const string right_up = "┐";
|
||||
const string left_down = "└";
|
||||
const string right_down = "┘";
|
||||
const string title_left = "┤";
|
||||
const string title_right = "├";
|
||||
const string div_up = "┬";
|
||||
const string div_down = "┴";
|
||||
|
||||
const string meter = "■";
|
||||
|
||||
const array<string, 10> superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" };
|
||||
|
||||
const unordered_flat_map<string, vector<string>> graph_symbols = {
|
||||
{ "braille_up", {
|
||||
" ", "⢀", "⢠", "⢰", "⢸",
|
||||
"⡀", "⣀", "⣠", "⣰", "⣸",
|
||||
"⡄", "⣄", "⣤", "⣴", "⣼",
|
||||
"⡆", "⣆", "⣦", "⣶", "⣾",
|
||||
"⡇", "⣇", "⣧", "⣷", "⣿"
|
||||
}},
|
||||
{"braille_down", {
|
||||
" ", "⠈", "⠘", "⠸", "⢸",
|
||||
"⠁", "⠉", "⠙", "⠹", "⢹",
|
||||
"⠃", "⠋", "⠛", "⠻", "⢻",
|
||||
"⠇", "⠏", "⠟", "⠿", "⢿",
|
||||
"⡇", "⡏", "⡟", "⡿", "⣿"
|
||||
}},
|
||||
{"block_up", {
|
||||
" ", "▗", "▗", "▐", "▐",
|
||||
"▖", "▄", "▄", "▟", "▟",
|
||||
"▖", "▄", "▄", "▟", "▟",
|
||||
"▌", "▙", "▙", "█", "█",
|
||||
"▌", "▙", "▙", "█", "█"
|
||||
}},
|
||||
{"block_down", {
|
||||
" ", "▝", "▝", "▐", "▐",
|
||||
"▘", "▀", "▀", "▜", "▜",
|
||||
"▘", "▀", "▀", "▜", "▜",
|
||||
"▌", "▛", "▛", "█", "█",
|
||||
"▌", "▛", "▛", "█", "█"
|
||||
}},
|
||||
{"tty_up", {
|
||||
" ", "░", "░", "▒", "▒",
|
||||
"░", "░", "▒", "▒", "█",
|
||||
"░", "▒", "▒", "▒", "█",
|
||||
"▒", "▒", "▒", "█", "█",
|
||||
"▒", "█", "█", "█", "█"
|
||||
}},
|
||||
{"tty_down", {
|
||||
" ", "░", "░", "▒", "▒",
|
||||
"░", "░", "▒", "▒", "█",
|
||||
"░", "▒", "▒", "▒", "█",
|
||||
"▒", "▒", "▒", "█", "█",
|
||||
"▒", "█", "█", "█", "█"
|
||||
}}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Draw {
|
||||
|
||||
using namespace Tools;
|
||||
|
||||
//* Create a box using values from a BoxConf struct and return as a string
|
||||
string createBox(BoxConf c){
|
||||
string out;
|
||||
string lcolor = (c.line_color.empty()) ? Theme::c("div_line") : c.line_color;
|
||||
string numbering = (c.num == 0) ? "" : Theme::c("hi_fg") + Symbols::superscript[c.num];
|
||||
|
||||
out = Fx::reset + lcolor;
|
||||
|
||||
//* Draw horizontal lines
|
||||
for (uint hpos : {c.y, c.y + c.height - 1}){
|
||||
out += Mv::to(hpos, c.x) + Symbols::h_line * (c.width - 1);
|
||||
}
|
||||
|
||||
//* Draw vertical lines and fill if enabled
|
||||
for (uint hpos : iota(c.y + 1, c.y + c.height - 1)){
|
||||
out += Mv::to(hpos, c.x) + Symbols::v_line +
|
||||
((c.fill) ? string(c.width - 2, ' ') : Mv::r(c.width - 2)) +
|
||||
Symbols::v_line;
|
||||
}
|
||||
|
||||
//* Draw corners
|
||||
out += Mv::to(c.y, c.x) + Symbols::left_up +
|
||||
Mv::to(c.y, c.x + c.width - 1) + Symbols::right_up +
|
||||
Mv::to(c.y + c.height - 1, c.x) + Symbols::left_down +
|
||||
Mv::to(c.y + c.height - 1, c.x + c.width - 1) + Symbols::right_down;
|
||||
|
||||
//* Draw titles if defined
|
||||
if (!c.title.empty()){
|
||||
out += Mv::to(c.y, c.x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + c.title +
|
||||
Fx::ub + lcolor + Symbols::title_right;
|
||||
}
|
||||
if (!c.title2.empty()){
|
||||
out += Mv::to(c.y + c.height - 1, c.x + 2) + Symbols::title_left + Theme::c("title") + c.title2 +
|
||||
Fx::ub + lcolor + Symbols::title_right;
|
||||
}
|
||||
|
||||
return out + Fx::reset + Mv::to(c.y + 1, c.x + 1);
|
||||
}
|
||||
|
||||
|
||||
void Meter::operator()(int width, string color_gradient, bool invert) {
|
||||
this->width = width;
|
||||
this->color_gradient = color_gradient;
|
||||
this->invert = invert;
|
||||
cache.clear();
|
||||
cache.insert(cache.begin(), 101, "");
|
||||
}
|
||||
|
||||
string Meter::operator()(int value) {
|
||||
if (width < 1) return "";
|
||||
value = clamp(value, 0, 100);
|
||||
if (!cache.at(value).empty()) return cache.at(value);
|
||||
string& out = cache.at(value);
|
||||
for (int i : iota(1, width + 1)) {
|
||||
int y = round((double)i * 100.0 / width);
|
||||
if (value >= y)
|
||||
out += Theme::g(color_gradient)[invert ? 100 - y : y] + Symbols::meter;
|
||||
else {
|
||||
out += Theme::c("meter_bg") + Symbols::meter * (width + 1 - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
out += Fx::reset;
|
||||
return out;
|
||||
}
|
||||
|
||||
void Graph::_create(const vector<long long>& data, int data_offset) {
|
||||
const bool mult = (data.size() - data_offset > 1);
|
||||
if (mult && (data.size() - data_offset) % 2 != 0) data_offset--;
|
||||
auto& graph_symbol = Symbols::graph_symbols.at(symbol + '_' + (invert ? "down" : "up"));
|
||||
array<int, 2> result;
|
||||
const float mod = (height == 1) ? 0.3 : 0.1;
|
||||
long long data_value = 0;
|
||||
if (mult && data_offset > 0) {
|
||||
last = data[data_offset - 1];
|
||||
if (max_value > 0) last = clamp((last + offset) * 100 / max_value, 0ll, 100ll);
|
||||
}
|
||||
|
||||
//? Horizontal iteration over values in <data>
|
||||
for (int i : iota(data_offset, (int)data.size())) {
|
||||
if (tty_mode && mult && i % 2 != 0) continue;
|
||||
else if (!tty_mode) current = !current;
|
||||
if (i == -1) { data_value = 0; last = 0; }
|
||||
else data_value = data[i];
|
||||
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
|
||||
//? Vertical iteration over height of graph
|
||||
for (int horizon : iota(0, height)){
|
||||
int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100;
|
||||
int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0;
|
||||
//? Calculate previous + current value to fit two values in 1 braille character
|
||||
int ai = 0;
|
||||
for (auto value : {last, data_value}) {
|
||||
if (value >= cur_high)
|
||||
result[ai++] = 4;
|
||||
else if (value <= cur_low)
|
||||
result[ai++] = 0;
|
||||
else {
|
||||
result[ai++] = round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod);
|
||||
if (no_zero && horizon == height - 1 && i != -1 && result[ai] == 0) result[ai] = 1;
|
||||
}
|
||||
}
|
||||
//? Generate braille symbol from 5x5 2D vector
|
||||
graphs[current][horizon] += (height == 1 && result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])];
|
||||
}
|
||||
if (mult && i > data_offset) last = data_value;
|
||||
|
||||
}
|
||||
last = data_value;
|
||||
if (height == 1)
|
||||
out = (last < 1 ? Theme::c("inactive_fg") : Theme::g(color_gradient)[last]) + graphs[current][0];
|
||||
else {
|
||||
out.clear();
|
||||
for (int i : iota(0, height)) {
|
||||
if (i > 0) out += Mv::d(1) + Mv::l(width);
|
||||
out += (invert) ? Theme::g(color_gradient)[i * 100 / (height - 1)] : Theme::g(color_gradient)[100 - (i * 100 / (height - 1))];
|
||||
out += (invert) ? graphs[current][ (height - 1) - i] : graphs[current][i];
|
||||
}
|
||||
}
|
||||
out += Fx::reset;
|
||||
}
|
||||
|
||||
|
||||
void Graph::operator()(int width, int height, string color_gradient, const vector<long long>& data, string symbol, bool invert, bool no_zero, long long max_value, long long offset) {
|
||||
graphs[true].clear(); graphs[false].clear();
|
||||
this->width = width; this->height = height;
|
||||
this->invert = invert; this->offset = offset;
|
||||
this->no_zero = no_zero;
|
||||
this->color_gradient = color_gradient;
|
||||
if (Config::getB("tty_mode") || symbol == "tty") {
|
||||
tty_mode = true;
|
||||
this->symbol = "tty";
|
||||
}
|
||||
else if (symbol != "default" && v_contains(Config::valid_graph_symbols, symbol)) this->symbol = symbol;
|
||||
else this->symbol = Config::getS("graph_symbol");
|
||||
if (max_value == 0 && offset > 0) max_value = 100;
|
||||
this->max_value = max_value;
|
||||
int value_width = ceil((float)data.size() / 2);
|
||||
int data_offset = 0;
|
||||
if (value_width > width) data_offset = data.size() - width * 2;
|
||||
|
||||
//? Populate the two switching graph vectors and fill empty space if data size < width
|
||||
for (int i : iota(0, height * 2)) {
|
||||
graphs[(i % 2 != 0)].push_back((value_width < width) ? ((height == 1) ? Mv::r(1) : " "s) * (width - value_width) : "");
|
||||
}
|
||||
if (data.size() == 0) return;
|
||||
this->_create(data, data_offset);
|
||||
}
|
||||
|
||||
|
||||
string& Graph::operator()(const vector<long long>& data, bool data_same) {
|
||||
if (data_same) return out;
|
||||
|
||||
//? Make room for new characters on graph
|
||||
bool select_graph = (tty_mode ? current : !current);
|
||||
for (int i : iota(0, height)) {
|
||||
if (graphs[select_graph][i].starts_with(Fx::e)) graphs[current][i].erase(0, 4);
|
||||
else graphs[select_graph][i].erase(0, 3);
|
||||
}
|
||||
this->_create(data, (int)data.size() - 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
string& Graph::operator()() {
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Box {
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace Proc {
|
||||
|
||||
// Draw::BoxConf box;
|
||||
|
||||
}
|
222
src/btop_draw.h
222
src/btop_draw.h
@ -16,63 +16,32 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <robin_hood.h>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <btop_config.h>
|
||||
#include <btop_tools.h>
|
||||
|
||||
#ifndef _btop_draw_included_
|
||||
#define _btop_draw_included_
|
||||
|
||||
using std::string, std::vector, robin_hood::unordered_flat_map, std::round, std::views::iota,
|
||||
std::string_literals::operator""s, std::clamp, std::array, std::floor;
|
||||
using std::string, std::vector, robin_hood::unordered_flat_map;
|
||||
|
||||
namespace Symbols {
|
||||
const string h_line = "─";
|
||||
const string v_line = "│";
|
||||
const string left_up = "┌";
|
||||
const string right_up = "┐";
|
||||
const string left_down = "└";
|
||||
const string right_down = "┘";
|
||||
const string title_left = "┤";
|
||||
const string title_right = "├";
|
||||
const string div_up = "┬";
|
||||
const string div_down = "┴";
|
||||
|
||||
const string meter = "■";
|
||||
|
||||
const array<string, 10> superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" };
|
||||
|
||||
const vector<string> graph_up = {
|
||||
" ", "⢀", "⢠", "⢰", "⢸",
|
||||
"⡀", "⣀", "⣠", "⣰", "⣸",
|
||||
"⡄", "⣄", "⣤", "⣴", "⣼",
|
||||
"⡆", "⣆", "⣦", "⣶", "⣾",
|
||||
"⡇", "⣇", "⣧", "⣷", "⣿"
|
||||
};
|
||||
|
||||
const vector<string> graph_down = {
|
||||
" ", "⠈", "⠘", "⠸", "⢸",
|
||||
"⠁", "⠉", "⠙", "⠹", "⢹",
|
||||
"⠃", "⠋", "⠛", "⠻", "⢻",
|
||||
"⠇", "⠏", "⠟", "⠿", "⢿",
|
||||
"⡇", "⡏", "⡟", "⡿", "⣿"
|
||||
};
|
||||
extern const string h_line;
|
||||
extern const string v_line;
|
||||
extern const string left_up;
|
||||
extern const string right_up;
|
||||
extern const string left_down;
|
||||
extern const string right_down;
|
||||
extern const string title_left;
|
||||
extern const string title_right;
|
||||
extern const string div_up;
|
||||
extern const string div_down;
|
||||
}
|
||||
|
||||
namespace Draw {
|
||||
|
||||
using namespace Tools;
|
||||
|
||||
struct BoxConf {
|
||||
uint x=0, y=0;
|
||||
uint width=0, height=0;
|
||||
@ -82,43 +51,7 @@ namespace Draw {
|
||||
};
|
||||
|
||||
//* Create a box using values from a BoxConf struct and return as a string
|
||||
string createBox(BoxConf c){
|
||||
string out;
|
||||
string lcolor = (c.line_color.empty()) ? Theme::c("div_line") : c.line_color;
|
||||
string numbering = (c.num == 0) ? "" : Theme::c("hi_fg") + Symbols::superscript[c.num];
|
||||
|
||||
out = Fx::reset + lcolor;
|
||||
|
||||
//* Draw horizontal lines
|
||||
for (uint hpos : {c.y, c.y + c.height - 1}){
|
||||
out += Mv::to(hpos, c.x) + Symbols::h_line * (c.width - 1);
|
||||
}
|
||||
|
||||
//* Draw vertical lines and fill if enabled
|
||||
for (uint hpos : iota(c.y + 1, c.y + c.height - 1)){
|
||||
out += Mv::to(hpos, c.x) + Symbols::v_line +
|
||||
((c.fill) ? string(c.width - 2, ' ') : Mv::r(c.width - 2)) +
|
||||
Symbols::v_line;
|
||||
}
|
||||
|
||||
//* Draw corners
|
||||
out += Mv::to(c.y, c.x) + Symbols::left_up +
|
||||
Mv::to(c.y, c.x + c.width - 1) + Symbols::right_up +
|
||||
Mv::to(c.y + c.height - 1, c.x) + Symbols::left_down +
|
||||
Mv::to(c.y + c.height - 1, c.x + c.width - 1) + Symbols::right_down;
|
||||
|
||||
//* Draw titles if defined
|
||||
if (!c.title.empty()){
|
||||
out += Mv::to(c.y, c.x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + c.title +
|
||||
Fx::ub + lcolor + Symbols::title_right;
|
||||
}
|
||||
if (!c.title2.empty()){
|
||||
out += Mv::to(c.y + c.height - 1, c.x + 2) + Symbols::title_left + Theme::c("title") + c.title2 +
|
||||
Fx::ub + lcolor + Symbols::title_right;
|
||||
}
|
||||
|
||||
return out + Fx::reset + Mv::to(c.y + 1, c.x + 1);
|
||||
}
|
||||
string createBox(BoxConf c);
|
||||
|
||||
//* Class holding a percentage meter
|
||||
class Meter {
|
||||
@ -128,139 +61,32 @@ namespace Draw {
|
||||
vector<string> cache;
|
||||
public:
|
||||
//* Set meter options
|
||||
void operator()(int width, string color_gradient, bool invert = false) {
|
||||
this->width = width;
|
||||
this->color_gradient = color_gradient;
|
||||
this->invert = invert;
|
||||
cache.clear();
|
||||
cache.insert(cache.begin(), 101, "");
|
||||
}
|
||||
void operator()(int width, string color_gradient, bool invert = false);
|
||||
|
||||
//* Return a string representation of the meter with given value
|
||||
string operator()(int value) {
|
||||
if (width < 1) return "";
|
||||
value = clamp(value, 0, 100);
|
||||
if (!cache.at(value).empty()) return cache.at(value);
|
||||
string& out = cache.at(value);
|
||||
for (int i : iota(1, width + 1)) {
|
||||
int y = round((double)i * 100.0 / width);
|
||||
if (value >= y)
|
||||
out += Theme::g(color_gradient)[invert ? 100 - y : y] + Symbols::meter;
|
||||
else {
|
||||
out += Theme::c("meter_bg") + Symbols::meter * (width + 1 - i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
out += Fx::reset;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
string operator()(int value);
|
||||
};
|
||||
|
||||
//* Class holding a graph
|
||||
class Graph {
|
||||
string out, color_gradient;
|
||||
string out, color_gradient, symbol = "default";
|
||||
int width = 0, height = 0;
|
||||
long long last = 0, max_value = 0, offset = 0;
|
||||
bool current = true, no_zero = false, invert = false;
|
||||
bool current = true, no_zero = false, invert = false, tty_mode = false;
|
||||
unordered_flat_map<bool, vector<string>> graphs = { {true, {}}, {false, {}}};
|
||||
|
||||
//* Create two representations of the graph to switch between to represent two values for each braille character
|
||||
void _create(const vector<long long>& data, int data_offset) {
|
||||
const bool mult = (data.size() - data_offset > 1);
|
||||
if (mult && (data.size() - data_offset) % 2 != 0) data_offset--;
|
||||
auto& graph_symbol = (invert) ? Symbols::graph_down : Symbols::graph_up;
|
||||
array<int, 2> result;
|
||||
const float mod = (height == 1) ? 0.3 : 0.1;
|
||||
long long data_value = 0;
|
||||
if (mult && data_offset > 0) {
|
||||
last = data[data_offset - 1];
|
||||
if (max_value > 0) last = clamp((last + offset) * 100 / max_value, 0ll, 100ll);
|
||||
}
|
||||
|
||||
//? Horizontal iteration over values in <data>
|
||||
for (int i : iota(data_offset, (int)data.size())) {
|
||||
current = !current;
|
||||
if (i == -1) { data_value = 0; last = 0; }
|
||||
else data_value = data[i];
|
||||
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
|
||||
//? Vertical iteration over height of graph
|
||||
for (int horizon : iota(0, height)){
|
||||
int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100;
|
||||
int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0;
|
||||
//? Calculate previous + current value to fit two values in 1 braille character
|
||||
int ai = 0;
|
||||
for (auto value : {last, data_value}) {
|
||||
if (value >= cur_high)
|
||||
result[ai++] = 4;
|
||||
else if (value <= cur_low)
|
||||
result[ai++] = 0;
|
||||
else {
|
||||
result[ai++] = round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod);
|
||||
if (no_zero && horizon == height - 1 && i != -1 && result[ai] == 0) result[ai] = 1;
|
||||
}
|
||||
}
|
||||
//? Generate braille symbol from 5x5 2D vector
|
||||
graphs[current][horizon] += (height == 1 && result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])];
|
||||
}
|
||||
if (mult && i > data_offset) last = data_value;
|
||||
|
||||
}
|
||||
last = data_value;
|
||||
if (height == 1)
|
||||
out = (last < 1 ? Theme::c("inactive_fg") : Theme::g(color_gradient)[last]) + graphs[current][0];
|
||||
else {
|
||||
out.clear();
|
||||
for (int i : iota(0, height)) {
|
||||
if (i > 0) out += Mv::d(1) + Mv::l(width);
|
||||
out += (invert) ? Theme::g(color_gradient)[i * 100 / (height - 1)] : Theme::g(color_gradient)[100 - (i * 100 / (height - 1))];
|
||||
out += (invert) ? graphs[current][ (height - 1) - i] : graphs[current][i];
|
||||
}
|
||||
}
|
||||
out += Fx::reset;
|
||||
}
|
||||
void _create(const vector<long long>& data, int data_offset);
|
||||
|
||||
public:
|
||||
//* Set graph options and initialize with data
|
||||
void operator()(int width, int height, string color_gradient, const vector<long long>& data, bool invert = false, bool no_zero = false, long long max_value = 0, long long offset = 0) {
|
||||
graphs[true].clear(); graphs[false].clear();
|
||||
this->width = width; this->height = height;
|
||||
this->invert = invert; this->offset = offset;
|
||||
this->no_zero = no_zero;
|
||||
this->color_gradient = color_gradient;
|
||||
if (max_value == 0 && offset > 0) max_value = 100;
|
||||
this->max_value = max_value;
|
||||
int value_width = ceil((float)data.size() / 2);
|
||||
int data_offset = 0;
|
||||
if (value_width > width) data_offset = data.size() - width * 2;
|
||||
|
||||
//? Populate the two switching graph vectors and fill empty space if data size < width
|
||||
auto& graph_symbol = (invert) ? Symbols::graph_down : Symbols::graph_up;
|
||||
for (int i : iota(0, height * 2)) {
|
||||
graphs[(i % 2 != 0)].push_back((value_width < width) ? ((height == 1) ? Mv::r(1) : graph_symbol[0]) * (width - value_width) : "");
|
||||
}
|
||||
if (data.size() == 0) return;
|
||||
this->_create(data, data_offset);
|
||||
}
|
||||
void operator()(int width, int height, string color_gradient, const vector<long long>& data, string symbol = "default", bool invert = false, bool no_zero = false, long long max_value = 0, long long offset = 0);
|
||||
|
||||
//* Add last value from back of <data> and return string representation of graph
|
||||
string& operator()(const vector<long long>& data, bool data_same = false) {
|
||||
if (data_same) return out;
|
||||
|
||||
//? Make room for new characters on graph
|
||||
for (int i : iota(0, height)) {
|
||||
if (graphs[(!current)][i].starts_with(Fx::e)) graphs[current][i].erase(0, 4);
|
||||
else graphs[(!current)][i].erase(0, 3);
|
||||
}
|
||||
this->_create(data, (int)data.size() - 1);
|
||||
return out;
|
||||
}
|
||||
string& operator()(const vector<long long>& data, bool data_same = false);
|
||||
|
||||
//* Return string representation of graph
|
||||
string& operator()() {
|
||||
return out;
|
||||
}
|
||||
string& operator()();
|
||||
};
|
||||
|
||||
}
|
||||
@ -276,8 +102,4 @@ namespace Proc {
|
||||
|
||||
// Draw::BoxConf box;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
}
|
@ -16,8 +16,7 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#ifndef _btop_input_included_
|
||||
#define _btop_input_included_
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <robin_hood.h>
|
||||
@ -31,7 +30,7 @@ using namespace Tools;
|
||||
/* The input functions relies on the following std::cin options being set:
|
||||
cin.sync_with_stdio(false);
|
||||
cin.tie(NULL);
|
||||
These will automatically be set when running Term::init() from btop_tools.h
|
||||
These will automatically be set when running Term::init() from btop_tools.cpp
|
||||
*/
|
||||
|
||||
//* Functions and variables for handling keyboard and mouse input
|
||||
@ -113,6 +112,4 @@ namespace Input {
|
||||
last.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
411
src/btop_linux.cpp
Normal file
411
src/btop_linux.cpp
Normal file
@ -0,0 +1,411 @@
|
||||
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <list>
|
||||
#include <robin_hood.h>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <btop_linux.h>
|
||||
#include <btop_config.h>
|
||||
#include <btop_tools.h>
|
||||
|
||||
|
||||
|
||||
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
|
||||
std::round, std::string_literals::operator""s, robin_hood::unordered_flat_map;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
||||
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
||||
|
||||
namespace Tools {
|
||||
double system_uptime(){
|
||||
string upstr;
|
||||
ifstream pread("/proc/uptime");
|
||||
getline(pread, upstr, ' ');
|
||||
pread.close();
|
||||
return stod(upstr);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Proc {
|
||||
namespace {
|
||||
struct p_cache {
|
||||
string name, cmd, user;
|
||||
uint64_t cpu_t = 0, cpu_s = 0;
|
||||
string prefix = "";
|
||||
size_t depth = 0;
|
||||
bool collapsed = false;
|
||||
};
|
||||
unordered_flat_map<uint, p_cache> cache;
|
||||
unordered_flat_map<string, string> uid_user;
|
||||
fs::path passwd_path;
|
||||
fs::file_time_type passwd_time;
|
||||
uint counter = 0;
|
||||
long page_size;
|
||||
long clk_tck;
|
||||
|
||||
}
|
||||
|
||||
fs::path proc_path;
|
||||
uint64_t old_cputimes = 0;
|
||||
size_t numpids = 500;
|
||||
atomic<bool> stop (false);
|
||||
atomic<bool> collecting (false);
|
||||
vector<string> sort_vector = {
|
||||
"pid",
|
||||
"name",
|
||||
"command",
|
||||
"threads",
|
||||
"user",
|
||||
"memory",
|
||||
"cpu direct",
|
||||
"cpu lazy",
|
||||
};
|
||||
|
||||
//* Generate process tree list
|
||||
void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth=0, bool collapsed=false){
|
||||
auto cur_pos = out_procs.size();
|
||||
if (!collapsed)
|
||||
out_procs.push_back(cur_proc);
|
||||
int children = 0;
|
||||
for (auto& p : in_procs) {
|
||||
if (p.ppid == (int)cur_proc.pid) {
|
||||
children++;
|
||||
if (collapsed) {
|
||||
out_procs.back().cpu_p += p.cpu_p;
|
||||
out_procs.back().mem += p.mem;
|
||||
out_procs.back().threads += p.threads;
|
||||
_tree_gen(p, in_procs, out_procs, cur_depth + 1, collapsed);
|
||||
}
|
||||
else _tree_gen(p, in_procs, out_procs, cur_depth + 1, cache.at(cur_proc.pid).collapsed);
|
||||
}
|
||||
}
|
||||
if (collapsed) return;
|
||||
|
||||
if (out_procs.size() > cur_pos + 1 && !out_procs.back().prefix.ends_with("] ")) {
|
||||
std::string_view n_prefix = out_procs.back().prefix;
|
||||
n_prefix.remove_suffix(8);
|
||||
out_procs.back().prefix = (string)n_prefix + " └─ ";
|
||||
}
|
||||
|
||||
string prefix = " ├─ ";
|
||||
if (children > 0) prefix = (cache.at(cur_proc.pid).collapsed) ? "[+] " : "[-] ";
|
||||
|
||||
out_procs.at(cur_pos).prefix = " │ "s * cur_depth + prefix;
|
||||
}
|
||||
|
||||
vector<proc_info> current_procs;
|
||||
|
||||
//* Collects and sorts process information from /proc, saves to and returns reference to Proc::current_procs;
|
||||
vector<proc_info>& collect(){
|
||||
atomic_wait_set(collecting);
|
||||
auto& sorting = Config::getS("proc_sorting");
|
||||
auto& reverse = Config::getB("proc_reversed");
|
||||
auto& filter = Config::getS("proc_filter");
|
||||
auto& per_core = Config::getB("proc_per_core");
|
||||
auto& tree = Config::getB("proc_tree");
|
||||
ifstream pread;
|
||||
auto uptime = system_uptime();
|
||||
vector<proc_info> procs;
|
||||
vector<uint> pid_list;
|
||||
procs.reserve((numpids + 10));
|
||||
pid_list.reserve(numpids + 10);
|
||||
int npids = 0;
|
||||
int cmult = (per_core) ? Global::coreCount : 1;
|
||||
(void)tree;
|
||||
|
||||
//* Update uid_user map if /etc/passwd changed since last run
|
||||
if (!passwd_path.empty() && fs::last_write_time(passwd_path) != passwd_time) {
|
||||
string r_uid, r_user;
|
||||
passwd_time = fs::last_write_time(passwd_path);
|
||||
uid_user.clear();
|
||||
pread.open(passwd_path);
|
||||
if (pread.good()) {
|
||||
while (!pread.eof()){
|
||||
getline(pread, r_user, ':');
|
||||
pread.ignore(SSmax, ':');
|
||||
getline(pread, r_uid, ':');
|
||||
uid_user[r_uid] = r_user;
|
||||
pread.ignore(SSmax, '\n');
|
||||
}
|
||||
}
|
||||
pread.close();
|
||||
}
|
||||
|
||||
//* Get cpu total times from /proc/stat
|
||||
uint64_t cputimes = 0;
|
||||
pread.open(proc_path / "stat");
|
||||
if (pread.good()) {
|
||||
pread.ignore(SSmax, ' ');
|
||||
for (uint64_t times; pread >> times; cputimes += times);
|
||||
pread.close();
|
||||
}
|
||||
else return current_procs;
|
||||
|
||||
//* Iterate over all pids in /proc
|
||||
for (auto& d: fs::directory_iterator(proc_path)){
|
||||
if (pread.is_open()) pread.close();
|
||||
if (stop.load()) {
|
||||
collecting.store(false);
|
||||
stop.store(false);
|
||||
return current_procs;
|
||||
}
|
||||
|
||||
bool new_cache = false;
|
||||
string pid_str = d.path().filename();
|
||||
if (d.is_directory() && isdigit(pid_str[0])) {
|
||||
npids++;
|
||||
proc_info new_proc (stoul(pid_str));
|
||||
pid_list.push_back(new_proc.pid);
|
||||
|
||||
//* Cache program name, command and username
|
||||
if (!cache.contains(new_proc.pid)) {
|
||||
string name, cmd, user;
|
||||
new_cache = true;
|
||||
pread.open(d.path() / "comm");
|
||||
if (pread.good()) {
|
||||
getline(pread, name);
|
||||
pread.close();
|
||||
}
|
||||
else continue;
|
||||
|
||||
pread.open(d.path() / "cmdline");
|
||||
if (pread.good()) {
|
||||
string tmpstr = "";
|
||||
while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " ";
|
||||
pread.close();
|
||||
if (!cmd.empty()) cmd.pop_back();
|
||||
}
|
||||
else continue;
|
||||
|
||||
pread.open(d.path() / "status");
|
||||
if (pread.good()) {
|
||||
string uid;
|
||||
while (!pread.eof()){
|
||||
string line;
|
||||
getline(pread, line, ':');
|
||||
if (line == "Uid") {
|
||||
pread.ignore();
|
||||
getline(pread, uid, '\t');
|
||||
break;
|
||||
} else {
|
||||
pread.ignore(SSmax, '\n');
|
||||
}
|
||||
}
|
||||
pread.close();
|
||||
user = (!uid.empty() && uid_user.contains(uid)) ? uid_user.at(uid) : uid;
|
||||
}
|
||||
else continue;
|
||||
cache[new_proc.pid] = {name, cmd, user};
|
||||
}
|
||||
|
||||
//* Match filter if defined
|
||||
if (!filter.empty()
|
||||
&& pid_str.find(filter) == string::npos
|
||||
&& cache[new_proc.pid].name.find(filter) == string::npos
|
||||
&& cache[new_proc.pid].cmd.find(filter) == string::npos
|
||||
&& cache[new_proc.pid].user.find(filter) == string::npos) {
|
||||
if (new_cache) cache.erase(new_proc.pid);
|
||||
continue;
|
||||
}
|
||||
new_proc.name = cache[new_proc.pid].name;
|
||||
new_proc.cmd = cache[new_proc.pid].cmd;
|
||||
new_proc.user = cache[new_proc.pid].user;
|
||||
|
||||
//* Parse /proc/[pid]/stat
|
||||
pread.open(d.path() / "stat");
|
||||
if (pread.good()) {
|
||||
string instr;
|
||||
getline(pread, instr);
|
||||
pread.close();
|
||||
size_t s_pos = 0, c_pos = 0, s_count = 0;
|
||||
uint64_t cpu_t = 0;
|
||||
|
||||
//? Skip pid and comm field and find comm fields closing ')'
|
||||
s_pos = instr.find_last_of(')') + 2;
|
||||
if (s_pos == string::npos) continue;
|
||||
|
||||
do {
|
||||
c_pos = instr.find(' ', s_pos);
|
||||
if (c_pos == string::npos) break;
|
||||
|
||||
switch (s_count) {
|
||||
case 0: { //? Process state
|
||||
new_proc.state = instr[s_pos];
|
||||
break;
|
||||
}
|
||||
case 1: { //? Process parent pid
|
||||
new_proc.ppid = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 11: { //? Process utime
|
||||
cpu_t = stoull(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 12: { //? Process stime
|
||||
cpu_t += stoull(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 16: { //? Process nice value
|
||||
new_proc.p_nice = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 17: { //? Process number of threads
|
||||
new_proc.threads = stoul(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 19: { //? Cache cpu seconds
|
||||
if (new_cache) cache[new_proc.pid].cpu_s = stoull(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 36: { //? CPU number last executed on
|
||||
new_proc.cpu_n = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
}
|
||||
s_pos = c_pos + 1;
|
||||
} while (s_count++ < 36);
|
||||
|
||||
if (s_count < 19) continue;
|
||||
|
||||
//? Process cpu usage since last update
|
||||
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0;
|
||||
|
||||
//? Process cumulative cpu usage since process start
|
||||
new_proc.cpu_c = ((double)cpu_t / clk_tck) / (uptime - (cache[new_proc.pid].cpu_s / clk_tck));
|
||||
|
||||
//? Update cache with latest cpu times
|
||||
cache[new_proc.pid].cpu_t = cpu_t;
|
||||
}
|
||||
else continue;
|
||||
|
||||
//* Get RSS memory in bytes from /proc/[pid]/statm
|
||||
pread.open(d.path() / "statm");
|
||||
if (pread.good()) {
|
||||
pread.ignore(SSmax, ' ');
|
||||
pread >> new_proc.mem;
|
||||
pread.close();
|
||||
new_proc.mem *= page_size;
|
||||
}
|
||||
|
||||
//* Create proc_info
|
||||
procs.push_back(new_proc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//* Sort processes
|
||||
rng::sort(procs, [sortint = v_index(sort_vector, sorting), &reverse](proc_info& a, proc_info& b) {
|
||||
switch (sortint) {
|
||||
case 0: return (reverse) ? a.pid < b.pid : a.pid > b.pid;
|
||||
case 1: return (reverse) ? a.name < b.name : a.name > b.name;
|
||||
case 2: return (reverse) ? a.cmd < b.cmd : a.cmd > b.cmd;
|
||||
case 3: return (reverse) ? a.threads < b.threads : a.threads > b.threads;
|
||||
case 4: return (reverse) ? a.user < b.user : a.user > b.user;
|
||||
case 5: return (reverse) ? a.mem < b.mem : a.mem > b.mem;
|
||||
case 6: return (reverse) ? a.cpu_p < b.cpu_p : a.cpu_p > b.cpu_p;
|
||||
case 7: return (reverse) ? a.cpu_c < b.cpu_c : a.cpu_c > b.cpu_c;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
|
||||
if (sorting == "cpu lazy" && !tree && !reverse) {
|
||||
double max = 10.0, target = 30.0;
|
||||
for (size_t i = 0, offset = 0; i < procs.size(); i++) {
|
||||
if (i <= 5 && procs[i].cpu_p > max)
|
||||
max = procs[i].cpu_p;
|
||||
else if (i == 6)
|
||||
target = (max > 30.0) ? max : 10.0;
|
||||
if (i == offset && procs[i].cpu_p > 30.0)
|
||||
offset++;
|
||||
else if
|
||||
(procs[i].cpu_p > target) rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
//* Generate tree view if enabled
|
||||
if (tree) {
|
||||
vector<proc_info> tree_procs;
|
||||
auto min_pid = rng::min(procs, rng::less{}, &proc_info::ppid).ppid;
|
||||
for (auto& p : procs) {
|
||||
if (p.ppid == min_pid) _tree_gen(p, procs, tree_procs);
|
||||
}
|
||||
procs.swap(tree_procs);
|
||||
}
|
||||
|
||||
|
||||
//* Clear dead processes from cache at a regular interval
|
||||
if (++counter >= 10000 || ((int)cache.size() > npids + 100)) {
|
||||
counter = 0;
|
||||
unordered_flat_map<uint, p_cache> r_cache;
|
||||
r_cache.reserve(pid_list.size());
|
||||
for (auto& p : pid_list) {
|
||||
if (cache.contains(p)) r_cache[p] = cache.at(p);
|
||||
}
|
||||
cache.swap(r_cache);
|
||||
}
|
||||
old_cputimes = cputimes;
|
||||
current_procs.swap(procs);
|
||||
numpids = npids;
|
||||
collecting.store(false);
|
||||
return current_procs;
|
||||
}
|
||||
|
||||
//* Initialize needed variables for collect
|
||||
void init(){
|
||||
proc_path = (fs::is_directory(fs::path("/proc")) && access("/proc", R_OK) != -1) ? "/proc" : "";
|
||||
if (proc_path.empty()) {
|
||||
string errmsg = "Proc filesystem not found or no permission to read from it!";
|
||||
Logger::error(errmsg);
|
||||
std::cout << "ERROR: " << errmsg << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path;
|
||||
if (passwd_path.empty()) Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
|
||||
|
||||
page_size = sysconf(_SC_PAGE_SIZE);
|
||||
if (page_size <= 0) {
|
||||
page_size = 4096;
|
||||
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
|
||||
}
|
||||
|
||||
clk_tck = sysconf(_SC_CLK_TCK);
|
||||
if (clk_tck <= 0) {
|
||||
clk_tck = 100;
|
||||
Logger::warning("Could not get system clocks per second. Defaulting to 100, processes cpu usage might be incorrect.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
392
src/btop_linux.h
392
src/btop_linux.h
@ -16,82 +16,30 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#ifndef _btop_linux_included_
|
||||
#define _btop_linux_included_
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
#include <streambuf>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <list>
|
||||
#include <robin_hood.h>
|
||||
|
||||
#include <unistd.h>
|
||||
using std::string, std::vector, std::atomic;
|
||||
|
||||
#include <btop_config.h>
|
||||
#include <btop_tools.h>
|
||||
|
||||
|
||||
|
||||
using std::string, std::vector, std::array, std::ifstream, std::atomic, std::numeric_limits, std::streamsize;
|
||||
using std::cout, std::flush, std::endl, std::string_literals::operator""s;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Tools;
|
||||
|
||||
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
||||
namespace Global {
|
||||
extern int coreCount;
|
||||
}
|
||||
|
||||
namespace Tools {
|
||||
double system_uptime(){
|
||||
string upstr;
|
||||
ifstream pread("/proc/uptime");
|
||||
getline(pread, upstr, ' ');
|
||||
pread.close();
|
||||
return stod(upstr);
|
||||
}
|
||||
double system_uptime();
|
||||
}
|
||||
|
||||
namespace Proc {
|
||||
namespace {
|
||||
struct p_cache {
|
||||
string name, cmd, user;
|
||||
uint64_t cpu_t = 0, cpu_s = 0;
|
||||
string prefix = "";
|
||||
size_t depth = 0;
|
||||
int collapsed = -1;
|
||||
};
|
||||
unordered_flat_map<uint, p_cache> cache;
|
||||
unordered_flat_map<string, string> uid_user;
|
||||
fs::path passwd_path;
|
||||
fs::file_time_type passwd_time;
|
||||
uint counter = 0;
|
||||
long page_size;
|
||||
long clk_tck;
|
||||
|
||||
}
|
||||
|
||||
fs::path proc_path;
|
||||
uint64_t old_cputimes = 0;
|
||||
size_t numpids = 500;
|
||||
atomic<bool> stop (false);
|
||||
atomic<bool> collecting (false);
|
||||
atomic<bool> drawing (false);
|
||||
vector<string> sort_vector = {
|
||||
"pid",
|
||||
"name",
|
||||
"command",
|
||||
"threads",
|
||||
"user",
|
||||
"memory",
|
||||
"cpu direct",
|
||||
"cpu lazy",
|
||||
};
|
||||
extern std::filesystem::path proc_path;
|
||||
extern size_t numpids;
|
||||
extern atomic<bool> stop;
|
||||
extern atomic<bool> collecting;
|
||||
extern vector<string> sort_vector;
|
||||
|
||||
//* Container for process information
|
||||
struct proc_info {
|
||||
@ -107,321 +55,11 @@ namespace Proc {
|
||||
string prefix = "";
|
||||
};
|
||||
|
||||
//* Generate process tree list
|
||||
void _tree_gen(proc_info& cur_proc, vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth=0, bool collapsed=false){
|
||||
auto cur_pos = out_procs.size();
|
||||
if (!collapsed)
|
||||
out_procs.push_back(cur_proc);
|
||||
int children = 0;
|
||||
for (auto& p : in_procs) {
|
||||
if (p.ppid == (int)cur_proc.pid) {
|
||||
children++;
|
||||
if (collapsed) {
|
||||
out_procs.back().cpu_p += p.cpu_p;
|
||||
out_procs.back().mem += p.mem;
|
||||
out_procs.back().threads += p.threads;
|
||||
_tree_gen(p, in_procs, out_procs, cur_depth + 1, collapsed);
|
||||
}
|
||||
else _tree_gen(p, in_procs, out_procs, cur_depth + 1, (cache.at(cur_proc.pid).collapsed == 1));
|
||||
}
|
||||
}
|
||||
if (collapsed) return;
|
||||
|
||||
if (out_procs.size() > cur_pos + 1 && !out_procs.back().prefix.ends_with("] ")) {
|
||||
std::string_view n_prefix = out_procs.back().prefix;
|
||||
n_prefix.remove_suffix(8);
|
||||
out_procs.back().prefix = (string)n_prefix + " └─ ";
|
||||
}
|
||||
|
||||
string prefix = " ├─ ";
|
||||
if (children > 0) prefix = (cache.at(cur_proc.pid).collapsed == 1) ? "[+] " : "[-] ";
|
||||
|
||||
out_procs.at(cur_pos).prefix = " │ "s * cur_depth + prefix;
|
||||
}
|
||||
|
||||
vector<proc_info> current_procs;
|
||||
|
||||
extern vector<proc_info> current_procs;
|
||||
|
||||
//* Collects and sorts process information from /proc, saves to and returns reference to Proc::current_procs;
|
||||
auto& collect(){
|
||||
atomic_wait_set(collecting);
|
||||
auto& sorting = Config::getS("proc_sorting");
|
||||
auto& reverse = Config::getB("proc_reversed");
|
||||
auto& filter = Config::getS("proc_filter");
|
||||
auto& per_core = Config::getB("proc_per_core");
|
||||
auto& tree = Config::getB("proc_tree");
|
||||
ifstream pread;
|
||||
auto uptime = system_uptime();
|
||||
vector<proc_info> procs;
|
||||
vector<uint> pid_list;
|
||||
procs.reserve((numpids + 10));
|
||||
pid_list.reserve(numpids + 10);
|
||||
int npids = 0;
|
||||
int cmult = (per_core) ? Global::coreCount : 1;
|
||||
(void)tree;
|
||||
|
||||
//* Update uid_user map if /etc/passwd changed since last run
|
||||
if (!passwd_path.empty() && fs::last_write_time(passwd_path) != passwd_time) {
|
||||
string r_uid, r_user;
|
||||
passwd_time = fs::last_write_time(passwd_path);
|
||||
uid_user.clear();
|
||||
pread.open(passwd_path);
|
||||
if (pread.good()) {
|
||||
while (!pread.eof()){
|
||||
getline(pread, r_user, ':');
|
||||
pread.ignore(SSmax, ':');
|
||||
getline(pread, r_uid, ':');
|
||||
uid_user[r_uid] = r_user;
|
||||
pread.ignore(SSmax, '\n');
|
||||
}
|
||||
}
|
||||
pread.close();
|
||||
}
|
||||
|
||||
//* Get cpu total times from /proc/stat
|
||||
uint64_t cputimes = 0;
|
||||
pread.open(proc_path / "stat");
|
||||
if (pread.good()) {
|
||||
pread.ignore(SSmax, ' ');
|
||||
for (uint64_t times; pread >> times; cputimes += times);
|
||||
pread.close();
|
||||
}
|
||||
else return current_procs;
|
||||
|
||||
//* Iterate over all pids in /proc
|
||||
for (auto& d: fs::directory_iterator(proc_path)){
|
||||
if (pread.is_open()) pread.close();
|
||||
if (stop.load()) {
|
||||
collecting.store(false);
|
||||
stop.store(false);
|
||||
return current_procs;
|
||||
}
|
||||
|
||||
bool new_cache = false;
|
||||
string pid_str = d.path().filename();
|
||||
if (d.is_directory() && isdigit(pid_str[0])) {
|
||||
npids++;
|
||||
proc_info new_proc (stoul(pid_str));
|
||||
pid_list.push_back(new_proc.pid);
|
||||
|
||||
//* Cache program name, command and username
|
||||
if (!cache.contains(new_proc.pid)) {
|
||||
string name, cmd, user;
|
||||
new_cache = true;
|
||||
pread.open(d.path() / "comm");
|
||||
if (pread.good()) {
|
||||
getline(pread, name);
|
||||
pread.close();
|
||||
}
|
||||
else continue;
|
||||
|
||||
pread.open(d.path() / "cmdline");
|
||||
if (pread.good()) {
|
||||
string tmpstr = "";
|
||||
while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " ";
|
||||
pread.close();
|
||||
if (!cmd.empty()) cmd.pop_back();
|
||||
}
|
||||
else continue;
|
||||
|
||||
pread.open(d.path() / "status");
|
||||
if (pread.good()) {
|
||||
string uid;
|
||||
while (!pread.eof()){
|
||||
string line;
|
||||
getline(pread, line, ':');
|
||||
if (line == "Uid") {
|
||||
pread.ignore();
|
||||
getline(pread, uid, '\t');
|
||||
break;
|
||||
} else {
|
||||
pread.ignore(SSmax, '\n');
|
||||
}
|
||||
}
|
||||
pread.close();
|
||||
user = (!uid.empty() && uid_user.contains(uid)) ? uid_user.at(uid) : uid;
|
||||
}
|
||||
else continue;
|
||||
cache[new_proc.pid] = {name, cmd, user};
|
||||
}
|
||||
|
||||
//* Match filter if defined
|
||||
if (!filter.empty()
|
||||
&& pid_str.find(filter) == string::npos
|
||||
&& cache[new_proc.pid].name.find(filter) == string::npos
|
||||
&& cache[new_proc.pid].cmd.find(filter) == string::npos
|
||||
&& cache[new_proc.pid].user.find(filter) == string::npos) {
|
||||
if (new_cache) cache.erase(new_proc.pid);
|
||||
continue;
|
||||
}
|
||||
new_proc.name = cache[new_proc.pid].name;
|
||||
new_proc.cmd = cache[new_proc.pid].cmd;
|
||||
new_proc.user = cache[new_proc.pid].user;
|
||||
|
||||
//* Parse /proc/[pid]/stat
|
||||
pread.open(d.path() / "stat");
|
||||
if (pread.good()) {
|
||||
string instr;
|
||||
getline(pread, instr);
|
||||
pread.close();
|
||||
size_t s_pos = 0, c_pos = 0, s_count = 0;
|
||||
uint64_t cpu_t = 0;
|
||||
|
||||
//? Skip pid and comm field and find comm fields closing ')'
|
||||
s_pos = instr.find_last_of(')') + 2;
|
||||
if (s_pos == string::npos) continue;
|
||||
|
||||
do {
|
||||
c_pos = instr.find(' ', s_pos);
|
||||
if (c_pos == string::npos) break;
|
||||
|
||||
switch (s_count) {
|
||||
case 0: { //? Process state
|
||||
new_proc.state = instr[s_pos];
|
||||
break;
|
||||
}
|
||||
case 1: { //? Process parent pid
|
||||
new_proc.ppid = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 11: { //? Process utime
|
||||
cpu_t = stoull(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 12: { //? Process stime
|
||||
cpu_t += stoull(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 16: { //? Process nice value
|
||||
new_proc.p_nice = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 17: { //? Process number of threads
|
||||
new_proc.threads = stoul(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 19: { //? Cache cpu seconds
|
||||
if (new_cache) cache[new_proc.pid].cpu_s = stoull(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 36: { //? CPU number last executed on
|
||||
new_proc.cpu_n = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
}
|
||||
s_pos = c_pos + 1;
|
||||
} while (s_count++ < 36);
|
||||
|
||||
if (s_count < 19) continue;
|
||||
|
||||
//? Process cpu usage since last update
|
||||
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0;
|
||||
|
||||
//? Process cumulative cpu usage since process start
|
||||
new_proc.cpu_c = ((double)cpu_t / clk_tck) / (uptime - (cache[new_proc.pid].cpu_s / clk_tck));
|
||||
|
||||
//? Update cache with latest cpu times
|
||||
cache[new_proc.pid].cpu_t = cpu_t;
|
||||
}
|
||||
else continue;
|
||||
|
||||
//* Get RSS memory in bytes from /proc/[pid]/statm
|
||||
pread.open(d.path() / "statm");
|
||||
if (pread.good()) {
|
||||
pread.ignore(SSmax, ' ');
|
||||
pread >> new_proc.mem;
|
||||
pread.close();
|
||||
new_proc.mem *= page_size;
|
||||
}
|
||||
|
||||
//* Create proc_info
|
||||
procs.push_back(new_proc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//* Sort processes
|
||||
std::ranges::sort(procs, [sortint = v_index(sort_vector, sorting), &reverse](proc_info& a, proc_info& b) {
|
||||
switch (sortint) {
|
||||
case 0: return (reverse) ? a.pid < b.pid : a.pid > b.pid;
|
||||
case 1: return (reverse) ? a.name < b.name : a.name > b.name;
|
||||
case 2: return (reverse) ? a.cmd < b.cmd : a.cmd > b.cmd;
|
||||
case 3: return (reverse) ? a.threads < b.threads : a.threads > b.threads;
|
||||
case 4: return (reverse) ? a.user < b.user : a.user > b.user;
|
||||
case 5: return (reverse) ? a.mem < b.mem : a.mem > b.mem;
|
||||
case 6: return (reverse) ? a.cpu_p < b.cpu_p : a.cpu_p > b.cpu_p;
|
||||
case 7: return (reverse) ? a.cpu_c < b.cpu_c : a.cpu_c > b.cpu_c;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
//* When using "cpu lazy" sorting push processes with high cpu usage to the front regardless of cumulative usage
|
||||
if (sorting == "cpu lazy" && !reverse) {
|
||||
double max = 10.0, target = 30.0;
|
||||
for (size_t i = 0, offset = 0; i < procs.size(); i++) {
|
||||
if (i <= 5 && procs[i].cpu_p > max) max = procs[i].cpu_p;
|
||||
else if (i == 6) target = (max > 30.0) ? max : 10.0;
|
||||
if (i == offset && procs[i].cpu_p > 30.0) offset++;
|
||||
else if (procs[i].cpu_p > target) rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
//* Generate tree view if enabled
|
||||
if (tree) {
|
||||
auto min_ppid = std::ranges::min(procs, [](proc_info& a, proc_info& b) { return a.ppid < b.ppid; }).ppid;
|
||||
vector<proc_info> tree_procs;
|
||||
for (auto& p : procs) {
|
||||
if (p.ppid == min_ppid) _tree_gen(p, procs, tree_procs);
|
||||
}
|
||||
procs.swap(tree_procs);
|
||||
}
|
||||
|
||||
|
||||
//* Clear dead processes from cache at a regular interval
|
||||
if (++counter >= 10000 || ((int)cache.size() > npids + 100)) {
|
||||
counter = 0;
|
||||
unordered_flat_map<uint, p_cache> r_cache;
|
||||
r_cache.reserve(pid_list.size());
|
||||
for (auto& p : pid_list) {
|
||||
if (cache.contains(p)) r_cache[p] = cache.at(p);
|
||||
}
|
||||
cache.swap(r_cache);
|
||||
}
|
||||
old_cputimes = cputimes;
|
||||
atomic_wait(drawing);
|
||||
current_procs.swap(procs);
|
||||
numpids = npids;
|
||||
collecting.store(false);
|
||||
return current_procs;
|
||||
}
|
||||
vector<proc_info>& collect();
|
||||
|
||||
//* Initialize needed variables for collect
|
||||
void init(){
|
||||
proc_path = (fs::is_directory(fs::path("/proc")) && access("/proc", R_OK) != -1) ? "/proc" : "";
|
||||
if (proc_path.empty()) {
|
||||
string errmsg = "Proc filesystem not found or no permission to read from it!";
|
||||
Logger::error(errmsg);
|
||||
cout << "ERROR: " << errmsg << endl;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path;
|
||||
if (passwd_path.empty()) Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
|
||||
|
||||
page_size = sysconf(_SC_PAGE_SIZE);
|
||||
if (page_size <= 0) {
|
||||
page_size = 4096;
|
||||
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
|
||||
}
|
||||
|
||||
clk_tck = sysconf(_SC_CLK_TCK);
|
||||
if (clk_tck <= 0) {
|
||||
clk_tck = 100;
|
||||
Logger::warning("Could not get system clocks per second. Defaulting to 100, processes cpu usage might be incorrect.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
void init();
|
||||
}
|
@ -16,8 +16,7 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#ifndef _btop_menu_included_
|
||||
#define _btop_menu_included_ 1
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -73,7 +72,3 @@ namespace Menu {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
288
src/btop_theme.cpp
Normal file
288
src/btop_theme.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
|
||||
#include <btop_tools.h>
|
||||
#include <btop_config.h>
|
||||
#include <btop_theme.h>
|
||||
|
||||
using std::round, std::vector, robin_hood::unordered_flat_map, std::stoi, std::views::iota, std::array,
|
||||
std::clamp, std::max, std::min, std::ceil, std::to_string;
|
||||
using namespace Tools;
|
||||
namespace rng = std::ranges;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Theme {
|
||||
|
||||
fs::path theme_dir;
|
||||
fs::path user_theme_dir;
|
||||
|
||||
const unordered_flat_map<string, string> Default_theme = {
|
||||
{ "main_bg", "#00" },
|
||||
{ "main_fg", "#cc" },
|
||||
{ "title", "#ee" },
|
||||
{ "hi_fg", "#969696" },
|
||||
{ "selected_bg", "#7e2626" },
|
||||
{ "selected_fg", "#ee" },
|
||||
{ "inactive_fg", "#40" },
|
||||
{ "graph_text", "#60" },
|
||||
{ "meter_bg", "#40" },
|
||||
{ "proc_misc", "#0de756" },
|
||||
{ "cpu_box", "#3d7b46" },
|
||||
{ "mem_box", "#8a882e" },
|
||||
{ "net_box", "#423ba5" },
|
||||
{ "proc_box", "#923535" },
|
||||
{ "div_line", "#30" },
|
||||
{ "temp_start", "#4897d4" },
|
||||
{ "temp_mid", "#5474e8" },
|
||||
{ "temp_end", "#ff40b6" },
|
||||
{ "cpu_start", "#50f095" },
|
||||
{ "cpu_mid", "#f2e266" },
|
||||
{ "cpu_end", "#fa1e1e" },
|
||||
{ "free_start", "#223014" },
|
||||
{ "free_mid", "#b5e685" },
|
||||
{ "free_end", "#dcff85" },
|
||||
{ "cached_start", "#0b1a29" },
|
||||
{ "cached_mid", "#74e6fc" },
|
||||
{ "cached_end", "#26c5ff" },
|
||||
{ "available_start", "#292107" },
|
||||
{ "available_mid", "#ffd77a" },
|
||||
{ "available_end", "#ffb814" },
|
||||
{ "used_start", "#3b1f1c" },
|
||||
{ "used_mid", "#d9626d" },
|
||||
{ "used_end", "#ff4769" },
|
||||
{ "download_start", "#231a63" },
|
||||
{ "download_mid", "#4f43a3" },
|
||||
{ "download_end", "#b0a9de" },
|
||||
{ "upload_start", "#510554" },
|
||||
{ "upload_mid", "#7d4180" },
|
||||
{ "upload_end", "#dcafde" },
|
||||
{ "process_start", "#80d0a3" },
|
||||
{ "process_mid", "#dcd179" },
|
||||
{ "process_end", "#d45454" }
|
||||
};
|
||||
|
||||
namespace {
|
||||
//* Convert 24-bit colors to 256 colors using 6x6x6 color cube
|
||||
int truecolor_to_256(int r, int g, int b){
|
||||
if (round((double)r / 11) == round((double)g / 11) && round((double)g / 11) == round((double)b / 11)) {
|
||||
return 232 + round((double)r / 11);
|
||||
} else {
|
||||
return round((double)r / 51) * 36 + round((double)g / 51) * 6 + round((double)b / 51) + 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string hex_to_color(string hexa, bool t_to_256, string depth){
|
||||
if (hexa.size() > 1){
|
||||
hexa.erase(0, 1);
|
||||
for (auto& c : hexa) if (!isxdigit(c)) {
|
||||
Logger::error("Invalid hex value: " + hexa);
|
||||
return "";
|
||||
}
|
||||
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
|
||||
|
||||
if (hexa.size() == 2){
|
||||
int h_int = stoi(hexa, 0, 16);
|
||||
if (t_to_256){
|
||||
return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m";
|
||||
} else {
|
||||
string h_str = to_string(h_int);
|
||||
return pre + h_str + ";" + h_str + ";" + h_str + "m";
|
||||
}
|
||||
}
|
||||
else if (hexa.size() == 6){
|
||||
if (t_to_256){
|
||||
return pre + to_string(truecolor_to_256(
|
||||
stoi(hexa.substr(0, 2), 0, 16),
|
||||
stoi(hexa.substr(2, 2), 0, 16),
|
||||
stoi(hexa.substr(4, 2), 0, 16))) + "m";
|
||||
} else {
|
||||
return pre +
|
||||
to_string(stoi(hexa.substr(0, 2), 0, 16)) + ";" +
|
||||
to_string(stoi(hexa.substr(2, 2), 0, 16)) + ";" +
|
||||
to_string(stoi(hexa.substr(4, 2), 0, 16)) + "m";
|
||||
}
|
||||
}
|
||||
else Logger::error("Invalid size of hex value: " + hexa);
|
||||
}
|
||||
else Logger::error("Hex value missing: " + hexa);
|
||||
return "";
|
||||
}
|
||||
|
||||
string dec_to_color(int r, int g, int b, bool t_to_256, string depth){
|
||||
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
|
||||
r = std::clamp(r, 0, 255);
|
||||
g = std::clamp(g, 0, 255);
|
||||
b = std::clamp(b, 0, 255);
|
||||
if (t_to_256) return pre + to_string(truecolor_to_256(r, g, b)) + "m";
|
||||
else return pre + to_string(r) + ";" + to_string(g) + ";" + to_string(b) + "m";
|
||||
}
|
||||
|
||||
array<int, 3> esc_to_rgb(string c_string){
|
||||
array<int, 3> rgb = {-1, -1, -1};
|
||||
if (c_string.size() >= 14){
|
||||
c_string.erase(0, 7);
|
||||
auto c_split = ssplit(c_string, ';');
|
||||
if (c_split.size() == 3){
|
||||
rgb[0] = stoi(c_split[0]);
|
||||
rgb[1] = stoi(c_split[1]);
|
||||
rgb[2] = stoi(c_split[2].erase(c_split[2].size()));
|
||||
}
|
||||
}
|
||||
return rgb;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
unordered_flat_map<string, string> colors;
|
||||
unordered_flat_map<string, array<int, 3>> rgbs;
|
||||
unordered_flat_map<string, array<string, 101>> gradients;
|
||||
|
||||
//* Convert hex color to a array of decimals
|
||||
array<int, 3> hex_to_dec(string hexa){
|
||||
if (hexa.size() > 1){
|
||||
hexa.erase(0, 1);
|
||||
for (auto& c : hexa) if (!isxdigit(c)) return array<int, 3>{-1, -1, -1};
|
||||
|
||||
if (hexa.size() == 2){
|
||||
int h_int = stoi(hexa, 0, 16);
|
||||
return array<int, 3>{h_int, h_int, h_int};
|
||||
}
|
||||
else if (hexa.size() == 6){
|
||||
return array<int, 3>{
|
||||
stoi(hexa.substr(0, 2), 0, 16),
|
||||
stoi(hexa.substr(2, 2), 0, 16),
|
||||
stoi(hexa.substr(4, 2), 0, 16)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {-1 ,-1 ,-1};
|
||||
}
|
||||
|
||||
//* Generate colors and rgb decimal vectors for the theme
|
||||
void generateColors(unordered_flat_map<string, string>& source){
|
||||
vector<string> t_rgb;
|
||||
string depth;
|
||||
bool t_to_256 = !Config::getB("truecolor");
|
||||
colors.clear(); rgbs.clear();
|
||||
for (auto& [name, color] : Default_theme) {
|
||||
depth = (name.ends_with("bg") && name != "meter_bg") ? "bg" : "fg";
|
||||
if (source.contains(name)) {
|
||||
if (source.at(name)[0] == '#') {
|
||||
colors[name] = hex_to_color(source.at(name), t_to_256, depth);
|
||||
rgbs[name] = hex_to_dec(source.at(name));
|
||||
}
|
||||
else {
|
||||
t_rgb = ssplit(source.at(name));
|
||||
if (t_rgb.size() != 3)
|
||||
Logger::error("Invalid RGB decimal value: \"" + source.at(name) + "\"");
|
||||
else {
|
||||
colors[name] = dec_to_color(stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2]), t_to_256, depth);
|
||||
rgbs[name] = array<int, 3>{stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2])};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (colors[name].empty()) {
|
||||
Logger::info("Missing color value for \"" + name + "\". Using value from default.");
|
||||
colors[name] = hex_to_color(color, t_to_256, depth);
|
||||
rgbs[name] = array<int, 3>{-1, -1, -1};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//* Generate color gradients from two or three colors, 101 values indexed 0-100
|
||||
void generateGradients(){
|
||||
gradients.clear();
|
||||
array<string, 101> c_gradient;
|
||||
bool t_to_256 = !Config::getB("truecolor");
|
||||
for (auto& [name, source_arr] : rgbs) {
|
||||
if (!name.ends_with("_start")) continue;
|
||||
array<array<int, 3>, 101> dec_arr;
|
||||
dec_arr[0][0] = -1;
|
||||
string wname = rtrim(name, "_start");
|
||||
array<array<int, 3>, 3> rgb_arr = {source_arr, rgbs[wname + "_mid"], rgbs[wname + "_end"]};
|
||||
|
||||
//? Only start iteration if gradient has a _end color value defined
|
||||
if (rgb_arr[2][0] >= 0) {
|
||||
|
||||
//? Split iteration in two passes of 50 + 51 instead of 101 if gradient has _start, _mid and _end values defined
|
||||
int rng = (rgb_arr[1][0] >= 0) ? 50 : 100;
|
||||
for (int rgb : iota(0, 3)){
|
||||
int arr1 = 0, offset = 0;
|
||||
int arr2 = (rng == 50) ? 1 : 2;
|
||||
for (int i : iota(0, 101)) {
|
||||
dec_arr[i][rgb] = rgb_arr[arr1][rgb] + (i - offset) * (rgb_arr[arr2][rgb] - rgb_arr[arr1][rgb]) / rng;
|
||||
|
||||
//? Switch source arrays from _start/_mid to _mid/_end at 50 passes if _mid is defined
|
||||
if (i == rng) { ++arr1; ++arr2; offset = 50;}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dec_arr[0][0] != -1) {
|
||||
int y = 0;
|
||||
for (auto& arr : dec_arr) c_gradient[y++] = dec_to_color(arr[0], arr[1], arr[2], t_to_256);
|
||||
}
|
||||
else {
|
||||
//? If only _start was defined fill array with _start color
|
||||
c_gradient.fill(colors[name]);
|
||||
}
|
||||
gradients[wname].swap(c_gradient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//* Set current theme using <source> map
|
||||
void set(unordered_flat_map<string, string> source){
|
||||
generateColors(source);
|
||||
generateGradients();
|
||||
Term::fg = colors.at("main_fg");
|
||||
Term::bg = colors.at("main_bg");
|
||||
Fx::reset = Fx::reset_base + Term::fg + Term::bg;
|
||||
}
|
||||
|
||||
//* Return escape code for color <name>
|
||||
const string& c(string name){
|
||||
return colors.at(name);
|
||||
}
|
||||
|
||||
//* Return array of escape codes for color gradient <name>
|
||||
const array<string, 101>& g(string name){
|
||||
return gradients.at(name);
|
||||
}
|
||||
|
||||
//* Return array of red, green and blue in decimal for color <name>
|
||||
const std::array<int, 3>& dec(string name){
|
||||
return rgbs.at(name);
|
||||
}
|
||||
|
||||
robin_hood::unordered_flat_map<string, string>& test_colors(){
|
||||
return colors;
|
||||
}
|
||||
robin_hood::unordered_flat_map<string, std::array<string, 101>>& test_gradients(){
|
||||
return gradients;
|
||||
}
|
||||
|
||||
}
|
262
src/btop_theme.h
262
src/btop_theme.h
@ -16,277 +16,49 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#ifndef _btop_theme_included_
|
||||
#define _btop_theme_included_
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <robin_hood.h>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
#include <btop_tools.h>
|
||||
#include <btop_config.h>
|
||||
|
||||
using std::string, std::round, std::vector, robin_hood::unordered_flat_map, std::stoi, std::views::iota, std::array, std::clamp, std::max, std::min, std::ceil;
|
||||
using namespace Tools;
|
||||
using std::string;
|
||||
|
||||
namespace Theme {
|
||||
extern std::filesystem::path theme_dir;
|
||||
extern std::filesystem::path user_theme_dir;
|
||||
|
||||
fs::path theme_dir;
|
||||
fs::path user_theme_dir;
|
||||
|
||||
const unordered_flat_map<string, string> Default_theme = {
|
||||
{ "main_bg", "#00" },
|
||||
{ "main_fg", "#cc" },
|
||||
{ "title", "#ee" },
|
||||
{ "hi_fg", "#969696" },
|
||||
{ "selected_bg", "#7e2626" },
|
||||
{ "selected_fg", "#ee" },
|
||||
{ "inactive_fg", "#40" },
|
||||
{ "graph_text", "#60" },
|
||||
{ "meter_bg", "#40" },
|
||||
{ "proc_misc", "#0de756" },
|
||||
{ "cpu_box", "#3d7b46" },
|
||||
{ "mem_box", "#8a882e" },
|
||||
{ "net_box", "#423ba5" },
|
||||
{ "proc_box", "#923535" },
|
||||
{ "div_line", "#30" },
|
||||
{ "temp_start", "#4897d4" },
|
||||
{ "temp_mid", "#5474e8" },
|
||||
{ "temp_end", "#ff40b6" },
|
||||
{ "cpu_start", "#50f095" },
|
||||
{ "cpu_mid", "#f2e266" },
|
||||
{ "cpu_end", "#fa1e1e" },
|
||||
{ "free_start", "#223014" },
|
||||
{ "free_mid", "#b5e685" },
|
||||
{ "free_end", "#dcff85" },
|
||||
{ "cached_start", "#0b1a29" },
|
||||
{ "cached_mid", "#74e6fc" },
|
||||
{ "cached_end", "#26c5ff" },
|
||||
{ "available_start", "#292107" },
|
||||
{ "available_mid", "#ffd77a" },
|
||||
{ "available_end", "#ffb814" },
|
||||
{ "used_start", "#3b1f1c" },
|
||||
{ "used_mid", "#d9626d" },
|
||||
{ "used_end", "#ff4769" },
|
||||
{ "download_start", "#231a63" },
|
||||
{ "download_mid", "#4f43a3" },
|
||||
{ "download_end", "#b0a9de" },
|
||||
{ "upload_start", "#510554" },
|
||||
{ "upload_mid", "#7d4180" },
|
||||
{ "upload_end", "#dcafde" },
|
||||
{ "process_start", "#80d0a3" },
|
||||
{ "process_mid", "#dcd179" },
|
||||
{ "process_end", "#d45454" }
|
||||
};
|
||||
|
||||
namespace {
|
||||
//* Convert 24-bit colors to 256 colors using 6x6x6 color cube
|
||||
int truecolor_to_256(int r, int g, int b){
|
||||
if (round((double)r / 11) == round((double)g / 11) && round((double)g / 11) == round((double)b / 11)) {
|
||||
return 232 + round((double)r / 11);
|
||||
} else {
|
||||
return round((double)r / 51) * 36 + round((double)g / 51) * 6 + round((double)b / 51) + 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
extern const robin_hood::unordered_flat_map<string, string> Default_theme;
|
||||
|
||||
//* Generate escape sequence for 24-bit or 256 color and return as a string
|
||||
//* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale
|
||||
//* t_to_256: [true|false] convert 24bit value to 256 color value
|
||||
//* depth: ["fg"|"bg"] for either a foreground color or a background color
|
||||
string hex_to_color(string hexa, bool t_to_256=false, string depth="fg"){
|
||||
if (hexa.size() > 1){
|
||||
hexa.erase(0, 1);
|
||||
for (auto& c : hexa) if (!isxdigit(c)) {
|
||||
Logger::error("Invalid hex value: " + hexa);
|
||||
return "";
|
||||
}
|
||||
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
|
||||
|
||||
if (hexa.size() == 2){
|
||||
int h_int = stoi(hexa, 0, 16);
|
||||
if (t_to_256){
|
||||
return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m";
|
||||
} else {
|
||||
string h_str = to_string(h_int);
|
||||
return pre + h_str + ";" + h_str + ";" + h_str + "m";
|
||||
}
|
||||
}
|
||||
else if (hexa.size() == 6){
|
||||
if (t_to_256){
|
||||
return pre + to_string(truecolor_to_256(
|
||||
stoi(hexa.substr(0, 2), 0, 16),
|
||||
stoi(hexa.substr(2, 2), 0, 16),
|
||||
stoi(hexa.substr(4, 2), 0, 16))) + "m";
|
||||
} else {
|
||||
return pre +
|
||||
to_string(stoi(hexa.substr(0, 2), 0, 16)) + ";" +
|
||||
to_string(stoi(hexa.substr(2, 2), 0, 16)) + ";" +
|
||||
to_string(stoi(hexa.substr(4, 2), 0, 16)) + "m";
|
||||
}
|
||||
}
|
||||
else Logger::error("Invalid size of hex value: " + hexa);
|
||||
}
|
||||
else Logger::error("Hex value missing: " + hexa);
|
||||
return "";
|
||||
}
|
||||
string hex_to_color(string hexa, bool t_to_256=false, string depth="fg");
|
||||
|
||||
//* Generate escape sequence for 24-bit or 256 color and return as a string
|
||||
//* Args r: [0-255], g: [0-255], b: [0-255]
|
||||
//* t_to_256: [true|false] convert 24bit value to 256 color value
|
||||
//* depth: ["fg"|"bg"] for either a foreground color or a background color
|
||||
string dec_to_color(int r, int g, int b, bool t_to_256=false, string depth="fg"){
|
||||
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
|
||||
r = std::clamp(r, 0, 255);
|
||||
g = std::clamp(g, 0, 255);
|
||||
b = std::clamp(b, 0, 255);
|
||||
if (t_to_256) return pre + to_string(truecolor_to_256(r, g, b)) + "m";
|
||||
else return pre + to_string(r) + ";" + to_string(g) + ";" + to_string(b) + "m";
|
||||
}
|
||||
string dec_to_color(int r, int g, int b, bool t_to_256=false, string depth="fg");
|
||||
|
||||
//* Return an array of red, green and blue, 0-255 values for a 24-bit color escape string
|
||||
auto esc_to_rgb(string c_string){
|
||||
array<int, 3> rgb = {-1, -1, -1};
|
||||
if (c_string.size() >= 14){
|
||||
c_string.erase(0, 7);
|
||||
auto c_split = ssplit(c_string, ";");
|
||||
if (c_split.size() == 3){
|
||||
rgb[0] = stoi(c_split[0]);
|
||||
rgb[1] = stoi(c_split[1]);
|
||||
rgb[2] = stoi(c_split[2].erase(c_split[2].size()));
|
||||
}
|
||||
}
|
||||
return rgb;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
unordered_flat_map<string, string> colors;
|
||||
unordered_flat_map<string, array<int, 3>> rgbs;
|
||||
unordered_flat_map<string, array<string, 101>> gradients;
|
||||
|
||||
//* Convert hex color to a array of decimals
|
||||
array<int, 3> hex_to_dec(string hexa){
|
||||
if (hexa.size() > 1){
|
||||
hexa.erase(0, 1);
|
||||
for (auto& c : hexa) if (!isxdigit(c)) return array<int, 3>{-1, -1, -1};
|
||||
|
||||
if (hexa.size() == 2){
|
||||
int h_int = stoi(hexa, 0, 16);
|
||||
return array<int, 3>{h_int, h_int, h_int};
|
||||
}
|
||||
else if (hexa.size() == 6){
|
||||
return array<int, 3>{
|
||||
stoi(hexa.substr(0, 2), 0, 16),
|
||||
stoi(hexa.substr(2, 2), 0, 16),
|
||||
stoi(hexa.substr(4, 2), 0, 16)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {-1 ,-1 ,-1};
|
||||
}
|
||||
|
||||
//* Generate colors and rgb decimal vectors for the theme
|
||||
void generateColors(unordered_flat_map<string, string>& source){
|
||||
vector<string> t_rgb;
|
||||
string depth;
|
||||
bool t_to_256 = !Config::getB("truecolor");
|
||||
colors.clear(); rgbs.clear();
|
||||
for (auto& [name, color] : Default_theme) {
|
||||
depth = (name.ends_with("bg") && name != "meter_bg") ? "bg" : "fg";
|
||||
if (source.contains(name)) {
|
||||
if (source.at(name)[0] == '#') {
|
||||
colors[name] = hex_to_color(source.at(name), t_to_256, depth);
|
||||
rgbs[name] = hex_to_dec(source.at(name));
|
||||
}
|
||||
else {
|
||||
t_rgb = ssplit(source.at(name), " ");
|
||||
if (t_rgb.size() != 3)
|
||||
Logger::error("Invalid RGB decimal value: \"" + source.at(name) + "\"");
|
||||
else {
|
||||
colors[name] = dec_to_color(stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2]), t_to_256, depth);
|
||||
rgbs[name] = array<int, 3>{stoi(t_rgb[0]), stoi(t_rgb[1]), stoi(t_rgb[2])};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (colors[name].empty()) {
|
||||
Logger::info("Missing color value for \"" + name + "\". Using value from default.");
|
||||
colors[name] = hex_to_color(color, t_to_256, depth);
|
||||
rgbs[name] = array<int, 3>{-1, -1, -1};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//* Generate color gradients from two or three colors, 101 values indexed 0-100
|
||||
void generateGradients(){
|
||||
gradients.clear();
|
||||
array<string, 101> c_gradient;
|
||||
bool t_to_256 = !Config::getB("truecolor");
|
||||
for (auto& [name, source_arr] : rgbs) {
|
||||
if (!name.ends_with("_start")) continue;
|
||||
array<array<int, 3>, 101> dec_arr;
|
||||
dec_arr[0][0] = -1;
|
||||
string wname = rtrim(name, "_start");
|
||||
array<array<int, 3>, 3> rgb_arr = {source_arr, rgbs[wname + "_mid"], rgbs[wname + "_end"]};
|
||||
|
||||
//? Only start iteration if gradient has a _end color value defined
|
||||
if (rgb_arr[2][0] >= 0) {
|
||||
|
||||
//? Split iteration in two passes of 50 + 51 instead of 101 if gradient has _start, _mid and _end values defined
|
||||
int rng = (rgb_arr[1][0] >= 0) ? 50 : 100;
|
||||
for (int rgb : iota(0, 3)){
|
||||
int arr1 = 0, offset = 0;
|
||||
int arr2 = (rng == 50) ? 1 : 2;
|
||||
for (int i : iota(0, 101)) {
|
||||
dec_arr[i][rgb] = rgb_arr[arr1][rgb] + (i - offset) * (rgb_arr[arr2][rgb] - rgb_arr[arr1][rgb]) / rng;
|
||||
|
||||
//? Switch source arrays from _start/_mid to _mid/_end at 50 passes if _mid is defined
|
||||
if (i == rng) { ++arr1; ++arr2; offset = 50;}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dec_arr[0][0] != -1) {
|
||||
int y = 0;
|
||||
for (auto& arr : dec_arr) c_gradient[y++] = dec_to_color(arr[0], arr[1], arr[2], t_to_256);
|
||||
}
|
||||
else {
|
||||
//? If only _start was defined fill array with _start color
|
||||
c_gradient.fill(colors[name]);
|
||||
}
|
||||
gradients[wname].swap(c_gradient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::array<int, 3> esc_to_rgb(string c_string);
|
||||
|
||||
//* Set current theme using <source> map
|
||||
void set(unordered_flat_map<string, string> source){
|
||||
generateColors(source);
|
||||
generateGradients();
|
||||
Term::fg = colors.at("main_fg");
|
||||
Term::bg = colors.at("main_bg");
|
||||
Fx::reset = Fx::reset_base + Term::fg + Term::bg;
|
||||
}
|
||||
void set(robin_hood::unordered_flat_map<string, string> source);
|
||||
|
||||
//* Return escape code for color <name>
|
||||
auto& c(string name){
|
||||
return colors.at(name);
|
||||
}
|
||||
const string& c(string name);
|
||||
|
||||
//* Return array of escape codes for color gradient <name>
|
||||
auto& g(string name){
|
||||
return gradients.at(name);
|
||||
}
|
||||
const std::array<string, 101>& g(string name);
|
||||
|
||||
//* Return array of red, green and blue in decimal for color <name>
|
||||
auto& dec(string name){
|
||||
return rgbs.at(name);
|
||||
}
|
||||
const std::array<int, 3>& dec(string name);
|
||||
|
||||
}
|
||||
//? Testing
|
||||
robin_hood::unordered_flat_map<string, string>& test_colors();
|
||||
robin_hood::unordered_flat_map<string, std::array<string, 101>>& test_gradients();
|
||||
|
||||
#endif
|
||||
}
|
432
src/btop_tools.cpp
Normal file
432
src/btop_tools.cpp
Normal file
@ -0,0 +1,432 @@
|
||||
/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <utility>
|
||||
#include <robin_hood.h>
|
||||
#include <thread>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <btop_tools.h>
|
||||
|
||||
using std::string_view, std::array, std::regex, std::max, std::to_string, std::cin,
|
||||
std::atomic, robin_hood::unordered_flat_map;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
|
||||
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
|
||||
|
||||
//* Collection of escape codes for text style and formatting
|
||||
namespace Fx {
|
||||
const string e = "\x1b[";
|
||||
const string b = e + "1m";
|
||||
const string ub = e + "22m";
|
||||
const string d = e + "2m";
|
||||
const string ud = e + "22m";
|
||||
const string i = e + "3m";
|
||||
const string ui = e + "23m";
|
||||
const string ul = e + "4m";
|
||||
const string uul = e + "24m";
|
||||
const string bl = e + "5m";
|
||||
const string ubl = e + "25m";
|
||||
const string s = e + "9m";
|
||||
const string us = e + "29m";
|
||||
const string reset_base = e + "0m";
|
||||
string reset = reset_base;
|
||||
|
||||
const regex escape_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m|f|s|u|C|D|A|B){1}");
|
||||
|
||||
const regex color_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m){1}");
|
||||
|
||||
string uncolor(string& s){
|
||||
return regex_replace(s, color_regex, "");
|
||||
}
|
||||
}
|
||||
|
||||
//* Collection of escape codes and functions for cursor manipulation
|
||||
namespace Mv {
|
||||
const string to(int line, int col){ return Fx::e + to_string(line) + ";" + to_string(col) + "f";}
|
||||
const string r(int x){ return Fx::e + to_string(x) + "C";}
|
||||
const string l(int x){ return Fx::e + to_string(x) + "D";}
|
||||
const string u(int x){ return Fx::e + to_string(x) + "A";}
|
||||
const string d(int x) { return Fx::e + to_string(x) + "B";}
|
||||
const string save = Fx::e + "s";
|
||||
const string restore = Fx::e + "u";
|
||||
}
|
||||
|
||||
|
||||
//* Collection of escape codes and functions for terminal manipulation
|
||||
namespace Term {
|
||||
|
||||
bool initialized = false;
|
||||
bool resized = false;
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
string fg, bg, current_tty;
|
||||
|
||||
const string hide_cursor = Fx::e + "?25l";
|
||||
const string show_cursor = Fx::e + "?25h";
|
||||
const string alt_screen = Fx::e + "?1049h";
|
||||
const string normal_screen = Fx::e + "?1049l";
|
||||
const string clear = Fx::e + "2J" + Fx::e + "0;0f";
|
||||
const string clear_end = Fx::e + "0J";
|
||||
const string clear_begin = Fx::e + "1J";
|
||||
const string mouse_on = Fx::e + "?1002h" + Fx::e + "?1015h" + Fx::e + "?1006h";
|
||||
const string mouse_off = Fx::e + "?1002l";
|
||||
const string mouse_direct_on = Fx::e + "?1003h";
|
||||
const string mouse_direct_off = Fx::e + "?1003l";
|
||||
|
||||
namespace {
|
||||
struct termios initial_settings;
|
||||
|
||||
//* Toggle terminal input echo
|
||||
bool echo(bool on=true){
|
||||
struct termios settings;
|
||||
if (tcgetattr(STDIN_FILENO, &settings)) return false;
|
||||
if (on) settings.c_lflag |= ECHO;
|
||||
else settings.c_lflag &= ~(ECHO);
|
||||
return 0 == tcsetattr(STDIN_FILENO, TCSANOW, &settings);
|
||||
}
|
||||
|
||||
//* Toggle need for return key when reading input
|
||||
bool linebuffered(bool on=true){
|
||||
struct termios settings;
|
||||
if (tcgetattr(STDIN_FILENO, &settings)) return false;
|
||||
if (on) settings.c_lflag |= ICANON;
|
||||
else settings.c_lflag &= ~(ICANON);
|
||||
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
|
||||
if (on) setlinebuf(stdin);
|
||||
else setbuf(stdin, NULL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool refresh(){
|
||||
struct winsize w;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||
if (width != w.ws_col || height != w.ws_row) {
|
||||
width = w.ws_col;
|
||||
height = w.ws_row;
|
||||
resized = true;
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
bool init(){
|
||||
if (!initialized){
|
||||
initialized = (bool)isatty(STDIN_FILENO);
|
||||
if (initialized) {
|
||||
tcgetattr(STDIN_FILENO, &initial_settings);
|
||||
current_tty = (string)ttyname(STDIN_FILENO);
|
||||
cin.sync_with_stdio(false);
|
||||
cin.tie(NULL);
|
||||
echo(false);
|
||||
linebuffered(false);
|
||||
refresh();
|
||||
resized = false;
|
||||
}
|
||||
}
|
||||
return initialized;
|
||||
}
|
||||
|
||||
void restore(){
|
||||
if (initialized) {
|
||||
echo(true);
|
||||
linebuffered(true);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
||||
|
||||
namespace Tools {
|
||||
|
||||
namespace {
|
||||
//? Units for floating_humanizer function
|
||||
const array<string, 11> Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"};
|
||||
const array<string, 11> Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"};
|
||||
}
|
||||
|
||||
size_t ulen(string str, const bool escape){
|
||||
if (escape) str = std::regex_replace(str, Fx::escape_regex, "");
|
||||
return rng::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
|
||||
}
|
||||
|
||||
string uresize(string str, const size_t len){
|
||||
if (str.size() < 1) return str;
|
||||
if (len < 1) return "";
|
||||
for (size_t x = 0, i = 0; i < str.size(); i++) {
|
||||
if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++;
|
||||
if (x == len + 1) {
|
||||
str.resize(i);
|
||||
str.shrink_to_fit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
uint64_t time_s(){
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
uint64_t time_ms(){
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
uint64_t time_micros(){
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
bool isbool(string& str){
|
||||
return (str == "true") || (str == "false") || (str == "True") || (str == "False");
|
||||
}
|
||||
|
||||
bool stobool(string& str){
|
||||
return (str == "true" || str == "True");
|
||||
}
|
||||
|
||||
bool isint(string& str){
|
||||
if (str.empty()) return false;
|
||||
size_t offset = (str[0] == '-' ? 1 : 0);
|
||||
return all_of(str.begin() + offset, str.end(), ::isdigit);
|
||||
}
|
||||
|
||||
string ltrim(const string& str, const string t_str){
|
||||
string_view str_v = str;
|
||||
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
|
||||
return (string)str_v;
|
||||
}
|
||||
|
||||
string rtrim(const string& str, const string t_str){
|
||||
string_view str_v = str;
|
||||
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size());
|
||||
return (string)str_v;
|
||||
}
|
||||
|
||||
string trim(const string& str, const string t_str){
|
||||
return ltrim(rtrim(str, t_str), t_str);
|
||||
}
|
||||
|
||||
vector<string> ssplit(const string& str, const char delim){
|
||||
vector<string> out;
|
||||
for (const auto& s : str | rng::views::split(delim)
|
||||
| rng::views::transform([](auto &&rng) {
|
||||
return string_view(&*rng.begin(), rng::distance(rng));
|
||||
})) {
|
||||
if (!s.empty()) out.emplace_back(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void sleep_ms(const uint& ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
|
||||
string ljust(string str, const size_t x, bool utf, bool escape, bool lim){
|
||||
if (utf || escape) {
|
||||
if (!escape && lim && ulen(str) > x) str = uresize(str, x);
|
||||
return str + string(max((int)(x - ulen(str, escape)), 0), ' ');
|
||||
}
|
||||
else {
|
||||
if (lim && str.size() > x) str.resize(x);
|
||||
return str + string(max((int)(x - str.size()), 0), ' ');
|
||||
}
|
||||
}
|
||||
|
||||
string rjust(string str, const size_t x, bool utf, bool escape, bool lim){
|
||||
if (utf || escape) {
|
||||
if (!escape && lim && ulen(str) > x) str = uresize(str, x);
|
||||
return string(max((int)(x - ulen(str, escape)), 0), ' ') + str;
|
||||
}
|
||||
else {
|
||||
if (lim && str.size() > x) str.resize(x);
|
||||
return string(max((int)(x - str.size()), 0), ' ') + str;
|
||||
}
|
||||
}
|
||||
|
||||
string trans(const string& str){
|
||||
size_t pos;
|
||||
string_view oldstr = str;
|
||||
string newstr;
|
||||
newstr.reserve(str.size());
|
||||
while ((pos = oldstr.find(' ')) != string::npos){
|
||||
newstr.append(oldstr.substr(0, pos));
|
||||
oldstr.remove_prefix(pos+1);
|
||||
pos = 1;
|
||||
while (pos < oldstr.size() && oldstr.at(pos) == ' ') pos++;
|
||||
newstr.append(Mv::r(pos));
|
||||
oldstr.remove_suffix(pos-1);
|
||||
}
|
||||
return (newstr.empty()) ? str : newstr + (string)oldstr;
|
||||
}
|
||||
|
||||
string sec_to_dhms(uint sec){
|
||||
string out;
|
||||
uint d, h, m;
|
||||
d = sec / (3600 * 24);
|
||||
sec %= 3600 * 24;
|
||||
h = sec / 3600;
|
||||
sec %= 3600;
|
||||
m = sec / 60;
|
||||
sec %= 60;
|
||||
if (d>0) out = to_string(d) + "d ";
|
||||
out += ((h<10) ? "0" : "") + to_string(h) + ":";
|
||||
out += ((m<10) ? "0" : "") + to_string(m) + ":";
|
||||
out += ((sec<10) ? "0" : "") + to_string(sec);
|
||||
return out;
|
||||
}
|
||||
|
||||
string floating_humanizer(uint64_t value, bool shorten, uint start, bool bit, bool per_second){
|
||||
string out;
|
||||
uint mult = (bit) ? 8 : 1;
|
||||
auto& units = (bit) ? Units_bit : Units_byte;
|
||||
|
||||
value *= 100 * mult;
|
||||
|
||||
while (value >= 102400){
|
||||
value >>= 10;
|
||||
if (value < 100){
|
||||
out = to_string(value);
|
||||
break;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
if (out.empty()) {
|
||||
out = to_string(value);
|
||||
if (out.size() == 4 && start > 0) { out.pop_back(); out.insert(2, ".");}
|
||||
else if (out.size() == 3 && start > 0) out.insert(1, ".");
|
||||
else if (out.size() >= 2) out.resize(out.size() - 2);
|
||||
}
|
||||
if (shorten){
|
||||
if (out.find('.') != string::npos) out = to_string((int)round(stof(out)));
|
||||
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
|
||||
out.push_back(units[start][0]);
|
||||
}
|
||||
else out += " " + units[start];
|
||||
|
||||
if (per_second) out += (bit) ? "ps" : "/s";
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string operator*(string str, size_t n){
|
||||
string out;
|
||||
out.reserve(str.size() * n);
|
||||
while (n-- > 0) out += str;
|
||||
return out;
|
||||
}
|
||||
|
||||
string strf_time(string strf){
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm bt {};
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(localtime_r(&in_time_t, &bt), strf.c_str());
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
#if (__GNUC__ > 10)
|
||||
//* Redirects to atomic wait
|
||||
void atomic_wait(atomic<bool>& atom, bool val){
|
||||
atom.wait(val);
|
||||
}
|
||||
#else
|
||||
//* Crude implementation of atomic wait for GCC 10
|
||||
void atomic_wait(atomic<bool>& atom, bool val){
|
||||
while (atom.load() == val) sleep_ms(1);
|
||||
}
|
||||
#endif
|
||||
|
||||
void atomic_wait_set(atomic<bool>& atom, bool val){
|
||||
atomic_wait(atom, val);
|
||||
atom.store(val);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace Logger {
|
||||
using namespace Tools;
|
||||
namespace {
|
||||
std::atomic<bool> busy (false);
|
||||
bool first = true;
|
||||
string tdf = "%Y/%m/%d (%T) | ";
|
||||
}
|
||||
|
||||
vector<string> log_levels = {
|
||||
"DISABLED",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"INFO",
|
||||
"DEBUG",
|
||||
};
|
||||
|
||||
size_t loglevel;
|
||||
fs::path logfile;
|
||||
|
||||
void set(string level){
|
||||
loglevel = v_index(log_levels, level);
|
||||
}
|
||||
|
||||
void log_write(uint level, string& msg){
|
||||
if (loglevel < level || logfile.empty()) return;
|
||||
atomic_wait_set(busy, true);
|
||||
std::error_code ec;
|
||||
if (fs::exists(logfile) && fs::file_size(logfile, ec) > 1024 << 10 && !ec) {
|
||||
auto old_log = logfile;
|
||||
old_log += ".1";
|
||||
if (fs::exists(old_log)) fs::remove(old_log, ec);
|
||||
if (!ec) fs::rename(logfile, old_log, ec);
|
||||
}
|
||||
if (!ec) {
|
||||
std::ofstream lwrite(logfile, std::ios::app);
|
||||
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";}
|
||||
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
|
||||
lwrite.close();
|
||||
}
|
||||
else logfile.clear();
|
||||
busy.store(false);
|
||||
}
|
||||
|
||||
void error(string msg){
|
||||
log_write(1, msg);
|
||||
}
|
||||
|
||||
void warning(string msg){
|
||||
log_write(2, msg);
|
||||
}
|
||||
|
||||
void info(string msg){
|
||||
log_write(3, msg);
|
||||
}
|
||||
|
||||
void debug(string msg){
|
||||
log_write(4, msg);
|
||||
}
|
||||
}
|
455
src/btop_tools.h
455
src/btop_tools.h
@ -16,238 +16,142 @@ indent = tab
|
||||
tab-size = 4
|
||||
*/
|
||||
|
||||
#ifndef _btop_tools_included_
|
||||
#define _btop_tools_included_
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <robin_hood.h>
|
||||
#include <ranges>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
using std::string, std::string_view, std::vector, std::array, std::regex, std::max, std::to_string, std::cin, std::atomic, robin_hood::unordered_flat_map;
|
||||
namespace fs = std::filesystem;
|
||||
using std::string, std::vector;
|
||||
|
||||
|
||||
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
|
||||
|
||||
namespace Global {
|
||||
extern string Version;
|
||||
}
|
||||
|
||||
//* Collection of escape codes for text style and formatting
|
||||
namespace Fx {
|
||||
//* Escape sequence start
|
||||
const string e = "\x1b[";
|
||||
|
||||
//* Bold on/off
|
||||
const string b = e + "1m";
|
||||
const string ub = e + "22m";
|
||||
|
||||
//* Dark on/off
|
||||
const string d = e + "2m";
|
||||
const string ud = e + "22m";
|
||||
|
||||
//* Italic on/off
|
||||
const string i = e + "3m";
|
||||
const string ui = e + "23m";
|
||||
|
||||
//* Underline on/off
|
||||
const string ul = e + "4m";
|
||||
const string uul = e + "24m";
|
||||
|
||||
//* Blink on/off
|
||||
const string bl = e + "5m";
|
||||
const string ubl = e + "25m";
|
||||
|
||||
//* Strike / crossed-out on/off
|
||||
const string s = e + "9m";
|
||||
const string us = e + "29m";
|
||||
extern const string e; //* Escape sequence start
|
||||
extern const string b; //* Bold on/off
|
||||
extern const string ub; //* Bold off
|
||||
extern const string d; //* Dark on
|
||||
extern const string ud; //* Dark off
|
||||
extern const string i; //* Italic on
|
||||
extern const string ui; //* Italic off
|
||||
extern const string ul; //* Underline on
|
||||
extern const string uul; //* Underline off
|
||||
extern const string bl; //* Blink on
|
||||
extern const string ubl; //* Blink off
|
||||
extern const string s; //* Strike/crossed-out on
|
||||
extern const string us; //* Strike/crossed-out on/off
|
||||
|
||||
//* Reset foreground/background color and text effects
|
||||
const string reset_base = e + "0m";
|
||||
extern const string reset_base;
|
||||
|
||||
//* Reset text effects and restore default foregrund and background color < Changed by C_Theme
|
||||
string reset = reset_base;
|
||||
//* Reset text effects and restore theme foregrund and background color
|
||||
extern string reset;
|
||||
|
||||
//* Regex for matching color, style and curse move escape sequences
|
||||
const regex escape_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m|f|s|u|C|D|A|B){1}");
|
||||
extern const std::regex escape_regex;
|
||||
|
||||
//* Regex for matching only color and style escape sequences
|
||||
const regex color_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m){1}");
|
||||
extern const std::regex color_regex;
|
||||
|
||||
//* Return a string with all colors and text styling removed
|
||||
string uncolor(string& s){
|
||||
return regex_replace(s, color_regex, "");
|
||||
}
|
||||
string uncolor(string& s);
|
||||
}
|
||||
|
||||
//* Collection of escape codes and functions for cursor manipulation
|
||||
namespace Mv {
|
||||
//* Move cursor to <line>, <column>
|
||||
string to(int line, int col){ return Fx::e + to_string(line) + ";" + to_string(col) + "f";}
|
||||
const string to(int line, int col);
|
||||
|
||||
//* Move cursor right <x> columns
|
||||
string r(int x){ return Fx::e + to_string(x) + "C";}
|
||||
const string r(int x);
|
||||
|
||||
//* Move cursor left <x> columns
|
||||
string l(int x){ return Fx::e + to_string(x) + "D";}
|
||||
const string l(int x);
|
||||
|
||||
//* Move cursor up x lines
|
||||
string u(int x){ return Fx::e + to_string(x) + "A";}
|
||||
const string u(int x);
|
||||
|
||||
//* Move cursor down x lines
|
||||
string d(int x) { return Fx::e + to_string(x) + "B";}
|
||||
const string d(int x);
|
||||
|
||||
//* Save cursor position
|
||||
const string save = Fx::e + "s";
|
||||
extern const string save;
|
||||
|
||||
//* Restore saved cursor postion
|
||||
const string restore = Fx::e + "u";
|
||||
extern const string restore;
|
||||
}
|
||||
|
||||
//* Collection of escape codes and functions for terminal manipulation
|
||||
namespace Term {
|
||||
|
||||
bool initialized = false;
|
||||
bool resized = false;
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
string fg, bg;
|
||||
|
||||
namespace {
|
||||
struct termios initial_settings;
|
||||
|
||||
//* Toggle terminal input echo
|
||||
bool echo(bool on=true){
|
||||
struct termios settings;
|
||||
if (tcgetattr(STDIN_FILENO, &settings)) return false;
|
||||
if (on) settings.c_lflag |= ECHO;
|
||||
else settings.c_lflag &= ~(ECHO);
|
||||
return 0 == tcsetattr(STDIN_FILENO, TCSANOW, &settings);
|
||||
}
|
||||
|
||||
//* Toggle need for return key when reading input
|
||||
bool linebuffered(bool on=true){
|
||||
struct termios settings;
|
||||
if (tcgetattr(STDIN_FILENO, &settings)) return false;
|
||||
if (on) settings.c_lflag |= ICANON;
|
||||
else settings.c_lflag &= ~(ICANON);
|
||||
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
|
||||
if (on) setlinebuf(stdin);
|
||||
else setbuf(stdin, NULL);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
extern bool initialized;
|
||||
extern bool resized;
|
||||
extern uint width;
|
||||
extern uint height;
|
||||
extern string fg, bg, current_tty;
|
||||
|
||||
//* Hide terminal cursor
|
||||
const string hide_cursor = Fx::e + "?25l";
|
||||
extern const string hide_cursor;
|
||||
|
||||
//* Show terminal cursor
|
||||
const string show_cursor = Fx::e + "?25h";
|
||||
extern const string show_cursor;
|
||||
|
||||
//* Switch to alternate screen
|
||||
const string alt_screen = Fx::e + "?1049h";
|
||||
extern const string alt_screen;
|
||||
|
||||
//* Switch to normal screen
|
||||
const string normal_screen = Fx::e + "?1049l";
|
||||
extern const string normal_screen;
|
||||
|
||||
//* Clear screen and set cursor to position 0,0
|
||||
const string clear = Fx::e + "2J" + Fx::e + "0;0f";
|
||||
extern const string clear;
|
||||
|
||||
//* Clear from cursor to end of screen
|
||||
const string clear_end = Fx::e + "0J";
|
||||
extern const string clear_end;
|
||||
|
||||
//* Clear from cursor to beginning of screen
|
||||
const string clear_begin = Fx::e + "1J";
|
||||
extern const string clear_begin;
|
||||
|
||||
//* Enable reporting of mouse position on click and release
|
||||
const string mouse_on = Fx::e + "?1002h" + Fx::e + "?1015h" + Fx::e + "?1006h";
|
||||
extern const string mouse_on;
|
||||
|
||||
//* Disable mouse reporting
|
||||
const string mouse_off = Fx::e + "?1002l";
|
||||
extern const string mouse_off;
|
||||
|
||||
//* Enable reporting of mouse position at any movement
|
||||
const string mouse_direct_on = Fx::e + "?1003h";
|
||||
extern const string mouse_direct_on;
|
||||
|
||||
//* Disable direct mouse reporting
|
||||
const string mouse_direct_off = Fx::e + "?1003l";
|
||||
extern const string mouse_direct_off;
|
||||
|
||||
//* Refresh variables holding current terminal width and height and return true if resized
|
||||
bool refresh(){
|
||||
struct winsize w;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||
if (width != w.ws_col || height != w.ws_row) {
|
||||
width = w.ws_col;
|
||||
height = w.ws_row;
|
||||
resized = true;
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
bool refresh();
|
||||
|
||||
//* Check for a valid tty, save terminal options and set new options
|
||||
bool init(){
|
||||
if (!initialized){
|
||||
initialized = (bool)isatty(STDIN_FILENO);
|
||||
if (initialized) {
|
||||
initialized = (0 == tcgetattr(STDIN_FILENO, &initial_settings));
|
||||
cin.sync_with_stdio(false);
|
||||
cin.tie(NULL);
|
||||
echo(false);
|
||||
linebuffered(false);
|
||||
refresh();
|
||||
resized = false;
|
||||
}
|
||||
}
|
||||
return initialized;
|
||||
}
|
||||
bool init();
|
||||
|
||||
//* Restore terminal options
|
||||
void restore(){
|
||||
if (initialized) {
|
||||
echo(true);
|
||||
linebuffered(true);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings);
|
||||
initialized = false;
|
||||
}
|
||||
}
|
||||
void restore();
|
||||
}
|
||||
|
||||
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
||||
|
||||
namespace Tools {
|
||||
|
||||
const auto SSmax = std::numeric_limits<std::streamsize>::max();
|
||||
|
||||
//* Return number of UTF8 characters in a string with option to disregard escape sequences
|
||||
size_t ulen(string str, const bool escape=false){
|
||||
if (escape) str = std::regex_replace(str, Fx::escape_regex, "");
|
||||
return std::count_if(str.begin(), str.end(),
|
||||
[](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
|
||||
}
|
||||
size_t ulen(string str, const bool escape=false);
|
||||
|
||||
//* Resize a string consisting of UTF8 characters (only reduces size)
|
||||
string uresize(string str, const size_t len){
|
||||
if (str.size() < 1) return str;
|
||||
if (len < 1) return "";
|
||||
for (size_t x = 0, i = 0; i < str.size(); i++) {
|
||||
if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++;
|
||||
if (x == len + 1) {
|
||||
str.resize(i);
|
||||
str.shrink_to_fit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
string uresize(string str, const size_t len);
|
||||
|
||||
//* Check if vector <vec> contains value <find_val>
|
||||
template <typename T>
|
||||
@ -262,269 +166,80 @@ namespace Tools {
|
||||
}
|
||||
|
||||
//* Return current time since epoch in seconds
|
||||
uint64_t time_s(){
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
uint64_t time_s();
|
||||
|
||||
//* Return current time since epoch in milliseconds
|
||||
uint64_t time_ms(){
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
uint64_t time_ms();
|
||||
|
||||
//* Return current time since epoch in microseconds
|
||||
uint64_t time_micros(){
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
uint64_t time_micros();
|
||||
|
||||
//* Check if a string is a valid bool value
|
||||
bool isbool(string& str){
|
||||
return (str == "true") || (str == "false") || (str == "True") || (str == "False");
|
||||
}
|
||||
bool isbool(string& str);
|
||||
|
||||
//* Convert string to bool, returning any value not equal to "true" or "True" as false
|
||||
bool stobool(string& str){
|
||||
return (str == "true" || str == "True") ? true : false;
|
||||
}
|
||||
bool stobool(string& str);
|
||||
|
||||
//* Check if a string is a valid integer value
|
||||
bool isint(string& str){
|
||||
if (str.empty()) return false;
|
||||
size_t offset = (str[0] == '-' ? 1 : 0);
|
||||
return all_of(str.begin() + offset, str.end(), ::isdigit);
|
||||
}
|
||||
bool isint(string& str);
|
||||
|
||||
//* Left-trim <t_str> from <str> and return new string
|
||||
string ltrim(const string& str, const string t_str = " "){
|
||||
string_view str_v = str;
|
||||
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
|
||||
return (string)str_v;
|
||||
}
|
||||
string ltrim(const string& str, const string t_str = " ");
|
||||
|
||||
//* Right-trim <t_str> from <str> and return new string
|
||||
string rtrim(const string& str, const string t_str = " "){
|
||||
string_view str_v = str;
|
||||
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size());
|
||||
return (string)str_v;
|
||||
}
|
||||
string rtrim(const string& str, const string t_str = " ");
|
||||
|
||||
//* Left-right-trim <t_str> from <str> and return new string
|
||||
string trim(const string& str, const string t_str = " "){
|
||||
return ltrim(rtrim(str, t_str), t_str);
|
||||
}
|
||||
string trim(const string& str, const string t_str = " ");
|
||||
|
||||
//* Split <string> at <delim> <time> number of times (0 for unlimited) and return vector
|
||||
vector<string> ssplit(const string& str, const string delim = " ", const int times = 0, const bool ignore_remainder=false){
|
||||
vector<string> out;
|
||||
string_view str_v = str;
|
||||
if (times > 0) out.reserve(times);
|
||||
if (!str_v.empty() && !delim.empty()){
|
||||
size_t pos = 0;
|
||||
int x = 0;
|
||||
while ((pos = str_v.find(delim)) != string::npos){
|
||||
if (str_v.substr(0, pos) != delim) out.emplace_back(str_v.substr(0, pos));
|
||||
str_v.remove_prefix(pos + delim.size());
|
||||
if (times > 0 && ++x >= times) break;
|
||||
}
|
||||
}
|
||||
if (!ignore_remainder) out.emplace_back(str_v);
|
||||
return out;
|
||||
}
|
||||
//* Split <string> at all occurrences of <delim> and return as vector of strings
|
||||
vector<string> ssplit(const string& str, const char delim = ' ');
|
||||
|
||||
//* Put current thread to sleep for <ms> milliseconds
|
||||
void sleep_ms(const uint& ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
void sleep_ms(const uint& ms);
|
||||
|
||||
//* Left justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
|
||||
string ljust(string str, const size_t x, bool utf=false, bool escape=false, bool lim=true){
|
||||
if (utf || escape) {
|
||||
if (!escape && lim && ulen(str) > x) str = uresize(str, x);
|
||||
return str + string(max((int)(x - ulen(str, escape)), 0), ' ');
|
||||
}
|
||||
else {
|
||||
if (lim && str.size() > x) str.resize(x);
|
||||
return str + string(max((int)(x - str.size()), 0), ' ');
|
||||
}
|
||||
}
|
||||
string ljust(string str, const size_t x, bool utf=false, bool escape=false, bool lim=true);
|
||||
|
||||
//* Right justify string <str> if <x> is greater than <str> length, limit return size to <x> by default
|
||||
string rjust(string str, const size_t x, bool utf=false, bool escape=false, bool lim=true){
|
||||
if (utf || escape) {
|
||||
if (!escape && lim && ulen(str) > x) str = uresize(str, x);
|
||||
return string(max((int)(x - ulen(str, escape)), 0), ' ') + str;
|
||||
}
|
||||
else {
|
||||
if (lim && str.size() > x) str.resize(x);
|
||||
return string(max((int)(x - str.size()), 0), ' ') + str;
|
||||
}
|
||||
}
|
||||
string rjust(string str, const size_t x, bool utf=false, bool escape=false, bool lim=true);
|
||||
|
||||
//* Replace whitespaces " " with escape code for move right
|
||||
string trans(string str){
|
||||
size_t pos;
|
||||
string newstr;
|
||||
while ((pos = str.find(' ')) != string::npos){
|
||||
newstr.append(str.substr(0, pos));
|
||||
str.erase(0, pos);
|
||||
pos = 1;
|
||||
while (pos < str.size() && str.at(pos) == ' ') pos++;
|
||||
newstr.append(Mv::r(pos));
|
||||
str.erase(0, pos);
|
||||
}
|
||||
return (newstr.empty()) ? str : newstr + str;
|
||||
}
|
||||
string trans(const string& str);
|
||||
|
||||
//* Convert seconds to format "Xd HH:MM:SS" and return string
|
||||
string sec_to_dhms(uint sec){
|
||||
string out;
|
||||
uint d, h, m;
|
||||
d = sec / (3600 * 24);
|
||||
sec %= 3600 * 24;
|
||||
h = sec / 3600;
|
||||
sec %= 3600;
|
||||
m = sec / 60;
|
||||
sec %= 60;
|
||||
if (d>0) out = to_string(d) + "d ";
|
||||
out += ((h<10) ? "0" : "") + to_string(h) + ":";
|
||||
out += ((m<10) ? "0" : "") + to_string(m) + ":";
|
||||
out += ((sec<10) ? "0" : "") + to_string(sec);
|
||||
return out;
|
||||
}
|
||||
|
||||
//? Units for floating_humanizer function
|
||||
const array<string, 11> Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"};
|
||||
const array<string, 11> Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"};
|
||||
string sec_to_dhms(uint sec);
|
||||
|
||||
//* Scales up in steps of 1024 to highest possible unit and returns string with unit suffixed
|
||||
//* bit=True or defaults to bytes
|
||||
//* start=int to set 1024 multiplier starting unit
|
||||
//* short=True always returns 0 decimals and shortens unit to 1 character
|
||||
string floating_humanizer(uint64_t value, bool shorten=false, uint start=0, bool bit=false, bool per_second=false){
|
||||
string out;
|
||||
uint mult = (bit) ? 8 : 1;
|
||||
auto& units = (bit) ? Units_bit : Units_byte;
|
||||
|
||||
value *= 100 * mult;
|
||||
|
||||
while (value >= 102400){
|
||||
value >>= 10;
|
||||
if (value < 100){
|
||||
out = to_string(value);
|
||||
break;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
if (out.empty()) {
|
||||
out = to_string(value);
|
||||
if (out.size() == 4 && start > 0) { out.pop_back(); out.insert(2, ".");}
|
||||
else if (out.size() == 3 && start > 0) out.insert(1, ".");
|
||||
else if (out.size() >= 2) out.resize(out.size() - 2);
|
||||
}
|
||||
if (shorten){
|
||||
if (out.find('.') != string::npos) out = to_string((int)round(stof(out)));
|
||||
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
|
||||
out.push_back(units[start][0]);
|
||||
}
|
||||
else out += " " + units[start];
|
||||
|
||||
if (per_second) out += (bit) ? "ps" : "/s";
|
||||
return out;
|
||||
}
|
||||
string floating_humanizer(uint64_t value, bool shorten=false, uint start=0, bool bit=false, bool per_second=false);
|
||||
|
||||
//* Add std::string operator "*" : Repeat string <str> <n> number of times
|
||||
std::string operator*(string str, size_t n){
|
||||
string out;
|
||||
out.reserve(str.size() * n);
|
||||
while (n-- > 0) out += str;
|
||||
return out;
|
||||
}
|
||||
std::string operator*(string str, size_t n);
|
||||
|
||||
//* Return current time in <strf> format
|
||||
string strf_time(string strf){
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm bt {};
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(localtime_r(&in_time_t, &bt), strf.c_str());
|
||||
return ss.str();
|
||||
}
|
||||
string strf_time(string strf);
|
||||
|
||||
|
||||
#if (__GNUC__ > 10)
|
||||
//* Redirects to atomic wait
|
||||
void atomic_wait(atomic<bool>& atom, bool val=true){
|
||||
atom.wait(val);
|
||||
}
|
||||
#else
|
||||
//* Crude implementation of atomic wait for GCC 10
|
||||
void atomic_wait(atomic<bool>& atom, bool val=true){
|
||||
while (atom.load() == val) sleep_ms(1);
|
||||
}
|
||||
#endif
|
||||
//* Waits for <atom> to not be <val>
|
||||
void atomic_wait(std::atomic<bool>& atom, bool val=true);
|
||||
|
||||
//* Waits for <atom> to not be <val> and then sets it to <val> again
|
||||
void atomic_wait_set(atomic<bool>& atom, bool val=true){
|
||||
atomic_wait(atom, val);
|
||||
atom.store(val);
|
||||
}
|
||||
void atomic_wait_set(std::atomic<bool>& atom, bool val=true);
|
||||
|
||||
}
|
||||
|
||||
//* Simple logging implementation
|
||||
namespace Logger {
|
||||
using namespace Tools;
|
||||
namespace {
|
||||
std::atomic<bool> busy (false);
|
||||
bool first = true;
|
||||
string tdf = "%Y/%m/%d (%T) | ";
|
||||
vector<string> log_levels = {
|
||||
"DISABLED",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"INFO",
|
||||
"DEBUG"
|
||||
};
|
||||
}
|
||||
extern vector<string> log_levels;
|
||||
extern std::filesystem::path logfile;
|
||||
|
||||
fs::path logfile;
|
||||
uint loglevel = 2;
|
||||
|
||||
void log_write(uint level, string& msg){
|
||||
if (loglevel < level || logfile.empty()) return;
|
||||
atomic_wait_set(busy, true);
|
||||
std::error_code ec;
|
||||
if (fs::file_size(logfile, ec) > 1024 << 10 && !ec) {
|
||||
auto old_log = logfile;
|
||||
old_log += ".1";
|
||||
if (fs::exists(old_log)) fs::remove(old_log, ec);
|
||||
if (!ec) fs::rename(logfile, old_log, ec);
|
||||
}
|
||||
if (!ec) {
|
||||
std::ofstream lwrite(logfile, std::ios::app);
|
||||
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";}
|
||||
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
|
||||
lwrite.close();
|
||||
}
|
||||
else logfile.clear();
|
||||
busy.store(false);
|
||||
}
|
||||
|
||||
void error(string msg){
|
||||
log_write(1, msg);
|
||||
}
|
||||
|
||||
void warning(string msg){
|
||||
log_write(2, msg);
|
||||
}
|
||||
|
||||
void info(string msg){
|
||||
log_write(3, msg);
|
||||
}
|
||||
|
||||
void debug(string msg){
|
||||
log_write(4, msg);
|
||||
}
|
||||
void set(string level); //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
|
||||
void log_write(uint level, string& msg);
|
||||
void error(string msg);
|
||||
void warning(string msg);
|
||||
void info(string msg);
|
||||
void debug(string msg);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user