From e33b4b7b0c9e51c4015796fca1d492455e3cadcd Mon Sep 17 00:00:00 2001 From: aristocratos Date: Thu, 29 Jul 2021 23:40:56 +0200 Subject: [PATCH] Added Cpu::collect(), Cpu::get_cpuName() and Cpu::getHz() --- Makefile | 10 +- README.md | 6 +- src/btop.cpp | 118 +++++++++-------- src/btop_config.cpp | 52 +++++--- src/btop_config.hpp | 5 +- src/btop_draw.cpp | 302 +++++++++++++++++++++++++++----------------- src/btop_input.cpp | 38 ++++-- src/btop_linux.cpp | 269 ++++++++++++++++++++++++++++++++++----- src/btop_shared.hpp | 28 +++- src/btop_theme.cpp | 1 + src/btop_tools.cpp | 72 +++++++---- src/btop_tools.hpp | 41 ++++-- 12 files changed, 664 insertions(+), 278 deletions(-) diff --git a/Makefile b/Makefile index 30c9d60..e4ae032 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,11 @@ DEPEXT := d OBJEXT := o #Flags, Libraries and Includes -CXXFLAGS := -std=c++20 -pthread -O3 -Wall -Wextra -Wno-stringop-overread -pedantic +REQFLAGS := -std=c++20 -pthread +WARNFLAGS := -Wall -Wextra -Wno-stringop-overread -pedantic +OPTFLAGS := -O3 +CXXFLAGS := $(OPTFLAGS) $(WARNFLAGS) +LINKFLAGS += -pthread INC := -I$(INCDIR) -I$(SRCDIR) SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT)) @@ -73,8 +77,8 @@ btop: $(OBJECTS) #Compile $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) - $(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< - @$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) + $(CXX) $(REQFLAGS) $(CXXFLAGS) $(INC) -c -o $@ $< + @$(CXX) $(REQFLAGS) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) @cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp @sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT) @sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT) diff --git a/README.md b/README.md index c66ace2..09762bb 100644 --- a/README.md +++ b/README.md @@ -126,12 +126,13 @@ Options menu. #### Manual compilation and installation -Needs GCC/G++ 11 or higher, (GCC 10 is missing some C++20 features). +Needs GCC 10 or higher, (GCC 11 preferably). >Install dependencies (Ubuntu 21.04 Hirsute) ``` bash sudo apt install git build-essential gcc-11 g++-11 +#gcc-10 g++-10 if on older ``` >Clone and compile @@ -151,9 +152,10 @@ sudo make install # only use "sudo" when installing to a NON user owned directory ``` ->to make btop always run as root (to enable signal sending to any process and for /proc read permissions on some systems) +>to make btop always run as root (no need for `sudo` to enable signal sending to any process and to prevent /proc read permissions problems on some systems) ``` bash +# run after make install and use same PREFIX if any was used at install make su-setuid ``` diff --git a/src/btop.cpp b/src/btop.cpp index 53326cc..7925f77 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -71,6 +71,7 @@ namespace Global { string banner; size_t banner_width = 0; string overlay; + string clock; fs::path self_path; @@ -136,7 +137,7 @@ void argumentParser(const 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) { if (auto refreshed = Term::refresh() or force) { if (force and refreshed) force = false; Global::resized = true; @@ -160,13 +161,13 @@ void clean_quit(const int sig) { Runner::stop(); if (Term::initialized) { Term::restore(); - if (not Global::debuginit) cout << Term::mouse_off << 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; + std::cerr << "ERROR: " << Global::exit_error_msg << endl; } Config::write(); + Input::clear(); Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time)); if (sig != -1) exit(sig); } @@ -174,17 +175,13 @@ void clean_quit(const int sig) { //* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP void _sleep() { Runner::stop(); - if (Term::initialized) { - Term::restore(); - if (not Global::debuginit) cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush; - } + Term::restore(); std::raise(SIGSTOP); } //* Handler for SIGCONT; re-initialize terminal and force a resize event void _resume() { Term::init(); - if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << Term::mouse_on << flush; term_resize(true); } @@ -255,9 +252,11 @@ namespace Runner { string output; string overlay; + string clock; //* Secondary thread; run collect, draw and print out void _runner(const vector boxes, const bool no_update, const bool force_redraw) { + atomic_lock lck(active); auto timestamp = time_micros(); output.clear(); @@ -291,48 +290,57 @@ namespace Runner { } if (stopping) { - active = false; - active.notify_all(); return; } - if (output.empty()) { - output += "No boxes shown!"; + if (boxes.empty()) { + output = Term::clear + Mv::to(10, 10) + "No boxes shown!"; } //? If overlay isn't empty, print output without color and effects and then print overlay on top - cout << Term::sync_start << (overlay.empty() ? output : Theme::c("inactive_fg") + Fx::uncolor(output) + overlay) << Term::sync_end << flush; + cout << Term::sync_start << (overlay.empty() ? output + clock : Theme::c("inactive_fg") + Fx::uncolor(output + clock) + 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; + cout << Fx::reset << Mv::to(1, 20) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush; - Input::interrupt = true; - - active = false; - active.notify_all(); + // Input::interrupt = true; } - //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all" for all boxes + //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all": all boxes void run(const string& box, const bool no_update, const bool force_redraw) { - active.wait(true); - Config::unlock(); + atomic_wait(active); if (stopping) return; - active = true; - Config::lock(); - 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); - run_thread.detach(); + if (box == "overlay") { + cout << Term::sync_start << Global::overlay << Term::sync_end; + } + else if (box == "clock") { + if (not Global::clock.empty()) + cout << Term::sync_start << Global::clock << Term::sync_end; + } + else { + Config::unlock(); + Config::lock(); + + if (not Global::overlay.empty()) + overlay = Global::overlay; + else if (not overlay.empty()) + overlay.clear(); + + if (not Global::clock.empty()) + clock = Global::clock; + else if (not clock.empty()) + clock.clear(); + + std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw); + run_thread.detach(); + } } //* Stops any secondary thread running and unlocks config void stop() { stopping = true; - active.wait(true); - Config::unlock(); + atomic_wait(active); stopping = false; } @@ -357,7 +365,6 @@ int main(int argc, char **argv) { std::signal(SIGCONT, _signal_handler); std::signal(SIGWINCH, _signal_handler); - //? 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) { @@ -367,8 +374,8 @@ 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 $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl; + cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled.\n" + << "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"; @@ -398,9 +405,10 @@ int main(int argc, char **argv) { } //? Config init - { vector load_errors; - Config::load(Config::conf_file, load_errors); + { vector load_warnings; + Config::load(Config::conf_file, load_warnings); + if (Config::current_boxes.empty()) Config::check_boxes(Config::getS("shown_boxes")); Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor"))); if (Global::debug) Logger::set("DEBUG"); @@ -408,7 +416,7 @@ int main(int argc, char **argv) { Logger::info("Logger set to " + Config::getS("log_level")); - for (const auto& err_str : load_errors) Logger::warning(err_str); + for (const auto& err_str : load_warnings) Logger::warning(err_str); } //? Try to find and set a UTF-8 locale @@ -442,9 +450,7 @@ int main(int argc, char **argv) { //? Initialize terminal and set options if (not Term::init()) { - string err_msg = "No tty detected!\nbtop++ needs an interactive shell to run."; - Logger::error(err_msg); - cout << "ERROR: " << err_msg << endl; + Global::exit_error_msg = "No tty detected!\nbtop++ needs an interactive shell to run."; clean_quit(1); } @@ -477,15 +483,9 @@ int main(int argc, char **argv) { //? Calculate sizes of all boxes Draw::calcSizes(); - 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 << Term::mouse_on; + //? Print out box outlines cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush; - //? Start first collection and drawing run - Runner::run(); - //* ------------------------------------------------ TESTING ------------------------------------------------------ @@ -563,10 +563,10 @@ int main(int argc, char **argv) { for (long long i = 0; i <= 100; i++) mydata.push_back(i); for (long long i = 100; i >= 0; i--) mydata.push_back(i); // mydata.push_back(50); - auto mydata2 = mydata; - mydata2.push_back(10); - for (long long i = 0; i <= 100; i++) mydata2.push_back(i); - for (long long i = 100; i >= 0; i--) mydata2.push_back(i); + deque mydata2 = {2, 3}; + // mydata2.push_back(10); + // for (long long i = 0; i <= 100; i++) mydata2.push_back(i); + // for (long long i = 100; i >= 0; i--) mydata2.push_back(i); @@ -574,9 +574,9 @@ int main(int argc, char **argv) { cout << Draw::createBox(5, 23, Term::width - 10, 12, Theme::c("proc_box"), false, "block", "", 2); cout << Draw::createBox(5, 36, Term::width - 10, 12, Theme::c("proc_box"), false, "tty", "", 3) << flush; auto kts = time_micros(); - Draw::Graph kgraph {Term::width - 13, 10, "cpu", mydata, "block", false, false}; - Draw::Graph kgraph2 {Term::width - 13, 10, "upload", {0, 1}, "block", false, false}; - Draw::Graph kgraph3 {Term::width - 13, 10, "download", {}, "block", false, false}; + Draw::Graph kgraph {Term::width - 13, 10, "cpu", mydata, "braille", false, false}; + Draw::Graph kgraph2 {Term::width - 13, 10, "upload", {0}, "braille", false, false}; + Draw::Graph kgraph3 {Term::width - 13, 10, "download", mydata2, "braille", false, false}; cout << Mv::restore << kgraph(mydata, true) @@ -584,12 +584,18 @@ 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; + // Input::wait(); + for (;;) { - mydata.back() = std::rand() % 101; + mydata.push_back(std::rand() % 101); + mydata2.push_back(mydata.back()); + if (mydata.size() > 1000) mydata.pop_front(); + if (mydata2.size() > 1000) mydata2.pop_front(); + kgraph3 = {Term::width - 13, 10, "cpu", mydata2, "braille", false, false}; kts = time_micros(); cout << Term::sync_start << Mv::restore << kgraph(mydata) << Mv::restore << Mv::d(13) << kgraph2(mydata) - << Mv::restore << Mv::d(26) << kgraph3(mydata) + << Mv::restore << Mv::d(26) << kgraph3(mydata2) << Term::sync_end << endl; cout << Mv::d(1) << "Time: " << time_micros() - kts << " μs. " << flush; if (Input::poll()) { diff --git a/src/btop_config.cpp b/src/btop_config.cpp index e4218e8..2b3e708 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -85,6 +85,8 @@ namespace Config { {"proc_info_smaps", "#* Use /proc/[pid]/smaps for memory information in the process info box (slow but more accurate)"}, + {"proc_left", "#* Show proc box on left side of screen instead of right."}, + {"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."}, @@ -95,6 +97,8 @@ namespace Config { {"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."}, + {"cpu_bottom", "#* Show cpu box at bottom of screen instead of top."}, + {"show_uptime", "#* Shows the system uptime in the CPU box."}, {"check_temp", "#* Show cpu temperature."}, @@ -118,6 +122,8 @@ namespace Config { {"mem_graphs", "#* Show graphs instead of meters for memory values."}, + {"mem_below_net", "#* Show mem box below net box instead of above."}, + {"show_swap", "#* If swap memory should be shown in memory box."}, {"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."}, @@ -191,14 +197,17 @@ namespace Config { {"proc_per_core", false}, {"proc_mem_bytes", true}, {"proc_info_smaps", false}, + {"proc_left", false}, {"cpu_invert_lower", true}, {"cpu_single_graph", false}, + {"cpu_bottom", false}, {"show_uptime", true}, {"check_temp", true}, {"show_coretemp", true}, {"show_cpu_freq", true}, {"background_update", true}, {"mem_graphs", true}, + {"mem_below_net", false}, {"show_swap", true}, {"swap_disk", true}, {"show_disks", true}, @@ -233,7 +242,7 @@ namespace Config { vector valid_boxes = { "cpu", "mem", "net", "proc" }; bool _locked(const string& name) { - writelock.wait(true); + atomic_wait(writelock); 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(); @@ -245,7 +254,7 @@ namespace Config { vector current_boxes; void lock() { - writelock.wait(true); + atomic_wait(writelock); locked = true; } @@ -259,9 +268,7 @@ namespace Config { void unlock() { if (not locked) return; - writelock.wait(true); - writelock = true; - + atomic_lock lck(writelock); try { if (Proc::shown) { ints.at("selected_pid") = Proc::selected_pid; @@ -290,8 +297,6 @@ namespace Config { } locked = false; - writelock = false; - writelock.notify_all(); } bool check_boxes(const string& boxes) { @@ -299,11 +304,25 @@ namespace Config { for (auto& box : new_boxes) { if (not v_contains(valid_boxes, box)) return false; } - current_boxes.swap(new_boxes); + current_boxes = move(new_boxes); return true; } - void load(const fs::path& conf_file, vector& load_errors) { + void toggle_box(const string& box) { + if (not v_contains(current_boxes, box)) + current_boxes.push_back(box); + else + current_boxes.erase(rng::find(current_boxes, box)); + + string new_boxes; + if (not current_boxes.empty()) { + for (const auto& b : current_boxes) new_boxes += b + ' '; + new_boxes.pop_back(); + } + Config::set("shown_boxes", new_boxes); + } + + void load(const fs::path& conf_file, vector& load_warnings) { if (conf_file.empty()) return; else if (not fs::exists(conf_file)) { @@ -337,14 +356,14 @@ namespace Config { if (bools.contains(name)) { cread >> value; if (not isbool(value)) - load_errors.push_back("Got an invalid bool value for config name: " + name); + load_warnings.push_back("Got an invalid bool value for config name: " + name); else bools.at(name) = stobool(value); } else if (ints.contains(name)) { cread >> value; if (not isint(value)) - load_errors.push_back("Got an invalid integer value for config name: " + name); + load_warnings.push_back("Got an invalid integer value for config name: " + name); else ints.at(name) = stoi(value); } @@ -356,19 +375,19 @@ namespace Config { else cread >> value; if (name == "log_level" and not v_contains(Logger::log_levels, value)) - load_errors.push_back("Invalid log_level: " + value); + load_warnings.push_back("Invalid log_level: " + value); else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value)) - load_errors.push_back("Invalid graph symbol identifier: " + value); + load_warnings.push_back("Invalid graph symbol identifier: " + value); else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) - load_errors.push_back("Invalid box name in shown_boxes. Using default."); + load_warnings.push_back("Invalid box name(s) in shown_boxes: " + value); else strings.at(name) = value; } cread.ignore(SSmax, '\n'); } - cread.close(); - if (not load_errors.empty()) write_new = true; + + if (not load_warnings.empty()) write_new = true; } } @@ -388,7 +407,6 @@ namespace Config { else if (bools.contains(name)) cwrite << (bools.at(name) ? "True" : "False"); } - cwrite.close(); } } } \ No newline at end of file diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 46f56ab..9c3a742 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -45,6 +45,9 @@ namespace Config { //* Check if string only contains space seperated valid names for boxes bool check_boxes(const string& boxes); + //* Toggle box and update config string shown_boxes + void toggle_box(const string& box); + bool _locked(const string& name); //* Return bool for config key @@ -84,7 +87,7 @@ namespace Config { void unlock(); //* Load the config file from disk - void load(const std::filesystem::path& conf_file, vector& load_errors); + void load(const std::filesystem::path& conf_file, vector& load_warnings); //* Write the config file to disk void write(); diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index addd162..3854781 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -36,16 +36,26 @@ using namespace Tools; namespace rng = std::ranges; namespace Symbols { - const string h_line = "─"; - const string v_line = "│"; - const string left_up = "┌"; - const string right_up = "┐"; - const string left_down = "└"; - const string right_down = "┘"; - const string title_left = "┤"; - const string title_right = "├"; - const string div_up = "┬"; - const string div_down = "┴"; + const string h_line = "─"; + const string v_line = "│"; + const string dotted_v_line = "╎"; + const string left_up = "┌"; + const string right_up = "┐"; + const string left_down = "└"; + const string right_down = "┘"; + const string round_left_up = "╭"; + const string round_right_up = "╮"; + const string round_left_down = "╰"; + const string round_right_down = "╯"; + const string title_left_down = "┘"; + const string title_right_down = "└"; + const string title_left = "┐"; + const string title_right = "┌"; + const string div_right = "┤"; + const string div_left = "├"; + const string div_up = "┬"; + const string div_down = "┴"; + const string up = "↑"; const string down = "↓"; @@ -190,7 +200,12 @@ namespace Draw { string createBox(const int x, const int y, const int width, const int height, string line_color, const bool fill, const string title, const string title2, const int num) { string out; if (line_color.empty()) line_color = Theme::c("div_line"); - const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (Config::getB("tty_mode") ? std::to_string(num) : Symbols::superscript[num]); + const auto& tty_mode = Config::getB("tty_mode"); + const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript[num]); + const auto& right_up = (tty_mode ? Symbols::right_up : Symbols::round_right_up); + const auto& left_up = (tty_mode ? Symbols::left_up : Symbols::round_left_up); + const auto& right_down = (tty_mode ? Symbols::right_down : Symbols::round_right_down); + const auto& left_down = (tty_mode ? Symbols::left_down : Symbols::round_left_down); out = Fx::reset + line_color; @@ -207,10 +222,10 @@ namespace Draw { } //? Draw corners - out += Mv::to(y, x) + Symbols::left_up - + Mv::to(y, x + width - 1) + Symbols::right_up - + Mv::to(y + height - 1, x) + Symbols::left_down - + Mv::to(y + height - 1, x + width - 1) + Symbols::right_down; + out += Mv::to(y, x) + left_up + + Mv::to(y, x + width - 1) + right_up + + Mv::to(y + height - 1, x) +left_down + + Mv::to(y + height - 1, x + width - 1) + right_down; //? Draw titles if defined if (not title.empty()) { @@ -218,8 +233,8 @@ namespace Draw { + Fx::ub + line_color + Symbols::title_right; } if (not title2.empty()) { - out += Mv::to(y + height - 1, x + 2) + Symbols::title_left + Theme::c("title") + title2 - + Fx::ub + line_color + Symbols::title_right; + out += Mv::to(y + height - 1, x + 2) + Symbols::title_left_down + Fx::b + numbering + Theme::c("title") + title2 + + Fx::ub + line_color + Symbols::title_right_down; } return out + Fx::reset + Mv::to(y + 1, x + 1); @@ -251,7 +266,6 @@ namespace Draw { //* Graph class ------------------------------------------------------------------------------------------------------------> void Graph::_create(const deque& data, int data_offset) { const bool mult = (data.size() - data_offset > 1); - if (mult and (data.size() - data_offset) % 2 != 0) data_offset--; const auto& graph_symbol = Symbols::graph_symbols.at(symbol + '_' + (invert ? "down" : "up")); array result; const float mod = (height == 1) ? 0.3 : 0.1; @@ -262,10 +276,10 @@ namespace Draw { } //? Horizontal iteration over values in - for (int i : iota(data_offset, (int)data.size())) { + for (const int& i : iota(data_offset, (int)data.size())) { if (tty_mode and mult and i % 2 != 0) continue; - else if (not tty_mode) current = not current; - if (i == -1) { + else if (not tty_mode and mult) current = not current; + if (i < 0) { data_value = 0; last = 0; } @@ -292,7 +306,7 @@ namespace Draw { //? Generate graph symbol from 5x5 2D vector graphs[current][horizon] += (height == 1 and result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])]; } - if (mult and i > data_offset) last = data_value; + if (mult and i >= 0) last = data_value; } last = data_value; out.clear(); @@ -312,21 +326,22 @@ namespace Draw { if (not color_gradient.empty()) out += Fx::reset; } - Graph::Graph() {}; + Graph::Graph() {} Graph::Graph(int width, int height, const string& color_gradient, const deque& data, const string& symbol, bool invert, bool no_zero, long long max_value, long long offset) : width(width), height(height), color_gradient(color_gradient), invert(invert), no_zero(no_zero), offset(offset) { - if (Config::getB("tty_mode") or symbol == "tty") { - tty_mode = true; - this->symbol = "tty"; - } + if (Config::getB("tty_mode") or symbol == "tty") { tty_mode = true; this->symbol = "tty"; } else if (symbol != "default") this->symbol = symbol; else this->symbol = Config::getS("graph_symbol"); + if (max_value == 0 and offset > 0) max_value = 100; this->max_value = max_value; - int value_width = ceil((float)data.size() / 2); - int data_offset = 0; - if (value_width > width) data_offset = data.size() - width * 2; + const int value_width = ceil((double)data.size() / 2); + int data_offset = (value_width > width) ? data.size() - width * 2 : 0; + + if ((data.size() - data_offset) % 2 != 0) { + data_offset--; + } //? Populate the two switching graph vectors and fill empty space if data size < width for (const int& i : iota(0, height * 2)) { @@ -334,19 +349,17 @@ namespace Draw { graphs[(i % 2 != 0)].push_back((value_width < width) ? ((height == 1) ? Mv::r(1) : " "s) * (width - value_width) : ""); } if (data.size() == 0) return; - if (data.size() == 1) data_offset--; this->_create(data, data_offset); - // this->_create((data.size() == 1 ? deque{0, data[0]} : data), data_offset); } string& Graph::operator()(const deque& data, const bool data_same) { if (data_same) return out; //? Make room for new characters on graph - bool select_graph = (tty_mode) ? current : not current; + if (not tty_mode) current = not current; for (const int& i : iota(0, height)) { - if (graphs[select_graph][i][1] == '[') graphs[select_graph][i].erase(0, 4); - else graphs[select_graph][i].erase(0, 3); + if (graphs[current][i][1] == '[') graphs[current][i].erase(0, 4); + else graphs[current][i].erase(0, 3); } this->_create(data, (int)data.size() - 1); return out; @@ -369,13 +382,35 @@ namespace Cpu { string box; string draw(const cpu_info& cpu, const bool force_redraw, const bool data_same) { - (void)cpu; (void)data_same; string out; + out.reserve(width * height); if (redraw or force_redraw) { - redraw = false; + auto& cpu_bottom = Config::getB("cpu_bottom"); + const int button_y = cpu_bottom ? y + height - 1 : y; out += box; + const string title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); + const string title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); + out += Mv::to(button_y, x + 10) + title_left + Theme::c("hi_fg") + Fx::b + 'm' + Theme::c("title") + "enu" + Fx::ub + title_right; + } + + if (Config::getB("show_cpu_freq") and not cpuHz.empty()) + out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * max(0ul, 7 - cpuHz.size()) + + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; + + + //! VALS + + out += Mv::to(y + 2, x + 5) + Theme::c("title") + Fx::b + ljust("Cpu total=" + to_string(cpu.cpu_percent.at("total").back()), 20); + out += Mv::to(y + 4, x + 5); + for (int i = 0; const auto& cper : cpu.core_percent) { out += ljust("Core" + to_string(i++) + "=" + to_string(cper.back()), 10); } + + out += Mv::to(y + 6, x + 5); + for (const auto& [name, value] : cpu.cpu_percent) { out += ljust(name + "=" + to_string(value.back()), 12); } + + + redraw = false; return out; } @@ -392,7 +427,7 @@ namespace Mem { string draw(const mem_info& mem, const bool force_redraw, const bool data_same) { (void)mem; (void)data_same; - string out; + string out = Mv::to(0, 0); if (redraw or force_redraw) { redraw = false; out += box; @@ -414,7 +449,7 @@ namespace Net { string draw(const net_info& net, const bool force_redraw, const bool data_same) { (void)net; (void)data_same; - string out; + string out = Mv::to(0, 0); if (redraw or force_redraw) { redraw = false; out += box; @@ -504,9 +539,10 @@ namespace Proc { string draw(const vector& plist, const bool force_redraw, const bool data_same) { auto& proc_tree = Config::getB("proc_tree"); const bool show_detailed = (Config::getB("show_detailed") and Proc::detailed.last_pid == (size_t)Config::getI("detailed_pid")); - const bool proc_gradient = (Config::getB("proc_gradient") and not Config::getB("tty_mode") and not Config::getB("lowcolor")); + const bool proc_gradient = (Config::getB("proc_gradient") and not Config::getB("lowcolor") and Theme::gradients.contains("proc")); auto& proc_colors = Config::getB("proc_colors"); - const auto& graph_symbol = (Config::getB("tty_mode") ? "tty" : Config::getS("graph_symbol_proc")); + const auto& tty_mode = Config::getB("tty_mode"); + const auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc")); const auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? "braille_up" : graph_symbol + "_up"))[1]; const auto& mem_bytes = Config::getB("proc_mem_bytes"); start = Config::getI("proc_start"); @@ -516,14 +552,17 @@ namespace Proc { const int height = show_detailed ? Proc::height - 8 : Proc::height; const int select_max = show_detailed ? Proc::select_max - 8 : Proc::select_max; int numpids = Proc::numpids; + if (force_redraw) redraw = true; string out; out.reserve(width * height); //* Redraw elements not needed to be updated every cycle - if (redraw or force_redraw) { + if (redraw) { out = box; const string title_left = Theme::c("proc_box") + Symbols::title_left; const string title_right = Theme::c("proc_box") + Symbols::title_right; + const string title_left_down = Theme::c("proc_box") + Symbols::title_left_down; + const string title_right_down = Theme::c("proc_box") + Symbols::title_right_down; for (const auto& key : {"T", "K", "S", "enter"}) if (Input::mouse_mappings.contains(key)) Input::mouse_mappings.erase(key); @@ -551,10 +590,11 @@ namespace Proc { //? Draw structure of details box const string pid_str = to_string(detailed.entry.pid); - out += Mv::to(y, x) + title_right + Symbols::h_line + title_left + Theme::c("hi_fg") + Fx::b + Symbols::superscript[4] - + Theme::c("title") + "proc" + Fx::ub + title_right + Symbols::h_line * (width - 10) + title_left - + Mv::to(d_y, dgraph_x + 2) + title_left + Fx::b + Theme::c("title") + pid_str + Fx::ub + title_left - + title_right + Fx::b + Theme::c("title") + uresize(detailed.entry.name, dgraph_width - pid_str.size() - 7) + Fx::ub + title_right; + out += Mv::to(y, x) + Theme::c("proc_box") + Symbols::div_left + Symbols::h_line + title_left + Theme::c("hi_fg") + Fx::b + + (tty_mode ? "4" : Symbols::superscript[4]) + Theme::c("title") + "proc" + + Fx::ub + title_right + Symbols::h_line * (width - 10) + Symbols::div_right + + Mv::to(d_y, dgraph_x + 2) + title_left + Fx::b + Theme::c("title") + pid_str + Fx::ub + title_right + + title_left + Fx::b + Theme::c("title") + uresize(detailed.entry.name, dgraph_width - pid_str.size() - 7, true) + Fx::ub + title_right; out += Mv::to(d_y, d_x - 1) + Theme::c("proc_box") + Symbols::div_up + Mv::to(y, d_x - 1) + Symbols::div_down + Theme::c("div_line"); for (const int& i : iota(1, 8)) out += Mv::to(d_y + i, d_x - 1) + Symbols::v_line; @@ -565,20 +605,44 @@ namespace Proc { int mouse_x = d_x + 2; out += Mv::to(d_y, d_x + 1); if (width > 55) { - out += title_left + hi_color + Fx::b + 'T' + t_color + "erminate" + Fx::ub + title_right; - if (alive and selected == 0) Input::mouse_mappings["T"] = {d_y, mouse_x, 1, 9}; + out += title_left + hi_color + Fx::b + 't' + t_color + "erminate" + Fx::ub + title_right; + if (alive and selected == 0) Input::mouse_mappings["t"] = {d_y, mouse_x, 1, 9}; mouse_x += 11; } - out += title_left + hi_color + Fx::b + 'K' + t_color + "ill" + Fx::ub + title_right - + title_left + hi_color + Fx::b + 'S' + t_color + "ignals" + Fx::ub + title_right + out += title_left + hi_color + Fx::b + 'k' + t_color + "ill" + Fx::ub + title_right + + title_left + hi_color + Fx::b + 's' + t_color + "ignals" + Fx::ub + title_right + Mv::to(d_y, d_x + d_width - 10) + title_left + t_color + Fx::b + hide + Symbols::enter + Fx::ub + title_right; if (alive and selected == 0) { - Input::mouse_mappings["K"] = {d_y, mouse_x, 1, 4}; + Input::mouse_mappings["k"] = {d_y, mouse_x, 1, 4}; mouse_x += 6; - Input::mouse_mappings["S"] = {d_y, mouse_x, 1, 7}; + Input::mouse_mappings["s"] = {d_y, mouse_x, 1, 7}; } if (selected == 0) Input::mouse_mappings["enter"] = {d_y, d_x + d_width - 9, 1, 6}; + //? Labels + const int item_fit = floor((double)(d_width - 2) / 10); + const int item_width = floor((double)(d_width - 2) / min(item_fit, 8)); + out += Mv::to(d_y + 1, d_x + 1) + Fx::b + Theme::c("title") + + cjust("Status:", item_width) + + cjust("Elapsed:", item_width); + if (item_fit >= 3) out += cjust("IO/R:", item_width); + if (item_fit >= 4) out += cjust("IO/W:", item_width); + if (item_fit >= 5) out += cjust("Parent:", item_width); + if (item_fit >= 6) out += cjust("User:", item_width); + if (item_fit >= 7) out += cjust("Nice:", item_width); + if (item_fit >= 8) out += cjust("Threads:", item_width); + + //? Command line + for (int i = 0; const auto& l : {'C', 'M', 'D'}) + out += Mv::to(d_y + 5 + i++, d_x + 1) + l; + + out += Theme::c("main_fg") + Fx::ub; + const int cmd_size = ulen(detailed.entry.cmd, true); + for (int num_lines = min(3, (int)ceil((double)cmd_size / (d_width - 5))), i = 0; i < num_lines; i++) { + out += Mv::to(d_y + 5 + (num_lines == 1 ? 1 : i), d_x + 3) + + cjust(luresize(detailed.entry.cmd, cmd_size - (d_width - 5) * i, true), d_width - 5, true, true); + } + } //? Filter @@ -627,22 +691,22 @@ namespace Proc { const string t_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("title")); const string hi_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("hi_fg")); int mouse_x = x + 14; - out += Mv::to(y + height - 1, x + 1) + title_left + Fx::b + hi_color + Symbols::up + Theme::c("title") + " select " + down_button + Fx::ub + title_right - + title_left + Fx::b + t_color + "info " + hi_color + Symbols::enter + Fx::ub + title_right; + out += Mv::to(y + height - 1, x + 1) + title_left_down + Fx::b + hi_color + Symbols::up + Theme::c("title") + " select " + down_button + Fx::ub + title_right_down + + title_left_down + Fx::b + t_color + "info " + hi_color + Symbols::enter + Fx::ub + title_right_down; if (selected > 0) Input::mouse_mappings["enter"] = {y + height - 1, mouse_x, 1, 6}; mouse_x += 8; if (width > 60) { - out += title_left + Fx::b + hi_color + 'T' + t_color + "erminate" + Fx::ub + title_right; - if (selected > 0) Input::mouse_mappings["T"] = {y + height - 1, mouse_x, 1, 9}; + out += title_left_down + Fx::b + hi_color + 't' + t_color + "erminate" + Fx::ub + title_right_down; + if (selected > 0) Input::mouse_mappings["t"] = {y + height - 1, mouse_x, 1, 9}; mouse_x += 11; } if (width > 55) { - out += title_left + Fx::b + hi_color + 'K' + t_color + "ill" + Fx::ub + title_right; - if (selected > 0) Input::mouse_mappings["K"] = {y + height - 1, mouse_x, 1, 4}; + out += title_left_down + Fx::b + hi_color + 'k' + t_color + "ill" + Fx::ub + title_right_down; + if (selected > 0) Input::mouse_mappings["k"] = {y + height - 1, mouse_x, 1, 4}; mouse_x += 6; } - out += title_left + Fx::b + hi_color + 'S' + t_color + "ignals" + Fx::ub + title_right; - if (selected > 0) Input::mouse_mappings["S"] = {y + height - 1, mouse_x, 1, 7}; + out += title_left_down + Fx::b + hi_color + 's' + t_color + "ignals" + Fx::ub + title_right_down; + if (selected > 0) Input::mouse_mappings["s"] = {y + height - 1, mouse_x, 1, 7}; //? Labels for fields in list if (not proc_tree) @@ -671,45 +735,34 @@ namespace Proc { string cpu_str = (alive ? to_string(detailed.entry.cpu_p) : ""); if (alive) { cpu_str.resize((detailed.entry.cpu_p < 10 or detailed.entry.cpu_p >= 100 ? 3 : 4)); - cpu_str += '%' + Mv::r(1) + (dgraph_width < 20 ? "C" : "Core") + to_string(detailed.entry.cpu_n); + cpu_str += '%' + Mv::r(1) + (dgraph_width < 20 ? "C" : "Core") + to_string(detailed.entry.cpu_n + 1); } - out += Mv::to(d_y + 1, dgraph_x + 1) + detailed_cpu_graph(detailed.cpu_percent, (data_same or not alive)) + out += Mv::to(d_y + 1, dgraph_x + 1) + Fx::ub + detailed_cpu_graph(detailed.cpu_percent, (redraw or data_same or not alive)) + Mv::to(d_y + 1, dgraph_x + 1) + Theme::c("title") + Fx::b + cpu_str; for (int i = 0; const auto& l : {'C', 'P', 'U'}) out += Mv::to(d_y + 3 + i++, dgraph_x + 1) + l; //? Info part of box - const string mv_down = Mv::l(item_width) + Mv::d(1) + Fx::ub + Theme::c("main_fg"); - const string mv_up = Mv::u(1) + Fx::b + Theme::c("title"); - const string stat_color = (not alive ? Theme::c("inactive_fg") : (detailed.status == "Running" ? Theme::c("proc_misc") : "")); - out += Mv::to(d_y + 1, d_x + 1) - + cjust("Status:", item_width) + mv_down + stat_color + cjust(detailed.status, item_width) + mv_up - + cjust("Elapsed:", item_width) + mv_down + cjust(detailed.elapsed, item_width); - if (item_fit >= 3) out += mv_up + cjust("IO/R:", item_width) + mv_down + cjust(detailed.io_read, item_width); - if (item_fit >= 4) out += mv_up + cjust("IO/W:", item_width) + mv_down + cjust(detailed.io_write, item_width); - if (item_fit >= 5) out += mv_up + cjust("Parent:", item_width) + mv_down + cjust(detailed.parent, item_width); - if (item_fit >= 6) out += mv_up + cjust("User:", item_width) + mv_down + cjust(detailed.entry.user, item_width); - if (item_fit >= 7) out += mv_up + cjust("Nice:", item_width) + mv_down + cjust(to_string(detailed.entry.p_nice), item_width); - if (item_fit >= 8) out += mv_up + cjust("Threads:", item_width) + mv_down + cjust(to_string(detailed.entry.threads), item_width); + const string stat_color = (not alive ? Theme::c("inactive_fg") : (detailed.status == "Running" ? Theme::c("proc_misc") : Theme::c("main_fg"))); + out += Mv::to(d_y + 2, d_x + 1) + stat_color + Fx::ub + + cjust(detailed.status, item_width) + Theme::c("main_fg") + + cjust(detailed.elapsed, item_width); + if (item_fit >= 3) out += cjust(detailed.io_read, item_width); + if (item_fit >= 4) out += cjust(detailed.io_write, item_width); + if (item_fit >= 5) out += cjust(detailed.parent, item_width, true); + if (item_fit >= 6) out += cjust(detailed.entry.user, item_width, true); + if (item_fit >= 7) out += cjust(to_string(detailed.entry.p_nice), item_width); + if (item_fit >= 8) out += cjust(to_string(detailed.entry.threads), item_width); const double mem_p = (double)detailed.mem_bytes.back() * 100 / Shared::totalMem; - // const int mem_fuzz = min(detailed.mem * 100 / (Shared::totalMem / 10), 100ul); string mem_str = to_string(mem_p); mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); - out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + mem_str + "% ", d_width / 3) + out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + mem_str + "% ", (d_width / 3) - 2) + Theme::c("inactive_fg") + Fx::ub + graph_bg * (d_width / 3) + Mv::l(d_width / 3) - + Theme::c("proc_misc") + detailed_mem_graph(detailed.mem_bytes, (data_same or not alive)) + ' ' + + Theme::c("proc_misc") + detailed_mem_graph(detailed.mem_bytes, (redraw or data_same or not alive)) + ' ' + Theme::c("title") + Fx::b + detailed.memory; - for (int i = 0; const auto& l : {'C', 'M', 'D'}) - out += Mv::to(d_y + 5 + i++, d_x + 1) + l; - out += Theme::c("main_fg") + Fx::ub; - const int cmd_size = ulen(detailed.entry.cmd); - for (int num_lines = min(3, (int)ceil((double)cmd_size / (d_width - 5))), i = 0; i < num_lines; i++) { - out += Mv::to(d_y + 5 + (num_lines == 1 ? 1 : i), d_x + 3) - + cjust(luresize(detailed.entry.cmd, cmd_size - (d_width - 5) * i), d_width - 5, true); - } } @@ -731,16 +784,15 @@ namespace Proc { if (is_selected) selected_pid = (int)p.pid; //? Update graphs for processes with above 0.0% cpu usage, delete if below 0.1% 10x times - if (not data_same and (p.cpu_p > 0 or p_counters.contains(p.pid))) { - if (not p_graphs.contains(p.pid)) { + const bool has_graph = p_counters.contains(p.pid); + if ((p.cpu_p > 0 and not has_graph) or (not data_same and has_graph)) { + if (not has_graph) { p_graphs[p.pid] = {5, 1, "", {}, graph_symbol}; p_counters[p.pid] = 0; } - else if (p.cpu_p < 0.1) { - if (++p_counters[p.pid] >= 10) { - p_graphs.erase(p.pid); - p_counters.erase(p.pid); - } + else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) { + p_graphs.erase(p.pid); + p_counters.erase(p.pid); } else p_counters[p.pid] = 0; @@ -784,8 +836,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, prog_size) + ' ' + end - + (cmd_size > 0 ? g_color + ljust(p.cmd, cmd_size, true) + ' ' : ""); + + c_color + ljust(p.name, prog_size, true) + ' ' + end + + (cmd_size > 0 ? g_color + ljust(p.cmd, cmd_size, true, true) + ' ' : ""); } //? Tree view line else { @@ -798,8 +850,8 @@ namespace Proc { width_left -= (ulen(p.name) + 1); } if (width_left > 7 and not p.cmd.empty()) { - out += g_color + uresize(p.cmd, width_left - 1) + ' '; - width_left -= (ulen(p.cmd) + 1); + out += g_color + uresize(p.cmd, width_left - 1, true) + ' '; + width_left -= (ulen(p.cmd, true) + 1); } out += string(max(0, width_left), ' '); } @@ -813,8 +865,8 @@ namespace Proc { mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); mem_str += '%'; } - out += (thread_size > 0 ? t_color + rjust(to_string(p.threads), thread_size) + ' ' + end : "" ) - + g_color + ljust(p.user, user_size) + ' ' + out += (thread_size > 0 ? t_color + rjust(to_string(min(p.threads, 9999ul)), thread_size) + ' ' + end : "" ) + + g_color + ljust(((int)p.user.size() > user_size ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + graph_bg * 5 + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs[p.pid]({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' @@ -836,8 +888,8 @@ namespace Proc { //? Current selection and number of processes string location = to_string(start + selected) + '/' + to_string(numpids); string loc_clear = Symbols::h_line * max(0ul, 9 - location.size()); - out += Mv::to(y+height, x+width - 3 - max(9, (int)location.size())) + Theme::c("proc_box") + loc_clear - + Symbols::title_left + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right; + out += Mv::to(y + height - 1, x+width - 3 - max(9, (int)location.size())) + Theme::c("proc_box") + loc_clear + + Symbols::title_left_down + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right_down; //? Clear out left over graphs from dead processes at a regular interval if (not data_same and ++counter >= 1000) { @@ -862,6 +914,9 @@ namespace Proc { namespace Draw { void calcSizes() { auto& boxes = Config::getS("shown_boxes"); + auto& cpu_bottom = Config::getB("cpu_bottom"); + auto& mem_below_net = Config::getB("mem_below_net"); + auto& proc_left = Config::getB("proc_left"); Cpu::box.clear(); Mem::box.clear(); @@ -870,6 +925,8 @@ namespace Draw { Input::mouse_mappings.clear(); + Cpu::x = Mem::x = Net::x = Proc::x = 1; + Cpu::y = Mem::y = Net::y = Proc::y = 1; Cpu::width = Mem::width = Net::width = Proc::width = 0; Cpu::height = Mem::height = Net::height = Proc::height = 0; Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true; @@ -882,38 +939,40 @@ namespace Draw { //* Calculate and draw cpu box outlines if (Cpu::shown) { using namespace Cpu; + const bool show_temp = (Config::getB("check_temp") and got_sensors); width = round((double)Term::width * width_p / 100); height = max(8, (int)round((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100)); + x = 1; + y = cpu_bottom ? Term::height - height + 1 : 1; b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5))); - if (b_columns * (21 + 12 * got_sensors) < width - (width / 3)) { + if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) { b_column_size = 2; - b_width = (21 + 12 * got_sensors) * b_columns - (b_columns - 1); + b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1); } - else if (b_columns * (15 + 6 * got_sensors) < width - (width / 3)) { + else if (b_columns * (15 + 6 * show_temp) < width - (width / 3)) { b_column_size = 1; - b_width = (15 + 6 * got_sensors) * b_columns - (b_columns - 1); + b_width = (15 + 6 * show_temp) * b_columns - (b_columns - 1); } - else if (b_columns * (8 + 6 * got_sensors) < width - (width / 3)) { + else if (b_columns * (8 + 6 * show_temp) < width - (width / 3)) { b_column_size = 0; } else { - b_columns = (width - width / 3) / (8 + 6 * got_sensors); + b_columns = (width - width / 3) / (8 + 6 * show_temp); b_column_size = 0; } - if (b_column_size == 0) b_width = (8 + 6 * got_sensors) * b_columns + 1; + if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1; b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4); - b_x = width - b_width - 1; + b_x = x + width - b_width - 1; b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1; - box = createBox(x, y, width, height, Theme::c("cpu_box"), true, "cpu", "", 1); - box += Mv::to(y, x + 10) + Theme::c("cpu_box") + Symbols::title_left + Fx::b + Theme::c("hi_fg") - + 'M' + Theme::c("title") + "enu" + Fx::ub + Theme::c("cpu_box") + Symbols::title_right; + box = createBox(x, y, width, height, Theme::c("cpu_box"), true, (cpu_bottom ? "" : "cpu"), (cpu_bottom ? "cpu" : ""), 1); auto& custom = Config::getS("custom_cpu_name"); - box += createBox(b_x, b_y, b_width, b_height, "", false, uresize((custom.empty() ? cpuName : custom) , b_width - 14)); + const string cpu_title = uresize((custom.empty() ? Cpu::cpuName : custom) , b_width - 14); + box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title); } //* Calculate and draw mem box outlines @@ -926,7 +985,11 @@ namespace Draw { width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); height = round((double)Term::height * (100 - Cpu::height_p * Cpu::shown - Net::height_p * Net::shown) / 100) + 1; if (height + Cpu::height > Term::height) height = Term::height - Cpu::height; - y = Cpu::height + 1; + x = (proc_left and Proc::shown) ? Term::width - width + 1: 1; + if (mem_below_net and Net::shown) + y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); + else + y = cpu_bottom ? 1 : Cpu::height + 1; if (show_disks) { mem_width = ceil((double)(width - 3) / 2); @@ -975,10 +1038,15 @@ namespace Draw { using namespace Net; width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); height = Term::height - Cpu::height - Mem::height; - y = Term::height - height + 1; + x = (proc_left and Proc::shown) ? Term::width - width + 1 : 1; + if (mem_below_net and Mem::shown) + y = cpu_bottom ? 1 : Cpu::height + 1; + else + y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); + b_width = (width > 45) ? 27 : 19; b_height = (height > 10) ? 9 : height - 2; - b_x = width - b_width - 1; + b_x = x + width - b_width - 1; b_y = y + ((height - 2) / 2) - b_height / 2 + 1; d_graph_height = round((double)(height - 2) / 2); u_graph_height = height - 2 - d_graph_height; @@ -992,8 +1060,8 @@ namespace Draw { using namespace Proc; width = Term::width - (Mem::shown ? Mem::width : (Net::shown ? Net::width : 0)); height = Term::height - Cpu::height; - x = Term::width - width + 1; - y = Cpu::height + 1; + x = proc_left ? 1 : Term::width - width + 1; + y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + 1; select_max = height - 3; box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4); } diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 52f7eeb..28e97fb 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -162,13 +162,14 @@ namespace Input { string wait() { while (cin.rdbuf()->in_avail() < 1) { - if (interrupt) { interrupt = false; return ""; } + // if (interrupt) { interrupt = false; return ""; } sleep_ms(10); } return get(); } void clear() { + if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax); last.clear(); } @@ -177,9 +178,23 @@ namespace Input { try { auto& filtering = Config::getB("proc_filtering"); if (not filtering and key == "q") clean_quit(0); - bool recollect = false; + bool no_update = true; bool redraw = true; + //? Global input actions + if (not filtering) { + bool keep_going = false; + if (is_in(key, "1", "2", "3", "4")) { + static const array boxes = {"cpu", "mem", "net", "proc"}; + Config::toggle_box(boxes.at(std::stoi(key) - 1)); + term_resize(true); + } + else + keep_going = true; + + if (not keep_going) return; + } + //? Input actions for proc box if (Proc::shown) { bool keep_going = false; @@ -253,7 +268,6 @@ namespace Input { else if (current_selection == 0 or line - y - 1 == 0) redraw = true; Config::set("proc_selected", line - y - 1); - Runner::run("proc", true, false); } else if (line == y + 1) { if (Proc::selection("page_up") == -1) return; @@ -284,31 +298,35 @@ namespace Input { Config::set("proc_last_selected", Config::getI("proc_selected")); Config::set("proc_selected", 0); Config::set("show_detailed", true); - recollect = redraw = true; } else if (Config::getB("show_detailed")) { if (Config::getI("proc_last_selected") > 0) Config::set("proc_selected", Config::getI("proc_last_selected")); Config::set("proc_last_selected", 0); Config::set("detailed_pid", 0); Config::set("show_detailed", false); - redraw = true; } } - else if (key == "T") { + else if (is_in(key, "+", "-", "space") and Config::getB("proc_tree") and Config::getI("proc_selected") > 0) { + atomic_wait(Runner::active); + auto& pid = Config::getI("selected_pid"); + if (key == "+" or key == "space") Proc::expand = pid; + if (key == "-" or key == "space") Proc::collapse = pid; + } + else if (key == "t") { Logger::debug(key); return; } - else if (key == "K") { + else if (key == "k") { Logger::debug(key); return; } - else if (key == "S") { + else if (key == "s") { Logger::debug(key); return; } else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) { proc_mouse_scroll: - recollect = redraw = false; + redraw = false; auto old_selected = Config::getI("proc_selected"); auto new_selected = Proc::selection(key); if (new_selected == -1) @@ -319,7 +337,7 @@ namespace Input { else keep_going = true; if (not keep_going) { - Runner::run("proc", not recollect, redraw); + Runner::run("proc", no_update, redraw); return; } } diff --git a/src/btop_linux.cpp b/src/btop_linux.cpp index 564db16..76e486a 100644 --- a/src/btop_linux.cpp +++ b/src/btop_linux.cpp @@ -22,13 +22,15 @@ tab-size = 4 #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::max, std::min, std::string_literals::operator""s; + std::round, std::max, std::min, std::clamp, std::string_literals::operator""s; namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; @@ -45,29 +47,39 @@ namespace Tools { } } +namespace Cpu { + vector core_old_totals; + vector core_old_idles; + vector available_fields; + cpu_info current_cpu; +} + namespace Shared { - fs::path proc_path; + fs::path procPath; fs::path passwd_path; fs::file_time_type passwd_time; uint64_t totalMem; long pageSize, clkTck, coreCount; + string cpuName; void init() { - coreCount = sysconf(_SC_NPROCESSORS_ONLN); - if (Shared::coreCount < 1) { - Shared::coreCount = 1; - Logger::warning("Could not determine number of cores, defaulting to 1."); - } - proc_path = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : ""; - if (proc_path.empty()) + //? Shared global variables init + procPath = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : ""; + if (procPath.empty()) throw std::runtime_error("Proc filesystem not found or no permission to read from it!"); passwd_path = (fs::is_regular_file(fs::path("/etc/passwd")) and access("/etc/passwd", R_OK) != -1) ? "/etc/passwd" : ""; if (passwd_path.empty()) Logger::warning("Could not read /etc/passwd, will show UID instead of username."); + coreCount = sysconf(_SC_NPROCESSORS_ONLN); + if (coreCount < 1) { + coreCount = 1; + Logger::warning("Could not determine number of cores, defaulting to 1."); + } + pageSize = sysconf(_SC_PAGE_SIZE); if (pageSize <= 0) { pageSize = 4096; @@ -80,7 +92,7 @@ namespace Shared { Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect."); } - ifstream meminfo(Shared::proc_path / "meminfo"); + ifstream meminfo(Shared::procPath / "meminfo"); if (meminfo.good()) { meminfo.ignore(SSmax, ':'); meminfo >> totalMem; @@ -88,19 +100,205 @@ namespace Shared { } if (not meminfo.good() or totalMem == 0) throw std::runtime_error("Could not get total memory size from /proc/meminfo"); + + //? Init for namespace Cpu + Cpu::current_cpu.core_percent.insert(Cpu::current_cpu.core_percent.begin(), Shared::coreCount, {}); + Cpu::current_cpu.temp.insert(Cpu::current_cpu.temp.begin(), Shared::coreCount + 1, {}); + Cpu::core_old_totals.insert(Cpu::core_old_totals.begin(), Shared::coreCount, 0); + Cpu::core_old_idles.insert(Cpu::core_old_idles.begin(), Shared::coreCount, 0); + Cpu::collect(); + for (auto& [field, vec] : Cpu::current_cpu.cpu_percent) { + if (not vec.empty()) Cpu::available_fields.push_back(field); + } + Cpu::cpuName = Cpu::get_cpuName(); } } namespace Cpu { bool got_sensors = false; - string cpuName = ""; + string cpuName; + string cpuHz; - cpu_info current_cpu; + const array time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"}; + + unordered_flat_map cpu_old = { + {"totals", 0}, + {"idles", 0}, + {"user", 0}, + {"nice", 0}, + {"system", 0}, + {"idle", 0}, + {"iowait", 0}, + {"irq", 0}, + {"softirq", 0}, + {"steal", 0}, + {"guest", 0}, + {"guest_nice", 0} + }; + + string get_cpuName() { + string name; + ifstream cpuinfo(Shared::procPath / "cpuinfo"); + if (cpuinfo.good()) { + for (string instr; getline(cpuinfo, instr, ':') and not instr.starts_with("model name");) + cpuinfo.ignore(SSmax, '\n'); + if (cpuinfo.bad()) return name; + cpuinfo.ignore(1); + getline(cpuinfo, name); + auto name_vec = ssplit(name); + + if ((s_contains(name, "Xeon") or v_contains(name_vec, "Duo")) and v_contains(name_vec, "CPU")) { + auto cpu_pos = v_index(name_vec, "CPU"s); + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')')) + name = name_vec.at(cpu_pos + 1); + else + name.clear(); + } + else if (v_contains(name_vec, "Ryzen")) { + auto ryz_pos = v_index(name_vec, "Ryzen"s); + name = "Ryzen" + (ryz_pos < name_vec.size() - 1 ? ' ' + name_vec.at(ryz_pos + 1) : "") + + (ryz_pos < name_vec.size() - 2 ? ' ' + name_vec.at(ryz_pos + 2) : ""); + } + else if (s_contains(name, "Intel") and v_contains(name_vec, "CPU")) { + auto cpu_pos = v_index(name_vec, "CPU"s); + if (cpu_pos < name_vec.size() - 1 and not name_vec.at(cpu_pos + 1).ends_with(')') and name_vec.at(cpu_pos + 1) != "@") + name = name_vec.at(cpu_pos + 1); + else + name.clear(); + } + else + name.clear(); + + if (name.empty() and not name_vec.empty()) { + for (const auto& n : name_vec) { + if (n == "@") break; + name += n + ' '; + } + name.pop_back(); + for (const auto& reg : {regex("Processor"), regex("CPU"), regex("\\(R\\)"), regex("\\(TM\\)"), regex("Intel"), + regex("AMD"), regex("Core"), regex("\\d?\\.?\\d+[mMgG][hH][zZ]")}) { + name = std::regex_replace(name, reg, ""); + } + name = trim(name); + } + } + + return name; + } + + string get_cpuHz() { + static bool failed = false; + if (failed) return ""; + string cpuhz; + try { + ifstream cpuinfo(Shared::procPath / "cpuinfo"); + if (cpuinfo.good()) { + string instr; + while (getline(cpuinfo, instr, ':') and not instr.starts_with("cpu MHz")) + cpuinfo.ignore(SSmax, '\n'); + cpuinfo.ignore(1); + getline(cpuinfo, instr); + if (instr.empty()) throw std::runtime_error(""); + + int hz_int = round(std::stod(instr)); + + if (hz_int >= 1000) { + if (hz_int >= 10000) cpuhz = to_string((int)round((double)hz_int / 1000)); // Future proof until we reach THz speeds :) + else cpuhz = to_string(round((double)hz_int / 100) / 10.0).substr(0, 3); + cpuhz += " GHz"; + } + else if (hz_int > 0) + cpuhz = to_string(hz_int) + " MHz"; + } + } + catch (...) { + failed = true; + Logger::warning("Failed to get cpu clock speed from /proc/cpuinfo."); + cpuhz.clear(); + } + + return cpuhz; + } cpu_info collect(const bool no_update) { - (void)no_update; - return current_cpu; + if (no_update and not current_cpu.cpu_percent.at("total").empty()) return current_cpu; + auto& cpu = current_cpu; + // const auto& cpu_sensor = Config::getS("cpu_sensor"); + + string short_str; + ifstream cread; + + //? Get cpu total times for all cores from /proc/stat + cread.open(Shared::procPath / "stat"); + if (cread.good()) { + for (int i = 0; getline(cread, short_str, ' ') and short_str.starts_with("cpu"); i++) { + + //? Excepted on kernel 2.6.3> : 0=user, 1=nice, 2=system, 3=idle, 4=iowait, 5=irq, 6=softirq, 7=steal, 8=guest, 9=guest_nice + vector times; + uint64_t total_sum = 0; + + for (uint64_t val; cread >> val; total_sum += val) { + times.push_back(val); + } + cread.clear(); + if (times.size() < 4) throw std::runtime_error("Malformatted /proc/stat"); + + //? Subtract fields 8-9 and any future unknown fields + const uint64_t totals = total_sum - (times.size() > 8 ? std::accumulate(times.begin() + 8, times.end(), 0) : 0); + + //? Add iowait field if present + const uint64_t idles = times[3] + (times.size() > 4 ? times[4] : 0); + + //? Calculate values for totals from first line of stat + if (i == 0) { + const uint64_t calc_totals = totals - cpu_old["totals"]; + const uint64_t calc_idles = idles - cpu_old["idles"]; + cpu_old["totals"] = totals; + cpu_old["idles"] = idles; + + //? Total usage of cpu + cpu.cpu_percent["total"].push_back(clamp((uint64_t)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ul, 100ul)); + + //? Reduce size if there are more values than needed for graph + while ((int)cpu.cpu_percent["total"].size() > Term::width * 2) cpu.cpu_percent["total"].pop_front(); + + //? Populate cpu.cpu_percent with all fields from stat + for (int ii = 0; const auto& val : times) { + cpu.cpu_percent[time_names.at(ii)].push_back(clamp((uint64_t)round((double)(val - cpu_old[time_names.at(ii)]) * 100 / calc_totals), 0ul, 100ul)); + cpu_old[time_names.at(ii)] = val; + + //? Reduce size if there are more values than needed for graph + while ((int)cpu.cpu_percent[time_names.at(ii)].size() > Term::width * 2) cpu.cpu_percent[time_names.at(ii)].pop_front(); + + if (++ii == 10) break; + } + } + //? Calculate cpu total for each core + else { + if (i > Shared::coreCount) break; + const uint64_t calc_totals = totals - core_old_totals[i-1];; + const uint64_t calc_idles = idles - core_old_idles[i-1];; + core_old_totals[i-1] = totals; + core_old_idles[i-1] = idles; + + cpu.core_percent[i-1].push_back(clamp((uint64_t)round((double)(calc_totals - calc_idles) * 100 / calc_totals), 0ul, 100ul)); + + //? Reduce size if there are more values than needed for graph + if ((int)cpu.core_percent[i-1].size() > 20) cpu.core_percent[i-1].pop_front(); + + } + } + cread.close(); + } + else { + throw std::runtime_error("Failed to read /proc/stat"); + } + + if (Config::getB("show_cpu_freq")) + cpuHz = get_cpuHz(); + + return cpu; } } @@ -143,9 +341,9 @@ namespace Proc { int counter = 0; } + int collapse = -1, expand = -1; uint64_t old_cputimes = 0; atomic numpids = 0; - size_t reserve_pids = 500; vector sort_vector = { "pid", "name", @@ -174,7 +372,6 @@ namespace Proc { //* 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) { - if (Runner::stopping) return; auto cur_pos = out_procs.size(); bool filtering = false; @@ -195,7 +392,6 @@ namespace Proc { if (not collapsed and not filtering) { out_procs.push_back(cur_proc); if (std::string_view cmd_view = cur_proc.cmd; not cmd_view.empty()) { - // 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) @@ -225,7 +421,7 @@ namespace Proc { //* Get detailed info for selected process void _collect_details(const size_t pid, const uint64_t uptime, vector& procs) { - fs::path pid_path = Shared::proc_path / std::to_string(pid); + fs::path pid_path = Shared::procPath / std::to_string(pid); if (pid != detailed.last_pid) { detailed = {}; @@ -238,6 +434,7 @@ namespace Proc { detailed.entry = *p; //? Update cpu percent deque for process cpu graph + if (not Config::getB("proc_per_core")) detailed.entry.cpu_p *= Shared::coreCount; detailed.cpu_percent.push_back(round(detailed.entry.cpu_p)); while (detailed.cpu_percent.size() > (size_t)Term::width) detailed.cpu_percent.pop_front(); @@ -334,12 +531,12 @@ namespace Proc { string short_str; const double uptime = system_uptime(); vector procs; - procs.reserve(reserve_pids + 10); - int npids = 0; + procs.reserve(current_procs.size() + 10); const int cmult = (per_core) ? Shared::coreCount : 1; bool got_detailed = false; if (no_update and not cache.empty()) { procs = current_procs; + if (show_detailed and detailed_pid != detailed.last_pid) _collect_details(detailed_pid, round(uptime), procs); goto proc_no_update; } @@ -363,7 +560,7 @@ namespace Proc { //* Get cpu total times from /proc/stat cputimes = 0; - pread.open(Shared::proc_path / "stat"); + pread.open(Shared::procPath / "stat"); if (pread.good()) { pread.ignore(SSmax, ' '); for (uint64_t times; pread >> times; cputimes += times); @@ -372,7 +569,7 @@ namespace Proc { else throw std::runtime_error("Failure to read /proc/stat"); //* Iterate over all pids in /proc - for (const auto& d: fs::directory_iterator(Shared::proc_path)) { + for (const auto& d: fs::directory_iterator(Shared::procPath)) { if (Runner::stopping) return current_procs; if (pread.is_open()) pread.close(); @@ -380,7 +577,6 @@ namespace Proc { const string pid_str = d.path().filename(); if (not isdigit(pid_str[0])) continue; - npids++; proc_info new_proc (stoul(pid_str)); //* Cache program name, command and username @@ -503,7 +699,7 @@ namespace Proc { new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0; //? Process cumulative cpu usage since process start - new_proc.cpu_c = ((double)cpu_t / Shared::clkTck) / (uptime - (cache[new_proc.pid].cpu_s / Shared::clkTck)); + new_proc.cpu_c = (double)cpu_t / ((uptime * Shared::clkTck) - cache[new_proc.pid].cpu_s); //? Update cache with latest cpu times cache[new_proc.pid].cpu_t = cpu_t; @@ -518,7 +714,7 @@ namespace Proc { } //* Clear dead processes from cache at a regular interval - if (++counter >= 10000 or ((int)cache.size() > npids + 100)) { + if (++counter >= 10000 or (cache.size() > procs.size() + 100)) { counter = 0; unordered_flat_map r_cache; r_cache.reserve(procs.size()); @@ -529,10 +725,6 @@ namespace Proc { cache = std::move(r_cache); } - old_cputimes = cputimes; - reserve_pids = npids; - current_procs = procs; - //* Update the details info box for process if active if (show_detailed and got_detailed) { _collect_details(detailed_pid, round(uptime), procs); @@ -542,6 +734,9 @@ namespace Proc { redraw = true; } + old_cputimes = cputimes; + current_procs = procs; + proc_no_update: //* Match filter if defined @@ -587,6 +782,19 @@ namespace Proc { //* Generate tree view if enabled if (tree) { + if (collapse > -1 and collapse == expand) { + if (cache.contains(collapse)) cache.at(collapse).collapsed = not cache.at(collapse).collapsed; + collapse = expand = -1; + } + else if (collapse > -1) { + if (cache.contains(collapse)) cache.at(collapse).collapsed = true; + collapse = -1; + } + else if (expand > -1) { + if (cache.contains(expand)) cache.at(expand).collapsed = false; + expand = -1; + } + vector tree_procs; tree_procs.reserve(procs.size()); @@ -595,10 +803,9 @@ namespace Proc { //? Start recursive iteration over processes with the lowest shared parent pids for (const auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) { - _tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, filter); + _tree_gen(p, procs, tree_procs, 0, false, filter); } - if (Runner::stopping) return current_procs; procs = std::move(tree_procs); } diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 606655f..10ed8d5 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -29,6 +29,7 @@ tab-size = 4 using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array; void clean_quit(const int sig=-1); +void term_resize(bool force=false); void banner_gen(); namespace Global { @@ -38,6 +39,7 @@ namespace Global { extern string banner; extern atomic resized; extern string overlay; + extern string clock; } namespace Runner { @@ -66,12 +68,26 @@ namespace Shared { namespace Cpu { - extern string box, cpuName; + extern string box; extern int x, y, width, height; extern bool shown, redraw, got_sensors; + extern string cpuName, cpuHz; struct cpu_info { - vector> percent; + unordered_flat_map> cpu_percent = { + {"total", {}}, + {"user", {}}, + {"nice", {}}, + {"system", {}}, + {"idle", {}}, + {"iowait", {}}, + {"irq", {}}, + {"softirq", {}}, + {"steal", {}}, + {"guest", {}}, + {"guest_nice", {}} + }; + vector> core_percent; vector> temp; array load_avg; }; @@ -81,6 +97,12 @@ namespace Cpu { //* Draw contents of cpu box using as source string draw(const cpu_info& cpu, const bool force_redraw=false, const bool data_same=false); + + //* Try to get name of cpu + string get_cpuName(); + + //* Try to get current cpu clock speed + string get_cpuHz(); } namespace Mem { @@ -136,7 +158,7 @@ namespace Proc { extern bool shown, redraw; extern int select_max; extern atomic detailed_pid; - extern int selected_pid, start, selected; + extern int selected_pid, start, selected, collapse, expand; //? Contains the valid sorting options for processes extern vector sort_vector; diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 6ec3a06..bce4a00 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -21,6 +21,7 @@ tab-size = 4 #include #include #include +#include #include #include diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 78a99cb..8e19b97 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -32,7 +32,7 @@ tab-size = 4 #include #include -using std::string_view, std::array, std::max, std::floor, std::to_string, std::cin, robin_hood::unordered_flat_map; +using std::string_view, std::array, std::max, std::floor, std::to_string, std::cin, std::cout, std::flush, robin_hood::unordered_flat_map; namespace fs = std::filesystem; namespace rng = std::ranges; @@ -93,6 +93,7 @@ namespace Term { echo(false); linebuffered(false); refresh(); + cout << alt_screen << hide_cursor << mouse_on << flush; Global::resized = false; } } @@ -104,6 +105,7 @@ namespace Term { echo(true); linebuffered(true); tcsetattr(STDIN_FILENO, TCSANOW, &initial_settings); + cout << Term::mouse_off << Term::normal_screen << Term::show_cursor << flush; initialized = false; } } @@ -113,11 +115,12 @@ namespace Term { namespace Tools { - string uresize(string str, const size_t len) { + string uresize(string str, const size_t len, const bool wide) { if (len < 1 or str.empty()) return ""; for (size_t x = 0, i = 0; i < str.size(); i++) { - if ((static_cast(str.at(i)) & 0xC0) != 0x80) x++; - if (x == len + 1) { + if (wide and static_cast(str.at(i)) > 0xef) x += 2; + else if ((static_cast(str.at(i)) & 0xC0) != 0x80) x++; + if (x >= len + 1) { str.resize(i); str.shrink_to_fit(); break; @@ -126,14 +129,18 @@ namespace Tools { return str; } - string luresize(string str, const size_t len) { + string luresize(string str, const size_t len, const bool wide) { if (len < 1 or str.empty()) return ""; for (size_t x = 0, last_pos = 0, i = str.size() - 1; i > 0 ; i--) { - if ((static_cast(str.at(i)) & 0xC0) != 0x80) { + if (wide and static_cast(str.at(i)) > 0xef) { + x += 2; + last_pos = max(0ul, i - 1); + } + else if ((static_cast(str.at(i)) & 0xC0) != 0x80) { x++; last_pos = i; } - if (x == len) { + if (x >= len) { str = str.substr(last_pos); str.shrink_to_fit(); break; @@ -165,9 +172,9 @@ 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 wide, const bool limit) { if (utf) { - if (limit and ulen(str) > x) return uresize(str, x); + if (limit and ulen(str, wide) > x) return uresize(str, x, wide); return str + string(max((int)(x - ulen(str)), 0), ' '); } else { @@ -176,9 +183,9 @@ 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 wide, const bool limit) { if (utf) { - if (limit and ulen(str) > x) return uresize(str, x); + if (limit and ulen(str, wide) > x) return uresize(str, x, wide); return string(max((int)(x - ulen(str)), 0), ' ') + str; } else { @@ -187,9 +194,9 @@ namespace Tools { } } - string cjust(string str, const size_t x, const bool utf, const bool limit) { + string cjust(string str, const size_t x, const bool utf, const bool wide, const bool limit) { if (utf) { - if (limit and ulen(str) > x) return uresize(str, x); + if (limit and ulen(str, wide) > x) return uresize(str, x, wide); return string(max((int)ceil((double)(x - ulen(str)) / 2), 0), ' ') + str + string(max((int)floor((double)(x - ulen(str)) / 2), 0), ' '); } else { @@ -272,6 +279,15 @@ namespace Tools { return ss.str(); } + atomic_lock::atomic_lock(atomic& atom) : atom(atom) { + while (not this->atom.compare_exchange_strong(this->not_true, true)); + } + + atomic_lock::~atomic_lock() { + this->atom.store(false); + atomic_notify(this->atom); + } + } namespace Logger { @@ -291,22 +307,24 @@ namespace Logger { void log_write(const size_t level, const string& msg) { if (loglevel < level or logfile.empty()) return; - busy.wait(true); - busy = true; + atomic_lock lck(busy); std::error_code ec; - if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) { - auto old_log = logfile; - old_log += ".1"; - if (fs::exists(old_log)) fs::remove(old_log, ec); - if (not ec) fs::rename(logfile, old_log, ec); + try { + if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) { + auto old_log = logfile; + old_log += ".1"; + if (fs::exists(old_log)) fs::remove(old_log, ec); + if (not ec) fs::rename(logfile, old_log, ec); + } + if (not ec) { + std::ofstream lwrite(logfile, std::ios::app); + if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";} + lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n"; + } + else logfile.clear(); } - if (not ec) { - std::ofstream lwrite(logfile, std::ios::app); - if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";} - lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n"; + catch (const std::exception& e) { + throw std::runtime_error("Exception in Logger::log_write() : " + (string)e.what()); } - else logfile.clear(); - busy = false; - busy.notify_one(); } } diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 67b55a6..e684f11 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -125,16 +125,17 @@ namespace Term { namespace Tools { constexpr auto SSmax = std::numeric_limits::max(); - //* Return number of UTF8 characters in a string - inline size_t ulen(const string& str) { - return std::ranges::count_if(str, [](char c) { return (static_cast(c) & 0xC0) != 0x80; } ); - }; + //* Return number of UTF8 characters in a string (counts UTF-8 characters with a width > 1 as 2 characters) + inline size_t ulen(const string& str, const bool wide=false) { + return std::ranges::count_if(str, [](char c) { return (static_cast(c) & 0xC0) != 0x80; }) + + (wide ? std::ranges::count_if(str, [](char c) { return (static_cast(c) > 0xef); }) : 0); + } //* Resize a string consisting of UTF8 characters (only reduces size) - string uresize(const string str, const size_t len); + string uresize(const string str, const size_t len, const bool wide=false); //* Resize a string consisting of UTF8 characters from left (only reduces size) - string luresize(const string str, const size_t len); + string luresize(const string str, const size_t len, const bool wide=false); //* Return with only uppercase characters inline string str_to_upper(string str) { @@ -211,22 +212,22 @@ namespace Tools { //* Left/right-trim from and return new string inline string trim(const string& str, const string& t_str = " ") { return ltrim(rtrim(str, t_str), t_str); - }; + } //* Split at all occurrences of and return as vector of strings vector ssplit(const string& str, const char& delim = ' '); //* Put current thread to sleep for milliseconds - inline void sleep_ms(const size_t& ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); }; + inline void sleep_ms(const size_t& ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } //* Left justify string if is greater than length, limit return size to by default - string ljust(string str, const size_t x, const bool utf=false, const bool limit=true); + string ljust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true); //* Right justify string if is greater than length, limit return size to by default - string rjust(string str, const size_t x, const bool utf=false, const bool limit=true); + string rjust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true); //* Center justify string if is greater than length, limit return size to by default - string cjust(string str, const size_t x, const bool utf=false, const bool limit=true); + string cjust(string str, const size_t x, const bool utf=false, const bool wide=false, const bool limit=true); //* Replace whitespaces " " with escape code for move right string trans(const string& str); @@ -246,6 +247,24 @@ namespace Tools { //* Return current time in format string strf_time(const string& strf); +#if __GNUC__ < 11 + inline void atomic_wait(const atomic& atom, const bool old=true) noexcept { while (atom.load() == old); } + inline void atomic_notify(const atomic& atom) noexcept { (void)atom; } +#else + inline void atomic_wait(const atomic& atom, const bool old=true) noexcept { atom.wait(old); } + inline void atomic_notify(const atomic& atom) noexcept { atom.notify_all(); } +#endif + + //* Waits for atomic to be false and sets it to true on construct, sets to false and notifies on destruct + class atomic_lock { + atomic& atom; + bool not_true = false; + public: + atomic_lock(atomic& atom); + ~atomic_lock(); + }; + + } //* Simple logging implementation