This commit is contained in:
aristocratos 2021-07-21 03:17:34 +02:00
parent 77ef41daea
commit 5d4e2ce182
16 changed files with 804 additions and 779 deletions

View file

@ -20,14 +20,34 @@
* Tab size: 4 * 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). For proposing changes to this document create a [new issue](https://github.com/aristocratos/btop/issues/new/choose).

View file

@ -16,19 +16,11 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <string>
#include <array>
#include <list>
#include <vector>
#include <csignal> #include <csignal>
#include <thread> #include <thread>
#include <future>
#include <atomic>
#include <numeric> #include <numeric>
#include <ranges> #include <ranges>
#include <filesystem>
#include <unistd.h> #include <unistd.h>
#include <robin_hood.h>
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include <exception> #include <exception>
@ -58,8 +50,14 @@ tab-size = 4
#error Platform not supported! #error Platform not supported!
#endif #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 { namespace Global {
const std::vector<std::array<std::string, 2>> Banner_src = { const vector<array<string, 2>> Banner_src = {
{"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"}, {"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
{"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"}, {"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
{"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"}, {"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
@ -67,19 +65,12 @@ namespace Global {
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
}; };
const std::string Version = "0.0.30"; const string Version = "0.0.30";
int coreCount; 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; string banner;
size_t banner_width = 0; size_t banner_width = 0;
string overlay;
string exit_error_msg; string exit_error_msg;
atomic<bool> thread_exception (false); atomic<bool> thread_exception (false);
@ -88,6 +79,7 @@ namespace Global {
bool debuginit = false; bool debuginit = false;
bool debug = false; bool debug = false;
bool utf_force = false;
uint64_t start_time; uint64_t start_time;
@ -100,15 +92,10 @@ namespace Global {
//* A simple argument parser //* A simple argument parser
void argumentParser(int argc, char **argv){ void argumentParser(const int& argc, char **argv) {
string argument;
for(int i = 1; i < argc; i++) { for(int i = 1; i < argc; i++) {
argument = argv[i]; string argument = argv[i];
if (argument == "-v" or argument == "--version") { if (argument == "-h" or argument == "--help") {
cout << "btop version: " << Global::Version << endl;
exit(0);
}
else if (argument == "-h" or argument == "--help") {
cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n" cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n"
<< "optional arguments:\n" << "optional arguments:\n"
<< " -h, --help show this help message and exit\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" << " -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_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
<< " +t, --tty_off force (OFF) tty mode\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" << " --debug start with loglevel set to DEBUG, overriding value set in config\n"
<< endl; << endl;
exit(0); exit(0);
} }
else if (argument == "--debug") if (argument == "-v" or argument == "--version") {
Global::debug = true; 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") { else if (argument == "-t" or argument == "--tty_on") {
Config::set("tty_mode", true); Config::set("tty_mode", true);
Global::arg_tty = true; Global::arg_tty = true;
@ -130,9 +123,10 @@ void argumentParser(int argc, char **argv){
Config::set("tty_mode", false); Config::set("tty_mode", false);
Global::arg_tty = true; Global::arg_tty = true;
} }
else if (argument == "-lc" or argument == "--low-color") { else if (argument == "--utf-force")
Global::arg_low_color = true; Global::utf_force = true;
} else if (argument == "--debug")
Global::debug = true;
else { else {
cout << " Unknown argument: " << argument << "\n" << cout << " Unknown argument: " << argument << "\n" <<
" Use -h or --help for help." << endl; " 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 //* 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 (auto refreshed = Term::refresh() or force) {
if (force and refreshed) force = false; if (force and refreshed) force = false;
Global::resized = true; Global::resized = true;
@ -160,7 +154,7 @@ void term_resize(bool force=false){
} }
//* Exit handler; stops threads, restores terminal and saves config changes //* 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; if (Global::quitting) return;
Global::quitting = true; Global::quitting = true;
Runner::stop(); Runner::stop();
@ -168,14 +162,17 @@ void clean_quit(int sig){
Term::restore(); Term::restore();
if (not Global::debuginit) cout << Term::normal_screen << Term::show_cursor << flush; 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(); Config::write();
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time)); 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); if (sig != -1) exit(sig);
} }
//* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP //* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
void _sleep(){ void _sleep() {
Runner::stop(); Runner::stop();
if (Term::initialized) { if (Term::initialized) {
Term::restore(); Term::restore();
@ -185,7 +182,7 @@ void _sleep(){
} }
//* Handler for SIGCONT; re-initialize terminal and force a resize event //* Handler for SIGCONT; re-initialize terminal and force a resize event
void _resume(){ void _resume() {
Term::init(); Term::init();
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << flush; if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << flush;
term_resize(true); term_resize(true);
@ -195,7 +192,7 @@ void _exit_handler() {
clean_quit(-1); clean_quit(-1);
} }
void _signal_handler(int sig) { void _signal_handler(const int sig) {
switch (sig) { switch (sig) {
case SIGINT: case SIGINT:
clean_quit(0); clean_quit(0);
@ -212,24 +209,22 @@ void _signal_handler(int sig) {
} }
} }
//? Generate the btop++ banner //* Generate the btop++ banner
void banner_gen() { 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.clear();
Global::banner_width = 0; Global::banner_width = 0;
auto tty_mode = (Config::getB("tty_mode")); string b_color, bg, fg, oc, letter;
for (auto line: Global::Banner_src) { auto& lowcolor = Config::getB("lowcolor");
if (auto w = ulen(line[1]); w > Global::banner_width) Global::banner_width = w; 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) { if (tty_mode) {
fg = (z > 2) ? "\x1b[31m" : "\x1b[91m"; fg = (z > 2) ? "\x1b[31m" : "\x1b[91m";
bg = (z > 2) ? "\x1b[90m" : "\x1b[37m"; bg = (z > 2) ? "\x1b[90m" : "\x1b[37m";
} }
else { else {
fg = Theme::hex_to_color(line[0], lowcolor); 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); bg = Theme::dec_to_color(bg_i, bg_i, bg_i, lowcolor);
} }
for (size_t i = 0; i < line[1].size(); i += 3) { 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; + 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 { namespace Runner {
atomic<bool> active (false); atomic<bool> active (false);
atomic<bool> stopping (false); atomic<bool> stopping (false);
atomic<bool> has_output (false);
atomic<uint64_t> time_spent (0);
string output; 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) { void _runner(const vector<string> boxes, const bool no_update, const bool force_redraw, const bool interrupt) {
auto timestamp = time_micros(); auto timestamp = time_micros();
string out; output.clear();
out.reserve(output.size());
for (auto& box : boxes) { for (const auto& box : boxes) {
if (stopping) break; if (stopping) break;
try { try {
if (box == "cpu") { 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") { 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") { 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") { 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) { catch (const std::exception& e) {
@ -289,7 +283,6 @@ namespace Runner {
Global::exit_error_msg = "Exception in runner thread -> " Global::exit_error_msg = "Exception in runner thread -> "
+ fname + "::draw(" + fname + "::collect(no_update=" + (no_update ? "true" : "false") + fname + "::draw(" + fname + "::collect(no_update=" + (no_update ? "true" : "false")
+ "), force_redraw=" + (force_redraw ? "true" : "false") + ") : " + (string)e.what(); + "), force_redraw=" + (force_redraw ? "true" : "false") + ") : " + (string)e.what();
Logger::error(Global::exit_error_msg);
Global::thread_exception = true; Global::thread_exception = true;
Input::interrupt = true; Input::interrupt = true;
stopping = true; stopping = true;
@ -303,31 +296,39 @@ namespace Runner {
return; return;
} }
if (out.empty()) { if (output.empty()) {
out += "No boxes shown!"; output += "No boxes shown!";
} }
output.swap(out); //? If overlay isn't empty, print output without color and effects and then print overlay over
time_spent = time_micros() - timestamp; cout << Term::sync_start << (overlay.empty() ? output : Theme::c("inactive_fg") + Fx::uncolor(output) + overlay) << Term::sync_end << flush;
has_output = true;
//! 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; if (interrupt) Input::interrupt = true;
active = false; 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); active.wait(true);
Config::unlock();
if (stopping) return; if (stopping) return;
active = true; active = true;
Config::lock(); Config::lock();
vector<string> boxes; if (not Global::overlay.empty())
if (box == "all") boxes = Config::current_boxes; overlay = Global::overlay;
else boxes.push_back(box); else if (not overlay.empty())
std::thread run_thread(_runner, boxes, no_update, force_redraw, input_interrupt); overlay.clear();
std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw, input_interrupt);
run_thread.detach(); run_thread.detach();
} }
//* Stops any secondary thread running and unlocks config
void stop() { void stop() {
stopping = true; stopping = true;
active.wait(true); active.wait(true);
@ -335,25 +336,20 @@ namespace Runner {
stopping = false; stopping = false;
} }
string get_output() {
active.wait(true);
Config::unlock();
has_output = false;
return output;
}
} }
//? --------------------------------------------- Main starts here! --------------------------------------------------- //* --------------------------------------------- Main starts here! ---------------------------------------------------
int main(int argc, char **argv){ int main(int argc, char **argv) {
//? Init //? ------------------------------------------------ INIT ---------------------------------------------------------
Global::start_time = time_s(); Global::start_time = time_s();
cout.setf(std::ios::boolalpha); //? Call argument parser if launched with arguments
if (argc > 1) argumentParser(argc, argv); if (argc > 1) argumentParser(argc, argv);
//? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
std::atexit(_exit_handler); std::atexit(_exit_handler);
std::at_quick_exit(_exit_handler); std::at_quick_exit(_exit_handler);
std::signal(SIGINT, _signal_handler); std::signal(SIGINT, _signal_handler);
@ -365,14 +361,15 @@ int main(int argc, char **argv){
#if defined(LINUX) #if defined(LINUX)
Global::coreCount = sysconf(_SC_NPROCESSORS_ONLN); Global::coreCount = sysconf(_SC_NPROCESSORS_ONLN);
if (Global::coreCount < 1) Global::coreCount = 1; if (Global::coreCount < 1) Global::coreCount = 1;
{
std::error_code ec; { std::error_code ec;
Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename(); Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
} }
#endif #endif
//? Setup paths for config, log and themes //? Setup paths for config, log and user themes
for (auto env : {"XDG_CONFIG_HOME", "HOME"}) { for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) { if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) {
Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
break; break;
@ -381,7 +378,7 @@ int main(int argc, char **argv){
if (not Config::conf_dir.empty()) { 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)) { 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 << "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 { else {
Config::conf_file = Config::conf_dir / "btop.conf"; 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(); 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()) { if (std::error_code ec; not Global::self_path.empty()) {
Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec); 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 (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()) { if (Theme::theme_dir.empty()) {
for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) { 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) { 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")); 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")) { //? Try to find and set a UTF-8 locale
string err_msg = "No UTF-8 locale was detected! Symbols might not look as intended.\n" if (bool found = false; not str_to_upper((string)std::setlocale(LC_ALL, NULL)).ends_with("UTF-8")) {
"Make sure your $LANG evironment variable is set and with a UTF-8 locale."; if (const string lang = (string)getenv("LANG"); str_to_upper(lang).ends_with("UTF-8")) {
Logger::warning(err_msg); found = true;
cout << "WARNING: " << err_msg << endl; 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 //? 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"); Logger::info("Real tty detected, setting 16 color mode and using tty friendly graph symbols");
} }
//? Platform dependent init and error check
//? Platform init and error check
Shared::init(); Shared::init();
//? Update list of available themes and generate the selected theme
// Config::set("truecolor", false);
auto thts = time_micros();
//? Update theme list and generate the theme
Theme::updateThemes(); Theme::updateThemes();
Theme::setTheme(); Theme::setTheme();
//? Create the btop++ banner //? Create the btop++ banner
banner_gen(); 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 ------------------------------------------------------ //* ------------------------------------------------ TESTING ------------------------------------------------------
Global::debuginit = false; if (false) {
Draw::calcSizes(); cout << "Current: " << std::setlocale(LC_ALL, NULL) << endl;
exit(0);
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;
}
//* Test theme //* Test theme
if (false) { if (false) {
string key; string key;
bool no_redraw = false; bool no_redraw = false;
Config::unlock();
auto theme_index = v_index(Theme::themes, Config::getS("color_theme")); auto theme_index = v_index(Theme::themes, Config::getS("color_theme"));
uint64_t timer = 0;
while (key != "q") { while (key != "q") {
key.clear(); key.clear();
if (not no_redraw) { 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; 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 << " "; cout << rjust(item.first, 15) << ":" << item.second << ""s * 10 << Fx::reset << " ";
if (++i == 4) { if (++i == 4) {
i = 0; i = 0;
@ -499,7 +521,7 @@ int main(int argc, char **argv){
cout << "Gradients:"; cout << "Gradients:";
for (auto& [name, cvec] : Theme::test_gradients()) { for (const auto& [name, cvec] : Theme::gradients) {
cout << endl << rjust(name + ":", 10); cout << endl << rjust(name + ":", 10);
for (auto& color : cvec) { for (auto& color : cvec) {
cout << color << ""; cout << color << "";
@ -512,7 +534,6 @@ int main(int argc, char **argv){
no_redraw = true; no_redraw = true;
key = Input::wait(); key = Input::wait();
if (key.empty()) continue; if (key.empty()) continue;
thts = time_micros();
if (key == "right") { if (key == "right") {
if (theme_index == Theme::themes.size() - 1) theme_index = 0; if (theme_index == Theme::themes.size() - 1) theme_index = 0;
else theme_index++; else theme_index++;
@ -524,7 +545,9 @@ int main(int argc, char **argv){
else continue; else continue;
no_redraw = false; no_redraw = false;
Config::set("color_theme", Theme::themes.at(theme_index)); Config::set("color_theme", Theme::themes.at(theme_index));
timer = time_micros();
Theme::setTheme(); 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::restore << Mv::d(26) << kgraph3(mydata, true) << '\n'
<< Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl; << Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
list<uint64_t> ktavg;
for (;;) { for (;;) {
mydata.back() = std::rand() % 101; mydata.back() = std::rand() % 101;
kts = time_micros(); kts = time_micros();
@ -566,9 +588,7 @@ int main(int argc, char **argv){
<< Mv::restore << Mv::d(13) << kgraph2(mydata) << Mv::restore << Mv::d(13) << kgraph2(mydata)
<< Mv::restore << Mv::d(26) << kgraph3(mydata) << Mv::restore << Mv::d(26) << kgraph3(mydata)
<< Term::sync_end << endl; << Term::sync_end << endl;
ktavg.push_front(time_micros() - kts); cout << Mv::d(1) << "Time: " << time_micros() - kts << " μs. " << flush;
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;
if (Input::poll()) { if (Input::poll()) {
if (Input::get() == "space") Input::wait(); if (Input::get() == "space") Input::wait();
else break; else break;
@ -583,15 +603,15 @@ int main(int argc, char **argv){
//* ------------------------------------------------ MAIN LOOP ---------------------------------------------------- //? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
uint64_t future_time = time_ms(), rcount = 0; uint64_t update_ms = Config::getI("update_ms");
list<uint64_t> avgtimes; auto future_time = time_ms();
try { try {
while (not true not_eq not false) { 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); 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) //? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
term_resize(); term_resize();
@ -604,31 +624,28 @@ int main(int argc, char **argv){
Runner::run("all", true, true); Runner::run("all", true, true);
} }
//? Print out any available output from secondary thread //? Start secondary collect & draw thread at the interval set by <update_ms> config value
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
if (time_ms() >= future_time) { if (time_ms() >= future_time) {
Runner::run("all"); Runner::run("all");
update_ms = Config::getI("update_ms");
future_time = time_ms() + 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()) { 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) if (future_time - current_time > update_ms)
future_time = current_time; 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()); Input::process(Input::get());
}
//? Break the loop at 1000ms intervals or if input polling was interrupted
else else
break; break;
} }
@ -637,7 +654,6 @@ int main(int argc, char **argv){
} }
catch (std::exception& e) { catch (std::exception& e) {
Global::exit_error_msg = "Exception in main loop -> " + (string)e.what(); Global::exit_error_msg = "Exception in main loop -> " + (string)e.what();
Logger::error(Global::exit_error_msg);
clean_quit(1); clean_quit(1);
} }

View file

@ -17,7 +17,6 @@ tab-size = 4
*/ */
#include <array> #include <array>
#include <robin_hood.h>
#include <ranges> #include <ranges>
#include <atomic> #include <atomic>
#include <fstream> #include <fstream>
@ -26,215 +25,214 @@ tab-size = 4
#include <btop_shared.hpp> #include <btop_shared.hpp>
#include <btop_tools.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 fs = std::filesystem;
namespace rng = std::ranges; namespace rng = std::ranges;
using namespace Tools; using namespace Tools;
//* Functions and variables for reading and writing the btop config file //* Functions and variables for reading and writing the btop config file
namespace Config { namespace Config {
namespace {
atomic<bool> locked (false);
atomic<bool> writelock (false);
bool write_new;
vector<array<string, 2>> descriptions = { atomic<bool> locked (false);
{"color_theme", "#* Full path to a bashtop/bpytop/btop++ formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes."}, 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" {"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
"#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
{"graph_symbol", "#* Default symbols to use for graph creation, \"braille\", \"block\" or \"tty\".\n" {"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n"
"#* \"braille\" offers the highest resolution but might not be included in all fonts.\n" "#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
"#* \"block\" has half the resolution of braille but uses more common characters.\n"
"#* \"tty\" uses only 3 different symbols but will work with most fonts and should work in a real TTY.\n"
"#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."},
{"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."}, {"graph_symbol", "#* 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" {"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."},
"#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)."},
{"proc_sorting", "#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n" {"proc_update_mult", "#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n"
"#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly."}, "#* 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" {"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
"#* Select from a list of detected attributes from the options menu."},
{"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n" {"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."}, "#* 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" {"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
"#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"."},
{"mem_graphs", "#* Show graphs instead of meters for memory values."}, {"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" {"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
"#* Example: \"/dev/sda:100, /dev/sdb:20\"."},
{"net_download", "#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"."}, {"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" {"show_battery", "#* Show battery stats in top right if battery is present."},
"#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}
};
unordered_flat_map<string, string> strings = { {"log_level", "#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n"
{"color_theme", "Default"}, "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}
{"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, bool> bools = { unordered_flat_map<string, string> strings = {
{"theme_background", true}, {"color_theme", "Default"},
{"truecolor", true}, {"shown_boxes", "cpu mem net proc"},
{"proc_reversed", false}, {"graph_symbol", "braille"},
{"proc_tree", false}, {"graph_symbol_cpu", "default"},
{"proc_colors", true}, {"graph_symbol_mem", "default"},
{"proc_gradient", true}, {"graph_symbol_net", "default"},
{"proc_per_core", false}, {"graph_symbol_proc", "default"},
{"proc_mem_bytes", true}, {"proc_sorting", "cpu lazy"},
{"cpu_invert_lower", true}, {"cpu_graph_upper", "total"},
{"cpu_single_graph", false}, {"cpu_graph_lower", "total"},
{"show_uptime", true}, {"cpu_sensor", "Auto"},
{"check_temp", true}, {"temp_scale", "celsius"},
{"show_coretemp", true}, {"draw_clock", "%X"},
{"show_cpu_freq", true}, {"custom_cpu_name", ""},
{"background_update", true}, {"disks_filter", ""},
{"mem_graphs", true}, {"io_graph_speeds", ""},
{"show_swap", true}, {"net_download", "10M"},
{"swap_disk", true}, {"net_upload", "10M"},
{"show_disks", true}, {"net_iface", ""},
{"only_physical", true}, {"log_level", "WARNING"},
{"use_fstab", false}, {"proc_filter", ""},
{"show_io_stat", true}, {"proc_command", ""},
{"io_mode", false}, };
{"io_graph_combined", false}, unordered_flat_map<string, string> stringsTmp;
{"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, int> ints = { unordered_flat_map<string, bool> bools = {
{"update_ms", 2000}, {"theme_background", true},
{"proc_update_mult", 2}, {"truecolor", true},
{"detailed_pid", 0}, {"proc_reversed", false},
{"selected_pid", 0}, {"proc_tree", false},
{"proc_start", 0}, {"proc_colors", true},
{"proc_selected", 0}, {"proc_gradient", true},
}; {"proc_per_core", false},
unordered_flat_map<string, int> intsTmp; {"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){ vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
writelock.wait(true);
if (not write_new and rng::find_if(descriptions, [&name](const auto& a){ return a.at(0) == name; }) != descriptions.end()) bool _locked(const string& name) {
write_new = true; writelock.wait(true);
return locked.load(); 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; fs::path conf_dir;
@ -242,36 +240,22 @@ namespace Config {
vector<string> current_boxes; vector<string> current_boxes;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" }; void set(string name, bool value) {
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){
if (_locked(name)) boolsTmp.insert_or_assign(name, value); if (_locked(name)) boolsTmp.insert_or_assign(name, value);
else bools.at(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); if (_locked(name)) intsTmp.insert_or_assign(name, value);
ints.at(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); if (_locked(name)) stringsTmp.insert_or_assign(name, value);
else strings.at(name) = value; else strings.at(name) = value;
} }
void flip(string name){ void flip(string name) {
if (_locked(name)) { if (_locked(name)) {
if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name); if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name);
else boolsTmp.insert_or_assign(name, (not bools.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); else bools.at(name) = not bools.at(name);
} }
void lock(){ void lock() {
writelock.wait(true); writelock.wait(true);
locked = true; locked = true;
} }
void unlock(){ void unlock() {
if (not locked) return; if (not locked) return;
writelock.wait(true);
writelock = true; writelock = true;
if (Proc::shown) { try {
ints.at("selected_pid") = Proc::selected_pid; if (Proc::shown) {
ints.at("proc_start") = Proc::start; ints.at("selected_pid") = Proc::selected_pid;
ints.at("proc_selected") = Proc::selected; ints.at("proc_start") = Proc::start;
} ints.at("proc_selected") = Proc::selected;
}
for (auto& item : stringsTmp){ for (auto& item : stringsTmp) {
strings.at(item.first) = item.second; strings.at(item.first) = item.second;
} }
stringsTmp.clear(); stringsTmp.clear();
for (auto& item : intsTmp){ for (auto& item : intsTmp) {
ints.at(item.first) = item.second; ints.at(item.first) = item.second;
} }
intsTmp.clear(); intsTmp.clear();
for (auto& item : boolsTmp){ for (auto& item : boolsTmp) {
bools.at(item.first) = item.second; 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; locked = false;
writelock = false; writelock = false;
writelock.notify_all(); writelock.notify_all();
} }
bool check_boxes(string boxes){ bool check_boxes(string boxes) {
auto new_boxes = ssplit(boxes); auto new_boxes = ssplit(boxes);
for (auto& box : new_boxes) { for (auto& box : new_boxes) {
if (not v_contains(valid_boxes, box)) return false; if (not v_contains(valid_boxes, box)) return false;
@ -323,7 +314,7 @@ namespace Config {
return true; 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()) if (conf_file.empty())
return; return;
else if (not fs::exists(conf_file)) { 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; if (conf_file.empty() or not write_new) return;
Logger::debug("Writing new config file"); Logger::debug("Writing new config file");
std::ofstream cwrite(conf_file, std::ios::trunc); std::ofstream cwrite(conf_file, std::ios::trunc);

View file

@ -20,9 +20,10 @@ tab-size = 4
#include <string> #include <string>
#include <vector> #include <vector>
#include <robin_hood.h>
#include <filesystem> #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 //* Functions and variables for reading and writing the btop config file
namespace Config { namespace Config {
@ -30,7 +31,11 @@ namespace Config {
extern std::filesystem::path conf_dir; extern std::filesystem::path conf_dir;
extern std::filesystem::path conf_file; 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; extern vector<string> current_boxes;
@ -38,13 +43,13 @@ namespace Config {
bool check_boxes(string boxes); bool check_boxes(string boxes);
//* Return bool for config key <name> //* 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> //* 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> //* 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> //* Set config key <name> to bool <value>
void set(string name, bool value); void set(string name, bool value);
@ -58,7 +63,7 @@ namespace Config {
//* Flip config key bool <name> //* Flip config key bool <name>
void flip(string 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(); void lock();
//* Unlock config and write any cached values to config //* Unlock config and write any cached values to config

View file

@ -17,8 +17,6 @@ tab-size = 4
*/ */
#include <array> #include <array>
#include <map>
#include <ranges>
#include <algorithm> #include <algorithm>
#include <cmath> #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, using std::round, std::views::iota, std::string_literals::operator""s, std::clamp, std::array, std::floor, std::max, std::min,
std::to_string; std::to_string;
namespace rng = std::ranges;
using namespace Tools; using namespace Tools;
namespace Symbols { namespace Symbols {
@ -100,7 +97,7 @@ namespace Symbols {
namespace Draw { 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 out;
string lcolor = (line_color.empty()) ? Theme::c("div_line") : line_color; 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]); 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; out = Fx::reset + lcolor;
//? Draw horizontal lines //? 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); out += Mv::to(hpos, x) + Symbols::h_line * (width - 1);
} }
//? Draw vertical lines and fill if enabled //? 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 + out += Mv::to(hpos, x) + Symbols::v_line +
((fill) ? string(width - 2, ' ') : Mv::r(width - 2)) + ((fill) ? string(width - 2, ' ') : Mv::r(width - 2)) +
Symbols::v_line; Symbols::v_line;
@ -126,11 +123,11 @@ namespace Draw {
Mv::to(y + height - 1, x + width - 1) + Symbols::right_down; Mv::to(y + height - 1, x + width - 1) + Symbols::right_down;
//? Draw titles if defined //? 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 + out += Mv::to(y, x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + title +
Fx::ub + lcolor + Symbols::title_right; 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 + out += Mv::to(y + height - 1, x + 2) + Symbols::title_left + Theme::c("title") + title2 +
Fx::ub + lcolor + Symbols::title_right; Fx::ub + lcolor + Symbols::title_right;
} }
@ -186,7 +183,7 @@ namespace Draw {
else data_value = data[i]; else data_value = data[i];
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll); if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
//? Vertical iteration over height of graph //? 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_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100;
int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0; 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 //? Calculate previous + current value to fit two values in 1 braille character
@ -338,7 +335,7 @@ namespace Proc {
string box; string box;
void selection(string cmd_key) { void selection(const string& cmd_key) {
auto start = Config::getI("proc_start"); auto start = Config::getI("proc_start");
auto selected = Config::getI("proc_selected"); auto selected = Config::getI("proc_selected");
int numpids = Proc::numpids; int numpids = Proc::numpids;
@ -371,7 +368,7 @@ namespace Proc {
Config::set("proc_selected", selected); 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& filter = Config::getS("proc_filter");
auto& filtering = Config::getB("proc_filtering"); auto& filtering = Config::getB("proc_filtering");
auto& proc_tree = Config::getB("proc_tree"); auto& proc_tree = Config::getB("proc_tree");
@ -390,7 +387,7 @@ namespace Proc {
redraw = false; redraw = false;
out = box; out = box;
out += Mv::to(y, x) + Mv::r(12) 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: " + trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: "
+ string(Config::getS("proc_sorting")), width - 23 - ulen(filter))); + string(Config::getS("proc_sorting")), width - 23 - ulen(filter)));
@ -420,7 +417,7 @@ namespace Proc {
//* Iteration over processes //* Iteration over processes
int lc = 0; int lc = 0;
for (int n=0; auto& p : plist){ for (int n=0; auto& p : plist) {
if (n++ < start) continue; if (n++ < start) continue;
bool is_selected = (lc + 1 == selected); bool is_selected = (lc + 1 == selected);
if (is_selected) selected_pid = (int)p.pid; if (is_selected) selected_pid = (int)p.pid;
@ -463,8 +460,8 @@ namespace Proc {
if (not proc_tree) { if (not proc_tree) {
out += Mv::to(y+2+lc, x+1) out += Mv::to(y+2+lc, x+1)
+ g_color + rjust(to_string(p.pid), 8) + ' ' + g_color + rjust(to_string(p.pid), 8) + ' '
+ c_color + ljust(p.name, (width < 70 ? width - 46 : 16)) + end + c_color + ljust(p.name, (width < 70 ? width - 46 : 15)) + end
+ (width >= 70 ? g_color + ljust(p.cmd, width - 62, true) : ""); + (width >= 70 ? g_color + ' ' + ljust(p.cmd, width - 62, true) : "");
} }
//? Tree view line //? Tree view line
else { else {
@ -509,7 +506,7 @@ namespace Proc {
} }
namespace Draw { namespace Draw {
void calcSizes(){ void calcSizes() {
auto& boxes = Config::getS("shown_boxes"); auto& boxes = Config::getS("shown_boxes");
Cpu::box.clear(); Cpu::box.clear();

View file

@ -22,24 +22,8 @@ tab-size = 4
#include <vector> #include <vector>
#include <robin_hood.h> #include <robin_hood.h>
#include <deque> #include <deque>
#include <atomic>
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque, std::atomic; using std::string, std::vector, robin_hood::unordered_flat_map, std::deque;
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
namespace Draw { namespace Draw {
@ -60,7 +44,7 @@ namespace Draw {
string operator()(int value); string operator()(int value);
}; };
//* Class holding a graph //* Class holding a percentage graph
class Graph { class Graph {
string out, color_gradient, symbol = "default"; string out, color_gradient, symbol = "default";
int width = 0, height = 0; int width = 0, height = 0;

View file

@ -72,7 +72,7 @@ namespace Input {
string last = ""; string last = "";
bool poll(int timeout){ bool poll(int timeout) {
if (timeout < 1) return cin.rdbuf()->in_avail() > 0; if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
while (timeout > 0) { while (timeout > 0) {
if (cin.rdbuf()->in_avail() > 0) return true; if (cin.rdbuf()->in_avail() > 0) return true;
@ -83,10 +83,10 @@ namespace Input {
return false; return false;
} }
string get(){ string get() {
string key; string key;
while (cin.rdbuf()->in_avail() > 0 and key.size() < 100) key += cin.get(); 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.substr(0,2) == Fx::e) key.erase(0, 1);
if (Key_escapes.contains(key)) key = Key_escapes.at(key); if (Key_escapes.contains(key)) key = Key_escapes.at(key);
else if (ulen(key) > 1) key = ""; else if (ulen(key) > 1) key = "";
@ -95,7 +95,7 @@ namespace Input {
return key; return key;
} }
string wait(){ string wait() {
while (cin.rdbuf()->in_avail() < 1) { while (cin.rdbuf()->in_avail() < 1) {
if (interrupt) { interrupt = false; return ""; } if (interrupt) { interrupt = false; return ""; }
sleep_ms(10); sleep_ms(10);
@ -103,11 +103,11 @@ namespace Input {
return get(); return get();
} }
void clear(){ void clear() {
last.clear(); last.clear();
} }
void process(const string key){ void process(const string key) {
if (key.empty()) return; if (key.empty()) return;
try { try {
auto& filtering = Config::getB("proc_filtering"); auto& filtering = Config::getB("proc_filtering");
@ -120,28 +120,58 @@ namespace Input {
bool keep_going = false; bool keep_going = false;
if (filtering) { if (filtering) {
string filter = Config::getS("proc_filter"); string filter = Config::getS("proc_filter");
if (key == "enter") Config::set("proc_filtering", false); if (key == "enter")
else if (key == "backspace" and not filter.empty()) filter = uresize(filter, ulen(filter) - 1); Config::set("proc_filtering", false);
else if (key == "space") filter.push_back(' ');
else if (ulen(key) == 1) filter.append(key); else if (key == "backspace" and not filter.empty())
else return; 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); Config::set("proc_filter", filter);
} }
else if (key == "left") { else if (key == "left") {
int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting")); 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)); Config::set("proc_sorting", Proc::sort_vector.at(cur_i));
} }
else if (key == "right") { else if (key == "right") {
int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting")); 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)); Config::set("proc_sorting", Proc::sort_vector.at(cur_i));
} }
else if (key == "f") { Config::flip("proc_filtering"); recollect = false; } else if (key == "f") {
else if (key == "t") Config::flip("proc_tree"); Config::flip("proc_filtering");
else if (key == "r") Config::flip("proc_reversed"); recollect = false;
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 == "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")) { else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) {
Proc::selection(key); Proc::selection(key);
recollect = false; recollect = false;

View file

@ -18,25 +18,16 @@ tab-size = 4
#if defined(__linux__) #if defined(__linux__)
#include <string>
#include <vector>
#include <atomic>
#include <fstream> #include <fstream>
#include <filesystem>
#include <ranges> #include <ranges>
#include <list>
#include <cmath> #include <cmath>
#include <iostream>
#include <cmath> #include <cmath>
#include <unistd.h> #include <unistd.h>
#include <btop_shared.hpp> #include <btop_shared.hpp>
#include <btop_config.hpp> #include <btop_config.hpp>
#include <btop_tools.hpp> #include <btop_tools.hpp>
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize, using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
std::round, std::string_literals::operator""s; std::round, std::string_literals::operator""s;
namespace fs = std::filesystem; namespace fs = std::filesystem;
@ -46,7 +37,7 @@ using namespace Tools;
//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
namespace Tools { namespace Tools {
double system_uptime(){ double system_uptime() {
string upstr; string upstr;
ifstream pread("/proc/uptime"); ifstream pread("/proc/uptime");
getline(pread, upstr, ' '); getline(pread, upstr, ' ');
@ -63,13 +54,11 @@ namespace Shared {
long page_size; long page_size;
long clk_tck; long clk_tck;
void init(){ void init() {
proc_path = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : ""; proc_path = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : "";
if (proc_path.empty()) { if (proc_path.empty()) {
string errmsg = "Proc filesystem not found or no permission to read from it!"; Global::exit_error_msg = "Proc filesystem not found or no permission to read from it!";
Logger::error(errmsg); clean_quit(1);
std::cout << "ERROR: " << errmsg << std::endl;
exit(1);
} }
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path; 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); clk_tck = sysconf(_SC_CLK_TCK);
if (clk_tck <= 0) { if (clk_tck <= 0) {
clk_tck = 100; 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; detail_container detailed;
//* Generate process tree list //* 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; if (Runner::stopping) return;
auto cur_pos = out_procs.size(); auto cur_pos = out_procs.size();
bool filtering = false; bool filtering = false;
@ -193,12 +182,13 @@ namespace Proc {
if (not collapsed and not filtering) { if (not collapsed and not filtering) {
out_procs.push_back(cur_proc); out_procs.push_back(cur_proc);
if (auto& cmdline = cache.at(cur_proc.pid).cmd; not cmdline.empty() and not cmdline.starts_with("(")) { 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())); std::string_view cmd_view = cmdline;
cmdline = cmdline.substr(std::min(cmdline.find_last_of('/') + 1, cmdline.size())); cmd_view = cmd_view.substr(0, std::min(cmd_view.find(' '), cmd_view.size()));
if (cmdline == cur_proc.name) cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
if (cmd_view == cur_proc.name)
cmdline.clear(); cmdline.clear();
else else
cmdline = '(' + cmdline + ')'; cmdline = '(' + (string)cmd_view + ')';
out_procs.back().cmd = cmdline; out_procs.back().cmd = cmdline;
} }
} }
@ -222,7 +212,7 @@ namespace Proc {
} }
//* Get detailed info for selected process //* 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); fs::path pid_path = Shared::proc_path / std::to_string(pid);
if (pid != detailed.last_pid) { if (pid != detailed.last_pid) {
@ -274,8 +264,8 @@ namespace Proc {
else else
detailed.memory = floating_humanizer(rss, false, 1); detailed.memory = floating_humanizer(rss, false, 1);
} }
catch (std::invalid_argument const&) {} catch (const std::invalid_argument&) {}
catch (std::out_of_range const&) {} catch (const std::out_of_range&) {}
} }
d_read.close(); d_read.close();
} }
@ -302,8 +292,8 @@ namespace Proc {
d_read.ignore(SSmax, '\n'); d_read.ignore(SSmax, '\n');
} }
} }
catch (std::invalid_argument const&) {} catch (const std::invalid_argument&) {}
catch (std::out_of_range const&) {} catch (const std::out_of_range&) {}
} }
d_read.close(); d_read.close();
} }
@ -313,27 +303,27 @@ namespace Proc {
} }
//* Collects and sorts process information from /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; if (return_last) return current_procs;
auto& sorting = Config::getS("proc_sorting"); const auto& sorting = Config::getS("proc_sorting");
auto reverse = Config::getB("proc_reversed"); const auto& reverse = Config::getB("proc_reversed");
auto& filter = Config::getS("proc_filter"); const auto& filter = Config::getS("proc_filter");
auto per_core = Config::getB("proc_per_core"); const auto& per_core = Config::getB("proc_per_core");
auto tree = Config::getB("proc_tree"); const auto& tree = Config::getB("proc_tree");
if (tree_state != tree) { if (tree_state != tree) {
cache.clear(); cache.clear();
tree_state = tree; tree_state = tree;
} }
auto show_detailed = Config::getB("show_detailed"); const auto& show_detailed = Config::getB("show_detailed");
size_t detailed_pid = Config::getI("detailed_pid"); const size_t detailed_pid = Config::getI("detailed_pid");
ifstream pread; ifstream pread;
string long_string; string long_string;
string short_str; string short_str;
double uptime = system_uptime(); const double uptime = system_uptime();
vector<proc_info> procs; vector<proc_info> procs;
procs.reserve(reserve_pids + 10); procs.reserve(reserve_pids + 10);
int npids = 0; int npids = 0;
int cmult = (per_core) ? Global::coreCount : 1; const int cmult = (per_core) ? Global::coreCount : 1;
bool got_detailed = false; bool got_detailed = false;
bool detailed_filtered = false; bool detailed_filtered = false;
@ -344,7 +334,7 @@ namespace Proc {
uid_user.clear(); uid_user.clear();
pread.open(Shared::passwd_path); pread.open(Shared::passwd_path);
if (pread.good()) { if (pread.good()) {
while (not pread.eof()){ while (not pread.eof()) {
getline(pread, r_user, ':'); getline(pread, r_user, ':');
pread.ignore(SSmax, ':'); pread.ignore(SSmax, ':');
getline(pread, r_uid, ':'); getline(pread, r_uid, ':');
@ -366,12 +356,12 @@ namespace Proc {
else return current_procs; else return current_procs;
//* Iterate over all pids in /proc //* 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) if (Runner::stopping)
return current_procs; return current_procs;
if (pread.is_open()) pread.close(); 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; if (not isdigit(pid_str[0])) continue;
npids++; npids++;
@ -386,7 +376,6 @@ namespace Proc {
pread.close(); pread.close();
size_t name_offset = rng::count(name, ' '); size_t name_offset = rng::count(name, ' ');
pread.open(d.path() / "cmdline"); pread.open(d.path() / "cmdline");
if (not pread.good()) continue; if (not pread.good()) continue;
long_string.clear(); long_string.clear();
@ -394,12 +383,11 @@ namespace Proc {
pread.close(); pread.close();
if (not cmd.empty()) cmd.pop_back(); if (not cmd.empty()) cmd.pop_back();
pread.open(d.path() / "status"); pread.open(d.path() / "status");
if (not pread.good()) continue; if (not pread.good()) continue;
string uid; string uid;
string line; string line;
while (not pread.eof()){ while (not pread.eof()) {
getline(pread, line, ':'); getline(pread, line, ':');
if (line == "Uid") { if (line == "Uid") {
pread.ignore(); pread.ignore();
@ -533,7 +521,7 @@ namespace Proc {
} }
//* Sort processes //* 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)) { switch (v_index(sort_vector, sorting)) {
case 0: { rng::sort(procs, cmp, &proc_info::pid); break; } case 0: { rng::sort(procs, cmp, &proc_info::pid); break; }
case 1: { rng::sort(procs, cmp, &proc_info::name); 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 //* 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") { if (not tree and not reverse and sorting == "cpu lazy") {
double max = 10.0, target = 30.0; 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) if (i <= 5 and procs[i].cpu_p > max)
max = procs[i].cpu_p; max = procs[i].cpu_p;
else if (i == 6) else if (i == 6)
target = (max > 30.0) ? max : 10.0; target = (max > 30.0) ? max : 10.0;
if (i == offset and procs[i].cpu_p > 30.0) if (i == offset and procs[i].cpu_p > 30.0)
offset++; 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); 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); rng::stable_sort(procs, rng::less{}, &proc_info::ppid);
//? Start recursive iteration over processes with the lowest shared parent pids //? 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); _tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, filter);
} }
if (Runner::stopping) return current_procs; if (Runner::stopping) return current_procs;
procs.swap(tree_procs); procs = std::move(tree_procs);
} }
@ -583,17 +573,17 @@ namespace Proc {
counter = 0; counter = 0;
unordered_flat_map<size_t, p_cache> r_cache; unordered_flat_map<size_t, p_cache> r_cache;
r_cache.reserve(procs.size()); 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)) if (cache.contains(p.pid))
r_cache[p.pid] = cache.at(p.pid); r_cache[p.pid] = cache.at(p.pid);
}); });
cache.swap(r_cache); cache = std::move(r_cache);
} }
old_cputimes = cputimes; old_cputimes = cputimes;
numpids = (int)procs.size(); numpids = (int)procs.size();
current_procs.swap(procs);
reserve_pids = npids; reserve_pids = npids;
current_procs = std::move(procs);
return current_procs; return current_procs;
} }
} }

75
src/btop_menu.cpp Normal file
View file

@ -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", {
"╔═╗ ╦ ╦ ╦╔╦╗ ",
"║═╬╗║ ║ ║ ║ ",
"╚═╝╚╚═╝ ╩ ╩ "
} }
} }
};
}

View file

@ -19,56 +19,14 @@ tab-size = 4
#pragma once #pragma once
#include <string> #include <string>
#include <vector>
#include <array>
#include <atomic> #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 { namespace Menu {
atomic<bool> active(false); extern atomic<bool> active;
extern string output;
const unordered_flat_map<string, unordered_map<string, vector<string>>> Menus = {
{ "options", {
{ "normal", {
"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
"│ │├─┘ │ ││ ││││└─┐",
"└─┘┴ ┴ ┴└─┘┘└┘└─┘"
} },
{ "selected", {
"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝"
} }
} },
{ "help", {
{ "normal", {
"┬ ┬┌─┐┬ ┌─┐",
"├─┤├┤ │ ├─┘",
"┴ ┴└─┘┴─┘┴ "
} },
{ "selected", {
"╦ ╦╔═╗╦ ╔═╗",
"╠═╣║╣ ║ ╠═╝",
"╩ ╩╚═╝╩═╝╩ "
} }
} },
{ "quit", {
{ "normal", {
"┌─┐ ┬ ┬ ┬┌┬┐",
"│─┼┐│ │ │ │ ",
"└─┘└└─┘ ┴ ┴ "
} },
{ "selected", {
"╔═╗ ╦ ╦ ╦╔╦╗ ",
"║═╬╗║ ║ ║ ║ ",
"╚═╝╚╚═╝ ╩ ╩ "
} }
} }
};
} }

View file

@ -28,7 +28,8 @@ tab-size = 4
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array; 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 { namespace Global {
extern const string Version; extern const string Version;
@ -37,14 +38,16 @@ namespace Global {
extern int coreCount; extern int coreCount;
extern string banner; extern string banner;
extern atomic<bool> resized; extern atomic<bool> resized;
extern string overlay;
} }
namespace Runner { namespace Runner {
extern atomic<bool> active; extern atomic<bool> active;
extern atomic<bool> reading;
extern atomic<bool> stopping; 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(); void stop();
} }
@ -162,7 +165,7 @@ namespace Proc {
vector<proc_info>& collect(const bool return_last=false); vector<proc_info>& collect(const bool return_last=false);
//* Update current selection and view //* 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 //* Draw contents of proc box using <plist> as data source
string draw(const vector<proc_info>& plist, bool force_redraw=false); string draw(const vector<proc_info>& plist, bool force_redraw=false);

View file

@ -16,10 +16,8 @@ indent = tab
tab-size = 4 tab-size = 4
*/ */
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include <unordered_map>
#include <ranges> #include <ranges>
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
@ -28,17 +26,23 @@ tab-size = 4
#include <btop_config.hpp> #include <btop_config.hpp>
#include <btop_theme.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; std::clamp, std::max, std::min, std::ceil, std::to_string;
using namespace Tools; using namespace Tools;
namespace rng = std::ranges; namespace rng = std::ranges;
namespace fs = std::filesystem; namespace fs = std::filesystem;
string Term::fg, Term::bg;
string Fx::reset = reset_base;
namespace Theme { namespace Theme {
fs::path theme_dir; fs::path theme_dir;
fs::path user_theme_dir; fs::path user_theme_dir;
vector<string> themes; 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 = { const unordered_flat_map<string, string> Default_theme = {
{ "main_bg", "#00" }, { "main_bg", "#00" },
@ -131,18 +135,21 @@ namespace Theme {
}; };
namespace { namespace {
//* Convert 24-bit colors to 256 colors using 6x6x6 color cube //* Convert 24-bit colors to 256 colors
int truecolor_to_256(int r, int g, int b){ int truecolor_to_256(const int& r, const int& g, const int& b) {
if (round((double)r / 11) == round((double)g / 11) and round((double)g / 11) == round((double)b / 11)) { //? Use upper 232-255 greyscale values if the downscaled red, green and blue are the same value
return 232 + round((double)r / 11); if (int red = round((double)r / 11); red == round((double)g / 11) and round((double)g / 11) == round((double)b / 11)) {
} else { 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; 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){ string hex_to_color(string hexa, const bool& t_to_256, const string& depth) {
if (hexa.size() > 1){ if (hexa.size() > 1) {
hexa.erase(0, 1); hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) { for (auto& c : hexa) if (not isxdigit(c)) {
Logger::error("Invalid hex value: " + hexa); 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;"); 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); 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"; return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m";
} else { } else {
string h_str = to_string(h_int); string h_str = to_string(h_int);
return pre + h_str + ";" + h_str + ";" + h_str + "m"; return pre + h_str + ";" + h_str + ";" + h_str + "m";
} }
} }
else if (hexa.size() == 6){ else if (hexa.size() == 6) {
if (t_to_256){ if (t_to_256) {
return pre + to_string(truecolor_to_256( return pre + to_string(truecolor_to_256(
stoi(hexa.substr(0, 2), 0, 16), stoi(hexa.substr(0, 2), 0, 16),
stoi(hexa.substr(2, 2), 0, 16), stoi(hexa.substr(2, 2), 0, 16),
@ -178,7 +185,7 @@ namespace Theme {
return ""; 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;"); string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
r = std::clamp(r, 0, 255); r = std::clamp(r, 0, 255);
g = std::clamp(g, 0, 255); g = std::clamp(g, 0, 255);
@ -188,21 +195,17 @@ namespace Theme {
} }
namespace { 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 //* Convert hex color to a array of decimals
array<int, 3> hex_to_dec(string hexa){ array<int, 3> hex_to_dec(string hexa) {
if (hexa.size() > 1){ if (hexa.size() > 1) {
hexa.erase(0, 1); hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) return array<int, 3>{-1, -1, -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); int h_int = stoi(hexa, 0, 16);
return array<int, 3>{h_int, h_int, h_int}; return array<int, 3>{h_int, h_int, h_int};
} }
else if (hexa.size() == 6){ else if (hexa.size() == 6) {
return array<int, 3>{ return array<int, 3>{
stoi(hexa.substr(0, 2), 0, 16), stoi(hexa.substr(0, 2), 0, 16),
stoi(hexa.substr(2, 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 //* 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; vector<string> t_rgb;
string depth; string depth;
bool t_to_256 = Config::getB("lowcolor"); const bool& t_to_256 = Config::getB("lowcolor");
colors.clear(); rgbs.clear(); 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")) { if (name == "main_bg" and not Config::getB("theme_background")) {
colors[name] = "\x1b[49m"; colors[name] = "\x1b[49m";
rgbs[name] = {-1, -1, -1}; 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."); Logger::debug("Missing color value for \"" + name + "\". Using value from default.");
colors[name] = hex_to_color(color, t_to_256, depth); 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 //* Generate color gradients from two or three colors, 101 values indexed 0-100
void generateGradients(){ void generateGradients() {
gradients.clear(); gradients.clear();
array<string, 101> c_gradient; const bool& t_to_256 = Config::getB("lowcolor");
bool t_to_256 = Config::getB("lowcolor");
rgbs.insert({ //? Insert values for processes greyscale gradient and processes color gradient
{"proc_start", rgbs["main_fg"]}, {"proc_mid", {-1, -1, -1}}, {"proc_end", rgbs["inactive_fg"]}, rgbs.insert({ { "proc_start", rgbs["main_fg"] },
{"proc_color_start", rgbs["inactive_fg"]}, {"proc_color_mid", {-1, -1, -1}}, {"proc_color_end", rgbs["process_start"]} { "proc_mid", {-1, -1, -1} },
}); { "proc_end", rgbs["inactive_fg"] },
for (auto& [name, source_arr] : rgbs) { { "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; if (not name.ends_with("_start")) continue;
array<array<int, 3>, 101> dec_arr; const string color_name = rtrim(name, "_start");
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"]};
//? Only start iteration if gradient has a _end color value defined //? input_colors[start,mid,end][red,green,blue]
if (rgb_array[2][0] >= 0) { 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 //? output_colors[red,green,blue][0-100]
int cur_range = (rgb_array[1][0] >= 0) ? 50 : 100; array<array<int, 3>, 101> output_colors;
for (int rgb : iota(0, 3)){ 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 start = 0, offset = 0;
int end = (cur_range == 50) ? 1 : 2; int end = (current_range == 50) ? 1 : 2;
for (int i : iota(0, 101)) { for (const 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; 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 //? Switch source arrays from start->mid to mid->end at 50 passes if mid is defined
if (i == cur_range) { ++start; ++end; offset = 50;} if (i == current_range) { ++start; ++end; offset = 50; }
} }
} }
} }
if (dec_arr[0][0] != -1) { //? Generate color escape codes for the generated rgb decimals
int y = 0; array<string, 101> color_gradient;
for (auto& arr : dec_arr) c_gradient[y++] = dec_to_color(arr[0], arr[1], arr[2], t_to_256); 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 { else {
//? If only _start was defined fill array with _start color //? If only start was defined fill array with start color
c_gradient.fill(colors[name]); 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 //* Set colors and generate gradients for the TTY theme
void generateTTYColors(){ void generateTTYColors() {
rgbs.clear(); rgbs.clear();
gradients.clear(); gradients.clear();
colors = TTY_theme; colors = TTY_theme;
for (auto& c : colors) { for (const auto& c : colors) {
if (not c.first.ends_with("_start")) continue; 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"; string section = "_start";
int split = colors.at(base_name + "_mid").empty() ? 50 : 33; 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); gradients[base_name][i] = colors.at(base_name + section);
if (i == split) { if (i == split) {
section = (split == 33) ? "_mid" : "_end"; section = (split == 33) ? "_mid" : "_end";
@ -326,9 +361,9 @@ namespace Theme {
} }
//* Load a .theme file from disk //* Load a .theme file from disk
auto loadFile(string filename){ auto loadFile(const string& filename) {
unordered_flat_map<string, string> theme_out; unordered_flat_map<string, string> theme_out;
fs::path filepath = filename; const fs::path filepath = filename;
if (not fs::exists(filepath)) if (not fs::exists(filepath))
return Default_theme; return Default_theme;
@ -354,21 +389,20 @@ namespace Theme {
theme_out[name] = value; theme_out[name] = value;
} }
themefile.close();
return theme_out; return theme_out;
} }
return Default_theme; return Default_theme;
} }
} }
void updateThemes(){ void updateThemes() {
themes.clear(); themes.clear();
themes.push_back("Default"); themes.push_back("Default");
themes.push_back("TTY"); themes.push_back("TTY");
for (const auto& path : { theme_dir, user_theme_dir } ) { for (const auto& path : { theme_dir, user_theme_dir } ) {
if (path.empty()) continue; 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) { if (file.path().extension() == ".theme" and access(file.path().c_str(), R_OK) != -1) {
themes.push_back(file.path().c_str()); themes.push_back(file.path().c_str());
} }
@ -377,7 +411,7 @@ namespace Theme {
} }
void setTheme(){ void setTheme() {
string theme = Config::getS("color_theme"); string theme = Config::getS("color_theme");
if (theme == "TTY" or Config::getB("tty_mode")) if (theme == "TTY" or Config::getB("tty_mode"))
generateTTYColors(); generateTTYColors();
@ -390,26 +424,4 @@ namespace Theme {
Fx::reset = Fx::reset_base + Term::fg + Term::bg; Fx::reset = Fx::reset_base + Term::fg + Term::bg;
} }
//* Return escape code for color <name>
const string& c(string name){
return colors.at(name);
}
//* Return array of escape codes for color gradient <name>
const array<string, 101>& g(string name){
return gradients.at(name);
}
//* Return array of red, green and blue in decimal for color <name>
const std::array<int, 3>& dec(string name){
return rgbs.at(name);
}
unordered_flat_map<string, string>& test_colors(){
return colors;
}
unordered_flat_map<string, std::array<string, 101>>& test_gradients(){
return gradients;
}
} }

View file

@ -20,9 +20,10 @@ tab-size = 4
#include <string> #include <string>
#include <robin_hood.h> #include <robin_hood.h>
#include <array>
#include <filesystem> #include <filesystem>
using std::string; using std::string, robin_hood::unordered_flat_map, std::array;
namespace Theme { namespace Theme {
extern std::filesystem::path theme_dir; extern std::filesystem::path theme_dir;
@ -35,13 +36,13 @@ namespace Theme {
//* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale //* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale
//* t_to_256: [true|false] convert 24bit value to 256 color value //* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color //* 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 //* Generate escape sequence for 24-bit or 256 color and return as a string
//* Args r: [0-255], g: [0-255], b: [0-255] //* Args r: [0-255], g: [0-255], b: [0-255]
//* t_to_256: [true|false] convert 24bit value to 256 color value //* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color //* 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 //* Update list of paths for available themes
void updateThemes(); void updateThemes();
@ -49,17 +50,17 @@ namespace Theme {
//* Set current theme from current "color_theme" value in config //* Set current theme from current "color_theme" value in config
void setTheme(); 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> //* 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> //* 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> //* Return array of red, green and blue in decimal for color <name>
const std::array<int, 3>& dec(string name); inline const std::array<int, 3>& dec(string name) { return rgbs.at(name); }
//? Testing
robin_hood::unordered_flat_map<string, string>& test_colors();
robin_hood::unordered_flat_map<string, std::array<string, 101>>& test_gradients();
} }

View file

@ -32,74 +32,25 @@ tab-size = 4
#include <btop_shared.hpp> #include <btop_shared.hpp>
#include <btop_tools.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 fs = std::filesystem;
namespace rng = std::ranges; namespace rng = std::ranges;
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------ //? ------------------------------------------------- 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 //* Collection of escape codes and functions for terminal manipulation
namespace Term { namespace Term {
atomic<bool> initialized = false; atomic<bool> initialized = false;
atomic<int> width = 0; atomic<int> width = 0;
atomic<int> height = 0; atomic<int> height = 0;
string fg, bg, current_tty; string 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";
namespace { namespace {
struct termios initial_settings; struct termios initial_settings;
//* Toggle terminal input echo //* Toggle terminal input echo
bool echo(bool on=true){ bool echo(bool on=true) {
struct termios settings; struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false; if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ECHO; if (on) settings.c_lflag |= ECHO;
@ -108,7 +59,7 @@ namespace Term {
} }
//* Toggle need for return key when reading input //* Toggle need for return key when reading input
bool linebuffered(bool on=true){ bool linebuffered(bool on=true) {
struct termios settings; struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false; if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ICANON; if (on) settings.c_lflag |= ICANON;
@ -120,7 +71,7 @@ namespace Term {
} }
} }
bool refresh(){ bool refresh() {
struct winsize w; struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (width != w.ws_col or height != w.ws_row) { if (width != w.ws_col or height != w.ws_row) {
@ -131,8 +82,8 @@ namespace Term {
return false; return false;
} }
bool init(){ bool init() {
if (not initialized){ if (not initialized) {
initialized = (bool)isatty(STDIN_FILENO); initialized = (bool)isatty(STDIN_FILENO);
if (initialized) { if (initialized) {
tcgetattr(STDIN_FILENO, &initial_settings); tcgetattr(STDIN_FILENO, &initial_settings);
@ -148,7 +99,7 @@ namespace Term {
return initialized; return initialized;
} }
void restore(){ void restore() {
if (initialized) { if (initialized) {
echo(true); echo(true);
linebuffered(true); linebuffered(true);
@ -162,7 +113,7 @@ namespace Term {
namespace Tools { namespace Tools {
string uresize(string str, const size_t len){ string uresize(string str, const size_t len) {
if (len < 1) return ""; if (len < 1) return "";
for (size_t x = 0, i = 0; i < str.size(); i++) { for (size_t x = 0, i = 0; i < str.size(); i++) {
if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++; if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++;
@ -175,19 +126,19 @@ namespace Tools {
return str; 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; string_view str_v = str;
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size()); while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
return (string)str_v; 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; string_view str_v = str;
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size()); while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size());
return (string)str_v; 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; vector<string> out;
for (const auto& s : str | rng::views::split(delim) for (const auto& s : str | rng::views::split(delim)
| rng::views::transform([](auto &&rng) { | rng::views::transform([](auto &&rng) {
@ -198,7 +149,7 @@ namespace Tools {
return out; 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 (utf) {
if (limit and ulen(str) > x) str = uresize(str, x); if (limit and ulen(str) > x) str = uresize(str, x);
return str + string(max((int)(x - ulen(str)), 0), ' '); 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 (utf) {
if (limit and ulen(str) > x) str = uresize(str, x); if (limit and ulen(str) > x) str = uresize(str, x);
return string(max((int)(x - ulen(str)), 0), ' ') + str; return string(max((int)(x - ulen(str)), 0), ' ') + str;
@ -220,12 +171,11 @@ namespace Tools {
} }
} }
string trans(const string& str){ string trans(const string& str) {
size_t pos;
string_view oldstr = str; string_view oldstr = str;
string newstr; string newstr;
newstr.reserve(str.size()); 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)); newstr.append(oldstr.substr(0, pos));
size_t x = 0; size_t x = 0;
while (pos + x < oldstr.size() and oldstr.at(pos + x) == ' ') x++; while (pos + x < oldstr.size() and oldstr.at(pos + x) == ' ') x++;
@ -235,34 +185,29 @@ namespace Tools {
return (newstr.empty()) ? str : newstr + (string)oldstr; return (newstr.empty()) ? str : newstr + (string)oldstr;
} }
string sec_to_dhms(size_t sec){ string sec_to_dhms(size_t seconds) {
string out; size_t days = seconds / 86400; seconds %= 86400;
size_t d, h, m; size_t hours = seconds / 3600; seconds %= 3600;
d = sec / (3600 * 24); size_t minutes = seconds / 60; seconds %= 60;
sec %= 3600 * 24; string out = (days > 0 ? to_string(days) + "d " : "")
h = sec / 3600; + (hours < 10 ? "0" : "") + to_string(hours) + ":"
sec %= 3600; + (minutes < 10 ? "0" : "") + to_string(minutes) + ":"
m = sec / 60; + (seconds < 10 ? "0" : "") + to_string(seconds);
sec %= 60;
if (d>0) out = to_string(d) + "d ";
out += ((h<10) ? "0" : "") + to_string(h) + ":";
out += ((m<10) ? "0" : "") + to_string(m) + ":";
out += ((sec<10) ? "0" : "") + to_string(sec);
return out; 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; 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_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"}; 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; value *= 100 * mult;
while (value >= 102400){ while (value >= 102400) {
value >>= 10; value >>= 10;
if (value < 100){ if (value < 100) {
out = to_string(value); out = to_string(value);
break; break;
} }
@ -274,7 +219,7 @@ namespace Tools {
else if (out.size() == 3 and start > 0) out.insert(1, "."); else if (out.size() == 3 and start > 0) out.insert(1, ".");
else if (out.size() >= 2) out.resize(out.size() - 2); 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.find('.') != string::npos) out = to_string((int)round(stof(out)));
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;} if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
out.push_back(units[start][0]); out.push_back(units[start][0]);
@ -285,16 +230,15 @@ namespace Tools {
return out; return out;
} }
std::string operator*(string str, size_t n){ std::string operator*(const string& str, size_t n) {
if (n == 0) return ""; string new_str;
str.reserve(str.size() * n); new_str.reserve(str.size() * n);
for (string org_str = str; n > 1; n--) str.append(org_str); for (; n > 0; n--) new_str.append(str);
return str; return new_str;
} }
string strf_time(const string& strf){ string strf_time(const string& strf) {
auto now = std::chrono::system_clock::now(); auto in_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::tm bt {}; std::tm bt {};
std::stringstream ss; std::stringstream ss;
ss << std::put_time(localtime_r(&in_time_t, &bt), strf.c_str()); ss << std::put_time(localtime_r(&in_time_t, &bt), strf.c_str());
@ -308,25 +252,17 @@ namespace Logger {
namespace { namespace {
std::atomic<bool> busy (false); std::atomic<bool> busy (false);
bool first = true; 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; size_t loglevel;
fs::path logfile; fs::path logfile;
void set(const string level){ void set(const string& level) {
loglevel = v_index(log_levels, 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; if (loglevel < level or logfile.empty()) return;
busy.wait(true); busy.wait(true);
busy = true; busy = true;
@ -341,7 +277,6 @@ namespace Logger {
std::ofstream lwrite(logfile, std::ios::app); std::ofstream lwrite(logfile, std::ios::app);
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";} 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 << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
lwrite.close();
} }
else logfile.clear(); else logfile.clear();
busy = false; busy = false;

View file

@ -28,67 +28,65 @@ tab-size = 4
#include <thread> #include <thread>
using std::string, std::vector, std::atomic; using std::string, std::vector, std::atomic, std::to_string, std::regex;
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------ //? ------------------------------------------------- NAMESPACES ------------------------------------------------------
//* Collection of escape codes for text style and formatting //* Collection of escape codes for text style and formatting
namespace Fx { namespace Fx {
extern const string e; //* Escape sequence start const string e = "\x1b["; //* Escape sequence start
extern const string b; //* Bold on/off const string b = e + "1m"; //* Bold on/off
extern const string ub; //* Bold off const string ub = e + "22m"; //* Bold off
extern const string d; //* Dark on const string d = e + "2m"; //* Dark on
extern const string ud; //* Dark off const string ud = e + "22m"; //* Dark off
extern const string i; //* Italic on const string i = e + "3m"; //* Italic on
extern const string ui; //* Italic off const string ui = e + "23m"; //* Italic off
extern const string ul; //* Underline on const string ul = e + "4m"; //* Underline on
extern const string uul; //* Underline off const string uul = e + "24m"; //* Underline off
extern const string bl; //* Blink on const string bl = e + "5m"; //* Blink on
extern const string ubl; //* Blink off const string ubl = e + "25m"; //* Blink off
extern const string s; //* Strike/crossed-out on const string s = e + "9m"; //* Strike/crossed-out on
extern const string us; //* Strike/crossed-out on/off const string us = e + "29m"; //* Strike/crossed-out on/off
//* Reset foreground/background color and text effects //* 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 //* Reset text effects and restore theme foregrund and background color
extern string reset; extern string reset;
//* Regex for matching color, style and curse move escape sequences //* 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 //* 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 //* Return a string with all colors and text styling removed
inline string uncolor(const string& s){ inline string uncolor(const string& s) { return regex_replace(s, color_regex, ""); }
return regex_replace(s, color_regex, "");
};
} }
//* Collection of escape codes and functions for cursor manipulation //* Collection of escape codes and functions for cursor manipulation
namespace Mv { namespace Mv {
//* Move cursor to <line>, <column> //* 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 //* 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 //* 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 //* 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 //* 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 //* Save cursor position
extern const string save; const string save = Fx::e + "s";
//* Restore saved cursor postion //* Restore saved cursor postion
extern const string restore; const string restore = Fx::e + "u";
} }
//* Collection of escape codes and functions for terminal manipulation //* Collection of escape codes and functions for terminal manipulation
@ -98,44 +96,19 @@ namespace Term {
extern atomic<int> height; extern atomic<int> height;
extern string fg, bg, current_tty; extern string fg, bg, current_tty;
//* Hide terminal cursor const string hide_cursor = Fx::e + "?25l";
extern const string hide_cursor; const string show_cursor = Fx::e + "?25h";
const string alt_screen = Fx::e + "?1049h";
//* Show terminal cursor const string normal_screen = Fx::e + "?1049l";
extern const string show_cursor; const string clear = Fx::e + "2J" + Fx::e + "0;0f";
const string clear_end = Fx::e + "0J";
//* Switch to alternate screen const string clear_begin = Fx::e + "1J";
extern const string alt_screen; 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";
//* Switch to normal screen const string mouse_direct_on = Fx::e + "?1003h"; //? Enable reporting of mouse position at any movement
extern const string normal_screen; const string mouse_direct_off = Fx::e + "?1003l";
const string sync_start = Fx::e + "?2026h"; //? Start of terminal synchronized output
//* Clear screen and set cursor to position 0,0 const string sync_end = Fx::e + "?2026l"; //? End of terminal synchronized output
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;
//* Returns true if terminal has been resized and updates width and height //* Returns true if terminal has been resized and updates width and height
bool refresh(); bool refresh();
@ -153,7 +126,7 @@ namespace Tools {
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max(); constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
//* Return number of UTF8 characters in a string //* 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; } ); 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); string uresize(const string str, const size_t len);
//* Return <str> with only uppercase characters //* Return <str> with only uppercase characters
inline string str_to_upper(string str){ inline string str_to_upper(string str) {
std::ranges::for_each(str, [](auto& c){ c = ::toupper(c); } ); std::ranges::for_each(str, [](auto& c) { c = ::toupper(c); } );
return str; return str;
} }
//* Return <str> with only lowercase characters //* Return <str> with only lowercase characters
inline string str_to_lower(string str){ inline string str_to_lower(string str) {
std::ranges::for_each(str, [](char& c){ c = ::tolower(c); } ); std::ranges::for_each(str, [](char& c) { c = ::tolower(c); } );
return str; return str;
} }
@ -197,32 +170,32 @@ namespace Tools {
} }
//* Return current time since epoch in seconds //* 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 std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
} }
//* Return current time since epoch in milliseconds //* 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 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
} }
//* Return current time since epoch in microseconds //* 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(); 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 //* 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"); 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 //* 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"); return (str == "true" or str == "True");
} }
//* Check if a string is a valid integer value //* 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); 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 = " "); string rtrim(const string& str, const string& t_str = " ");
//* Left/right-trim <t_str> from <str> and return new string //* 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); return ltrim(rtrim(str, t_str), t_str);
}; };
@ -252,17 +225,17 @@ namespace Tools {
//* Replace whitespaces " " with escape code for move right //* Replace whitespaces " " with escape code for move right
string trans(const string& str); string trans(const string& str);
//* Convert seconds to format "Xd HH:MM:SS" and return string //* Convert seconds to format "<days>d <hours>:<minutes>:<seconds>" and return string
string sec_to_dhms(size_t sec); 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 //* bit=True or defaults to bytes
//* start=int to set 1024 multiplier starting unit //* start=int to set 1024 multiplier starting unit
//* short=True always returns 0 decimals and shortens unit to 1 character //* 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 //* Add std::string operator * : Repeat string <str> <n> number of times
std::string operator*(string str, size_t n); std::string operator*(const string& str, size_t n);
//* Return current time in <strf> format //* Return current time in <strf> format
string strf_time(const string& strf); string strf_time(const string& strf);
@ -271,14 +244,22 @@ namespace Tools {
//* Simple logging implementation //* Simple logging implementation
namespace Logger { namespace Logger {
extern const vector<string> log_levels; const vector<string> log_levels = {
"DISABLED",
"ERROR",
"WARNING",
"INFO",
"DEBUG",
};
extern std::filesystem::path logfile; 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); void log_write(const size_t level, const string& msg);
inline void error(const string msg){ log_write(1, msg); } inline void error(const string msg) { log_write(1, msg); }
inline void warning(const string msg){ log_write(2, msg); } inline void warning(const string msg) { log_write(2, msg); }
inline void info(const string msg){ log_write(3, msg); } inline void info(const string msg) { log_write(3, msg); }
inline void debug(const string msg){ log_write(4, msg); } inline void debug(const string msg) { log_write(4, msg); }
} }

27
src/menu.hpp Normal file
View file

@ -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;
}