mirror of https://github.com/aristocratos/btop.git
Loads...
This commit is contained in:
parent
77ef41daea
commit
5d4e2ce182
|
@ -20,14 +20,34 @@
|
|||
|
||||
* Tab size: 4
|
||||
|
||||
## Optimization
|
||||
* Alternative operators `and`, `or` and `not`.
|
||||
|
||||
* Avoid writing to disk if possible.
|
||||
* Opening curly braces `{` at the end of the same line as the statement/condition.
|
||||
|
||||
* Make sure variables/vectors/maps/classes etc. are cleaned up if not reused.
|
||||
## General guidelines
|
||||
|
||||
* Compare cpu and memory usage with and without your code and look for alternatives if they cause a noticeable negative impact.
|
||||
* Don't force a programming style. Use object oriented, functional, data oriented, etc., where it's suitable.
|
||||
|
||||
For questions contact Aristocratos at admin@qvantnet.com
|
||||
* Use [RAII](https://en.cppreference.com/w/cpp/language/raii).
|
||||
|
||||
* Make use of the standard algorithms library, (re)watch [C++ Seasoning](https://www.youtube.com/watch?v=W2tWOdzgXHA) and [105 STL Algorithms](https://www.youtube.com/watch?v=bFSnXNIsK4A) for inspiration.
|
||||
|
||||
* Make use of the included [robin_hood unordered map & set](https://github.com/martinus/robin-hood-hashing)
|
||||
|
||||
* Do not add includes if the same functionality can be achieved using the already included libraries.
|
||||
|
||||
* Use descriptive names for variables.
|
||||
|
||||
* Use comments if not very obvious what your code is doing.
|
||||
|
||||
* Add comments as labels for what's currently happening in bigger sections of code for better readability.
|
||||
|
||||
* Avoid writing to disk.
|
||||
|
||||
* If using the logger functions, be sensible, only call it if something of importance has changed.
|
||||
|
||||
* Benchmark your code and look for alternatives if they cause a noticeable negative impact.
|
||||
|
||||
For questions open a new discussion thread or send a mail to jakob@qvantnet.com
|
||||
|
||||
For proposing changes to this document create a [new issue](https://github.com/aristocratos/btop/issues/new/choose).
|
||||
|
|
294
src/btop.cpp
294
src/btop.cpp
|
@ -16,19 +16,11 @@ indent = tab
|
|||
tab-size = 4
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <csignal>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <atomic>
|
||||
#include <numeric>
|
||||
#include <ranges>
|
||||
#include <filesystem>
|
||||
#include <unistd.h>
|
||||
#include <robin_hood.h>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
|
@ -58,8 +50,14 @@ tab-size = 4
|
|||
#error Platform not supported!
|
||||
#endif
|
||||
|
||||
using std::string, std::string_view, std::vector, std::array, std::atomic, std::endl, std::cout, std::min;
|
||||
using std::flush, std::endl, std::string_literals::operator""s, std::to_string;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
||||
namespace Global {
|
||||
const std::vector<std::array<std::string, 2>> Banner_src = {
|
||||
const vector<array<string, 2>> Banner_src = {
|
||||
{"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
|
||||
{"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
|
||||
{"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
|
||||
|
@ -67,19 +65,12 @@ namespace Global {
|
|||
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
|
||||
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
|
||||
};
|
||||
const std::string Version = "0.0.30";
|
||||
const string Version = "0.0.30";
|
||||
|
||||
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, std::to_string, std::round;
|
||||
namespace fs = std::filesystem;
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
||||
namespace Global {
|
||||
string banner;
|
||||
size_t banner_width = 0;
|
||||
string overlay;
|
||||
|
||||
string exit_error_msg;
|
||||
atomic<bool> thread_exception (false);
|
||||
|
@ -88,6 +79,7 @@ namespace Global {
|
|||
|
||||
bool debuginit = false;
|
||||
bool debug = false;
|
||||
bool utf_force = false;
|
||||
|
||||
uint64_t start_time;
|
||||
|
||||
|
@ -100,15 +92,10 @@ namespace Global {
|
|||
|
||||
|
||||
//* A simple argument parser
|
||||
void argumentParser(int argc, char **argv){
|
||||
string argument;
|
||||
void argumentParser(const int& argc, char **argv) {
|
||||
for(int i = 1; i < argc; i++) {
|
||||
argument = argv[i];
|
||||
if (argument == "-v" or argument == "--version") {
|
||||
cout << "btop version: " << Global::Version << endl;
|
||||
exit(0);
|
||||
}
|
||||
else if (argument == "-h" or argument == "--help") {
|
||||
string argument = argv[i];
|
||||
if (argument == "-h" or argument == "--help") {
|
||||
cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n"
|
||||
<< "optional arguments:\n"
|
||||
<< " -h, --help show this help message and exit\n"
|
||||
|
@ -116,12 +103,18 @@ void argumentParser(int argc, char **argv){
|
|||
<< " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\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"
|
||||
<< " --utf-foce force start even if no UTF-8 locale was detected"
|
||||
<< " --debug start with loglevel set to DEBUG, overriding value set in config\n"
|
||||
<< endl;
|
||||
exit(0);
|
||||
}
|
||||
else if (argument == "--debug")
|
||||
Global::debug = true;
|
||||
if (argument == "-v" or argument == "--version") {
|
||||
cout << "btop version: " << Global::Version << endl;
|
||||
exit(0);
|
||||
}
|
||||
else if (argument == "-lc" or argument == "--low-color") {
|
||||
Global::arg_low_color = true;
|
||||
}
|
||||
else if (argument == "-t" or argument == "--tty_on") {
|
||||
Config::set("tty_mode", true);
|
||||
Global::arg_tty = true;
|
||||
|
@ -130,9 +123,10 @@ void argumentParser(int argc, char **argv){
|
|||
Config::set("tty_mode", false);
|
||||
Global::arg_tty = true;
|
||||
}
|
||||
else if (argument == "-lc" or argument == "--low-color") {
|
||||
Global::arg_low_color = true;
|
||||
}
|
||||
else if (argument == "--utf-force")
|
||||
Global::utf_force = true;
|
||||
else if (argument == "--debug")
|
||||
Global::debug = true;
|
||||
else {
|
||||
cout << " Unknown argument: " << argument << "\n" <<
|
||||
" Use -h or --help for help." << endl;
|
||||
|
@ -142,7 +136,7 @@ void argumentParser(int argc, char **argv){
|
|||
}
|
||||
|
||||
//* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true
|
||||
void term_resize(bool force=false){
|
||||
void term_resize(bool force=false) {
|
||||
if (auto refreshed = Term::refresh() or force) {
|
||||
if (force and refreshed) force = false;
|
||||
Global::resized = true;
|
||||
|
@ -160,7 +154,7 @@ void term_resize(bool force=false){
|
|||
}
|
||||
|
||||
//* Exit handler; stops threads, restores terminal and saves config changes
|
||||
void clean_quit(int sig){
|
||||
void clean_quit(const int sig) {
|
||||
if (Global::quitting) return;
|
||||
Global::quitting = true;
|
||||
Runner::stop();
|
||||
|
@ -168,14 +162,17 @@ void clean_quit(int sig){
|
|||
Term::restore();
|
||||
if (not Global::debuginit) cout << Term::normal_screen << Term::show_cursor << flush;
|
||||
}
|
||||
if (not Global::exit_error_msg.empty()) {
|
||||
Logger::error(Global::exit_error_msg);
|
||||
cout << "ERROR: " << Global::exit_error_msg << endl;
|
||||
}
|
||||
Config::write();
|
||||
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
|
||||
if (not Global::exit_error_msg.empty()) cout << Global::exit_error_msg << endl;
|
||||
if (sig != -1) exit(sig);
|
||||
}
|
||||
|
||||
//* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
|
||||
void _sleep(){
|
||||
void _sleep() {
|
||||
Runner::stop();
|
||||
if (Term::initialized) {
|
||||
Term::restore();
|
||||
|
@ -185,7 +182,7 @@ void _sleep(){
|
|||
}
|
||||
|
||||
//* Handler for SIGCONT; re-initialize terminal and force a resize event
|
||||
void _resume(){
|
||||
void _resume() {
|
||||
Term::init();
|
||||
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << flush;
|
||||
term_resize(true);
|
||||
|
@ -195,7 +192,7 @@ void _exit_handler() {
|
|||
clean_quit(-1);
|
||||
}
|
||||
|
||||
void _signal_handler(int sig) {
|
||||
void _signal_handler(const int sig) {
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
clean_quit(0);
|
||||
|
@ -212,24 +209,22 @@ void _signal_handler(int sig) {
|
|||
}
|
||||
}
|
||||
|
||||
//? Generate the btop++ banner
|
||||
//* Generate the btop++ banner
|
||||
void banner_gen() {
|
||||
size_t z = 0;
|
||||
string b_color, bg, fg, oc, letter;
|
||||
auto& lowcolor = Config::getB("lowcolor");
|
||||
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;
|
||||
string b_color, bg, fg, oc, letter;
|
||||
auto& lowcolor = Config::getB("lowcolor");
|
||||
auto& tty_mode = Config::getB("tty_mode");
|
||||
for (size_t z = 0; const auto& line : Global::Banner_src) {
|
||||
if (const auto w = ulen(line[1]); w > Global::banner_width) Global::banner_width = w;
|
||||
if (tty_mode) {
|
||||
fg = (z > 2) ? "\x1b[31m" : "\x1b[91m";
|
||||
bg = (z > 2) ? "\x1b[90m" : "\x1b[37m";
|
||||
}
|
||||
else {
|
||||
fg = Theme::hex_to_color(line[0], lowcolor);
|
||||
bg_i = 120 - z * 12;
|
||||
int bg_i = 120 - z * 12;
|
||||
bg = Theme::dec_to_color(bg_i, bg_i, bg_i, lowcolor);
|
||||
}
|
||||
for (size_t i = 0; i < line[1].size(); i += 3) {
|
||||
|
@ -253,34 +248,33 @@ void banner_gen() {
|
|||
+ Fx::i + "v" + Global::Version + Fx::ui;
|
||||
}
|
||||
|
||||
//? Manages secondary thread for collection and drawing of boxes
|
||||
//* Manages secondary thread for collection and drawing of boxes
|
||||
namespace Runner {
|
||||
atomic<bool> active (false);
|
||||
atomic<bool> stopping (false);
|
||||
atomic<bool> has_output (false);
|
||||
atomic<uint64_t> time_spent (0);
|
||||
|
||||
string output;
|
||||
string overlay;
|
||||
|
||||
//* Secondary thread; run collect, draw and print out
|
||||
void _runner(const vector<string> boxes, const bool no_update, const bool force_redraw, const bool interrupt) {
|
||||
auto timestamp = time_micros();
|
||||
string out;
|
||||
out.reserve(output.size());
|
||||
output.clear();
|
||||
|
||||
for (auto& box : boxes) {
|
||||
for (const auto& box : boxes) {
|
||||
if (stopping) break;
|
||||
try {
|
||||
if (box == "cpu") {
|
||||
out += Cpu::draw(Cpu::collect(no_update), force_redraw);
|
||||
output += Cpu::draw(Cpu::collect(no_update), force_redraw);
|
||||
}
|
||||
else if (box == "mem") {
|
||||
out += Mem::draw(Mem::collect(no_update), force_redraw);
|
||||
output += Mem::draw(Mem::collect(no_update), force_redraw);
|
||||
}
|
||||
else if (box == "net") {
|
||||
out += Net::draw(Net::collect(no_update), force_redraw);
|
||||
output += Net::draw(Net::collect(no_update), force_redraw);
|
||||
}
|
||||
else if (box == "proc") {
|
||||
out += Proc::draw(Proc::collect(no_update), force_redraw);
|
||||
output += Proc::draw(Proc::collect(no_update), force_redraw);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
|
@ -289,7 +283,6 @@ namespace Runner {
|
|||
Global::exit_error_msg = "Exception in runner thread -> "
|
||||
+ fname + "::draw(" + fname + "::collect(no_update=" + (no_update ? "true" : "false")
|
||||
+ "), force_redraw=" + (force_redraw ? "true" : "false") + ") : " + (string)e.what();
|
||||
Logger::error(Global::exit_error_msg);
|
||||
Global::thread_exception = true;
|
||||
Input::interrupt = true;
|
||||
stopping = true;
|
||||
|
@ -303,31 +296,39 @@ namespace Runner {
|
|||
return;
|
||||
}
|
||||
|
||||
if (out.empty()) {
|
||||
out += "No boxes shown!";
|
||||
if (output.empty()) {
|
||||
output += "No boxes shown!";
|
||||
}
|
||||
|
||||
output.swap(out);
|
||||
time_spent = time_micros() - timestamp;
|
||||
has_output = true;
|
||||
//? If overlay isn't empty, print output without color and effects and then print overlay over
|
||||
cout << Term::sync_start << (overlay.empty() ? output : Theme::c("inactive_fg") + Fx::uncolor(output) + overlay) << Term::sync_end << flush;
|
||||
|
||||
//! DEBUG stats -->
|
||||
cout << Fx::reset << Mv::to(2, 2) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
|
||||
|
||||
if (interrupt) Input::interrupt = true;
|
||||
|
||||
active = false;
|
||||
active.notify_one();
|
||||
active.notify_all();
|
||||
}
|
||||
|
||||
void run(const string box, const bool no_update, const bool force_redraw, const bool input_interrupt) {
|
||||
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all" for all boxes
|
||||
void run(const string& box, const bool no_update, const bool force_redraw, const bool input_interrupt) {
|
||||
active.wait(true);
|
||||
Config::unlock();
|
||||
if (stopping) return;
|
||||
active = true;
|
||||
Config::lock();
|
||||
vector<string> boxes;
|
||||
if (box == "all") boxes = Config::current_boxes;
|
||||
else boxes.push_back(box);
|
||||
std::thread run_thread(_runner, boxes, no_update, force_redraw, input_interrupt);
|
||||
if (not Global::overlay.empty())
|
||||
overlay = Global::overlay;
|
||||
else if (not overlay.empty())
|
||||
overlay.clear();
|
||||
|
||||
std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw, input_interrupt);
|
||||
run_thread.detach();
|
||||
}
|
||||
|
||||
//* Stops any secondary thread running and unlocks config
|
||||
void stop() {
|
||||
stopping = true;
|
||||
active.wait(true);
|
||||
|
@ -335,25 +336,20 @@ namespace Runner {
|
|||
stopping = false;
|
||||
}
|
||||
|
||||
string get_output() {
|
||||
active.wait(true);
|
||||
Config::unlock();
|
||||
has_output = false;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//? --------------------------------------------- Main starts here! ---------------------------------------------------
|
||||
int main(int argc, char **argv){
|
||||
//* --------------------------------------------- Main starts here! ---------------------------------------------------
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
//? Init
|
||||
//? ------------------------------------------------ INIT ---------------------------------------------------------
|
||||
|
||||
Global::start_time = time_s();
|
||||
|
||||
cout.setf(std::ios::boolalpha);
|
||||
//? Call argument parser if launched with arguments
|
||||
if (argc > 1) argumentParser(argc, argv);
|
||||
|
||||
//? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
|
||||
std::atexit(_exit_handler);
|
||||
std::at_quick_exit(_exit_handler);
|
||||
std::signal(SIGINT, _signal_handler);
|
||||
|
@ -365,14 +361,15 @@ int main(int argc, char **argv){
|
|||
#if defined(LINUX)
|
||||
Global::coreCount = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
if (Global::coreCount < 1) Global::coreCount = 1;
|
||||
{
|
||||
std::error_code ec;
|
||||
Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
|
||||
|
||||
{ std::error_code ec;
|
||||
Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
//? Setup paths for config, log and themes
|
||||
for (auto env : {"XDG_CONFIG_HOME", "HOME"}) {
|
||||
//? Setup paths for config, log and user themes
|
||||
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
|
||||
if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) {
|
||||
Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
|
||||
break;
|
||||
|
@ -381,7 +378,7 @@ int main(int argc, char **argv){
|
|||
if (not Config::conf_dir.empty()) {
|
||||
if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not fs::create_directories(Config::conf_dir, ec)) {
|
||||
cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled." << endl;
|
||||
cout << "Make sure your $HOME environment variable is correctly set to fix this." << endl;
|
||||
cout << "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
|
||||
}
|
||||
else {
|
||||
Config::conf_file = Config::conf_dir / "btop.conf";
|
||||
|
@ -390,11 +387,12 @@ int main(int argc, char **argv){
|
|||
if (not fs::exists(Theme::user_theme_dir) and not fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear();
|
||||
}
|
||||
}
|
||||
//? Try to find global btop theme path relative to binary path
|
||||
if (std::error_code ec; not Global::self_path.empty()) {
|
||||
Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
|
||||
if (ec or not fs::is_directory(Theme::theme_dir) or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
|
||||
}
|
||||
|
||||
//? If relative path failed, check two most common absolute paths
|
||||
if (Theme::theme_dir.empty()) {
|
||||
for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) {
|
||||
if (fs::is_directory(fs::path(theme_path)) and access(theme_path, R_OK) != -1) {
|
||||
|
@ -415,14 +413,36 @@ int main(int argc, char **argv){
|
|||
|
||||
Logger::info("Logger set to " + Config::getS("log_level"));
|
||||
|
||||
for (auto& err_str : load_errors) Logger::warning(err_str);
|
||||
for (const auto& err_str : load_errors) Logger::warning(err_str);
|
||||
}
|
||||
|
||||
if (not string(getenv("LANG")).ends_with("UTF-8") and not string(getenv("LANG")).ends_with("utf-8")) {
|
||||
string err_msg = "No UTF-8 locale was detected! Symbols might not look as intended.\n"
|
||||
"Make sure your $LANG evironment variable is set and with a UTF-8 locale.";
|
||||
Logger::warning(err_msg);
|
||||
cout << "WARNING: " << err_msg << endl;
|
||||
//? Try to find and set a UTF-8 locale
|
||||
if (bool found = false; not str_to_upper((string)std::setlocale(LC_ALL, NULL)).ends_with("UTF-8")) {
|
||||
if (const string lang = (string)getenv("LANG"); str_to_upper(lang).ends_with("UTF-8")) {
|
||||
found = true;
|
||||
std::setlocale(LC_ALL, lang.c_str());
|
||||
}
|
||||
else if (const string loc = std::locale("").name(); not loc.empty()) {
|
||||
try {
|
||||
for (auto& l : ssplit(loc, ';')) {
|
||||
if (str_to_upper(l).ends_with("UTF-8")) {
|
||||
found = true;
|
||||
std::setlocale(LC_ALL, l.substr(l.find('=') + 1).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::out_of_range&) { found = false; }
|
||||
}
|
||||
|
||||
if (not found and Global::utf_force)
|
||||
Logger::warning("No UTF-8 locale detected! Forcing start with --utf-force argument.");
|
||||
else if (not found) {
|
||||
Global::exit_error_msg = "No UTF-8 locale detected! Use --utf-force argument to start anyway.";
|
||||
clean_quit(1);
|
||||
}
|
||||
else
|
||||
Logger::debug("Setting LC_ALL=" + (string)std::setlocale(LC_ALL, NULL));
|
||||
}
|
||||
|
||||
//? Initialize terminal and set options
|
||||
|
@ -443,52 +463,54 @@ int main(int argc, char **argv){
|
|||
Logger::info("Real tty detected, setting 16 color mode and using tty friendly graph symbols");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//? Platform init and error check
|
||||
//? Platform dependent init and error check
|
||||
Shared::init();
|
||||
|
||||
|
||||
|
||||
// Config::set("truecolor", false);
|
||||
|
||||
auto thts = time_micros();
|
||||
|
||||
//? Update theme list and generate the theme
|
||||
//? Update list of available themes and generate the selected theme
|
||||
Theme::updateThemes();
|
||||
Theme::setTheme();
|
||||
|
||||
//? Create the btop++ banner
|
||||
banner_gen();
|
||||
|
||||
//? Calculate sizes of all boxes
|
||||
Draw::calcSizes();
|
||||
|
||||
//? Start first collection and drawing run
|
||||
Runner::run();
|
||||
|
||||
Global::debuginit = false; //! Debug -- remove
|
||||
|
||||
//? Switch to alternative terminal buffer, hide cursor, and print out box outlines
|
||||
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor;
|
||||
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
|
||||
|
||||
|
||||
//* ------------------------------------------------ TESTING ------------------------------------------------------
|
||||
|
||||
|
||||
Global::debuginit = false;
|
||||
if (false) {
|
||||
|
||||
Draw::calcSizes();
|
||||
|
||||
|
||||
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << Term::clear << endl;
|
||||
|
||||
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
|
||||
cout << "Current: " << std::setlocale(LC_ALL, NULL) << endl;
|
||||
exit(0);
|
||||
|
||||
}
|
||||
|
||||
//* Test theme
|
||||
if (false) {
|
||||
string key;
|
||||
bool no_redraw = false;
|
||||
Config::unlock();
|
||||
auto theme_index = v_index(Theme::themes, Config::getS("color_theme"));
|
||||
uint64_t timer = 0;
|
||||
while (key != "q") {
|
||||
key.clear();
|
||||
|
||||
if (not no_redraw) {
|
||||
cout << "\nTheme generation of " << fs::path(Config::getS("color_theme")).filename().replace_extension("") << " took " << time_micros() - thts << "μs" << endl;
|
||||
|
||||
cout << "Colors:" << endl;
|
||||
cout << Fx::reset << Term::clear << "Theme: " << Config::getS("color_theme") << ". Generation took " << timer << " μs." << endl;
|
||||
size_t i = 0;
|
||||
for(auto& item : Theme::test_colors()) {
|
||||
for(const auto& item : Theme::colors) {
|
||||
cout << rjust(item.first, 15) << ":" << item.second << "■"s * 10 << Fx::reset << " ";
|
||||
if (++i == 4) {
|
||||
i = 0;
|
||||
|
@ -499,7 +521,7 @@ int main(int argc, char **argv){
|
|||
|
||||
|
||||
cout << "Gradients:";
|
||||
for (auto& [name, cvec] : Theme::test_gradients()) {
|
||||
for (const auto& [name, cvec] : Theme::gradients) {
|
||||
cout << endl << rjust(name + ":", 10);
|
||||
for (auto& color : cvec) {
|
||||
cout << color << "■";
|
||||
|
@ -512,7 +534,6 @@ int main(int argc, char **argv){
|
|||
no_redraw = true;
|
||||
key = Input::wait();
|
||||
if (key.empty()) continue;
|
||||
thts = time_micros();
|
||||
if (key == "right") {
|
||||
if (theme_index == Theme::themes.size() - 1) theme_index = 0;
|
||||
else theme_index++;
|
||||
|
@ -524,7 +545,9 @@ int main(int argc, char **argv){
|
|||
else continue;
|
||||
no_redraw = false;
|
||||
Config::set("color_theme", Theme::themes.at(theme_index));
|
||||
timer = time_micros();
|
||||
Theme::setTheme();
|
||||
timer = time_micros() - timer;
|
||||
|
||||
}
|
||||
|
||||
|
@ -558,7 +581,6 @@ int main(int argc, char **argv){
|
|||
<< Mv::restore << Mv::d(26) << kgraph3(mydata, true) << '\n'
|
||||
<< Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
|
||||
|
||||
list<uint64_t> ktavg;
|
||||
for (;;) {
|
||||
mydata.back() = std::rand() % 101;
|
||||
kts = time_micros();
|
||||
|
@ -566,9 +588,7 @@ int main(int argc, char **argv){
|
|||
<< Mv::restore << Mv::d(13) << kgraph2(mydata)
|
||||
<< Mv::restore << Mv::d(26) << kgraph3(mydata)
|
||||
<< Term::sync_end << endl;
|
||||
ktavg.push_front(time_micros() - kts);
|
||||
if (ktavg.size() > 100) ktavg.pop_back();
|
||||
cout << Mv::d(1) << "Time: " << ktavg.front() << " μs. Avg: " << accumulate(ktavg.begin(), ktavg.end(), 0) / ktavg.size() << " μs. " << flush;
|
||||
cout << Mv::d(1) << "Time: " << time_micros() - kts << " μs. " << flush;
|
||||
if (Input::poll()) {
|
||||
if (Input::get() == "space") Input::wait();
|
||||
else break;
|
||||
|
@ -583,15 +603,15 @@ int main(int argc, char **argv){
|
|||
|
||||
|
||||
|
||||
//* ------------------------------------------------ MAIN LOOP ----------------------------------------------------
|
||||
//? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
|
||||
|
||||
uint64_t future_time = time_ms(), rcount = 0;
|
||||
list<uint64_t> avgtimes;
|
||||
uint64_t update_ms = Config::getI("update_ms");
|
||||
auto future_time = time_ms();
|
||||
|
||||
try {
|
||||
while (not true not_eq not false) {
|
||||
//? Check for exceptions in secondary thread and exit with fail signal if true
|
||||
if (Global::thread_exception) clean_quit(1);
|
||||
uint64_t update_ms = Config::getI("update_ms");
|
||||
|
||||
//? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
|
||||
term_resize();
|
||||
|
@ -604,31 +624,28 @@ int main(int argc, char **argv){
|
|||
Runner::run("all", true, true);
|
||||
}
|
||||
|
||||
//? Print out any available output from secondary thread
|
||||
if (Runner::has_output) {
|
||||
cout << Term::sync_start << Runner::get_output() << Term::sync_end << flush;
|
||||
|
||||
//! DEBUG stats -->
|
||||
avgtimes.push_front(Runner::time_spent);
|
||||
if (avgtimes.size() > 30) avgtimes.pop_back();
|
||||
cout << Fx::reset << Mv::to(2, 2) << "Runner took: " << rjust(to_string(avgtimes.front()), 5) << " μs. Average: " <<
|
||||
rjust(to_string(accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size()), 5) << " μs of " << avgtimes.size() <<
|
||||
" samples. Run count: " << ++rcount << ". " << flush;
|
||||
//! <--
|
||||
}
|
||||
|
||||
//? Start collect & draw thread at the interval set by <update_ms> config value
|
||||
//? Start secondary collect & draw thread at the interval set by <update_ms> config value
|
||||
if (time_ms() >= future_time) {
|
||||
Runner::run("all");
|
||||
update_ms = Config::getI("update_ms");
|
||||
future_time = time_ms() + update_ms;
|
||||
}
|
||||
|
||||
//? Loop over input polling and input action processing and check for external clock changes
|
||||
//? Loop over input polling and input action processing
|
||||
for (auto current_time = time_ms(); current_time < future_time and not Global::resized; current_time = time_ms()) {
|
||||
|
||||
//? Check for external clock changes to avoid a timer bugs
|
||||
if (future_time - current_time > update_ms)
|
||||
future_time = current_time;
|
||||
else if (Input::poll(future_time - current_time))
|
||||
|
||||
//? Poll for input and process any input detected
|
||||
else if (Input::poll(min(1000ul, future_time - current_time))) {
|
||||
if (not Runner::active)
|
||||
Config::unlock();
|
||||
Input::process(Input::get());
|
||||
}
|
||||
|
||||
//? Break the loop at 1000ms intervals or if input polling was interrupted
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
@ -637,7 +654,6 @@ int main(int argc, char **argv){
|
|||
}
|
||||
catch (std::exception& e) {
|
||||
Global::exit_error_msg = "Exception in main loop -> " + (string)e.what();
|
||||
Logger::error(Global::exit_error_msg);
|
||||
clean_quit(1);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ tab-size = 4
|
|||
*/
|
||||
|
||||
#include <array>
|
||||
#include <robin_hood.h>
|
||||
#include <ranges>
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
|
@ -26,215 +25,214 @@ tab-size = 4
|
|||
#include <btop_shared.hpp>
|
||||
#include <btop_tools.hpp>
|
||||
|
||||
using robin_hood::unordered_flat_map, std::map, std::array, std::atomic;
|
||||
using 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", "#* Full path to a bashtop/bpytop/btop++ formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes."},
|
||||
atomic<bool> locked (false);
|
||||
atomic<bool> writelock (false);
|
||||
bool write_new;
|
||||
|
||||
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
|
||||
const vector<array<string, 2>> descriptions = {
|
||||
{"color_theme", "#* Full path to a bashtop/bpytop/btop++ formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes."},
|
||||
|
||||
{"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
|
||||
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
|
||||
|
||||
{"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n"
|
||||
"#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
|
||||
{"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."},
|
||||
{"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n"
|
||||
"#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
|
||||
|
||||
{"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
{"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_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
{"graph_symbol_cpu", "# 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_mem", "# 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\"."},
|
||||
{"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
|
||||
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
|
||||
{"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
|
||||
|
||||
{"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."},
|
||||
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
|
||||
|
||||
{"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)."},
|
||||
{"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_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_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_reversed", "#* Reverse sorting order, True or False."},
|
||||
{"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_tree", "#* Show processes as a tree."},
|
||||
{"proc_reversed", "#* Reverse sorting order, True or False."},
|
||||
|
||||
{"proc_colors", "#* Use the cpu graph colors in the process list."},
|
||||
{"proc_tree", "#* Show processes as a tree."},
|
||||
|
||||
{"proc_gradient", "#* Use a darkening gradient in the process list."},
|
||||
{"proc_colors", "#* Use the cpu graph colors 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_gradient", "#* Use a darkening gradient in the process list."},
|
||||
|
||||
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
|
||||
{"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."},
|
||||
|
||||
{"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."},
|
||||
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
|
||||
|
||||
{"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_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_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
|
||||
{"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_single_graph", "#* Set to True to completely disable the lower CPU graph."},
|
||||
{"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
|
||||
|
||||
{"show_uptime", "#* Shows the system uptime in the CPU box."},
|
||||
{"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
|
||||
|
||||
{"check_temp", "#* Show cpu temperature."},
|
||||
{"show_uptime", "#* Shows the system uptime in the CPU box."},
|
||||
|
||||
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
|
||||
{"check_temp", "#* Show cpu temperature."},
|
||||
|
||||
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
|
||||
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
|
||||
|
||||
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
|
||||
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
|
||||
|
||||
{"show_cpu_freq", "#* Show CPU frequency."},
|
||||
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
|
||||
|
||||
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
|
||||
{"show_cpu_freq", "#* Show CPU frequency."},
|
||||
|
||||
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
|
||||
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
|
||||
|
||||
{"custom_cpu_name", "#* Custom cpu model name, 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."},
|
||||
|
||||
{"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\"."},
|
||||
{"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
|
||||
|
||||
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
|
||||
{"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\"."},
|
||||
|
||||
{"show_swap", "#* If swap memory should be shown in memory box."},
|
||||
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
|
||||
|
||||
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
|
||||
{"show_swap", "#* If swap memory should be shown in memory box."},
|
||||
|
||||
{"show_disks", "#* If mem box should be split to also show disks info."},
|
||||
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
|
||||
|
||||
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
|
||||
{"show_disks", "#* If mem box should be split to also show disks info."},
|
||||
|
||||
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
|
||||
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
|
||||
|
||||
{"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
|
||||
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
|
||||
|
||||
{"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
|
||||
{"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
|
||||
|
||||
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
|
||||
{"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
|
||||
|
||||
{"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\"."},
|
||||
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
|
||||
|
||||
{"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\"."},
|
||||
{"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_upload", ""},
|
||||
{"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_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
|
||||
{"net_upload", ""},
|
||||
|
||||
{"net_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
|
||||
{"net_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
|
||||
|
||||
{"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_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
|
||||
|
||||
{"net_iface", "#* Starts with the Network Interface specified here."},
|
||||
{"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."},
|
||||
|
||||
{"show_battery", "#* Show battery stats in top right if battery is present."},
|
||||
{"net_iface", "#* Starts with the Network Interface specified here."},
|
||||
|
||||
{"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."}
|
||||
};
|
||||
{"show_battery", "#* Show battery stats in top right if battery is present."},
|
||||
|
||||
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", ""},
|
||||
{"proc_command", ""},
|
||||
};
|
||||
unordered_flat_map<string, string> stringsTmp;
|
||||
{"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, 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},
|
||||
{"lowcolor", false},
|
||||
{"show_detailed", false},
|
||||
{"proc_filtering", false},
|
||||
};
|
||||
unordered_flat_map<string, bool> boolsTmp;
|
||||
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", ""},
|
||||
{"proc_command", ""},
|
||||
};
|
||||
unordered_flat_map<string, string> stringsTmp;
|
||||
|
||||
unordered_flat_map<string, int> ints = {
|
||||
{"update_ms", 2000},
|
||||
{"proc_update_mult", 2},
|
||||
{"detailed_pid", 0},
|
||||
{"selected_pid", 0},
|
||||
{"proc_start", 0},
|
||||
{"proc_selected", 0},
|
||||
};
|
||||
unordered_flat_map<string, int> intsTmp;
|
||||
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},
|
||||
{"lowcolor", false},
|
||||
{"show_detailed", false},
|
||||
{"proc_filtering", false},
|
||||
};
|
||||
unordered_flat_map<string, bool> boolsTmp;
|
||||
|
||||
vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
|
||||
unordered_flat_map<string, int> ints = {
|
||||
{"update_ms", 2000},
|
||||
{"proc_update_mult", 2},
|
||||
{"detailed_pid", 0},
|
||||
{"selected_pid", 0},
|
||||
{"proc_start", 0},
|
||||
{"proc_selected", 0},
|
||||
};
|
||||
unordered_flat_map<string, int> intsTmp;
|
||||
|
||||
bool _locked(const string& name){
|
||||
writelock.wait(true);
|
||||
if (not write_new and rng::find_if(descriptions, [&name](const auto& a){ return a.at(0) == name; }) != descriptions.end())
|
||||
write_new = true;
|
||||
return locked.load();
|
||||
}
|
||||
vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
|
||||
|
||||
bool _locked(const string& name) {
|
||||
writelock.wait(true);
|
||||
if (not write_new and 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;
|
||||
|
@ -242,36 +240,22 @@ namespace Config {
|
|||
|
||||
vector<string> current_boxes;
|
||||
|
||||
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
|
||||
|
||||
const bool& getB(const string& name){
|
||||
return bools.at(name);
|
||||
}
|
||||
|
||||
const int& getI(const string& name){
|
||||
return ints.at(name);
|
||||
}
|
||||
|
||||
const string& getS(const string& name){
|
||||
return strings.at(name);
|
||||
}
|
||||
|
||||
void set(string name, bool value){
|
||||
void set(string name, bool value) {
|
||||
if (_locked(name)) boolsTmp.insert_or_assign(name, value);
|
||||
else bools.at(name) = value;
|
||||
}
|
||||
|
||||
void set(string name, int value){
|
||||
void set(string name, int value) {
|
||||
if (_locked(name)) intsTmp.insert_or_assign(name, value);
|
||||
ints.at(name) = value;
|
||||
}
|
||||
|
||||
void set(string name, string value){
|
||||
void set(string name, string value) {
|
||||
if (_locked(name)) stringsTmp.insert_or_assign(name, value);
|
||||
else strings.at(name) = value;
|
||||
}
|
||||
|
||||
void flip(string name){
|
||||
void flip(string name) {
|
||||
if (_locked(name)) {
|
||||
if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name);
|
||||
else boolsTmp.insert_or_assign(name, (not bools.at(name)));
|
||||
|
@ -279,42 +263,49 @@ namespace Config {
|
|||
else bools.at(name) = not bools.at(name);
|
||||
}
|
||||
|
||||
void lock(){
|
||||
void lock() {
|
||||
writelock.wait(true);
|
||||
locked = true;
|
||||
}
|
||||
|
||||
void unlock(){
|
||||
void unlock() {
|
||||
if (not locked) return;
|
||||
writelock.wait(true);
|
||||
writelock = true;
|
||||
|
||||
if (Proc::shown) {
|
||||
ints.at("selected_pid") = Proc::selected_pid;
|
||||
ints.at("proc_start") = Proc::start;
|
||||
ints.at("proc_selected") = Proc::selected;
|
||||
}
|
||||
try {
|
||||
if (Proc::shown) {
|
||||
ints.at("selected_pid") = Proc::selected_pid;
|
||||
ints.at("proc_start") = Proc::start;
|
||||
ints.at("proc_selected") = Proc::selected;
|
||||
}
|
||||
|
||||
for (auto& item : stringsTmp){
|
||||
strings.at(item.first) = item.second;
|
||||
}
|
||||
stringsTmp.clear();
|
||||
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 : intsTmp) {
|
||||
ints.at(item.first) = item.second;
|
||||
}
|
||||
intsTmp.clear();
|
||||
|
||||
for (auto& item : boolsTmp){
|
||||
bools.at(item.first) = item.second;
|
||||
for (auto& item : boolsTmp) {
|
||||
bools.at(item.first) = item.second;
|
||||
}
|
||||
boolsTmp.clear();
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
Global::exit_error_msg = "Exception during Config::unlock() : " + (string)e.what();
|
||||
clean_quit(1);
|
||||
}
|
||||
boolsTmp.clear();
|
||||
|
||||
locked = false;
|
||||
writelock = false;
|
||||
writelock.notify_all();
|
||||
}
|
||||
|
||||
bool check_boxes(string boxes){
|
||||
bool check_boxes(string boxes) {
|
||||
auto new_boxes = ssplit(boxes);
|
||||
for (auto& box : new_boxes) {
|
||||
if (not v_contains(valid_boxes, box)) return false;
|
||||
|
@ -323,7 +314,7 @@ namespace Config {
|
|||
return true;
|
||||
}
|
||||
|
||||
void load(fs::path conf_file, vector<string>& load_errors){
|
||||
void load(fs::path conf_file, vector<string>& load_errors) {
|
||||
if (conf_file.empty())
|
||||
return;
|
||||
else if (not fs::exists(conf_file)) {
|
||||
|
@ -392,7 +383,7 @@ namespace Config {
|
|||
}
|
||||
}
|
||||
|
||||
void write(){
|
||||
void write() {
|
||||
if (conf_file.empty() or not write_new) return;
|
||||
Logger::debug("Writing new config file");
|
||||
std::ofstream cwrite(conf_file, std::ios::trunc);
|
||||
|
|
|
@ -20,9 +20,10 @@ tab-size = 4
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <robin_hood.h>
|
||||
#include <filesystem>
|
||||
|
||||
using std::string, std::vector;
|
||||
using std::string, std::vector, robin_hood::unordered_flat_map;
|
||||
|
||||
//* Functions and variables for reading and writing the btop config file
|
||||
namespace Config {
|
||||
|
@ -30,7 +31,11 @@ namespace Config {
|
|||
extern std::filesystem::path conf_dir;
|
||||
extern std::filesystem::path conf_file;
|
||||
|
||||
extern const vector<string> valid_graph_symbols;
|
||||
extern unordered_flat_map<string, string> strings;
|
||||
extern unordered_flat_map<string, bool> bools;
|
||||
extern unordered_flat_map<string, int> ints;
|
||||
|
||||
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
|
||||
|
||||
extern vector<string> current_boxes;
|
||||
|
||||
|
@ -38,13 +43,13 @@ namespace Config {
|
|||
bool check_boxes(string boxes);
|
||||
|
||||
//* Return bool for config key <name>
|
||||
const bool& getB(const string& name);
|
||||
inline const bool& getB(const string& name) { return bools.at(name); }
|
||||
|
||||
//* Return integer for config key <name>
|
||||
const int& getI(const string& name);
|
||||
inline const int& getI(const string& name) { return ints.at(name); }
|
||||
|
||||
//* Return string for config key <name>
|
||||
const string& getS(const string& name);
|
||||
inline const string& getS(const string& name) { return strings.at(name); }
|
||||
|
||||
//* Set config key <name> to bool <value>
|
||||
void set(string name, bool value);
|
||||
|
@ -58,7 +63,7 @@ namespace Config {
|
|||
//* Flip config key bool <name>
|
||||
void flip(string name);
|
||||
|
||||
//* Wait if locked then lock config and cache changes until unlock
|
||||
//* Lock config and cache changes until unlocked
|
||||
void lock();
|
||||
|
||||
//* Unlock config and write any cached values to config
|
||||
|
|
|
@ -17,8 +17,6 @@ tab-size = 4
|
|||
*/
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
|
@ -32,7 +30,6 @@ tab-size = 4
|
|||
using std::round, std::views::iota, std::string_literals::operator""s, std::clamp, std::array, std::floor, std::max, std::min,
|
||||
std::to_string;
|
||||
|
||||
namespace rng = std::ranges;
|
||||
using namespace Tools;
|
||||
|
||||
namespace Symbols {
|
||||
|
@ -100,7 +97,7 @@ namespace Symbols {
|
|||
|
||||
namespace Draw {
|
||||
|
||||
string createBox(int x, int y, int width, int height, string line_color, bool fill, string title, string title2, int num){
|
||||
string createBox(int x, int y, int width, int height, string line_color, bool fill, string title, string title2, int num) {
|
||||
string out;
|
||||
string lcolor = (line_color.empty()) ? Theme::c("div_line") : line_color;
|
||||
string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (Config::getB("tty_mode") ? std::to_string(num) : Symbols::superscript[num]);
|
||||
|
@ -108,12 +105,12 @@ namespace Draw {
|
|||
out = Fx::reset + lcolor;
|
||||
|
||||
//? Draw horizontal lines
|
||||
for (int hpos : {y, y + height - 1}){
|
||||
for (int hpos : {y, y + height - 1}) {
|
||||
out += Mv::to(hpos, x) + Symbols::h_line * (width - 1);
|
||||
}
|
||||
|
||||
//? Draw vertical lines and fill if enabled
|
||||
for (int hpos : iota(y + 1, y + height - 1)){
|
||||
for (int hpos : iota(y + 1, y + height - 1)) {
|
||||
out += Mv::to(hpos, x) + Symbols::v_line +
|
||||
((fill) ? string(width - 2, ' ') : Mv::r(width - 2)) +
|
||||
Symbols::v_line;
|
||||
|
@ -126,11 +123,11 @@ namespace Draw {
|
|||
Mv::to(y + height - 1, x + width - 1) + Symbols::right_down;
|
||||
|
||||
//? Draw titles if defined
|
||||
if (not title.empty()){
|
||||
if (not title.empty()) {
|
||||
out += Mv::to(y, x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + title +
|
||||
Fx::ub + lcolor + Symbols::title_right;
|
||||
}
|
||||
if (not title2.empty()){
|
||||
if (not title2.empty()) {
|
||||
out += Mv::to(y + height - 1, x + 2) + Symbols::title_left + Theme::c("title") + title2 +
|
||||
Fx::ub + lcolor + Symbols::title_right;
|
||||
}
|
||||
|
@ -186,7 +183,7 @@ namespace Draw {
|
|||
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)){
|
||||
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
|
||||
|
@ -338,7 +335,7 @@ namespace Proc {
|
|||
|
||||
string box;
|
||||
|
||||
void selection(string cmd_key) {
|
||||
void selection(const string& cmd_key) {
|
||||
auto start = Config::getI("proc_start");
|
||||
auto selected = Config::getI("proc_selected");
|
||||
int numpids = Proc::numpids;
|
||||
|
@ -371,7 +368,7 @@ namespace Proc {
|
|||
Config::set("proc_selected", selected);
|
||||
}
|
||||
|
||||
string draw(const vector<proc_info>& plist, bool force_redraw){
|
||||
string draw(const vector<proc_info>& plist, bool force_redraw) {
|
||||
auto& filter = Config::getS("proc_filter");
|
||||
auto& filtering = Config::getB("proc_filtering");
|
||||
auto& proc_tree = Config::getB("proc_tree");
|
||||
|
@ -390,7 +387,7 @@ namespace Proc {
|
|||
redraw = false;
|
||||
out = box;
|
||||
out += Mv::to(y, x) + Mv::r(12)
|
||||
+ trans("Filter: " + filter + (filtering ? Fx::bl + "█" + Fx::reset : " "))
|
||||
+ trans("Filter: " + filter + (filtering ? Fx::bl + "█"s + Fx::reset : " "))
|
||||
+ trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: "
|
||||
+ string(Config::getS("proc_sorting")), width - 23 - ulen(filter)));
|
||||
|
||||
|
@ -420,7 +417,7 @@ namespace Proc {
|
|||
|
||||
//* Iteration over processes
|
||||
int lc = 0;
|
||||
for (int n=0; auto& p : plist){
|
||||
for (int n=0; auto& p : plist) {
|
||||
if (n++ < start) continue;
|
||||
bool is_selected = (lc + 1 == selected);
|
||||
if (is_selected) selected_pid = (int)p.pid;
|
||||
|
@ -463,8 +460,8 @@ namespace Proc {
|
|||
if (not proc_tree) {
|
||||
out += Mv::to(y+2+lc, x+1)
|
||||
+ g_color + rjust(to_string(p.pid), 8) + ' '
|
||||
+ c_color + ljust(p.name, (width < 70 ? width - 46 : 16)) + end
|
||||
+ (width >= 70 ? g_color + ljust(p.cmd, width - 62, true) : "");
|
||||
+ c_color + ljust(p.name, (width < 70 ? width - 46 : 15)) + end
|
||||
+ (width >= 70 ? g_color + ' ' + ljust(p.cmd, width - 62, true) : "");
|
||||
}
|
||||
//? Tree view line
|
||||
else {
|
||||
|
@ -509,7 +506,7 @@ namespace Proc {
|
|||
}
|
||||
|
||||
namespace Draw {
|
||||
void calcSizes(){
|
||||
void calcSizes() {
|
||||
auto& boxes = Config::getS("shown_boxes");
|
||||
|
||||
Cpu::box.clear();
|
||||
|
|
|
@ -22,24 +22,8 @@ tab-size = 4
|
|||
#include <vector>
|
||||
#include <robin_hood.h>
|
||||
#include <deque>
|
||||
#include <atomic>
|
||||
|
||||
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque, std::atomic;
|
||||
|
||||
namespace Symbols {
|
||||
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;
|
||||
}
|
||||
|
||||
//! See btop_shared.hpp for draw & misc functions for boxes
|
||||
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque;
|
||||
|
||||
namespace Draw {
|
||||
|
||||
|
@ -60,7 +44,7 @@ namespace Draw {
|
|||
string operator()(int value);
|
||||
};
|
||||
|
||||
//* Class holding a graph
|
||||
//* Class holding a percentage graph
|
||||
class Graph {
|
||||
string out, color_gradient, symbol = "default";
|
||||
int width = 0, height = 0;
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace Input {
|
|||
|
||||
string last = "";
|
||||
|
||||
bool poll(int timeout){
|
||||
bool poll(int timeout) {
|
||||
if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
|
||||
while (timeout > 0) {
|
||||
if (cin.rdbuf()->in_avail() > 0) return true;
|
||||
|
@ -83,10 +83,10 @@ namespace Input {
|
|||
return false;
|
||||
}
|
||||
|
||||
string get(){
|
||||
string get() {
|
||||
string key;
|
||||
while (cin.rdbuf()->in_avail() > 0 and key.size() < 100) key += cin.get();
|
||||
if (not key.empty()){
|
||||
if (not key.empty()) {
|
||||
if (key.substr(0,2) == Fx::e) key.erase(0, 1);
|
||||
if (Key_escapes.contains(key)) key = Key_escapes.at(key);
|
||||
else if (ulen(key) > 1) key = "";
|
||||
|
@ -95,7 +95,7 @@ namespace Input {
|
|||
return key;
|
||||
}
|
||||
|
||||
string wait(){
|
||||
string wait() {
|
||||
while (cin.rdbuf()->in_avail() < 1) {
|
||||
if (interrupt) { interrupt = false; return ""; }
|
||||
sleep_ms(10);
|
||||
|
@ -103,11 +103,11 @@ namespace Input {
|
|||
return get();
|
||||
}
|
||||
|
||||
void clear(){
|
||||
void clear() {
|
||||
last.clear();
|
||||
}
|
||||
|
||||
void process(const string key){
|
||||
void process(const string key) {
|
||||
if (key.empty()) return;
|
||||
try {
|
||||
auto& filtering = Config::getB("proc_filtering");
|
||||
|
@ -120,28 +120,58 @@ namespace Input {
|
|||
bool keep_going = false;
|
||||
if (filtering) {
|
||||
string filter = Config::getS("proc_filter");
|
||||
if (key == "enter") Config::set("proc_filtering", false);
|
||||
else if (key == "backspace" and not filter.empty()) filter = uresize(filter, ulen(filter) - 1);
|
||||
else if (key == "space") filter.push_back(' ');
|
||||
else if (ulen(key) == 1) filter.append(key);
|
||||
else return;
|
||||
if (key == "enter")
|
||||
Config::set("proc_filtering", false);
|
||||
|
||||
else if (key == "backspace" and not filter.empty())
|
||||
filter = uresize(filter, ulen(filter) - 1);
|
||||
|
||||
else if (key == "space")
|
||||
filter.push_back(' ');
|
||||
|
||||
else if (ulen(key) == 1)
|
||||
filter.append(key);
|
||||
|
||||
else
|
||||
return;
|
||||
|
||||
Config::set("proc_filter", filter);
|
||||
}
|
||||
else if (key == "left") {
|
||||
int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting"));
|
||||
if (--cur_i < 0) cur_i = Proc::sort_vector.size() - 1;
|
||||
if (--cur_i < 0)
|
||||
cur_i = Proc::sort_vector.size() - 1;
|
||||
Config::set("proc_sorting", Proc::sort_vector.at(cur_i));
|
||||
}
|
||||
else if (key == "right") {
|
||||
int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting"));
|
||||
if (++cur_i > (int)Proc::sort_vector.size() - 1) cur_i = 0;
|
||||
if (++cur_i > (int)Proc::sort_vector.size() - 1)
|
||||
cur_i = 0;
|
||||
Config::set("proc_sorting", Proc::sort_vector.at(cur_i));
|
||||
}
|
||||
else if (key == "f") { Config::flip("proc_filtering"); recollect = false; }
|
||||
else if (key == "t") Config::flip("proc_tree");
|
||||
else if (key == "r") Config::flip("proc_reversed");
|
||||
else if (key == "c") Config::flip("proc_per_core");
|
||||
else if (key == "delete" and not Config::getS("proc_filter").empty()) Config::set("proc_filter", ""s);
|
||||
else if (key == "f") {
|
||||
Config::flip("proc_filtering");
|
||||
recollect = false;
|
||||
}
|
||||
else if (key == "t")
|
||||
Config::flip("proc_tree");
|
||||
|
||||
else if (key == "r")
|
||||
Config::flip("proc_reversed");
|
||||
|
||||
else if (key == "c")
|
||||
Config::flip("proc_per_core");
|
||||
|
||||
else if (key == "delete" and not Config::getS("proc_filter").empty())
|
||||
Config::set("proc_filter", ""s);
|
||||
|
||||
else if (key == "ö") {
|
||||
if (Global::overlay.empty())
|
||||
Global::overlay = Mv::to(Term::height / 2, Term::width / 2) + "\x1b[1;32mTESTING";
|
||||
else
|
||||
Global::overlay.clear();
|
||||
Runner::run("all", true, true);
|
||||
}
|
||||
else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) {
|
||||
Proc::selection(key);
|
||||
recollect = false;
|
||||
|
|
|
@ -18,25 +18,16 @@ tab-size = 4
|
|||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <list>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <btop_shared.hpp>
|
||||
#include <btop_config.hpp>
|
||||
#include <btop_tools.hpp>
|
||||
|
||||
|
||||
|
||||
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
|
||||
std::round, std::string_literals::operator""s;
|
||||
namespace fs = std::filesystem;
|
||||
|
@ -46,7 +37,7 @@ using namespace Tools;
|
|||
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
||||
|
||||
namespace Tools {
|
||||
double system_uptime(){
|
||||
double system_uptime() {
|
||||
string upstr;
|
||||
ifstream pread("/proc/uptime");
|
||||
getline(pread, upstr, ' ');
|
||||
|
@ -63,13 +54,11 @@ namespace Shared {
|
|||
long page_size;
|
||||
long clk_tck;
|
||||
|
||||
void init(){
|
||||
void init() {
|
||||
proc_path = (fs::is_directory(fs::path("/proc")) and 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);
|
||||
Global::exit_error_msg = "Proc filesystem not found or no permission to read from it!";
|
||||
clean_quit(1);
|
||||
}
|
||||
|
||||
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path;
|
||||
|
@ -84,7 +73,7 @@ namespace Shared {
|
|||
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.");
|
||||
Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,7 +160,7 @@ namespace Proc {
|
|||
detail_container detailed;
|
||||
|
||||
//* Generate process tree list
|
||||
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false){
|
||||
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false) {
|
||||
if (Runner::stopping) return;
|
||||
auto cur_pos = out_procs.size();
|
||||
bool filtering = false;
|
||||
|
@ -193,12 +182,13 @@ namespace Proc {
|
|||
if (not collapsed and not filtering) {
|
||||
out_procs.push_back(cur_proc);
|
||||
if (auto& cmdline = cache.at(cur_proc.pid).cmd; not cmdline.empty() and not cmdline.starts_with("(")) {
|
||||
cmdline = cmdline.substr(0, std::min(cmdline.find(' '), cmdline.size()));
|
||||
cmdline = cmdline.substr(std::min(cmdline.find_last_of('/') + 1, cmdline.size()));
|
||||
if (cmdline == cur_proc.name)
|
||||
std::string_view cmd_view = cmdline;
|
||||
cmd_view = cmd_view.substr(0, std::min(cmd_view.find(' '), cmd_view.size()));
|
||||
cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
|
||||
if (cmd_view == cur_proc.name)
|
||||
cmdline.clear();
|
||||
else
|
||||
cmdline = '(' + cmdline + ')';
|
||||
cmdline = '(' + (string)cmd_view + ')';
|
||||
out_procs.back().cmd = cmdline;
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +212,7 @@ namespace Proc {
|
|||
}
|
||||
|
||||
//* Get detailed info for selected process
|
||||
void _collect_details(size_t pid, uint64_t uptime, vector<proc_info>& procs, bool is_filtered){
|
||||
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs, const bool is_filtered) {
|
||||
fs::path pid_path = Shared::proc_path / std::to_string(pid);
|
||||
|
||||
if (pid != detailed.last_pid) {
|
||||
|
@ -274,8 +264,8 @@ namespace Proc {
|
|||
else
|
||||
detailed.memory = floating_humanizer(rss, false, 1);
|
||||
}
|
||||
catch (std::invalid_argument const&) {}
|
||||
catch (std::out_of_range const&) {}
|
||||
catch (const std::invalid_argument&) {}
|
||||
catch (const std::out_of_range&) {}
|
||||
}
|
||||
d_read.close();
|
||||
}
|
||||
|
@ -302,8 +292,8 @@ namespace Proc {
|
|||
d_read.ignore(SSmax, '\n');
|
||||
}
|
||||
}
|
||||
catch (std::invalid_argument const&) {}
|
||||
catch (std::out_of_range const&) {}
|
||||
catch (const std::invalid_argument&) {}
|
||||
catch (const std::out_of_range&) {}
|
||||
}
|
||||
d_read.close();
|
||||
}
|
||||
|
@ -313,27 +303,27 @@ namespace Proc {
|
|||
}
|
||||
|
||||
//* Collects and sorts process information from /proc
|
||||
vector<proc_info>& collect(const bool return_last){
|
||||
vector<proc_info>& collect(const bool return_last) {
|
||||
if (return_last) return current_procs;
|
||||
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");
|
||||
const auto& sorting = Config::getS("proc_sorting");
|
||||
const auto& reverse = Config::getB("proc_reversed");
|
||||
const auto& filter = Config::getS("proc_filter");
|
||||
const auto& per_core = Config::getB("proc_per_core");
|
||||
const auto& tree = Config::getB("proc_tree");
|
||||
if (tree_state != tree) {
|
||||
cache.clear();
|
||||
tree_state = tree;
|
||||
}
|
||||
auto show_detailed = Config::getB("show_detailed");
|
||||
size_t detailed_pid = Config::getI("detailed_pid");
|
||||
const auto& show_detailed = Config::getB("show_detailed");
|
||||
const size_t detailed_pid = Config::getI("detailed_pid");
|
||||
ifstream pread;
|
||||
string long_string;
|
||||
string short_str;
|
||||
double uptime = system_uptime();
|
||||
const double uptime = system_uptime();
|
||||
vector<proc_info> procs;
|
||||
procs.reserve(reserve_pids + 10);
|
||||
int npids = 0;
|
||||
int cmult = (per_core) ? Global::coreCount : 1;
|
||||
const int cmult = (per_core) ? Global::coreCount : 1;
|
||||
bool got_detailed = false;
|
||||
bool detailed_filtered = false;
|
||||
|
||||
|
@ -344,7 +334,7 @@ namespace Proc {
|
|||
uid_user.clear();
|
||||
pread.open(Shared::passwd_path);
|
||||
if (pread.good()) {
|
||||
while (not pread.eof()){
|
||||
while (not pread.eof()) {
|
||||
getline(pread, r_user, ':');
|
||||
pread.ignore(SSmax, ':');
|
||||
getline(pread, r_uid, ':');
|
||||
|
@ -366,12 +356,12 @@ namespace Proc {
|
|||
else return current_procs;
|
||||
|
||||
//* Iterate over all pids in /proc
|
||||
for (auto& d: fs::directory_iterator(Shared::proc_path)){
|
||||
for (const auto& d: fs::directory_iterator(Shared::proc_path)) {
|
||||
if (Runner::stopping)
|
||||
return current_procs;
|
||||
if (pread.is_open()) pread.close();
|
||||
|
||||
string pid_str = d.path().filename();
|
||||
const string pid_str = d.path().filename();
|
||||
if (not isdigit(pid_str[0])) continue;
|
||||
|
||||
npids++;
|
||||
|
@ -386,7 +376,6 @@ namespace Proc {
|
|||
pread.close();
|
||||
size_t name_offset = rng::count(name, ' ');
|
||||
|
||||
|
||||
pread.open(d.path() / "cmdline");
|
||||
if (not pread.good()) continue;
|
||||
long_string.clear();
|
||||
|
@ -394,12 +383,11 @@ namespace Proc {
|
|||
pread.close();
|
||||
if (not cmd.empty()) cmd.pop_back();
|
||||
|
||||
|
||||
pread.open(d.path() / "status");
|
||||
if (not pread.good()) continue;
|
||||
string uid;
|
||||
string line;
|
||||
while (not pread.eof()){
|
||||
while (not pread.eof()) {
|
||||
getline(pread, line, ':');
|
||||
if (line == "Uid") {
|
||||
pread.ignore();
|
||||
|
@ -533,7 +521,7 @@ namespace Proc {
|
|||
}
|
||||
|
||||
//* Sort processes
|
||||
auto cmp = [&reverse](const auto &a, const auto &b) { return (reverse ? a < b : a > b); };
|
||||
const auto cmp = [&reverse](const auto &a, const auto &b) { return (reverse ? a < b : a > b); };
|
||||
switch (v_index(sort_vector, sorting)) {
|
||||
case 0: { rng::sort(procs, cmp, &proc_info::pid); break; }
|
||||
case 1: { rng::sort(procs, cmp, &proc_info::name); break; }
|
||||
|
@ -548,15 +536,17 @@ namespace Proc {
|
|||
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
|
||||
if (not tree and not reverse and sorting == "cpu lazy") {
|
||||
double max = 10.0, target = 30.0;
|
||||
for (size_t i = 0, offset = 0; i < procs.size(); i++) {
|
||||
for (size_t i = 0, x = 0, offset = 0; i < procs.size(); i++) {
|
||||
if (i <= 5 and procs[i].cpu_p > max)
|
||||
max = procs[i].cpu_p;
|
||||
else if (i == 6)
|
||||
target = (max > 30.0) ? max : 10.0;
|
||||
if (i == offset and procs[i].cpu_p > 30.0)
|
||||
offset++;
|
||||
else if (procs[i].cpu_p > target)
|
||||
else if (procs[i].cpu_p > target) {
|
||||
rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
|
||||
if (++x > 10) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,12 +559,12 @@ namespace Proc {
|
|||
rng::stable_sort(procs, rng::less{}, &proc_info::ppid);
|
||||
|
||||
//? Start recursive iteration over processes with the lowest shared parent pids
|
||||
for (auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
|
||||
for (const auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
|
||||
_tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, filter);
|
||||
}
|
||||
|
||||
if (Runner::stopping) return current_procs;
|
||||
procs.swap(tree_procs);
|
||||
procs = std::move(tree_procs);
|
||||
}
|
||||
|
||||
|
||||
|
@ -583,17 +573,17 @@ namespace Proc {
|
|||
counter = 0;
|
||||
unordered_flat_map<size_t, p_cache> r_cache;
|
||||
r_cache.reserve(procs.size());
|
||||
rng::for_each(procs, [&r_cache](const auto &p){
|
||||
rng::for_each(procs, [&r_cache](const auto &p) {
|
||||
if (cache.contains(p.pid))
|
||||
r_cache[p.pid] = cache.at(p.pid);
|
||||
});
|
||||
cache.swap(r_cache);
|
||||
cache = std::move(r_cache);
|
||||
}
|
||||
|
||||
old_cputimes = cputimes;
|
||||
numpids = (int)procs.size();
|
||||
current_procs.swap(procs);
|
||||
reserve_pids = npids;
|
||||
current_procs = std::move(procs);
|
||||
return current_procs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/* 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 <vector>
|
||||
#include <deque>
|
||||
#include <robin_hood.h>
|
||||
#include <array>
|
||||
|
||||
#include <btop_menu.hpp>
|
||||
#include <btop_tools.hpp>
|
||||
#include <btop_config.hpp>
|
||||
#include <btop_theme.hpp>
|
||||
#include <btop_draw.hpp>
|
||||
|
||||
using std::vector, std::deque, robin_hood::unordered_flat_map, std::array;
|
||||
|
||||
namespace Menu {
|
||||
|
||||
atomic<bool> active (false);
|
||||
string output;
|
||||
|
||||
const unordered_flat_map<string, unordered_flat_map<string, vector<string>>> menus = {
|
||||
{ "options", {
|
||||
{ "normal", {
|
||||
"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
|
||||
"│ │├─┘ │ ││ ││││└─┐",
|
||||
"└─┘┴ ┴ ┴└─┘┘└┘└─┘"
|
||||
} },
|
||||
{ "selected", {
|
||||
"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
|
||||
"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
|
||||
"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝"
|
||||
} }
|
||||
} },
|
||||
{ "help", {
|
||||
{ "normal", {
|
||||
"┬ ┬┌─┐┬ ┌─┐",
|
||||
"├─┤├┤ │ ├─┘",
|
||||
"┴ ┴└─┘┴─┘┴ "
|
||||
} },
|
||||
{ "selected", {
|
||||
"╦ ╦╔═╗╦ ╔═╗",
|
||||
"╠═╣║╣ ║ ╠═╝",
|
||||
"╩ ╩╚═╝╩═╝╩ "
|
||||
} }
|
||||
} },
|
||||
{ "quit", {
|
||||
{ "normal", {
|
||||
"┌─┐ ┬ ┬ ┬┌┬┐",
|
||||
"│─┼┐│ │ │ │ ",
|
||||
"└─┘└└─┘ ┴ ┴ "
|
||||
} },
|
||||
{ "selected", {
|
||||
"╔═╗ ╦ ╦ ╦╔╦╗ ",
|
||||
"║═╬╗║ ║ ║ ║ ",
|
||||
"╚═╝╚╚═╝ ╩ ╩ "
|
||||
} }
|
||||
} }
|
||||
};
|
||||
}
|
|
@ -19,56 +19,14 @@ tab-size = 4
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <robin_hood.h>
|
||||
|
||||
using std::string, std::vector, std::unordered_map, std::array, std::atomic, robin_hood::unordered_flat_map;
|
||||
using std::string, std::atomic;
|
||||
|
||||
namespace Menu {
|
||||
|
||||
atomic<bool> active(false);
|
||||
|
||||
const unordered_flat_map<string, unordered_map<string, vector<string>>> Menus = {
|
||||
{ "options", {
|
||||
{ "normal", {
|
||||
"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
|
||||
"│ │├─┘ │ ││ ││││└─┐",
|
||||
"└─┘┴ ┴ ┴└─┘┘└┘└─┘"
|
||||
} },
|
||||
{ "selected", {
|
||||
"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
|
||||
"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
|
||||
"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝"
|
||||
} }
|
||||
} },
|
||||
{ "help", {
|
||||
{ "normal", {
|
||||
"┬ ┬┌─┐┬ ┌─┐",
|
||||
"├─┤├┤ │ ├─┘",
|
||||
"┴ ┴└─┘┴─┘┴ "
|
||||
} },
|
||||
{ "selected", {
|
||||
"╦ ╦╔═╗╦ ╔═╗",
|
||||
"╠═╣║╣ ║ ╠═╝",
|
||||
"╩ ╩╚═╝╩═╝╩ "
|
||||
} }
|
||||
} },
|
||||
{ "quit", {
|
||||
{ "normal", {
|
||||
"┌─┐ ┬ ┬ ┬┌┬┐",
|
||||
"│─┼┐│ │ │ │ ",
|
||||
"└─┘└└─┘ ┴ ┴ "
|
||||
} },
|
||||
{ "selected", {
|
||||
"╔═╗ ╦ ╦ ╦╔╦╗ ",
|
||||
"║═╬╗║ ║ ║ ║ ",
|
||||
"╚═╝╚╚═╝ ╩ ╩ "
|
||||
} }
|
||||
} }
|
||||
};
|
||||
|
||||
extern atomic<bool> active;
|
||||
extern string output;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ tab-size = 4
|
|||
|
||||
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array;
|
||||
|
||||
void clean_quit(int sig=-1);
|
||||
void clean_quit(const int sig=-1);
|
||||
void banner_gen();
|
||||
|
||||
namespace Global {
|
||||
extern const string Version;
|
||||
|
@ -37,14 +38,16 @@ namespace Global {
|
|||
extern int coreCount;
|
||||
extern string banner;
|
||||
extern atomic<bool> resized;
|
||||
extern string overlay;
|
||||
}
|
||||
|
||||
namespace Runner {
|
||||
|
||||
extern atomic<bool> active;
|
||||
extern atomic<bool> reading;
|
||||
extern atomic<bool> stopping;
|
||||
|
||||
void run(const string box="", const bool no_update=false, const bool force_redraw=false, const bool input_interrupt=true);
|
||||
void run(const string& box="", const bool no_update=false, const bool force_redraw=false, const bool input_interrupt=true);
|
||||
void stop();
|
||||
|
||||
}
|
||||
|
@ -162,7 +165,7 @@ namespace Proc {
|
|||
vector<proc_info>& collect(const bool return_last=false);
|
||||
|
||||
//* Update current selection and view
|
||||
void selection(string cmd_key);
|
||||
void selection(const string& cmd_key);
|
||||
|
||||
//* Draw contents of proc box using <plist> as data source
|
||||
string draw(const vector<proc_info>& plist, bool force_redraw=false);
|
||||
|
|
|
@ -16,10 +16,8 @@ indent = tab
|
|||
tab-size = 4
|
||||
*/
|
||||
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <ranges>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
@ -28,17 +26,23 @@ tab-size = 4
|
|||
#include <btop_config.hpp>
|
||||
#include <btop_theme.hpp>
|
||||
|
||||
using std::round, std::vector, robin_hood::unordered_flat_map, std::stoi, std::views::iota, std::array,
|
||||
using std::round, std::vector, std::stoi, std::views::iota,
|
||||
std::clamp, std::max, std::min, std::ceil, std::to_string;
|
||||
using namespace Tools;
|
||||
namespace rng = std::ranges;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
string Term::fg, Term::bg;
|
||||
string Fx::reset = reset_base;
|
||||
|
||||
namespace Theme {
|
||||
|
||||
fs::path theme_dir;
|
||||
fs::path user_theme_dir;
|
||||
vector<string> themes;
|
||||
unordered_flat_map<string, string> colors;
|
||||
unordered_flat_map<string, array<int, 3>> rgbs;
|
||||
unordered_flat_map<string, array<string, 101>> gradients;
|
||||
|
||||
const unordered_flat_map<string, string> Default_theme = {
|
||||
{ "main_bg", "#00" },
|
||||
|
@ -131,18 +135,21 @@ namespace Theme {
|
|||
};
|
||||
|
||||
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) and round((double)g / 11) == round((double)b / 11)) {
|
||||
return 232 + round((double)r / 11);
|
||||
} else {
|
||||
//* Convert 24-bit colors to 256 colors
|
||||
int truecolor_to_256(const int& r, const int& g, const int& b) {
|
||||
//? Use upper 232-255 greyscale values if the downscaled red, green and blue are the same value
|
||||
if (int red = round((double)r / 11); red == round((double)g / 11) and round((double)g / 11) == round((double)b / 11)) {
|
||||
return 232 + red;
|
||||
}
|
||||
//? Else use 6x6x6 color cube to calculate approximate colors
|
||||
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){
|
||||
string hex_to_color(string hexa, const bool& t_to_256, const string& depth) {
|
||||
if (hexa.size() > 1) {
|
||||
hexa.erase(0, 1);
|
||||
for (auto& c : hexa) if (not isxdigit(c)) {
|
||||
Logger::error("Invalid hex value: " + hexa);
|
||||
|
@ -150,17 +157,17 @@ namespace Theme {
|
|||
}
|
||||
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
|
||||
|
||||
if (hexa.size() == 2){
|
||||
if (hexa.size() == 2) {
|
||||
int h_int = stoi(hexa, 0, 16);
|
||||
if (t_to_256){
|
||||
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){
|
||||
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),
|
||||
|
@ -178,7 +185,7 @@ namespace Theme {
|
|||
return "";
|
||||
}
|
||||
|
||||
string dec_to_color(int r, int g, int b, bool t_to_256, string depth){
|
||||
string dec_to_color(int r, int g, int b, const bool& t_to_256, const 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);
|
||||
|
@ -188,21 +195,17 @@ namespace Theme {
|
|||
}
|
||||
|
||||
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){
|
||||
array<int, 3> hex_to_dec(string hexa) {
|
||||
if (hexa.size() > 1) {
|
||||
hexa.erase(0, 1);
|
||||
for (auto& c : hexa) if (not isxdigit(c)) return array<int, 3>{-1, -1, -1};
|
||||
|
||||
if (hexa.size() == 2){
|
||||
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){
|
||||
else if (hexa.size() == 6) {
|
||||
return array<int, 3>{
|
||||
stoi(hexa.substr(0, 2), 0, 16),
|
||||
stoi(hexa.substr(2, 2), 0, 16),
|
||||
|
@ -214,12 +217,12 @@ namespace Theme {
|
|||
}
|
||||
|
||||
//* Generate colors and rgb decimal vectors for the theme
|
||||
void generateColors(unordered_flat_map<string, string> source){
|
||||
void generateColors(const unordered_flat_map<string, string>& source) {
|
||||
vector<string> t_rgb;
|
||||
string depth;
|
||||
bool t_to_256 = Config::getB("lowcolor");
|
||||
const bool& t_to_256 = Config::getB("lowcolor");
|
||||
colors.clear(); rgbs.clear();
|
||||
for (auto& [name, color] : Default_theme) {
|
||||
for (const auto& [name, color] : Default_theme) {
|
||||
if (name == "main_bg" and not Config::getB("theme_background")) {
|
||||
colors[name] = "\x1b[49m";
|
||||
rgbs[name] = {-1, -1, -1};
|
||||
|
@ -252,70 +255,102 @@ namespace Theme {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (colors[name].empty()) {
|
||||
if (not colors.contains(name) and not is_in(name, "meter_bg", "process_start", "process_mid", "process_end", "graph_text")) {
|
||||
Logger::debug("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};
|
||||
rgbs[name] = hex_to_dec(color);
|
||||
}
|
||||
}
|
||||
//? Set fallback values for optional colors not defined in theme file
|
||||
if (not colors.contains("meter_bg")) {
|
||||
colors["meter_bg"] = colors.at("inactive_fg");
|
||||
rgbs["meter_bg"] = rgbs.at("inactive_fg");
|
||||
}
|
||||
if (not colors.contains("process_start")) {
|
||||
colors["process_start"] = colors.at("cpu_start");
|
||||
colors["process_mid"] = colors.at("cpu_mid");
|
||||
colors["process_end"] = colors.at("cpu_end");
|
||||
rgbs["process_start"] = rgbs.at("cpu_start");
|
||||
rgbs["process_mid"] = rgbs.at("cpu_mid");
|
||||
rgbs["process_end"] = rgbs.at("cpu_end");
|
||||
}
|
||||
if (not colors.contains("graph_text")) {
|
||||
colors["graph_text"] = colors.at("inactive_fg");
|
||||
rgbs["graph_text"] = rgbs.at("inactive_fg");
|
||||
}
|
||||
}
|
||||
|
||||
//* Generate color gradients from two or three colors, 101 values indexed 0-100
|
||||
void generateGradients(){
|
||||
void generateGradients() {
|
||||
gradients.clear();
|
||||
array<string, 101> c_gradient;
|
||||
bool t_to_256 = Config::getB("lowcolor");
|
||||
rgbs.insert({
|
||||
{"proc_start", rgbs["main_fg"]}, {"proc_mid", {-1, -1, -1}}, {"proc_end", rgbs["inactive_fg"]},
|
||||
{"proc_color_start", rgbs["inactive_fg"]}, {"proc_color_mid", {-1, -1, -1}}, {"proc_color_end", rgbs["process_start"]}
|
||||
});
|
||||
for (auto& [name, source_arr] : rgbs) {
|
||||
const bool& t_to_256 = Config::getB("lowcolor");
|
||||
|
||||
//? Insert values for processes greyscale gradient and processes color gradient
|
||||
rgbs.insert({ { "proc_start", rgbs["main_fg"] },
|
||||
{ "proc_mid", {-1, -1, -1} },
|
||||
{ "proc_end", rgbs["inactive_fg"] },
|
||||
{ "proc_color_start", rgbs["inactive_fg"] },
|
||||
{ "proc_color_mid", {-1, -1, -1} },
|
||||
{ "proc_color_end", rgbs["process_start"] },
|
||||
});
|
||||
|
||||
for (const auto& [name, source_arr] : rgbs) {
|
||||
if (not 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_array = {source_arr, rgbs[wname + "_mid"], rgbs[wname + "_end"]};
|
||||
const string color_name = rtrim(name, "_start");
|
||||
|
||||
//? Only start iteration if gradient has a _end color value defined
|
||||
if (rgb_array[2][0] >= 0) {
|
||||
//? input_colors[start,mid,end][red,green,blue]
|
||||
const array<array<int, 3>, 3> input_colors = {
|
||||
source_arr,
|
||||
rgbs[color_name + "_mid"],
|
||||
rgbs[color_name + "_end"]
|
||||
};
|
||||
|
||||
//? Split iteration in two passes of 50 + 51 instead of 101 if gradient has _start, _mid and _end values defined
|
||||
int cur_range = (rgb_array[1][0] >= 0) ? 50 : 100;
|
||||
for (int rgb : iota(0, 3)){
|
||||
//? output_colors[red,green,blue][0-100]
|
||||
array<array<int, 3>, 101> output_colors;
|
||||
output_colors[0][0] = -1;
|
||||
|
||||
//? Only start iteration if gradient has an end color defined
|
||||
if (input_colors[2][0] >= 0) {
|
||||
|
||||
//? Split iteration in two passes of 50 + 51 instead of one pass of 101 if gradient has start, mid and end values defined
|
||||
int current_range = (input_colors[1][0] >= 0) ? 50 : 100;
|
||||
for (const int& rgb : iota(0, 3)) {
|
||||
int start = 0, offset = 0;
|
||||
int end = (cur_range == 50) ? 1 : 2;
|
||||
for (int i : iota(0, 101)) {
|
||||
dec_arr[i][rgb] = rgb_array[start][rgb] + (i - offset) * (rgb_array[end][rgb] - rgb_array[start][rgb]) / cur_range;
|
||||
int end = (current_range == 50) ? 1 : 2;
|
||||
for (const int& i : iota(0, 101)) {
|
||||
output_colors[i][rgb] = input_colors[start][rgb] + (i - offset) * (input_colors[end][rgb] - input_colors[start][rgb]) / current_range;
|
||||
|
||||
//? Switch source arrays from _start/_mid to _mid/_end at 50 passes if _mid is defined
|
||||
if (i == cur_range) { ++start; ++end; offset = 50;}
|
||||
//? Switch source arrays from start->mid to mid->end at 50 passes if mid is defined
|
||||
if (i == current_range) { ++start; ++end; 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);
|
||||
//? Generate color escape codes for the generated rgb decimals
|
||||
array<string, 101> color_gradient;
|
||||
if (output_colors[0][0] != -1) {
|
||||
for (int y = 0; const auto& [red, green, blue] : output_colors)
|
||||
color_gradient[y++] = dec_to_color(red, green, blue, t_to_256);
|
||||
}
|
||||
else {
|
||||
//? If only _start was defined fill array with _start color
|
||||
c_gradient.fill(colors[name]);
|
||||
//? If only start was defined fill array with start color
|
||||
color_gradient.fill(colors[name]);
|
||||
}
|
||||
gradients[wname].swap(c_gradient);
|
||||
gradients[color_name] = std::move(color_gradient);
|
||||
}
|
||||
}
|
||||
|
||||
//* Set colors and generate gradients for the TTY theme
|
||||
void generateTTYColors(){
|
||||
void generateTTYColors() {
|
||||
rgbs.clear();
|
||||
gradients.clear();
|
||||
colors = TTY_theme;
|
||||
|
||||
for (auto& c : colors) {
|
||||
for (const auto& c : colors) {
|
||||
if (not c.first.ends_with("_start")) continue;
|
||||
string base_name = rtrim(c.first, "_start");
|
||||
const string base_name = rtrim(c.first, "_start");
|
||||
string section = "_start";
|
||||
int split = colors.at(base_name + "_mid").empty() ? 50 : 33;
|
||||
for (int i : iota(0, 101)) {
|
||||
for (const int& i : iota(0, 101)) {
|
||||
gradients[base_name][i] = colors.at(base_name + section);
|
||||
if (i == split) {
|
||||
section = (split == 33) ? "_mid" : "_end";
|
||||
|
@ -326,9 +361,9 @@ namespace Theme {
|
|||
}
|
||||
|
||||
//* Load a .theme file from disk
|
||||
auto loadFile(string filename){
|
||||
auto loadFile(const string& filename) {
|
||||
unordered_flat_map<string, string> theme_out;
|
||||
fs::path filepath = filename;
|
||||
const fs::path filepath = filename;
|
||||
if (not fs::exists(filepath))
|
||||
return Default_theme;
|
||||
|
||||
|
@ -354,21 +389,20 @@ namespace Theme {
|
|||
|
||||
theme_out[name] = value;
|
||||
}
|
||||
themefile.close();
|
||||
return theme_out;
|
||||
}
|
||||
return Default_theme;
|
||||
}
|
||||
}
|
||||
|
||||
void updateThemes(){
|
||||
void updateThemes() {
|
||||
themes.clear();
|
||||
themes.push_back("Default");
|
||||
themes.push_back("TTY");
|
||||
|
||||
for (const auto& path : { theme_dir, user_theme_dir } ) {
|
||||
if (path.empty()) continue;
|
||||
for (auto& file : fs::directory_iterator(path)){
|
||||
for (auto& file : fs::directory_iterator(path)) {
|
||||
if (file.path().extension() == ".theme" and access(file.path().c_str(), R_OK) != -1) {
|
||||
themes.push_back(file.path().c_str());
|
||||
}
|
||||
|
@ -377,7 +411,7 @@ namespace Theme {
|
|||
|
||||
}
|
||||
|
||||
void setTheme(){
|
||||
void setTheme() {
|
||||
string theme = Config::getS("color_theme");
|
||||
if (theme == "TTY" or Config::getB("tty_mode"))
|
||||
generateTTYColors();
|
||||
|
@ -390,26 +424,4 @@ namespace Theme {
|
|||
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);
|
||||
}
|
||||
|
||||
unordered_flat_map<string, string>& test_colors(){
|
||||
return colors;
|
||||
}
|
||||
unordered_flat_map<string, std::array<string, 101>>& test_gradients(){
|
||||
return gradients;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,9 +20,10 @@ tab-size = 4
|
|||
|
||||
#include <string>
|
||||
#include <robin_hood.h>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
|
||||
using std::string;
|
||||
using std::string, robin_hood::unordered_flat_map, std::array;
|
||||
|
||||
namespace Theme {
|
||||
extern std::filesystem::path theme_dir;
|
||||
|
@ -35,13 +36,13 @@ namespace Theme {
|
|||
//* 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");
|
||||
string hex_to_color(string hexa, const bool& t_to_256=false, const 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 dec_to_color(int r, int g, int b, const bool& t_to_256=false, const string& depth="fg");
|
||||
|
||||
//* Update list of paths for available themes
|
||||
void updateThemes();
|
||||
|
@ -49,17 +50,17 @@ namespace Theme {
|
|||
//* Set current theme from current "color_theme" value in config
|
||||
void setTheme();
|
||||
|
||||
extern unordered_flat_map<string, string> colors;
|
||||
extern unordered_flat_map<string, array<int, 3>> rgbs;
|
||||
extern unordered_flat_map<string, array<string, 101>> gradients;
|
||||
|
||||
//* Return escape code for color <name>
|
||||
const string& c(string name);
|
||||
inline const string& c(const string& name) { return colors.at(name); }
|
||||
|
||||
//* Return array of escape codes for color gradient <name>
|
||||
const std::array<string, 101>& g(string name);
|
||||
inline 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);
|
||||
|
||||
//? Testing
|
||||
robin_hood::unordered_flat_map<string, string>& test_colors();
|
||||
robin_hood::unordered_flat_map<string, std::array<string, 101>>& test_gradients();
|
||||
inline const std::array<int, 3>& dec(string name) { return rgbs.at(name); }
|
||||
|
||||
}
|
|
@ -32,74 +32,25 @@ tab-size = 4
|
|||
#include <btop_shared.hpp>
|
||||
#include <btop_tools.hpp>
|
||||
|
||||
using std::string_view, std::array, std::regex, std::max, std::to_string, std::cin, robin_hood::unordered_flat_map;
|
||||
using std::string_view, std::array, std::max, std::to_string, std::cin, 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}");
|
||||
}
|
||||
|
||||
//* 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 {
|
||||
|
||||
atomic<bool> initialized = false;
|
||||
atomic<int> width = 0;
|
||||
atomic<int> 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";
|
||||
const string sync_start = Fx::e + "?2026h";
|
||||
const string sync_end = Fx::e + "?2026l";
|
||||
string current_tty;
|
||||
|
||||
namespace {
|
||||
struct termios initial_settings;
|
||||
|
||||
//* Toggle terminal input echo
|
||||
bool echo(bool on=true){
|
||||
bool echo(bool on=true) {
|
||||
struct termios settings;
|
||||
if (tcgetattr(STDIN_FILENO, &settings)) return false;
|
||||
if (on) settings.c_lflag |= ECHO;
|
||||
|
@ -108,7 +59,7 @@ namespace Term {
|
|||
}
|
||||
|
||||
//* Toggle need for return key when reading input
|
||||
bool linebuffered(bool on=true){
|
||||
bool linebuffered(bool on=true) {
|
||||
struct termios settings;
|
||||
if (tcgetattr(STDIN_FILENO, &settings)) return false;
|
||||
if (on) settings.c_lflag |= ICANON;
|
||||
|
@ -120,7 +71,7 @@ namespace Term {
|
|||
}
|
||||
}
|
||||
|
||||
bool refresh(){
|
||||
bool refresh() {
|
||||
struct winsize w;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||
if (width != w.ws_col or height != w.ws_row) {
|
||||
|
@ -131,8 +82,8 @@ namespace Term {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool init(){
|
||||
if (not initialized){
|
||||
bool init() {
|
||||
if (not initialized) {
|
||||
initialized = (bool)isatty(STDIN_FILENO);
|
||||
if (initialized) {
|
||||
tcgetattr(STDIN_FILENO, &initial_settings);
|
||||
|
@ -148,7 +99,7 @@ namespace Term {
|
|||
return initialized;
|
||||
}
|
||||
|
||||
void restore(){
|
||||
void restore() {
|
||||
if (initialized) {
|
||||
echo(true);
|
||||
linebuffered(true);
|
||||
|
@ -162,7 +113,7 @@ namespace Term {
|
|||
|
||||
namespace Tools {
|
||||
|
||||
string uresize(string str, const size_t len){
|
||||
string uresize(string str, const size_t len) {
|
||||
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++;
|
||||
|
@ -175,19 +126,19 @@ namespace Tools {
|
|||
return str;
|
||||
}
|
||||
|
||||
string ltrim(const string& str, const string& t_str){
|
||||
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 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;
|
||||
}
|
||||
|
||||
vector<string> ssplit(const string& str, const char& delim){
|
||||
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) {
|
||||
|
@ -198,7 +149,7 @@ namespace Tools {
|
|||
return out;
|
||||
}
|
||||
|
||||
string ljust(string str, const size_t x, const bool utf, const bool limit){
|
||||
string ljust(string str, const size_t x, const bool utf, const bool limit) {
|
||||
if (utf) {
|
||||
if (limit and ulen(str) > x) str = uresize(str, x);
|
||||
return str + string(max((int)(x - ulen(str)), 0), ' ');
|
||||
|
@ -209,7 +160,7 @@ namespace Tools {
|
|||
}
|
||||
}
|
||||
|
||||
string rjust(string str, const size_t x, const bool utf, const bool limit){
|
||||
string rjust(string str, const size_t x, const bool utf, const bool limit) {
|
||||
if (utf) {
|
||||
if (limit and ulen(str) > x) str = uresize(str, x);
|
||||
return string(max((int)(x - ulen(str)), 0), ' ') + str;
|
||||
|
@ -220,12 +171,11 @@ namespace Tools {
|
|||
}
|
||||
}
|
||||
|
||||
string trans(const string& str){
|
||||
size_t pos;
|
||||
string trans(const string& str) {
|
||||
string_view oldstr = str;
|
||||
string newstr;
|
||||
newstr.reserve(str.size());
|
||||
while ((pos = oldstr.find(' ')) != string::npos){
|
||||
for (size_t pos; (pos = oldstr.find(' ')) != string::npos;) {
|
||||
newstr.append(oldstr.substr(0, pos));
|
||||
size_t x = 0;
|
||||
while (pos + x < oldstr.size() and oldstr.at(pos + x) == ' ') x++;
|
||||
|
@ -235,34 +185,29 @@ namespace Tools {
|
|||
return (newstr.empty()) ? str : newstr + (string)oldstr;
|
||||
}
|
||||
|
||||
string sec_to_dhms(size_t sec){
|
||||
string out;
|
||||
size_t 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);
|
||||
string sec_to_dhms(size_t seconds) {
|
||||
size_t days = seconds / 86400; seconds %= 86400;
|
||||
size_t hours = seconds / 3600; seconds %= 3600;
|
||||
size_t minutes = seconds / 60; seconds %= 60;
|
||||
string out = (days > 0 ? to_string(days) + "d " : "")
|
||||
+ (hours < 10 ? "0" : "") + to_string(hours) + ":"
|
||||
+ (minutes < 10 ? "0" : "") + to_string(minutes) + ":"
|
||||
+ (seconds < 10 ? "0" : "") + to_string(seconds);
|
||||
return out;
|
||||
}
|
||||
|
||||
string floating_humanizer(uint64_t value, bool shorten, size_t start, bool bit, bool per_second){
|
||||
string floating_humanizer(uint64_t value, const bool shorten, size_t start, const bool bit, const bool per_second) {
|
||||
string out;
|
||||
size_t mult = (bit) ? 8 : 1;
|
||||
const size_t mult = (bit) ? 8 : 1;
|
||||
static const array<string, 11> Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"};
|
||||
static const array<string, 11> Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"};
|
||||
auto& units = (bit) ? Units_bit : Units_byte;
|
||||
const auto& units = (bit) ? Units_bit : Units_byte;
|
||||
|
||||
value *= 100 * mult;
|
||||
|
||||
while (value >= 102400){
|
||||
while (value >= 102400) {
|
||||
value >>= 10;
|
||||
if (value < 100){
|
||||
if (value < 100) {
|
||||
out = to_string(value);
|
||||
break;
|
||||
}
|
||||
|
@ -274,7 +219,7 @@ namespace Tools {
|
|||
else if (out.size() == 3 and start > 0) out.insert(1, ".");
|
||||
else if (out.size() >= 2) out.resize(out.size() - 2);
|
||||
}
|
||||
if (shorten){
|
||||
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]);
|
||||
|
@ -285,16 +230,15 @@ namespace Tools {
|
|||
return out;
|
||||
}
|
||||
|
||||
std::string operator*(string str, size_t n){
|
||||
if (n == 0) return "";
|
||||
str.reserve(str.size() * n);
|
||||
for (string org_str = str; n > 1; n--) str.append(org_str);
|
||||
return str;
|
||||
std::string operator*(const string& str, size_t n) {
|
||||
string new_str;
|
||||
new_str.reserve(str.size() * n);
|
||||
for (; n > 0; n--) new_str.append(str);
|
||||
return new_str;
|
||||
}
|
||||
|
||||
string strf_time(const string& strf){
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(now);
|
||||
string strf_time(const string& strf) {
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
std::tm bt {};
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(localtime_r(&in_time_t, &bt), strf.c_str());
|
||||
|
@ -308,25 +252,17 @@ namespace Logger {
|
|||
namespace {
|
||||
std::atomic<bool> busy (false);
|
||||
bool first = true;
|
||||
string tdf = "%Y/%m/%d (%T) | ";
|
||||
const string tdf = "%Y/%m/%d (%T) | ";
|
||||
}
|
||||
|
||||
const vector<string> log_levels = {
|
||||
"DISABLED",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"INFO",
|
||||
"DEBUG",
|
||||
};
|
||||
|
||||
size_t loglevel;
|
||||
fs::path logfile;
|
||||
|
||||
void set(const string level){
|
||||
void set(const string& level) {
|
||||
loglevel = v_index(log_levels, level);
|
||||
}
|
||||
|
||||
void log_write(const size_t level, const string& msg){
|
||||
void log_write(const size_t level, const string& msg) {
|
||||
if (loglevel < level or logfile.empty()) return;
|
||||
busy.wait(true);
|
||||
busy = true;
|
||||
|
@ -341,7 +277,6 @@ namespace Logger {
|
|||
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 = false;
|
||||
|
|
|
@ -28,67 +28,65 @@ tab-size = 4
|
|||
#include <thread>
|
||||
|
||||
|
||||
using std::string, std::vector, std::atomic;
|
||||
using std::string, std::vector, std::atomic, std::to_string, std::regex;
|
||||
|
||||
|
||||
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
|
||||
|
||||
//* Collection of escape codes for text style and formatting
|
||||
namespace Fx {
|
||||
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
|
||||
const string e = "\x1b["; //* Escape sequence start
|
||||
const string b = e + "1m"; //* Bold on/off
|
||||
const string ub = e + "22m"; //* Bold off
|
||||
const string d = e + "2m"; //* Dark on
|
||||
const string ud = e + "22m"; //* Dark off
|
||||
const string i = e + "3m"; //* Italic on
|
||||
const string ui = e + "23m"; //* Italic off
|
||||
const string ul = e + "4m"; //* Underline on
|
||||
const string uul = e + "24m"; //* Underline off
|
||||
const string bl = e + "5m"; //* Blink on
|
||||
const string ubl = e + "25m"; //* Blink off
|
||||
const string s = e + "9m"; //* Strike/crossed-out on
|
||||
const string us = e + "29m"; //* Strike/crossed-out on/off
|
||||
|
||||
//* Reset foreground/background color and text effects
|
||||
extern const string reset_base;
|
||||
const string reset_base = e + "0m";
|
||||
|
||||
//* Reset text effects and restore theme foregrund and background color
|
||||
extern string reset;
|
||||
|
||||
//* Regex for matching color, style and curse move escape sequences
|
||||
extern const std::regex escape_regex;
|
||||
const regex escape_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m|f|s|u|C|D|A|B){1}");
|
||||
|
||||
//* Regex for matching only color and style escape sequences
|
||||
extern const std::regex color_regex;
|
||||
const regex color_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m){1}");
|
||||
|
||||
//* Return a string with all colors and text styling removed
|
||||
inline string uncolor(const string& s){
|
||||
return regex_replace(s, color_regex, "");
|
||||
};
|
||||
inline string uncolor(const string& s) { return regex_replace(s, color_regex, ""); }
|
||||
}
|
||||
|
||||
//* Collection of escape codes and functions for cursor manipulation
|
||||
namespace Mv {
|
||||
//* Move cursor to <line>, <column>
|
||||
const string to(int line, int col);
|
||||
inline string to(const int& line, const int& col) { return Fx::e + to_string(line) + ';' + to_string(col) + 'f'; }
|
||||
|
||||
//* Move cursor right <x> columns
|
||||
const string r(int x);
|
||||
inline string r(const int& x) { return Fx::e + to_string(x) + 'C'; }
|
||||
|
||||
//* Move cursor left <x> columns
|
||||
const string l(int x);
|
||||
inline string l(const int& x) { return Fx::e + to_string(x) + 'D'; }
|
||||
|
||||
//* Move cursor up x lines
|
||||
const string u(int x);
|
||||
inline string u(const int& x) { return Fx::e + to_string(x) + 'A'; }
|
||||
|
||||
//* Move cursor down x lines
|
||||
const string d(int x);
|
||||
inline string d(const int& x) { return Fx::e + to_string(x) + 'B'; }
|
||||
|
||||
//* Save cursor position
|
||||
extern const string save;
|
||||
const string save = Fx::e + "s";
|
||||
|
||||
//* Restore saved cursor postion
|
||||
extern const string restore;
|
||||
const string restore = Fx::e + "u";
|
||||
}
|
||||
|
||||
//* Collection of escape codes and functions for terminal manipulation
|
||||
|
@ -98,44 +96,19 @@ namespace Term {
|
|||
extern atomic<int> height;
|
||||
extern string fg, bg, current_tty;
|
||||
|
||||
//* Hide terminal cursor
|
||||
extern const string hide_cursor;
|
||||
|
||||
//* Show terminal cursor
|
||||
extern const string show_cursor;
|
||||
|
||||
//* Switch to alternate screen
|
||||
extern const string alt_screen;
|
||||
|
||||
//* Switch to normal screen
|
||||
extern const string normal_screen;
|
||||
|
||||
//* Clear screen and set cursor to position 0,0
|
||||
extern const string clear;
|
||||
|
||||
//* Clear from cursor to end of screen
|
||||
extern const string clear_end;
|
||||
|
||||
//* Clear from cursor to beginning of screen
|
||||
extern const string clear_begin;
|
||||
|
||||
//* Enable reporting of mouse position on click and release
|
||||
extern const string mouse_on;
|
||||
|
||||
//* Disable mouse reporting
|
||||
extern const string mouse_off;
|
||||
|
||||
//* Enable reporting of mouse position at any movement
|
||||
extern const string mouse_direct_on;
|
||||
|
||||
//* Disable direct mouse reporting
|
||||
extern const string mouse_direct_off;
|
||||
|
||||
//* Escape sequence for start of synchronized output
|
||||
extern const string sync_start;
|
||||
|
||||
//* Escape sequence for end of synchronized output
|
||||
extern const string sync_end;
|
||||
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"; //? Enable reporting of mouse position on click and release
|
||||
const string mouse_off = Fx::e + "?1002l";
|
||||
const string mouse_direct_on = Fx::e + "?1003h"; //? Enable reporting of mouse position at any movement
|
||||
const string mouse_direct_off = Fx::e + "?1003l";
|
||||
const string sync_start = Fx::e + "?2026h"; //? Start of terminal synchronized output
|
||||
const string sync_end = Fx::e + "?2026l"; //? End of terminal synchronized output
|
||||
|
||||
//* Returns true if terminal has been resized and updates width and height
|
||||
bool refresh();
|
||||
|
@ -153,7 +126,7 @@ namespace Tools {
|
|||
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
|
||||
|
||||
//* Return number of UTF8 characters in a string
|
||||
inline size_t ulen(const string& str){
|
||||
inline size_t ulen(const string& str) {
|
||||
return std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
|
||||
};
|
||||
|
||||
|
@ -161,14 +134,14 @@ namespace Tools {
|
|||
string uresize(const string str, const size_t len);
|
||||
|
||||
//* Return <str> with only uppercase characters
|
||||
inline string str_to_upper(string str){
|
||||
std::ranges::for_each(str, [](auto& c){ c = ::toupper(c); } );
|
||||
inline string str_to_upper(string str) {
|
||||
std::ranges::for_each(str, [](auto& c) { c = ::toupper(c); } );
|
||||
return str;
|
||||
}
|
||||
|
||||
//* Return <str> with only lowercase characters
|
||||
inline string str_to_lower(string str){
|
||||
std::ranges::for_each(str, [](char& c){ c = ::tolower(c); } );
|
||||
inline string str_to_lower(string str) {
|
||||
std::ranges::for_each(str, [](char& c) { c = ::tolower(c); } );
|
||||
return str;
|
||||
}
|
||||
|
||||
|
@ -197,32 +170,32 @@ namespace Tools {
|
|||
}
|
||||
|
||||
//* Return current time since epoch in seconds
|
||||
inline uint64_t time_s(){
|
||||
inline uint64_t time_s() {
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
//* Return current time since epoch in milliseconds
|
||||
inline uint64_t time_ms(){
|
||||
inline uint64_t time_ms() {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
//* Return current time since epoch in microseconds
|
||||
inline uint64_t time_micros(){
|
||||
inline uint64_t time_micros() {
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
//* Check if a string is a valid bool value
|
||||
inline bool isbool(const string& str){
|
||||
inline bool isbool(const string& str) {
|
||||
return (str == "true") or (str == "false") or (str == "True") or (str == "False");
|
||||
}
|
||||
|
||||
//* Convert string to bool, returning any value not equal to "true" or "True" as false
|
||||
inline bool stobool(const string& str){
|
||||
inline bool stobool(const string& str) {
|
||||
return (str == "true" or str == "True");
|
||||
}
|
||||
|
||||
//* Check if a string is a valid integer value
|
||||
inline bool isint(const string& str){
|
||||
inline bool isint(const string& str) {
|
||||
return all_of(str.begin() + (str[0] == '-' ? 1 : 0), str.end(), ::isdigit);
|
||||
}
|
||||
|
||||
|
@ -233,7 +206,7 @@ namespace Tools {
|
|||
string rtrim(const string& str, const string& t_str = " ");
|
||||
|
||||
//* Left/right-trim <t_str> from <str> and return new string
|
||||
inline string trim(const string& str, const string& t_str = " "){
|
||||
inline string trim(const string& str, const string& t_str = " ") {
|
||||
return ltrim(rtrim(str, t_str), t_str);
|
||||
};
|
||||
|
||||
|
@ -252,17 +225,17 @@ namespace Tools {
|
|||
//* Replace whitespaces " " with escape code for move right
|
||||
string trans(const string& str);
|
||||
|
||||
//* Convert seconds to format "Xd HH:MM:SS" and return string
|
||||
string sec_to_dhms(size_t sec);
|
||||
//* Convert seconds to format "<days>d <hours>:<minutes>:<seconds>" and return string
|
||||
string sec_to_dhms(size_t seconds);
|
||||
|
||||
//* Scales up in steps of 1024 to highest possible unit and returns string with unit suffixed
|
||||
//* Scales up in steps of 1024 to highest positive value 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, size_t start=0, bool bit=false, bool per_second=false);
|
||||
string floating_humanizer(uint64_t value, const bool shorten=false, size_t start=0, const bool bit=false, const bool per_second=false);
|
||||
|
||||
//* Add std::string operator "*" : Repeat string <str> <n> number of times
|
||||
std::string operator*(string str, size_t n);
|
||||
//* Add std::string operator * : Repeat string <str> <n> number of times
|
||||
std::string operator*(const string& str, size_t n);
|
||||
|
||||
//* Return current time in <strf> format
|
||||
string strf_time(const string& strf);
|
||||
|
@ -271,14 +244,22 @@ namespace Tools {
|
|||
|
||||
//* Simple logging implementation
|
||||
namespace Logger {
|
||||
extern const vector<string> log_levels;
|
||||
const vector<string> log_levels = {
|
||||
"DISABLED",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"INFO",
|
||||
"DEBUG",
|
||||
};
|
||||
extern std::filesystem::path logfile;
|
||||
|
||||
void set(const string level); //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
|
||||
//* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
|
||||
void set(const string& level);
|
||||
|
||||
void log_write(const size_t level, const string& msg);
|
||||
inline void error(const string msg){ log_write(1, msg); }
|
||||
inline void warning(const string msg){ log_write(2, msg); }
|
||||
inline void info(const string msg){ log_write(3, msg); }
|
||||
inline void debug(const string msg){ log_write(4, msg); }
|
||||
inline void error(const string msg) { log_write(1, msg); }
|
||||
inline void warning(const string msg) { log_write(2, msg); }
|
||||
inline void info(const string msg) { log_write(3, msg); }
|
||||
inline void debug(const string msg) { log_write(4, msg); }
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
using std::atomic;
|
||||
|
||||
namespace Menu {
|
||||
extern atomic<bool> active;
|
||||
}
|
Loading…
Reference in New Issue