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