diff --git a/Makefile b/Makefile index c144a0b..5dbd42a 100644 --- a/Makefile +++ b/Makefile @@ -16,13 +16,13 @@ ifeq ($(ARCH),unknown) ARCH := $(shell uname -m || echo unknown) endif ifeq ($(ARCH),x86_64) - ADDFLAGS = -fcf-protection + ADDFLAGS := -fcf-protection endif #? Make sure PLATFORM Darwin is OSX and not Darwin ifeq ($(PLATFORM),Darwin) ifeq ($(shell sw_vers >/dev/null 2>&1; echo $$?),0) - PLATFORM = OSX + PLATFORM := OSX endif endif @@ -32,17 +32,30 @@ override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0) #? Try to make sure we are using GCC/G++ version 11 or later if not instructed to use g++-10 ifneq ($(CXX),g++-10) - V_MAJOR = $(shell echo $(CXX_VERSION) | cut -f1 -d".") + V_MAJOR := $(shell echo $(CXX_VERSION) | cut -f1 -d".") ifneq ($(shell test $(V_MAJOR) -ge 11; echo $$?),0) ifeq ($(shell command -v g++-11 >/dev/null; echo $$?),0) - override CXX = g++-11 + override CXX := g++-11 override CXX_VERSION := $(shell $(CXX) -dumpfullversion -dumpversion || echo 0) endif endif endif +#? Pull in platform specific source files and get thread count +ifeq ($(PLATFORM),Linux) + PLATFORM_DIR := linux + THREADS := $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1) +else ifeq ($(PLATFORM),FreeBSD) + PLATFORM_DIR := freebsd + THREADS := $(shell getconf NPROCESSORS_ONLN 2>/dev/null || echo 1) +else ifeq ($(PLATFORM),OSX) + PLATFORM_DIR := osx + THREADS := $(shell sysctl -n hw.ncpu || echo 1) +else +$(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m")) +endif + #? Use all CPU cores (will only be set if using Make 4.3+) -THREADS := $(shell getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1) MAKEFLAGS := --jobs=$(THREADS) ifeq ($(THREADS),1) override THREADS := auto @@ -70,17 +83,6 @@ SU_GROUP := root SOURCES := $(shell find $(SRCDIR) -maxdepth 1 -type f -name *.$(SRCEXT)) -#? Pull in platform specific source files -ifeq ($(PLATFORM),Linux) - PLATFORM_DIR = linux -else ifeq ($(PLATFORM),FreeBSD) - PLATFORM_DIR = freebsd -else ifeq ($(PLATFORM),OSX) - PLATFORM_DIR = osx -else -$(error $(shell printf "\033[1;91mERROR: \033[97mUnsupported platform ($(PLATFORM))\033[0m")) -endif - SOURCES += $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEXT)) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) diff --git a/src/btop.cpp b/src/btop.cpp index 30597e9..bd1a41e 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -55,11 +55,9 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "0.5.0"; + const string Version = "0.9.0"; int coreCount; - string banner; - size_t banner_width = 0; string overlay; string clock; @@ -106,7 +104,7 @@ void argumentParser(const int& argc, char **argv) { << endl; exit(0); } - if (is_in(argument, "-v", "--version")) { + else if (is_in(argument, "-v", "--version")) { cout << "btop version: " << Global::Version << endl; exit(0); } @@ -236,105 +234,6 @@ void _signal_handler(const int sig) { } } -//* Generate the btop++ banner -void banner_gen() { - Global::banner.clear(); - Global::banner_width = 0; - 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); - 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) { - if (line[1][i] == ' ') { - letter = ' '; - i -= 2; - } - else - letter = line[1].substr(i, 3); - - b_color = (letter == "█") ? fg : bg; - if (b_color != oc) Global::banner += b_color; - Global::banner += letter; - oc = b_color; - } - if (++z < Global::Banner_src.size()) Global::banner += Mv::l(ulen(line[1])) + Mv::d(1); - } - Global::banner += Mv::r(18 - Global::Version.size()) - + (tty_mode ? "\x1b[0;40;37m" : Theme::dec_to_color(0,0,0, lowcolor, "bg") - + Theme::dec_to_color(150, 150, 150, lowcolor)) - + Fx::i + "v" + Global::Version + Fx::ui; -} - -bool update_clock() { - const auto& clock_format = Config::getS("clock_format"); - if (not Cpu::shown or clock_format.empty()) return false; - - static const unordered_flat_map clock_custom_format = { - {"/user", Tools::username()}, - {"/host", Tools::hostname()}, - {"/uptime", ""} - }; - static time_t c_time = 0; - static size_t clock_len = 0; - static string old_clock; - string new_clock; - - if (auto n_time = time(NULL); n_time == c_time) - return false; - else { - c_time = n_time; - new_clock = Tools::strf_time(clock_format); - if (new_clock == old_clock) return false; - old_clock = new_clock; - } - - auto& out = Global::clock; - const auto& cpu_bottom = Config::getB("cpu_bottom"); - const auto& x = Cpu::x; - const auto y = (cpu_bottom ? Cpu::y + Cpu::height - 1 : Cpu::y); - const auto& width = Cpu::width; - const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); - const auto& title_right = (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); - - - for (const auto& [c_format, replacement] : clock_custom_format) { - if (s_contains(new_clock, c_format)) { - if (c_format == "/uptime") { - string upstr = sec_to_dhms(system_uptime()); - if (upstr.size() > 8) upstr.resize(upstr.size() - 3); - new_clock = s_replace(new_clock, c_format, upstr); - } - else { - new_clock = s_replace(new_clock, c_format, replacement); - } - } - - } - - new_clock = uresize(new_clock, std::max(0, width - 56)); - out.clear(); - - if (new_clock.size() != clock_len) { - if (not Global::resized and clock_len > 0) out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len; - clock_len = new_clock.size(); - } - - out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left - + Theme::c("title") + Fx::b + new_clock + Theme::c("cpu_box") + Fx::ub + title_right; - - return true; -} - //* Manages secondary thread for collection and drawing of boxes namespace Runner { atomic active (false); @@ -366,6 +265,7 @@ namespace Runner { }; string output; + bool pause_output = false; sigset_t mask; pthread_t runner_id; pthread_mutex_t mtx; @@ -407,6 +307,7 @@ namespace Runner { bitset<8> box_mask; bool no_update; bool force_redraw; + bool background_update; string overlay; string clock; }; @@ -464,6 +365,7 @@ namespace Runner { //! DEBUG stats if (Global::debug) { + if (debug_bg.empty()) Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug"); debug_times.clear(); debug_times["total"] = {0, 0}; } @@ -499,7 +401,7 @@ namespace Runner { if (Global::debug) debug_timer("proc", draw_begin); //? Draw box - output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update); + if (not pause_output) output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("proc", draw_done); } @@ -527,7 +429,7 @@ namespace Runner { if (Global::debug) debug_timer("net", draw_begin); //? Draw box - output += Net::draw(net.get(), conf.force_redraw, conf.no_update); + if (not pause_output) output += Net::draw(net.get(), conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("net", draw_done); } @@ -555,7 +457,7 @@ namespace Runner { if (Global::debug) debug_timer("mem", draw_begin); //? Draw box - output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update); + if (not pause_output) output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("mem", draw_done); } @@ -583,7 +485,7 @@ namespace Runner { if (Global::debug) debug_timer("cpu", draw_begin); //? Draw box - output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update); + if (not pause_output) output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update); if (Global::debug) debug_timer("cpu", draw_done); } @@ -606,8 +508,11 @@ namespace Runner { continue; } + if (not pause_output) output += conf.clock; + if (not conf.overlay.empty() and not conf.background_update) pause_output = true; + //! DEBUG stats --> - if (Global::debug) { + if (Global::debug and not Menu::active) { output += debug_bg + Theme::c("title") + Fx::b + ljust(" Box", 9) + ljust("Collect μs", 12, true) + ljust("Draw μs", 9, true) + Theme::c("main_fg") + Fx::ub; for (const string name : {"cpu", "mem", "net", "proc", "total"}) { if (not debug_times.contains(name)) debug_times[name] = {0,0}; @@ -619,8 +524,8 @@ namespace Runner { //? If overlay isn't empty, print output without color and then print overlay on top cout << Term::sync_start << (conf.overlay.empty() - ? output + conf.clock - : Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output + conf.clock) + conf.overlay) + ? output + : (output.empty() ? "" : Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output)) + conf.overlay) << Term::sync_end << flush; } //* ----------------------------------------------- THREAD LOOP ----------------------------------------------- @@ -654,7 +559,9 @@ namespace Runner { box_mask |= box_bits.at(box); } - current_conf = {box_mask, no_update, force_redraw, Global::overlay, Global::clock}; + current_conf = {box_mask, no_update, force_redraw, (not Config::getB("tty_mode") and Config::getB("background_update")), Global::overlay, Global::clock}; + + if (Menu::active and not current_conf.background_update) Global::overlay.clear(); thread_trigger(); @@ -826,13 +733,6 @@ int main(int argc, char **argv) { exit(1); } - if (Global::debug) { - Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug"); - } - - //? Create the btop++ banner - banner_gen(); - //? Calculate sizes of all boxes Draw::calcSizes(); @@ -867,7 +767,7 @@ int main(int argc, char **argv) { //? Trigger secondary thread to redraw if terminal has been resized if (Global::resized) { Draw::calcSizes(); - update_clock(); + Draw::update_clock(); Global::resized = false; if (Menu::active) Menu::process(); else Runner::run("all", true, true); @@ -875,7 +775,7 @@ int main(int argc, char **argv) { } //? Update clock if needed - if (update_clock() and not Menu::active) { + if (Draw::update_clock() and not Menu::active) { Runner::run("clock"); } @@ -912,7 +812,7 @@ int main(int argc, char **argv) { } } - catch (std::exception& e) { + catch (const std::exception& e) { Global::exit_error_msg = "Exception in main loop -> " + (string)e.what(); exit(1); } diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 085a252..9773e01 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -39,7 +39,8 @@ namespace Config { bool write_new; const vector> descriptions = { - {"color_theme", "#* Full path to a bashtop/bpytop/btop++ formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes."}, + {"color_theme", "#* Name of a btop++/bpytop/bashtop formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes.\n" + "#* Themes should be placed in \"../share/btop/themes\" relative to binary or \"$HOME/.config/btop/themes\""}, {"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."}, @@ -139,9 +140,9 @@ namespace Config { {"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."}, - {"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."}, + {"show_io_stat", "#* Toggles if io activity % (disk busy time) should be shown in regular disk usage view."}, - {"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."}, + {"io_mode", "#* Toggles io mode for disks, showing big graphs for disk read/write speeds."}, {"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."}, @@ -160,7 +161,7 @@ namespace Config { {"show_battery", "#* Show battery stats in top right if battery is present."}, - {"log_level", "#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" + {"log_level", "#* Set loglevel for \"~/.config/btop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."} }; @@ -260,6 +261,98 @@ namespace Config { locked = true; } + string validError; + + bool intValid(const string& name, const string& value) { + int i_value; + try { + i_value = stoi(value); + } + catch (const std::invalid_argument&) { + validError = "Invalid numerical value!"; + return false; + } + catch (const std::out_of_range&) { + validError = "Value out of range!"; + return false; + } + + if (name == "update_ms" and i_value < 100) + validError = "Config value update_ms set too low (<100)."; + + else if (name == "update_ms" and i_value > 86400000) + validError = "Config value update_ms set too high (>86400000)."; + + else + return true; + + return false; + } + + bool stringValid(const string& name, const string& value) { + if (name == "log_level" and not v_contains(Logger::log_levels, value)) + validError = "Invalid log_level: " + value; + + else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value)) + validError = "Invalid graph symbol identifier: " + value; + + else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value))) + validError = "Invalid graph symbol identifier for" + name + ": " + value; + + else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) + validError = "Invalid box name(s) in shown_boxes!"; + + else if (name == "cpu_core_map") { + const auto maps = ssplit(value); + bool all_good = true; + for (const auto& map : maps) { + const auto map_split = ssplit(map, ':'); + if (map_split.size() != 2) + all_good = false; + else if (not isint(map_split.at(0)) or not isint(map_split.at(1))) + all_good = false; + + if (not all_good) { + validError = "Invalid formatting of cpu_core_map!"; + return false; + } + } + return true; + } + else if (name == "io_graph_speeds") { + const auto maps = ssplit(value); + bool all_good = true; + for (const auto& map : maps) { + const auto map_split = ssplit(map, ':'); + if (map_split.size() != 2) + all_good = false; + else if (map_split.at(0).empty() or not isint(map_split.at(1))) + all_good = false; + + if (not all_good) { + validError = "Invalid formatting of io_graph_speeds!"; + return false; + } + } + return true; + } + + else + return true; + + return false; + } + + string getAsString(const string& name) { + if (bools.contains(name)) + return (bools.at(name) ? "True" : "False"); + else if (ints.contains(name)) + return to_string(ints.at(name)); + else if (strings.contains(name)) + return strings.at(name); + return ""; + } + void flip(const string& name) { if (_locked(name)) { if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name); @@ -369,9 +462,8 @@ namespace Config { cread >> value; if (not isint(value)) load_warnings.push_back("Got an invalid integer value for config name: " + name); - else if (name == "update_ms" and stoi(value) < 100) { - load_warnings.push_back("Config value update_ms set too low (<100), setting (100)."); - ints.at(name) = 100; + else if (not intValid(name, value)) { + load_warnings.push_back(validError); } else ints.at(name) = stoi(value); @@ -383,14 +475,8 @@ namespace Config { } else cread >> value; - if (name == "log_level" and not v_contains(Logger::log_levels, value)) - load_warnings.push_back("Invalid log_level: " + value); - else if (name == "graph_symbol" and not v_contains(valid_graph_symbols, value)) - load_warnings.push_back("Invalid graph symbol identifier: " + value); - else if (name.starts_with("graph_symbol_") and (value != "default" and not v_contains(valid_graph_symbols, value))) - load_warnings.push_back("Invalid graph symbol identifier for" + name + ": " + value); - else if (name == "shown_boxes" and not value.empty() and not check_boxes(value)) - load_warnings.push_back("Invalid box name(s) in shown_boxes: " + value); + if (not stringValid(name, value)) + load_warnings.push_back(validError); else strings.at(name) = value; } diff --git a/src/btop_config.hpp b/src/btop_config.hpp index 35d13df..78552bd 100644 --- a/src/btop_config.hpp +++ b/src/btop_config.hpp @@ -39,7 +39,9 @@ namespace Config { extern unordered_flat_map intsTmp; const vector valid_graph_symbols = { "braille", "block", "tty" }; + const vector valid_graph_symbols_def = { "default", "braille", "block", "tty" }; const vector valid_boxes = { "cpu", "mem", "net", "proc" }; + const vector temp_scales = { "celsius", "fahrenheit", "kelvin", "rankine" }; extern vector current_boxes; @@ -60,6 +62,13 @@ namespace Config { //* Return string for config key inline const string& getS(const string& name) { return strings.at(name); } + string getAsString(const string& name); + + extern string validError; + + bool intValid(const string& name, const string& value); + bool stringValid(const string& name, const string& value); + //* Set config key to bool inline void set(const string& name, const bool& value) { if (_locked(name)) boolsTmp.insert_or_assign(name, value); diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index cc37ac8..1bd8fa8 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -90,8 +90,49 @@ namespace Symbols { namespace Draw { + string banner_gen(int y, int x, bool centered, bool redraw) { + static string banner; + static size_t width = 0; + if (redraw) banner.clear(); + if (banner.empty()) { + 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 > width) 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); + 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) { + if (line[1][i] == ' ') { + letter = ' '; + i -= 2; + } + else + letter = line[1].substr(i, 3); + + b_color = (letter == "█") ? fg : bg; + if (b_color != oc) banner += b_color; + banner += letter; + oc = b_color; + } + if (++z < Global::Banner_src.size()) banner += Mv::l(ulen(line[1])) + Mv::d(1); + } + banner += Mv::r(18 - Global::Version.size()) + + Theme::c("main_fg") + Fx::b + Fx::i + "v" + Global::Version + Fx::reset; + } + if (redraw) return ""; + return (centered ? Mv::to(y, Term::width / 2 - width / 2) : Mv::to(y, x)) + banner; + } + TextEdit::TextEdit() {} - TextEdit::TextEdit(string text) : text(text) { + TextEdit::TextEdit(string text, bool numeric) : numeric(numeric), text(text) { pos = this->text.size(); upos = ulen(this->text); } @@ -127,11 +168,12 @@ namespace Draw { const string first = uresize(text, upos + 1); text = uresize(first, ulen(first) - 1) + text.substr(first.size()); } - else if (key == "space") { + else if (key == "space" and not numeric) { text.insert(pos++, 1, ' '); upos++; } - else if (ulen(key) == 1) { + else if (ulen(key) == 1 and text.size() < text.max_size() - 20) { + if (numeric and not isint(key)) return false; if (key.size() == 1) { text.insert(pos++, 1, key.at(0)); upos++; @@ -171,6 +213,10 @@ namespace Draw { return text.substr(0, pos) + Fx::bl + "█" + Fx::ubl + text.substr(pos); } + void TextEdit::clear() { + this->text.clear(); + } + 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"); @@ -215,6 +261,69 @@ namespace Draw { return out + Fx::reset + Mv::to(y + 1, x + 1); } + bool update_clock() { + const auto& clock_format = Config::getS("clock_format"); + if (not Cpu::shown or clock_format.empty()) { + if (clock_format.empty() and not Global::clock.empty()) Global::clock.clear(); + return false; + } + + static const unordered_flat_map clock_custom_format = { + {"/user", Tools::username()}, + {"/host", Tools::hostname()}, + {"/uptime", ""} + }; + static time_t c_time = 0; + static size_t clock_len = 0; + static string old_clock; + string new_clock; + + if (auto n_time = time(NULL); n_time == c_time) + return false; + else { + c_time = n_time; + new_clock = Tools::strf_time(clock_format); + if (new_clock == old_clock) return false; + old_clock = new_clock; + } + + auto& out = Global::clock; + const auto& cpu_bottom = Config::getB("cpu_bottom"); + const auto& x = Cpu::x; + const auto y = (cpu_bottom ? Cpu::y + Cpu::height - 1 : Cpu::y); + const auto& width = Cpu::width; + const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); + const auto& title_right = (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); + + + for (const auto& [c_format, replacement] : clock_custom_format) { + if (s_contains(new_clock, c_format)) { + if (c_format == "/uptime") { + string upstr = sec_to_dhms(system_uptime()); + if (upstr.size() > 8) upstr.resize(upstr.size() - 3); + new_clock = s_replace(new_clock, c_format, upstr); + } + else { + new_clock = s_replace(new_clock, c_format, replacement); + } + } + + } + + new_clock = uresize(new_clock, std::max(0, width - 56)); + out.clear(); + + if (new_clock.size() != clock_len) { + if (not Global::resized and clock_len > 0) out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len; + clock_len = new_clock.size(); + } + + out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left + + Theme::c("title") + Fx::b + new_clock + Theme::c("cpu_box") + Fx::ub + title_right; + + return true; + } + //* Meter class ------------------------------------------------------------------------------------------------------------> Meter::Meter() {} @@ -294,7 +403,7 @@ namespace Draw { for (const int& i : iota(1, height + 1)) { if (i > 1) out += Mv::d(1) + Mv::l(width); if (not color_gradient.empty()) - out += (invert) ? Theme::g(color_gradient).at((i - 1) * 100 / (height - 1)) : Theme::g(color_gradient).at(100 - (i * 100 / height)); + out += (invert) ? Theme::g(color_gradient).at(i * 100 / height) : Theme::g(color_gradient).at(100 - ((i - 1) * 100 / height)); out += (invert) ? graphs.at(current).at(height - i) : graphs.at(current).at(i-1); } } @@ -374,7 +483,7 @@ namespace Cpu { auto& graph_lo_field = Config::getS("cpu_graph_lower"); auto& tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu")); - auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(1); + auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto& temp_scale = Config::getS("temp_scale"); string out; out.reserve(width * height); @@ -540,7 +649,7 @@ namespace Mem { auto& use_graphs = Config::getB("mem_graphs"); auto& tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_mem")); - // auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(1); + auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); string out; out.reserve(height * width); @@ -592,7 +701,7 @@ namespace Mem { for (const auto& [name, disk] : mem.disks) { if (disk.io_read.empty()) continue; - io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "available", disk.io_activity, graph_symbol, false, true}; + io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "", disk.io_activity, graph_symbol}; if (io_mode) { //? Create one combined graph for IO read/write if enabled @@ -672,7 +781,7 @@ namespace Mem { //? Disks if (show_disks) { const auto& disks = mem.disks; - cx = x + mem_width - 1; cy = 0; + cx = mem_width; cy = 0; const bool big_disk = disks_width >= 25; divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Fx::ub + Symbols::div_right + Mv::l(disks_width); if (io_mode) { @@ -688,7 +797,8 @@ namespace Mem { const string used_percent = to_string(disk.used_percent); out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2)) + Theme::c("main_fg") + used_percent + '%'; } - out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same); + out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(max(50ll, disk.io_activity.back())) + + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (++cy > height - 3) break; if (io_graph_combined) { auto comb_val = disk.io_read.back() + disk.io_write.back(); @@ -728,7 +838,8 @@ namespace Mem { out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)human_io.size() / 2)) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; if (show_io_stat and io_graphs.contains(mount + "_activity")) { - out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same); + out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(max(50ll, disk.io_activity.back())) + + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (not big_disk) out += Mv::to(y+1+cy, x+cx) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; } @@ -938,7 +1049,7 @@ namespace Proc { auto& proc_colors = Config::getB("proc_colors"); auto& tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc")); - auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(1); + auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto& mem_bytes = Config::getB("proc_mem_bytes"); start = Config::getI("proc_start"); selected = Config::getI("proc_selected"); @@ -1330,10 +1441,13 @@ namespace Draw { Proc::box.clear(); Global::clock.clear(); Global::overlay.clear(); + Runner::pause_output = false; + Runner::debug_bg.clear(); + Proc::p_counters.clear(); + Proc::p_graphs.clear(); if (Menu::active) Menu::redraw = true; Input::mouse_mappings.clear(); - Menu::mouse_mappings.clear(); Cpu::x = Mem::x = Net::x = Proc::x = 1; Cpu::y = Mem::y = Net::y = Proc::y = 1; diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index f6e078d..0e1e6a0 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -57,21 +57,28 @@ namespace Symbols { namespace Draw { + //* Generate if needed and return the btop++ banner + string banner_gen(int y=0, int x=0, bool centered=false, bool redraw=false); + //* An editable text field class TextEdit { size_t pos = 0; size_t upos = 0; + bool numeric; public: string text; TextEdit(); - TextEdit(string text); + TextEdit(string text, bool numeric=false); bool command(const string& key); string operator()(const size_t limit=0); + void clear(); }; //* Create a box and return as a string string createBox(const int x, const int y, const int width, const int height, string line_color="", const bool fill=false, const string title="", const string title2="", const int num=0); + bool update_clock(); + //* Class holding a percentage meter class Meter { int width; @@ -121,9 +128,10 @@ namespace Draw { //* Calculate sizes of boxes, draw outlines and save to enabled boxes namespaces void calcSizes(); - } namespace Proc { extern Draw::TextEdit filter; + extern unordered_flat_map p_graphs; + extern unordered_flat_map p_counters; } \ No newline at end of file diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 7f94aba..cf3a0d2 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -189,6 +189,21 @@ namespace Input { if (str_to_lower(key) == "q") { exit(0); } + else if (is_in(key, "escape", "m")) { + Menu::menuMask.set(Menu::Main); + Menu::process(); + return; + } + else if (is_in(key, "F1", "h")) { + Menu::menuMask.set(Menu::Help); + Menu::process(); + return; + } + else if (is_in(key, "F2", "o")) { + Menu::menuMask.set(Menu::Options); + Menu::process(); + return; + } else if (is_in(key, "1", "2", "3", "4")) { atomic_wait(Runner::active); static const array boxes = {"cpu", "mem", "net", "proc"}; @@ -330,6 +345,7 @@ namespace Input { return; } else if (key == "s" and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) { + if (Term::width < 80 or Term::height < 20) return; atomic_wait(Runner::active); if (Config::getB("show_detailed") and Config::getI("proc_selected") == 0 and Proc::detailed.status == "Dead") return; Menu::menuMask.set(Menu::SignalChoose); diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 4aaa45c..756c48b 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -22,6 +22,8 @@ tab-size = 4 #include #include #include +#include +#include #include #include @@ -30,8 +32,9 @@ tab-size = 4 #include #include -using std::deque, robin_hood::unordered_flat_map, std::array, std::views::iota, std::ref; +using std::deque, robin_hood::unordered_flat_map, std::array, std::views::iota, std::ref, std::max, std::min, std::ceil, std::clamp; using namespace Tools; +namespace fs = std::filesystem; namespace rng = std::ranges; namespace Menu { @@ -58,46 +61,487 @@ namespace Menu { unordered_flat_map mouse_mappings; - const unordered_flat_map>> menus = { - { "options", { - { "normal", { - "┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐", - "│ │├─┘ │ ││ ││││└─┐", - "└─┘┴ ┴ ┴└─┘┘└┘└─┘" - } }, - { "selected", { - "╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗", - "║ ║╠═╝ ║ ║║ ║║║║╚═╗", - "╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝" - } } - } }, - { "help", { - { "normal", { - "┬ ┬┌─┐┬ ┌─┐", - "├─┤├┤ │ ├─┘", - "┴ ┴└─┘┴─┘┴ " - } }, - { "selected", { - "╦ ╦╔═╗╦ ╔═╗", - "╠═╣║╣ ║ ╠═╝", - "╩ ╩╚═╝╩═╝╩ " - } } - } }, - { "quit", { - { "normal", { - "┌─┐ ┬ ┬ ┬┌┬┐", - "│─┼┐│ │ │ │ ", - "└─┘└└─┘ ┴ ┴ " - } }, - { "selected", { - "╔═╗ ╦ ╦ ╦╔╦╗ ", - "║═╬╗║ ║ ║ ║ ", - "╚═╝╚╚═╝ ╩ ╩ " - } } - } } + const array, 3> menu_normal = { + array{ + "┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐", + "│ │├─┘ │ ││ ││││└─┐", + "└─┘┴ ┴ ┴└─┘┘└┘└─┘" + }, + { + "┬ ┬┌─┐┬ ┌─┐", + "├─┤├┤ │ ├─┘", + "┴ ┴└─┘┴─┘┴ " + }, + { + "┌─┐ ┬ ┬ ┬┌┬┐", + "│─┼┐│ │ │ │ ", + "└─┘└└─┘ ┴ ┴ " + } }; - msgBox::msgBox() {}; - msgBox::msgBox(int width, int boxtype, vector& content, string title) + + const array, 3> menu_selected = { + array{ + "╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗", + "║ ║╠═╝ ║ ║║ ║║║║╚═╗", + "╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝" + }, + { + "╦ ╦╔═╗╦ ╔═╗", + "╠═╣║╣ ║ ╠═╝", + "╩ ╩╚═╝╩═╝╩ " + }, + { + "╔═╗ ╦ ╦ ╦╔╦╗ ", + "║═╬╗║ ║ ║ ║ ", + "╚═╝╚╚═╝ ╩ ╩ " + } + }; + + const array menu_width = {19, 12, 12}; + + const vector> help_text = { + {"Mouse 1", "Clicks buttons and selects in process list."}, + {"Mouse scroll", "Scrolls any scrollable list/text under cursor."}, + {"Esc, m", "Toggles main menu."}, + {"p", "Cycle view presets"}, + {"1", "Toggle CPU box."}, + {"2", "Toggle MEM box."}, + {"3", "Toggle NET box."}, + {"4", "Toggle PROC box."}, + {"d", "Toggle disks view in MEM box."}, + {"F2, o", "Shows options."}, + {"F1, h", "Shows this window."}, + {"ctrl + z", "Sleep program and put in background."}, + {"q, ctrl + c", "Quits program."}, + {"+, -", "Add/Subtract 100ms to/from update timer."}, + {"Up, Down", "Select in process list."}, + {"Enter", "Show detailed information for selected process."}, + {"Spacebar", "Expand/collapse the selected process in tree view."}, + {"Pg Up, Pg Down", "Jump 1 page in process list."}, + {"Home, End", "Jump to first or last page in process list."}, + {"Left, Right", "Select previous/next sorting column."}, + {"b, n", "Select previous/next network device."}, + {"i", "Toggle disks io mode with big graphs."}, + {"z", "Toggle totals reset for current network device"}, + {"a", "Toggle auto scaling for the network graphs."}, + {"y", "Toggle synced scaling mode for network graphs."}, + {"f", "To enter a process filter."}, + {"delete", "Clear any entered filter."}, + {"c", "Toggle per-core cpu usage of processes."}, + {"r", "Reverse sorting order in processes box."}, + {"e", "Toggle processes tree view."}, + {"Selected +, -", "Expand/collapse the selected process in tree view."}, + {"Selected t", "Terminate selected process with SIGTERM - 15."}, + {"Selected k", "Kill selected process with SIGKILL - 9."}, + {"Selected s", "Select or enter signal to send to process."}, + {"", " "}, + {"", "For bug reporting and project updates, visit:"}, + {"", "https://github.com/aristocratos/btop"}, + }; + + const vector>> categories = { + { + {"color_theme", + "Set color theme.", + "", + "Choose from all theme files in (usually)", + "\"/usr/[local/]share/btop/themes\" and", + "\"~/.config/btop/themes\".", + "", + "\"Default\" for builtin default theme.", + "\"TTY\" for builtin 16-color theme.", + "", + "For theme updates see:", + "https://github.com/aristocratos/btop"}, + {"theme_background", + "If the theme set background should be shown.", + "", + "Set to False if you want terminal background", + "transparency."}, + {"truecolor", + "Sets if 24-bit truecolor should be used.", + "", + "Will convert 24-bit colors to 256 color", + "(6x6x6 color cube) if False.", + "", + "Set to False if your terminal doesn't have", + "truecolor support and can't convert to", + "256-color."}, + {"force_tty", + "TTY mode.", + "", + "Set to true to force tty mode regardless", + "if a real tty has been detected or not.", + "", + "Will force 16-color mode and TTY theme,", + "set all graph symbols to \"tty\" and swap", + "out other non tty friendly symbols."}, + {"shown_boxes", + "Manually set which boxes to show.", + "", + "Available values are \"cpu mem net proc\".", + "Seperate values with whitespace.", + "", + "Toggle between presets with key \"p\"."}, + {"update_ms", + "Update time in milliseconds.", + "", + "Recommended 2000 ms or above for better", + "sample times for graphs.", + "", + "Min value: 100 ms", + "Max value: 86400000 ms = 24 hours."}, + {"rounded_corners", + "Rounded corners on boxes.", + "", + "True or False", + "", + "Is always False if TTY mode is ON."}, + {"graph_symbol", + "Default symbols to use for graph creation.", + "", + "\"braille\", \"block\" or \"tty\".", + "", + "\"braille\" offers the highest resolution but", + "might not be included in all fonts.", + "", + "\"block\" has half the resolution of braille", + "but uses more common characters.", + "", + "\"tty\" uses only 3 different symbols but will", + "work with most fonts.", + "", + "Note that \"tty\" only has half the horizontal", + "resolution of the other two,", + "so will show a shorter historical view."}, + {"clock_format", + "Draw a clock at top of screen.", + "(Only visible if cpu box is enabled!)", + "", + "Formatting according to strftime, empty", + "string to disable.", + "", + "Custom formatting options:", + "\"/host\" = hostname", + "\"/user\" = username", + "\"/uptime\" = system uptime", + "", + "Examples of strftime formats:", + "\"%X\" = locale HH:MM:SS", + "\"%H\" = 24h hour, \"%I\" = 12h hour", + "\"%M\" = minute, \"%S\" = second", + "\"%d\" = day, \"%m\" = month, \"%y\" = year"}, + {"background_update", + "Update main ui when menus are showing.", + "", + "True or False.", + "", + "Set this to false if the menus is flickering", + "too much for a comfortable experience."}, + {"show_battery", + "Show battery stats.", + "(Only visible if cpu box is enabled!)", + "", + "Show battery stats in the top right corner", + "if a battery is present."}, + {"log_level", + "Set loglevel for error.log", + "", + "\"ERROR\", \"WARNING\", \"INFO\" and \"DEBUG\".", + "", + "The level set includes all lower levels,", + "i.e. \"DEBUG\" will show all logging info."} + }, + { + {"cpu_bottom", + "Cpu box location.", + "", + "Show cpu box at bottom of screen instead", + "of top."}, + {"graph_symbol_cpu", + "Graph symbol to use for graphs in cpu box.", + "", + "\"default\", \"braille\", \"block\" or \"tty\".", + "", + "\"default\" for the general default symbol.",}, + {"cpu_graph_upper", + "Cpu upper graph.", + "", + "Sets the CPU stat shown in upper half of", + "the CPU graph.", + "", + "\"total\" = Total cpu usage.", + "\"user\" = User mode cpu usage.", + "\"system\" = Kernel mode cpu usage.", + "+ more depending on kernel."}, + {"cpu_graph_lower", + "Cpu lower graph.", + "", + "Sets the CPU stat shown in lower half of", + "the CPU graph.", + "", + "\"total\" = Total cpu usage.", + "\"user\" = User mode cpu usage.", + "\"system\" = Kernel mode cpu usage.", + "+ more depending on kernel."}, + {"cpu_invert_lower", + "Toggles orientation of the lower CPU graph.", + "", + "True or False."}, + {"cpu_single_graph", + "Completely disable the lower CPU graph.", + "", + "Shows only upper CPU graph and resizes it", + "to fit to box height.", + "", + "True or False."}, + {"check_temp", + "Enable cpu temperature reporting.", + "", + "True or False."}, + {"cpu_sensor", + "Cpu temperature sensor", + "", + "Select the sensor that corresponds to", + "your cpu temperature.", + "", + "Set to \"Auto\" for auto detection."}, + {"show_coretemp", + "Show temperatures for cpu cores.", + "", + "Only works if check_temp is True and", + "the system is reporting core temps."}, + {"cpu_core_map", + "Custom mapping between core and coretemp.", + "", + "Can be needed on certain cpus to get correct", + "temperature for correct core.", + "", + "Format: \"X:Y\"", + "X=core with wrong temp.", + "Y=core with correct temp.", + "Use space as separator between multiple", + "entries.", + "", + "Example: \"4:0 5:1 6:3\""}, + {"temp_scale", + "Which temperature scale to use.", + "", + "Celsius, default scale.", + "", + "Fahrenheit, the american one.", + "", + "Kelvin, 0 = absolute zero, 1 degree change", + "equals 1 degree change in Celsius.", + "", + "Rankine, 0 = abosulte zero, 1 degree change", + "equals 1 degree change in Fahrenheit."}, + {"show_cpu_freq", + "Show CPU frequency", + "", + "Can cause slowdowns on systems with many", + "cores and certain kernel versions."}, + {"custom_cpu_name", + "Custom cpu model name in cpu percentage box.", + "", + "Empty string to disable."}, + {"show_uptime", + "Shows the system uptime in the CPU box.", + "", + "Can also be shown in the clock by using", + "\"/uptime\" in the formatting.", + "", + "True or False."}, + }, + { + {"mem_below_net", + "Mem box location.", + "", + "Show mem box below net box instead of above."}, + {"graph_symbol_mem", + "Graph symbol to use for graphs in mem box.", + "", + "\"default\", \"braille\", \"block\" or \"tty\".", + "", + "\"default\" for the general default symbol.",}, + {"mem_graphs", + "Show graphs for memory values.", + "", + "True or False."}, + {"show_disks", + "Split memory box to also show disks.", + "", + "True or False."}, + {"show_io_stat", + "Toggle IO activity graphs.", + "", + "Show small IO graphs that for disk activity", + "percentage when not in IO mode.", + "", + "True or False."}, + {"io_mode", + "Toggles io mode for disks.", + "", + "Shows big graphs for disk read/write speeds", + "instead of used/free percentage meters.", + "", + "True or False."}, + {"io_graph_combined", + "Toggle combined read and write graphs.", + "", + "Only has effect if \"io mode\" is True.", + "", + "True or False."}, + {"io_graph_speeds", + "Set top speeds for the io graphs.", + "", + "Manually set which speed in MiB/s that", + "equals 100 percent in the io graphs.", + "(10 MiB/s by default).", + "", + "Format: \"device:speed\" seperate disks with a", + "comma \",\".", + "", + "Example: \"/dev/sda:100, /dev/sdb:20\"."}, + {"show_swap", + "If swap memory should be shown in memory box.", + "", + "True or False."}, + {"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.", + "", + "True or False."}, + {"use_fstab", + "Read disks list from /etc/fstab.", + "(Has no effect on macOS X)", + "", + "This also disables only_physical.", + "", + "True or False."}, + {"disks_filter", + "Optional filter for shown disks.", + "", + "Should be full path of a mountpoint.", + "Separate multiple values with", + "whitespace \" \".", + "", + "Begin line with \"exclude=\" to change to", + "exclude filter.", + "Oterwise defaults to \"most include\" filter.", + "", + "Example:", + "\"exclude=/boot /home/user\""}, + }, + { + {"graph_symbol_net", + "Graph symbol to use for graphs in net box.", + "", + "\"default\", \"braille\", \"block\" or \"tty\".", + "", + "\"default\" for the general default symbol.",}, + {"net_download", + "Fixed network graph download value.", + "", + "Value in Mebibytes, default \"100\".", + "", + "Can be toggled with auto button."}, + {"net_upload", + "Fixed network graph upload value.", + "", + "Value in Mebibytes, default \"100\".", + "", + "Can be toggled with auto button."}, + {"net_auto", + "Start in network graphs auto rescaling mode.", + "", + "Ignores any values set above at start and", + "rescales down to 10Kibibytes at the lowest.", + "", + "True or False."}, + {"net_sync", + "Network scale sync.", + "", + "Syncs the scaling for download and upload to", + "whichever currently has the highest scale.", + "", + "True or False."}, + {"net_iface", + "Network Interface.", + "", + "Manually set the starting Network Interface.", + "Will otherwise automatically choose the NIC", + "with the highest total download since boot."}, + }, + { + {"proc_left", + "Proc box location.", + "", + "Show proc box on left side of screen", + "instead of right."}, + {"graph_symbol_proc", + "Graph symbol to use for graphs in proc box.", + "", + "\"default\", \"braille\", \"block\" or \"tty\".", + "", + "\"default\" for the general default symbol.",}, + {"proc_sorting", + "Processes sorting option.", + "", + "Possible values:", + "\"pid\", \"program\", \"arguments\", \"threads\",", + "\"user\", \"memory\", \"cpu lazy\" and", + "\"cpu responsive\".", + "", + "\"cpu lazy\" updates top process over time.", + "\"cpu responsive\" updates top process", + "directly."}, + {"proc_reversed", + "Reverse processes sorting order.", + "", + "True or False."}, + {"proc_tree", + "Processes tree view.", + "", + "Set true to show processes grouped by", + "parents with lines drawn between parent", + "and child process."}, + {"proc_colors", + "Enable colors in process view.", + "", + "True or False."}, + {"proc_gradient", + "Enable process view gradient fade.", + "", + "Fades from top or current selection.", + "Max fade value is equal to current themes", + "\"inactive_fg\" color value."}, + {"proc_per_core", + "Process usage per core.", + "", + "If process cpu usage should be of the core", + "it's running on or usage of the total", + "available cpu power.", + "", + "If true and process is multithreaded", + "cpu usage can reach over 100%."}, + {"proc_mem_bytes", + "Show memory as bytes in process list.", + " ", + "Will show percentage of total memory", + "if False."}, + } + }; + + msgBox::msgBox() {} + msgBox::msgBox(int width, int boxtype, vector content, string title) : width(width), boxtype(boxtype) { const auto& tty_mode = Config::getB("tty_mode"); const auto& rounded = Config::getB("rounded_corners"); @@ -116,7 +560,7 @@ namespace Menu { box_contents = Draw::createBox(x, y, width, height, Theme::c("hi_fg"), true, title) + Mv::d(1); for (const auto& line : content) { - box_contents += Mv::save + Mv::r(width / 2 - Fx::uncolor(line).size() / 2) + line + Mv::restore + Mv::d(1); + box_contents += Mv::save + Mv::r(width / 2 - Fx::uncolor(line).size() / 2 - 1) + line + Mv::restore + Mv::d(1); } } @@ -184,7 +628,8 @@ namespace Menu { enum menuReturnCodes { NoChange, Changed, - Closed + Closed, + Switch }; int signalChoose(const string& key) { @@ -197,15 +642,21 @@ namespace Menu { if (redraw) { x = Term::width/2 - 40; y = Term::height/2 - 9; - bg = Draw::createBox(x, y, 80, 18, Theme::c("hi_fg"), true, "signals"); - bg += Mv::to(y+2, x+1) + Theme::c("title") + Fx::b + cjust("Send signal to PID " + to_string(s_pid) + " (" - + uresize((s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name")), 30) + ")", 78); + bg = Draw::createBox(x + 2, y, 78, 18, Theme::c("hi_fg"), true, "signals"); + bg += Mv::to(y+2, x+3) + Theme::c("title") + Fx::b + cjust("Send signal to PID " + to_string(s_pid) + " (" + + uresize((s_pid == Config::getI("detailed_pid") ? Proc::detailed.entry.name : Config::getS("selected_name")), 30) + ")", 76); } - - if (is_in(key, "escape", "q")) { + else if (is_in(key, "escape", "q")) { return Closed; } + else if (key.starts_with("button_")) { + if (int new_select = stoi(key.substr(7)); new_select == selected_signal) + goto ChooseEntering; + else + selected_signal = new_select; + } else if (is_in(key, "enter", "space") and selected_signal >= 0) { + ChooseEntering: signalKillRet = 0; if (s_pid < 1) { signalKillRet = ESRCH; @@ -224,7 +675,8 @@ namespace Menu { selected_signal = (selected_signal < 10 ? -1 : selected_signal / 10); } else if (key == "up" and selected_signal != 16) { - if (selected_signal < 6) selected_signal += 25; + if (selected_signal == 1) selected_signal = 31; + else if (selected_signal < 6) selected_signal += 25; else { bool offset = (selected_signal > 16); selected_signal -= 5; @@ -232,7 +684,8 @@ namespace Menu { } } else if (key == "down") { - if (selected_signal < 1 or selected_signal == 16) selected_signal = 1; + if (selected_signal == 31) selected_signal = 1; + else if (selected_signal < 1 or selected_signal == 16) selected_signal = 1; else if (selected_signal > 26) selected_signal -= 25; else { bool offset = (selected_signal < 16); @@ -241,43 +694,45 @@ namespace Menu { if (selected_signal > 31) selected_signal = 31; } } - else if (key == "left" and selected_signal > 1 and selected_signal != 16) { - selected_signal--; - if (selected_signal == 16) selected_signal--; + else if (key == "left" and selected_signal > 0 and selected_signal != 16) { + if (--selected_signal < 1) selected_signal = 31; + else if (selected_signal == 16) selected_signal--; } - else if (key == "right" and selected_signal < 31 and selected_signal != 16) { - selected_signal++; - if (selected_signal == 16) selected_signal++; + else if (key == "right" and selected_signal <= 31 and selected_signal != 16) { + if (++selected_signal > 31) selected_signal = 1; + else if (selected_signal == 16) selected_signal++; } else { retval = NoChange; } - int cy = y+3; - out = bg + Mv::to(cy++, x+1) + Theme::c("main_fg") + Fx::ub - + rjust("Enter signal number: ", 48) + (selected_signal >= 0 ? to_string(selected_signal) : "") + Fx::bl + "█" + Fx::ubl; + if (retval == Changed) { + int cy = y+4, cx = x+4; + out = bg + Mv::to(cy++, x+3) + Theme::c("main_fg") + Fx::ub + + rjust("Enter signal number: ", 48) + Theme::c("hi_fg") + (selected_signal >= 0 ? to_string(selected_signal) : "") + Theme::c("main_fg") + Fx::bl + "█" + Fx::ubl; - out += Mv::to(++cy, x+4); - auto sig_str = to_string(selected_signal); - for (int count = 0, i = 0; const auto& sig : P_Signals) { - if (count == 0 or count == 16) { count++; continue; } - if (i++ % 5 == 0) out += Mv::to(++cy, x+4); - if (count == selected_signal) out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b + ljust(to_string(count), 3) + ljust('(' + sig + ')', 12) + Fx::reset; - else out += Theme::c("hi_fg") + ljust(to_string(count), 3) + Theme::c("main_fg") + ljust('(' + sig + ')', 12); - count++; + auto sig_str = to_string(selected_signal); + for (int count = 0, i = 0; const auto& sig : P_Signals) { + if (count == 0 or count == 16) { count++; continue; } + if (i++ % 5 == 0) { ++cy; cx = x+4; } + out += Mv::to(cy, cx); + if (count == selected_signal) out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b + ljust(to_string(count), 3) + ljust('(' + sig + ')', 12) + Fx::reset; + else out += Theme::c("hi_fg") + ljust(to_string(count), 3) + Theme::c("main_fg") + ljust('(' + sig + ')', 12); + if (redraw) mouse_mappings["button_" + to_string(count)] = {cy, cx, 1, 15}; + count++; + cx += 15; + } + + cy++; + out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ENTER", 38) + Theme::c("main_fg") + Fx::ub + " | To send signal."; + mouse_mappings["enter"] = {cy, x, 1, 78}; + out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust( "↑ ↓ ← →", 38, true) + Theme::c("main_fg") + Fx::ub + " | To choose signal."; + out += Mv::to(++cy, x+3) + Fx::b + Theme::c("hi_fg") + rjust("ESC or \"q\"", 38) + Theme::c("main_fg") + Fx::ub + " | To abort."; + mouse_mappings["escape"] = {cy, x, 1, 78}; + + out += Fx::reset; } - cy++; - out += Mv::to(++cy, x+1) + Fx::b + rjust("ENTER | ", 35) + Fx::ub + "To send signal."; - out += Mv::to(++cy, x+1) + Fx::b + rjust( "↑ ↓ ← → | ", 35, true) + Fx::ub + "To choose signal."; - out += Mv::to(++cy, x+1) + Fx::b + rjust("ESC or \"q\" | ", 35) + Fx::ub + "To abort."; - - out += Fx::reset; - - - - - return (redraw ? Changed : retval); } @@ -354,16 +809,431 @@ namespace Menu { } int mainMenu(const string& key) { - (void)key; - return NoChange; + enum MenuItems { Options, Help, Quit }; + static int y = 0, selected = 0; + static vector colors_selected; + static vector colors_normal; + auto& tty_mode = Config::getB("tty_mode"); + if (bg.empty()) selected = 0; + int retval = Changed; + + if (redraw) { + y = Term::height/2 - 10; + bg = Draw::banner_gen(y, 0, true); + if (not tty_mode) { + colors_selected = { + Theme::hex_to_color(Global::Banner_src.at(0).at(0)), + Theme::hex_to_color(Global::Banner_src.at(2).at(0)), + Theme::hex_to_color(Global::Banner_src.at(4).at(0)) + }; + colors_normal = { + Theme::hex_to_color("#CC"), + Theme::hex_to_color("#AA"), + Theme::hex_to_color("#80") + }; + } + } + else if (key == "q") { + exit(0); + } + else if (is_in(key, "escape", "m", "mouse_click")) { + return Closed; + } + else if (key.starts_with("button_")) { + if (int new_select = key.back() - '0'; new_select == selected) + goto MainEntering; + else + selected = new_select; + } + else if (is_in(key, "enter", "space")) { + MainEntering: + switch (selected) { + case Options: + menuMask.set(Menus::Options); + currentMenu = Menus::Options; + return Switch; + case Help: + menuMask.set(Menus::Help); + currentMenu = Menus::Help; + return Switch; + case Quit: + exit(0); + } + } + else if (is_in(key, "down", "tab", "mouse_scroll_down")) { + if (++selected > 2) selected = 0; + } + else if (is_in(key, "up", "shift_tab", "mouse_scroll_up")) { + if (--selected < 0) selected = 2; + } + else { + retval = NoChange; + } + + + if (retval == Changed) { + auto& out = Global::overlay; + out = bg + Fx::reset + Fx::b; + auto cy = y + 7; + for (const auto& i : iota(0, 3)) { + if (tty_mode) out += (i == selected ? Theme::c("hi_fg") : Theme::c("main_fg")); + const auto& menu = (not tty_mode and i == selected ? menu_selected[i] : menu_normal[i]); + const auto& colors = (i == selected ? colors_selected : colors_normal); + if (redraw) mouse_mappings["button_" + to_string(i)] = {cy, Term::width/2 - menu_width[i]/2, 3, menu_width[i]}; + for (int ic = 0; const auto& line : menu) { + out += Mv::to(cy++, Term::width/2 - menu_width[i]/2) + (tty_mode ? "" : colors[ic++]) + line; + } + } + out += Fx::reset; + } + + + return (redraw ? Changed : retval); } + int optionsMenu(const string& key) { - (void)key; - return NoChange; + enum Predispositions { isBool, isInt, isString, is2D, isBrowseable, isEditable}; + static int y = 0, x = 0, height = 0, page = 0, pages = 0, selected = 0, select_max = 0, item_height = 0, selected_cat = 0, max_items = 0, last_sel = 0; + static bool editing = false; + static Draw::TextEdit editor; + static string warnings; + static bitset<8> selPred; + static const unordered_flat_map>> optionsList = { + {"color_theme", std::cref(Theme::themes)}, + {"log_level", std::cref(Logger::log_levels)}, + {"temp_scale", std::cref(Config::temp_scales)}, + {"proc_sorting", std::cref(Proc::sort_vector)}, + {"graph_symbol", std::cref(Config::valid_graph_symbols)}, + {"graph_symbol_cpu", std::cref(Config::valid_graph_symbols_def)}, + {"graph_symbol_mem", std::cref(Config::valid_graph_symbols_def)}, + {"graph_symbol_net", std::cref(Config::valid_graph_symbols_def)}, + {"graph_symbol_proc", std::cref(Config::valid_graph_symbols_def)}, + {"cpu_graph_upper", std::cref(Cpu::available_fields)}, + {"cpu_graph_lower", std::cref(Cpu::available_fields)}, + {"cpu_sensor", std::cref(Cpu::available_sensors)} + }; + auto& tty_mode = Config::getB("tty_mode"); + if (max_items == 0) { + for (const auto& cat : categories) { + if ((int)cat.size() > max_items) max_items = cat.size(); + } + } + if (bg.empty()) { + page = selected = selected_cat = last_sel = 0; + redraw = true; + Theme::updateThemes(); + } + int retval = Changed; + bool recollect = false; + bool screen_redraw = false; + bool theme_refresh = false; + + if (redraw) { + mouse_mappings.clear(); + selPred.reset(); + y = max(1, Term::height/2 - 3 - max_items); + x = Term::width/2 - 39; + height = min(Term::height - 7, max_items * 2 + 4); + if (height % 2 != 0) height--; + bg = Draw::banner_gen(y, 0, true) + + Draw::createBox(x, y + 6, 78, height, Theme::c("hi_fg"), true, "tab" + Symbols::right) + + Mv::to(y+8, x) + Theme::c("hi_fg") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * 29 + + Symbols::div_up + Symbols::h_line * (78 - 32) + Theme::c("hi_fg") + Symbols::div_right + + Mv::to(y+6+height - 1, x+30) + Symbols::div_down + Theme::c("div_line"); + for (const auto& i : iota(0, height - 4)) { + bg += Mv::to(y+9 + i, x + 30) + Symbols::v_line; + } + } + else if (not warnings.empty() and not key.empty()) { + auto ret = messageBox.input(key); + if (ret == msgBox::msgReturn::Ok_Yes or ret == msgBox::msgReturn::No_Esc) { + warnings.clear(); + messageBox.clear(); + } + } + else if (editing and not key.empty()) { + if (is_in(key, "escape", "mouse_click")) { + editor.clear(); + editing = false; + } + else if (key == "enter") { + const auto& option = categories[selected_cat][item_height * page + selected][0]; + if (selPred.test(isString) and Config::stringValid(option, editor.text)) { + Config::set(option, editor.text); + if (option == "shown_boxes") screen_redraw = true; + } + else if (selPred.test(isInt) and Config::intValid(option, editor.text)) { + Config::set(option, stoi(editor.text)); + } + else + warnings = Config::validError; + + editor.clear(); + editing = false; + } + else if (not editor.command(key)) + retval = NoChange; + } + else if (key == "mouse_click") { + const auto [mouse_x, mouse_y] = Input::mouse_pos; + if (mouse_x < x or mouse_x > x + 80 or mouse_y < y + 6 or mouse_y > y + 6 + height) { + return Closed; + } + else if (mouse_x < x + 30 and mouse_y > y + 8) { + auto m_select = ceil((double)(mouse_y - y - 8) / 2) - 1; + if (selected != m_select) + selected = m_select; + else if (selPred.test(isEditable)) + goto mouseEnter; + else retval = NoChange; + } + } + else if (is_in(key, "enter", "e", "E") and selPred.test(isEditable)) { + mouseEnter: + const auto& option = categories[selected_cat][item_height * page + selected][0]; + editor = Draw::TextEdit{Config::getAsString(option), selPred.test(isInt)}; + editing = true; + mouse_mappings.clear(); + } + else if (is_in(key, "escape", "q", "o", "backspace")) { + return Closed; + } + else if (is_in(key, "down", "mouse_scroll_down")) { + if (++selected > select_max or selected >= item_height) { + if (page < pages - 1) page++; + else if (pages > 1) page = 0; + selected = 0; + } + } + else if (is_in(key, "up", "mouse_scroll_up")) { + if (--selected < 0) { + if (page > 0) page--; + else if (pages > 1) page = pages - 1; + + selected = item_height - 1; + } + } + else if (pages > 1 and key == "page_down") { + if (++page >= pages) page = 0; + selected = 0; + } + else if (pages > 1 and key == "page_up") { + if (--page < 0) page = pages - 1; + selected = 0; + } + else if (key == "tab") { + if (++selected_cat >= (int)categories.size()) selected_cat = 0; + page = selected = 0; + } + else if (key == "shift_tab") { + if (--selected_cat < 0) selected_cat = (int)categories.size() - 1; + page = selected = 0; + } + else if (is_in(key, "1", "2", "3", "4", "5") or key.starts_with("select_cat_")) { + selected_cat = key.back() - '0' - 1; + page = selected = 0; + } + else if (is_in(key, "left", "right")) { + const auto& option = categories[selected_cat][item_height * page + selected][0]; + if (selPred.test(isInt)) { + const int mod = (option == "update_ms" ? 100 : 1); + long value = Config::getI(option); + if (key == "right") value += mod; + else value -= mod; + + if (Config::intValid(option, to_string(value))) + Config::set(option, static_cast(value)); + else { + warnings = Config::validError; + } + } + else if (selPred.test(isBool)) { + Config::flip(option); + screen_redraw = true; + if (option == "truecolor") { + theme_refresh = true; + Config::flip("lowcolor"); + } + else if (option == "force_tty") { + theme_refresh = true; + Config::flip("tty_mode"); + } + else if (is_in(option, "rounded_corners", "theme_background")) + theme_refresh = true; + } + else if (selPred.test(isBrowseable)) { + auto& optList = optionsList.at(option).get(); + int i = v_index(optList, Config::getS(option)); + + if (key == "right" and ++i >= (int)optList.size()) i = 0; + else if (key == "left" and --i < 0) i = optList.size() - 1; + Config::set(option, optList.at(i)); + + if (option == "color_theme") + theme_refresh = true; + else if (is_in(option, "proc_sorting", "cpu_sensor") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) + screen_redraw = true; + } + else + retval = NoChange; + } + else { + retval = NoChange; + } + + if (retval == Changed) { + Config::unlock(); + auto& out = Global::overlay; + out = bg; + item_height = min((int)categories[selected_cat].size(), (int)floor((double)(height - 4) / 2)); + pages = ceil((double)categories[selected_cat].size() / item_height); + if (page > pages - 1) page = pages - 1; + select_max = min(item_height - 1, (int)categories[selected_cat].size() - 1 - item_height * page); + if (selected > select_max) { + selected = select_max; + } + + if (selPred.none() or last_sel != (selected_cat << 8) + selected) { + selPred.reset(); + last_sel = (selected_cat << 8) + selected; + const auto& selOption = categories[selected_cat][item_height * page + selected][0]; + if (Config::ints.contains(selOption)) + selPred.set(isInt); + else if (Config::bools.contains(selOption)) + selPred.set(isBool); + else + selPred.set(isString); + + if (not selPred.test(isString)) + selPred.set(is2D); + else if (optionsList.contains(selOption)) { + selPred.set(isBrowseable); + } + if (not selPred.test(isBrowseable) and (selPred.test(isString) or selPred.test(isInt))) + selPred.set(isEditable); + } + + out += Mv::to(y+7, x+4); + for (int i = 0; const auto& m : {"general", "cpu", "mem", "net", "proc"}) { + out += Fx::b + (i == selected_cat + ? Theme::c("hi_fg") + '[' + Theme::c("title") + m + Theme::c("hi_fg") + ']' + : Theme::c("hi_fg") + to_string(i + 1) + Theme::c("title") + m + ' ') + + Mv::r(10); + if (string button_name = "select_cat_" + to_string(i + 1); not editing and not mouse_mappings.contains(button_name)) + mouse_mappings[button_name] = {y+6, x+2 + 15*i, 3, 15}; + i++; + } + if (pages > 1) { + out += Mv::to(y+6 + height, x+2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page " + + to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down; + } + auto cy = y+9; + for (int c = 0, i = max(0, item_height * page); c++ < item_height and i < (int)categories[selected_cat].size(); i++) { + const auto& option = categories[selected_cat][i][0]; + const auto& value = (option == "color_theme" ? (string) fs::path(Config::getS("color_theme")).stem() : Config::getAsString(option)); + + out += Mv::to(cy++, x + 1) + (c-1 == selected ? Theme::c("selected_bg") + Theme::c("selected_fg") : Theme::c("title")) + + Fx::b + cjust(capitalize(s_replace(option, "_", " ")) + + (c-1 == selected and selPred.test(isBrowseable) + ? ' ' + to_string(v_index(optionsList.at(option).get(), (option == "color_theme" ? Config::getS("color_theme") : value)) + 1) + '/' + to_string(optionsList.at(option).get().size()) + : ""), 29); + out += Mv::to(cy++, x + 1) + (c-1 == selected ? "" : Theme::c("main_fg")) + Fx::ub + " " + + (c-1 == selected and editing ? cjust(editor(24), 34, true) : cjust(value, 25, true)) + " "; + + if (c-1 == selected) { + if (not editing and (selPred.test(is2D) or selPred.test(isBrowseable))) { + out += Fx::b + Mv::to(cy-1, x+2) + Symbols::left + Mv::to(cy-1, x+28) + Symbols::right; + mouse_mappings["left"] = {cy-2, x, 2, 5}; + mouse_mappings["right"] = {cy-2, x+25, 2, 5}; + } + if (selPred.test(isEditable)) { + out += Fx::b + Mv::to(cy-1, x+28 - (not editing and selPred.test(isInt) ? 2 : 0)) + (tty_mode ? "E" : Symbols::enter); + } + out += Fx::reset + Theme::c("title") + Fx::b; + for (int cyy = y+7; const auto& desc : categories[selected_cat][i]) { + if (cyy++ == y+7) continue; + else if (cyy == y+10) out += Theme::c("main_fg") + Fx::ub; + else if (cyy > y + height + 4) break; + out += Mv::to(cyy, x+32) + desc; + } + } + } + + if (not warnings.empty()) { + messageBox = msgBox{min(78, (int)ulen(warnings) + 10), msgBox::BoxTypes::OK, {uresize(warnings, 74)}, "warning"}; + out += messageBox(); + } + + out += Fx::reset; + } + + if (theme_refresh) { + Theme::setTheme(); + Draw::banner_gen(0, 0, false, true); + screen_redraw = true; + redraw = true; + optionsMenu(""); + } + if (screen_redraw) { + auto overlay_bkp = Global::overlay; + Draw::calcSizes(); + Global::overlay = overlay_bkp; + recollect = true; + } + if (recollect) { + Runner::run("all", false, true); + retval = NoChange; + } + + return (redraw ? Changed : retval); } + int helpMenu(const string& key) { - (void)key; - return NoChange; + static int y = 0, x = 0, height = 0, page = 0, pages = 0; + if (bg.empty()) page = 0; + int retval = Changed; + + if (redraw) { + y = max(1, Term::height/2 - 4 - (int)(help_text.size() / 2)); + x = Term::width/2 - 39; + height = min(Term::height - 6, (int)help_text.size() + 3); + pages = ceil((double)help_text.size() / (height - 3)); + page = 0; + bg = Draw::banner_gen(y, 0, true); + bg += Draw::createBox(x, y + 6, 78, height, Theme::c("hi_fg"), true, "help"); + } + else if (is_in(key, "escape", "q", "h", "backspace", "space", "enter", "mouse_click")) { + return Closed; + } + else if (pages > 1 and is_in(key, "down", "page_down", "tab", "mouse_scroll_down")) { + if (++page >= pages) page = 0; + } + else if (pages > 1 and is_in(key, "up", "page_up", "shift_tab", "mouse_scroll_up")) { + if (--page < 0) page = pages - 1; + } + else { + retval = NoChange; + } + + + if (retval == Changed) { + auto& out = Global::overlay; + out = bg; + if (pages > 1) { + out += Mv::to(y+height+6, x + 2) + Theme::c("hi_fg") + Symbols::title_left_down + Fx::b + Symbols::up + Theme::c("title") + " page " + + to_string(page+1) + '/' + to_string(pages) + ' ' + Theme::c("hi_fg") + Symbols::down + Fx::ub + Symbols::title_right_down; + } + auto cy = y+7; + out += Mv::to(cy++, x + 1) + Theme::c("title") + Fx::b + cjust("Key:", 20) + "Description:"; + for (int c = 0, i = max(0, (height - 3) * page); c++ < height - 3 and i < (int)help_text.size(); i++) { + out += Mv::to(cy++, x + 1) + Theme::c("hi_fg") + Fx::b + cjust(help_text[i][0], 20) + + Theme::c("main_fg") + Fx::ub + help_text[i][1]; + } + out += Fx::reset; + } + + + return (redraw ? Changed : retval); } //* Add menus here and update enum Menus in header @@ -382,10 +1252,12 @@ namespace Menu { Menu::active = false; Global::overlay.clear(); Global::overlay.shrink_to_fit(); + Runner::pause_output = false; bg.clear(); bg.shrink_to_fit(); currentMenu = -1; Runner::run("all", true, true); + mouse_mappings.clear(); return; } @@ -400,6 +1272,9 @@ namespace Menu { auto retCode = menuFunc.at(currentMenu)(key); if (retCode == Closed) { menuMask.reset(currentMenu); + mouse_mappings.clear(); + bg.clear(); + Runner::pause_output = false; process(); } else if (redraw) { @@ -408,5 +1283,12 @@ namespace Menu { } else if (retCode == Changed) Runner::run("overlay"); + else if (retCode == Switch) { + Runner::pause_output = false; + bg.clear(); + redraw = true; + mouse_mappings.clear(); + process(); + } } -} \ No newline at end of file +} diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index 53f4e71..de47912 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -45,6 +45,7 @@ namespace Menu { string box_contents, button_left, button_right; int height = 0, width = 0, boxtype = 0, selected = 0, x = 0, y = 0; public: + enum BoxTypes { OK, YES_NO, NO_YES }; enum msgReturn { Invalid, Ok_Yes, @@ -52,7 +53,7 @@ namespace Menu { Select }; msgBox(); - msgBox(int width, int boxtype, vector& content, string title); + msgBox(int width, int boxtype, vector content, string title); //? Draw and return box as a string string operator()(); @@ -65,7 +66,7 @@ namespace Menu { }; extern bitset<8> menuMask; - + //* Enum for functions in vector menuFuncs enum Menus { SignalChoose, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index 1225515..a340746 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -33,6 +33,7 @@ void term_resize(bool force=false); void banner_gen(); namespace Global { + extern const vector> Banner_src; extern const string Version; extern atomic quitting; extern string exit_error_msg; @@ -49,6 +50,8 @@ namespace Runner { extern atomic reading; extern atomic stopping; extern pthread_t runner_id; + extern bool pause_output; + extern string debug_bg; void run(const string& box="", const bool no_update=false, const bool force_redraw=false); void stop(); @@ -74,6 +77,8 @@ namespace Cpu { extern int x, y, width, height, min_width, min_height; extern bool shown, redraw, got_sensors, cpu_temp_only; extern string cpuName, cpuHz; + extern vector available_fields; + extern vector available_sensors; struct cpu_info { unordered_flat_map> cpu_percent = { diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index 73f86c3..a912eb2 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -345,6 +345,8 @@ namespace Theme { rgbs.clear(); gradients.clear(); colors = TTY_theme; + if (not Config::getB("theme_background")) + colors["main_bg"] = "\x1b[49m"; for (const auto& c : colors) { if (not c.first.ends_with("_start")) continue; @@ -401,10 +403,10 @@ namespace Theme { themes.push_back("Default"); themes.push_back("TTY"); - for (const auto& path : { theme_dir, user_theme_dir } ) { + for (const auto& path : { user_theme_dir, theme_dir } ) { if (path.empty()) continue; for (auto& file : fs::directory_iterator(path)) { - if (file.path().extension() == ".theme" and access(file.path().c_str(), R_OK) != -1) { + if (file.path().extension() == ".theme" and access(file.path().c_str(), R_OK) != -1 and not v_contains(themes, file.path().c_str())) { themes.push_back(file.path().c_str()); } } @@ -413,11 +415,18 @@ namespace Theme { } void setTheme() { - string theme = Config::getS("color_theme"); + const auto& theme = Config::getS("color_theme"); + fs::path theme_path; + for (const fs::path p : themes) { + if (p == theme or p.stem() == theme or p.filename() == theme) { + theme_path = p; + break; + } + } if (theme == "TTY" or Config::getB("tty_mode")) generateTTYColors(); else { - generateColors((theme == "Default" ? Default_theme : loadFile(theme))); + generateColors((theme == "Default" or theme_path.empty() ? Default_theme : loadFile(theme_path))); generateGradients(); } Term::fg = colors.at("main_fg"); diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index d299d80..27e0286 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -46,6 +46,7 @@ namespace Term { atomic width = 0; atomic height = 0; string current_tty; + char* custombuf; namespace { struct termios initial_settings; @@ -107,11 +108,21 @@ namespace Term { if (initialized) { tcgetattr(STDIN_FILENO, &initial_settings); current_tty = (string)ttyname(STDIN_FILENO); + + //? Disable stream sync cin.sync_with_stdio(false); + cout.sync_with_stdio(false); + + //? Disable stream ties cin.tie(NULL); + cout.tie(NULL); echo(false); linebuffered(false); refresh(); + + //? Set 1MB buffer for cout + std::cout.rdbuf()->pubsetbuf(custombuf, 1048576); + cout << alt_screen << hide_cursor << mouse_on << flush; Global::resized = false; } @@ -169,11 +180,10 @@ namespace Tools { } string s_replace(const string& str, const string& from, const string& to) { - size_t start_pos = str.find(from); - if(start_pos == std::string::npos) - return str; string out = str; - out.replace(start_pos, from.length(), to); + for (size_t start_pos = out.find(from); start_pos != std::string::npos; start_pos = out.find(from)) { + out.replace(start_pos, from.length(), to); + } return out; } diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index 430f9de..c8ad871 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -214,9 +214,9 @@ namespace Tools { return is_in(str, "true", "True"); } - //* Check if a string is a valid integer value + //* Check if a string is a valid integer value (only postive) inline bool isint(const string& str) { - return all_of(str.begin() + (str[0] == '-' ? 1 : 0), str.end(), ::isdigit); + return all_of(str.begin(), str.end(), ::isdigit); } //* Left-trim from and return new string diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 254ceb9cf..abc14aa 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -43,6 +43,7 @@ namespace Cpu { vector core_old_totals; vector core_old_idles; vector available_fields; + vector available_sensors = {"Auto"}; cpu_info current_cpu; fs::path freq_path = "/sys/devices/system/cpu/cpufreq/policy0/scaling_cur_freq"; bool got_sensors = false, cpu_temp_only = false; @@ -133,6 +134,9 @@ namespace Shared { } Cpu::cpuName = Cpu::get_cpuName(); Cpu::got_sensors = Cpu::get_sensors(); + for (const auto& [sensor, ignored] : Cpu::found_sensors) { + Cpu::available_sensors.push_back(sensor); + } Cpu::core_mapping = Cpu::get_core_mapping(); //? Init for namespace Mem diff --git a/themes/old-default.theme b/themes/old-default.theme deleted file mode 100644 index ff47168..0000000 --- a/themes/old-default.theme +++ /dev/null @@ -1,100 +0,0 @@ -#Bashtop theme with default colors and black background -#by aristocratos - -# Colors should be in 6 or 2 character hexadecimal or single spaced rgb decimal: "#RRGGBB", "#BW" or "0-255 0-255 0-255" -# example for white: "#FFFFFF", "#ff" or "255 255 255". - -# All graphs and meters can be gradients -# For single color graphs leave "mid" and "end" variable empty. -# Use "start" and "end" variables for two color gradient -# Use "start", "mid" and "end" for three color gradient - -# Main background, empty for terminal default, need to be empty if you want transparent background -theme[main_bg]="#00" - -# Main text color -theme[main_fg]="#cc" - -# Title color for boxes -theme[title]="#ee" - -# Highlight color for keyboard shortcuts -theme[hi_fg]="#90" - -# Background color of selected item in processes box -theme[selected_bg]="#7e2626" - -# Foreground color of selected item in processes box -theme[selected_fg]="#ee" - -# Color of inactive/disabled text -theme[inactive_fg]="#40" - -# Color of text appearing on top of graphs, i.e uptime and current network graph scaling -theme[graph_text]="#60" - -# Background color of the percentage meters -theme[meter_bg]="#40" - -# Misc colors for processes box including mini cpu graphs, details memory graph and details status text -theme[proc_misc]="#0de756" - -# Cpu box outline color -theme[cpu_box]="#3d7b46" - -# Memory/disks box outline color -theme[mem_box]="#8a882e" - -# Net up/down box outline color -theme[net_box]="#423ba5" - -# Processes box outline color -theme[proc_box]="#923535" - -# Box divider line and small boxes line color -theme[div_line]="#30" - -# Temperature graph colors -theme[temp_start]="#4897d4" -theme[temp_mid]="#5474e8" -theme[temp_end]="#ff40b6" - -# CPU graph colors -theme[cpu_start]="#50f095" -theme[cpu_mid]="#f2e266" -theme[cpu_end]="#fa1e1e" - -# Mem/Disk free meter -theme[free_start]="#223014" -theme[free_mid]="#b5e685" -theme[free_end]="#dcff85" - -# Mem/Disk cached meter -theme[cached_start]="#0b1a29" -theme[cached_mid]="#74e6fc" -theme[cached_end]="#26c5ff" - -# Mem/Disk available meter -theme[available_start]="#292107" -theme[available_mid]="#ffd77a" -theme[available_end]="#ffb814" - -# Mem/Disk used meter -theme[used_start]="#3b1f1c" -theme[used_mid]="#d9626d" -theme[used_end]="#ff4769" - -# Download graph colors -theme[download_start]="#231a63" -theme[download_mid]="#4f43a3" -theme[download_end]="#b0a9de" - -# Upload graph colors -theme[upload_start]="#510554" -theme[upload_mid]="#7d4180" -theme[upload_end]="#dcafde" - -# Process box color gradient for threads, mem and cpu usage -theme[process_start]="#80d0a3" -theme[process_mid]="#dcd179" -theme[process_end]="#d45454" \ No newline at end of file