diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 93639b7..fd2bf38 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -25,6 +25,8 @@ assignees: aristocratos **Info (please complete the following information):** - btop++ version: `bpytop -v` + - Binary: [self compiled or static binary from release] + - (If compiled) Compiler and version: - Architecture: [x86_64, aarch64, etc.] `uname -m` - Platform: [Linux, FreeBSD, OsX] - (Linux) Kernel: `uname -r` @@ -34,6 +36,18 @@ assignees: aristocratos **Additional context** -contents of `~/.config/btop/error.log` +contents of `~/.config/btop/btop.log` (try running btop with `--debug` flag if error.log is empty) + +**GDB Backtrace** + +If btop++ is crashing at start the following steps could be helpful: + +1. run `gdb btop` + +2. `r` to run, wait for crash and press enter + +3. `bt` to get backtrace + +4. Copy and paste the backtrace here: diff --git a/.gitignore b/.gitignore index 6772cda..3817f1b 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ stage/ build bin btop +.*/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e38a6d..7354364 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,61 @@ +## v1.0.9 + +* Added: ifstream check and try-catch for stod() in Tools::system_uptime() + +* Fixed: Freeze on cin.ignore() + +## v1.0.8 + +* Fixed: Additional NULL checks in UTF-8 detection + +* Changed: Makefile: Only look for g++-11 if CXX=g++ + +* Fixed: Missing NULL check for ttyname + +* Changed: Only log tty name if known + +## v1.0.7 + +* Fixed: Crash when opening menu at too small size + +* Fixed: Cores not constrained to cpu box and core numbers above 100 cut off + +* Fixed: Scrollbar position incorrect in small lists and selection not working when filtering + +## v1.0.6 + +* Fixed: Check that getenv("LANG") is not NULL in UTF-8 check + +* Fixed: Processes not completely hidden when collapsed in tree mode + +* Fixed: Changed wrong filename error.log to btop.log + +## v1.0.5 + +* Fixed: Load AVG sizing when hiding temperatures + +* Fixed: Sizing constraints bug on start and boxes can be toggled from size error screen + +* Fixed: UTF-8 check crashing if LANG was set to non existant locale + +## v1.0.4 + +* Fixed: Use /proc/pid/statm if RSS memory from /proc/pid/stat is faulty + +## v1.0.3 + +* Fixed: stoi 0 literal pointer to nullptr and added more clamping for gradient array access + +## v1.0.2 + +* Fixed: ARCH detection in Makefile + +* Fixed: Color gradient array out of bounds, added clamp 0-100 for cpu percent values + +* Fixed: Menu size and preset size issues and added warnings for small terminal size + +* Fixed: Options menu page selection alignment + ## v1.0.1 * Fixed: UTF-8 check to include UTF8 diff --git a/Makefile b/Makefile index 9bce83d..e6b4d44 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,20 @@ BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m█ override BTOP_VERSION := $(shell head -n100 src/btop.cpp 2>/dev/null | grep "Version =" | cut -f2 -d"\"" || echo " unknown") override TIMESTAMP := $(shell date +%s 2>/dev/null || echo "0") +ifneq ($(QUIET),true) + override PRE := info + override QUIET := false +else + override PRE := info-quiet +endif + PREFIX ?= /usr/local #? NOTICE! Manually set PLATFORM and ARCH if not compiling for host system PLATFORM ?= $(shell uname -s || echo unknown) -ARCH ?= $(shell uname -p || echo unknown) +ARCH ?= $(shell uname -m || echo unknown) #? Only enable fcf-protection if on x86_64 -ifeq ($(ARCH),unknown) - ARCH := $(shell uname -m || echo unknown) -endif ifeq ($(ARCH),x86_64) override ADDFLAGS += -fcf-protection endif @@ -35,7 +39,7 @@ CXX ?= g++ 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) +ifeq ($(CXX),g++) 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) @@ -92,9 +96,9 @@ SOURCES += $(shell find $(SRCDIR)/$(PLATFORM_DIR) -type f -name *.$(SRCEXT)) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) #? Default Make -all: pre directories btop +all: $(PRE) directories btop -pre: +info: @printf " $(BANNER)\n" @printf "\033[1;92mPLATFORM \033[1;93m?| \033[0m$(PLATFORM)\n" @printf "\033[1;96mARCH \033[1;93m?| \033[0m$(ARCH)\n" @@ -109,6 +113,10 @@ pre: @printf "\n\033[1;92mBuilding btop++ \033[93m(\033[97mv$(BTOP_VERSION)\033[93m)\033[0m\n" +info-quiet: + + @printf "\n\033[1;92mBuilding btop++ \033[91m(\033[97mv$(BTOP_VERSION)\033[91m) \033[93m$(PLATFORM) \033[96m$(ARCH)\033[0m\n" + help: @printf " $(BANNER)\n" @printf "\033[1;97mbtop++ makefile\033[0m\n" @@ -144,7 +152,7 @@ install: @printf "\033[1;92mInstalling doc to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop\n" @mkdir -p $(DESTDIR)$(PREFIX)/share/btop @cp -p README.md $(DESTDIR)$(PREFIX)/share/btop - @printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\n" + @printf "\033[1;92mInstalling themes to: \033[1;97m$(DESTDIR)$(PREFIX)/share/btop/themes\033[0m\n" @cp -pr themes $(DESTDIR)$(PREFIX)/share/btop #? Set SUID bit for btop as $SU_USER in $SU_GROUP @@ -169,17 +177,17 @@ uninstall: btop: $(OBJECTS) @sleep 0.1 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") - @printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n" + @$(QUIET) || printf "\n\033[1;92mLinking and optimizing binary\033[37m...\033[0m\n" @$(CXX) -o $(TARGETDIR)/btop $^ $(LDFLAGS) || exit 1 @printf "\033[1;92m-> \033[1;37m$(TARGETDIR)/btop \033[100D\033[35C\033[1;93m(\033[1;97m$$(du -ah $(TARGETDIR)/btop | cut -f1)iB\033[1;93m) \033[92m(\033[97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $${TSTAMP} 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo '')\033[92m)\033[0m\n" - @printf "\n\033[1;92mBuild complete in \033[92m(\033[97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n" + printf "\n\033[1;92mBuild complete in \033[92m(\033[97m$$(date -d @$$(expr $$(date +%s 2>/dev/null || echo "0") - $(TIMESTAMP) 2>/dev/null) -u +%Mm:%Ss 2>/dev/null | sed 's/^00m://' || echo "unknown")\033[92m)\033[0m\n" #? Compile .ONESHELL: $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) @sleep 0.1 2>/dev/null || true @TSTAMP=$$(date +%s 2>/dev/null || echo "0") - @printf "\033[1;97mCompiling $<\033[0m\n" + @$(QUIET) || printf "\033[1;97mCompiling $<\033[0m\n" @$(CXX) $(CXXFLAGS) $(INC) -c -o $@ $< || exit 1 @$(CXX) $(CXXFLAGS) $(INC) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT) >/dev/null || exit 1 @cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp diff --git a/README.md b/README.md index d432377..6278b0a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # ![btop++](Img/logo.png) + + Packaging status + + ![Linux](https://img.shields.io/badge/-Linux-grey?logo=linux) ![Usage](https://img.shields.io/badge/Usage-System%20resource%20monitor-yellow) ![c++20](https://img.shields.io/badge/cpp-c%2B%2B20-green) @@ -163,9 +167,9 @@ Also needs a UTF8 locale and a font that covers: ## Installation -**Binary release (statically compiled)** +**Binary release (statically compiled, for kernel 3.2.0 and newer)** -1. **Download btop-(VERSION)-(PLATFORM)-(ARCH).tbz from latest release and unpack to a new folder** +1. **Download btop-(VERSION)-(PLATFORM)-(ARCH).tbz from [latest release](https://github.com/aristocratos/btop/releases/latest) and unpack to a new folder** 2. **Install (from created folder)** @@ -227,9 +231,13 @@ Also needs a UTF8 locale and a font that covers: Append `STATIC=true` to `make` command for static compilation. + Append `QUIET=true` for less verbose output. + Notice! Manually set `$ARCH` variable if cross-compiling - Use `$ADDFLAGS` variable for appending flags to both compiler and linker. + Use `ADDFLAGS` variable for appending flags to both compiler and linker. + + For example: `make ADDFLAGS=-march=native` might give a performance boost if compiling only for your own system. ``` bash make @@ -276,22 +284,27 @@ Also needs a UTF8 locale and a font that covers: ```bash make help ``` - + ## Install the snap [![btop](https://snapcraft.io/btop/badge.svg)](https://snapcraft.io/btop) - `sudo snap install btop` + * **Install the snap** + + ```bash + sudo snap install btop + ``` * **Connect the interfaces** - ``` - sudo snap connect btop:system-observe + + ```bash + sudo snap connect btop:system-observe sudo snap connect btop:physical-memory-observe - sudo snap connect btop:mount-observe + sudo snap connect btop:mount-observe sudo snap connect btop:hardware-observe sudo snap connect btop:network-observe sudo snap connect btop:process-control ``` - + ## Configurability @@ -485,7 +498,7 @@ net_iface = "br0" #* Show battery stats in top right if battery is present. show_battery = True -#* Set loglevel for "~/.config/btop/error.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG". +#* Set loglevel for "~/.config/btop/btop.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG". #* The level set includes all lower levels, i.e. "DEBUG" will show all logging info. log_level = "DEBUG" ``` @@ -501,7 +514,7 @@ optional arguments: -lc, --low-color disable truecolor, converts 24-bit colors to 256-color -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols +t, --tty_off force (OFF) tty mode - -p --preset start with preset, integer value between 0-9 + -p, --preset start with preset, integer value between 0-9 --utf-force force start even if no UTF-8 locale was detected --debug start in DEBUG mode: shows microsecond timer for information collect and screen draw functions and sets loglevel to DEBUG diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3b887ec..d41dbf4 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -25,16 +25,16 @@ architectures: package-repositories: - type: apt ppa: ubuntu-toolchain-r/test - + apps: btop: command: usr/local/bin/btop - command-chain: - - bin/homeishome-launch + command-chain: + - bin/homeishome-launch environment: LC_ALL: C.UTF-8 - LANG: C.UTF-8 - plugs: + LANG: C.UTF-8 + plugs: - mount-observe - process-control - system-observe @@ -43,30 +43,28 @@ apps: - network-observe - physical-memory-observe - home - + parts: - btop: + btop: source: https://github.com/aristocratos/btop source-type: git plugin: make make-parameters: - PREFIX=/usr/local - STATIC=true - build-packages: - - coreutils - - sed - - git - - build-essential - - gcc-11 - - g++-11 - + - coreutils + - sed + - git + - build-essential + - gcc-11 + - g++-11 override-pull: | snapcraftctl pull snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1)" - + homeishome-launch: plugin: nil stage-snaps: - - homeishome-launch - + - homeishome-launch + diff --git a/src/btop.cpp b/src/btop.cpp index ec12c12..af2ffcf 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -55,7 +55,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.0.0"; + const string Version = "1.0.9"; int coreCount; string overlay; @@ -79,6 +79,7 @@ namespace Global { uint64_t start_time; atomic resized (false); + atomic resizing (false); atomic quitting (false); atomic _runner_started (false); @@ -100,7 +101,7 @@ void argumentParser(const int& argc, char **argv) { << " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n" << " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n" << " +t, --tty_off force (OFF) tty mode\n" - << " -p --preset start with preset, integer value between 0-9\n" + << " -p, --preset start with preset, integer value between 0-9\n" << " --utf-force force start even if no UTF-8 locale was detected\n" << " --debug start in DEBUG mode: shows microsecond timer for information collect\n" << " and screen draw functions and sets loglevel to DEBUG\n" @@ -149,13 +150,17 @@ 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) { + if (Global::resizing) return; + atomic_lock lck(Global::resizing); if (auto refreshed = Term::refresh(); refreshed or force) { if (force and refreshed) force = false; } else return; + static const array all_boxes = {"cpu", "mem", "net", "proc"}; Global::resized = true; - Runner::stop(); + if (Runner::active) Runner::stop(); + Config::unlock(); auto boxes = Config::getS("shown_boxes"); auto min_size = Term::get_min_size(boxes); @@ -171,7 +176,16 @@ void term_resize(bool force) { << "Needed for current config:" << Mv::to((Term::height / 2) + 2, (Term::width / 2) - 10) << "Width = " << min_size.at(0) << " Height = " << min_size.at(1) << flush; while (not Term::refresh() and not Input::poll()) sleep_ms(10); - if (Input::poll() and Input::get() == "q") exit(0); + if (Input::poll()) { + auto key = Input::get(); + if (key == "q") + exit(0); + else if (is_in(key, "1", "2", "3", "4")) { + Config::current_preset = -1; + Config::toggle_box(all_boxes.at(std::stoi(key) - 1)); + boxes = Config::getS("shown_boxes"); + } + } min_size = Term::get_min_size(boxes); } else if (not Term::refresh()) break; @@ -644,8 +658,8 @@ int main(int argc, char **argv) { //? Setup paths for config, log and user themes for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) { - if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) { - Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); + if (std::getenv(env) != NULL and access(std::getenv(env), W_OK) != -1) { + Config::conf_dir = fs::path(std::getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop"); break; } } @@ -704,22 +718,28 @@ int main(int argc, char **argv) { } //? Try to find and set a UTF-8 locale - if (bool found = false; not str_to_upper(s_replace(string(std::setlocale(LC_ALL, NULL)), "-", "")).ends_with("UTF8")) { - if (const string lang = (string)getenv("LANG"); str_to_upper(s_replace(lang, "-", "")).ends_with("UTF8")) { - found = true; - std::setlocale(LC_ALL, lang.c_str()); + if (bool found = false; std::setlocale(LC_ALL, NULL) == NULL or not str_to_upper(s_replace((string)std::setlocale(LC_ALL, NULL), "-", "")).ends_with("UTF8")) { + if (std::getenv("LANG") != NULL and str_to_upper(s_replace((string)std::getenv("LANG"), "-", "")).ends_with("UTF8")) { + if (std::setlocale(LC_ALL, std::getenv("LANG")) != NULL) { + found = true; + } } - else if (const string loc = std::locale("").name(); not loc.empty()) { - try { - for (auto& l : ssplit(loc, ';')) { - if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) { - found = true; - std::setlocale(LC_ALL, l.substr(l.find('=') + 1).c_str()); - break; + else { + if (setenv("LANG", "", 1) == 0) { + try { + if (const auto loc = std::locale("").name(); not loc.empty() and loc != "*") { + for (auto& l : ssplit(loc, ';')) { + if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) { + if (std::setlocale(LC_ALL, l.substr(l.find('=') + 1).c_str()) != NULL) { + found = true; + } + break; + } + } } } + catch (...) { found = false; } } - catch (const std::out_of_range&) { found = false; } } if (not found and Global::utf_force) @@ -738,7 +758,7 @@ int main(int argc, char **argv) { exit(1); } - Logger::info("Running on " + Term::current_tty); + if (Term::current_tty != "unknown") Logger::info("Running on " + Term::current_tty); if (not Global::arg_tty and Config::getB("force_tty")) { Config::set("tty_mode", true); Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols"); @@ -777,20 +797,19 @@ int main(int argc, char **argv) { Config::current_preset = min(Global::arg_preset, (int)Config::preset_list.size() - 1); Config::apply_preset(Config::preset_list.at(Config::current_preset)); } - Draw::calcSizes(); { const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes")); if (Term::height < y or Term::width < x) { - Global::exit_error_msg = "Terminal size to small for current config.\n(WxH) Current: " + to_string(Term::width) - + 'x' + to_string(Term::height) + " | Needed: " + to_string(x) + 'x' + to_string(y) - + "\nResize terminal or disable some boxes in config file to fix this."; - exit(1); + term_resize(true); + Global::resized = false; + Input::interrupt = false; } } //? Print out box outlines + Draw::calcSizes(); cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush; diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 077ef93..2a1bbc0 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -167,7 +167,7 @@ namespace Config { {"show_battery", "#* Show battery stats in top right if battery is present."}, - {"log_level", "#* Set loglevel for \"~/.config/btop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" + {"log_level", "#* Set loglevel for \"~/.config/btop/btop.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n" "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."} }; @@ -224,7 +224,7 @@ namespace Config { {"swap_disk", true}, {"show_disks", true}, {"only_physical", true}, - {"use_fstab", false}, + {"use_fstab", true}, {"show_io_stat", true}, {"io_mode", false}, {"io_graph_combined", false}, @@ -306,15 +306,26 @@ namespace Config { //* Apply selected preset void apply_preset(const string& preset) { string boxes; + for (const auto& box : ssplit(preset, ',')) { const auto& vals = ssplit(box, ':'); boxes += vals.at(0) + ' '; + } + if (not boxes.empty()) boxes.pop_back(); + + auto min_size = Term::get_min_size(boxes); + if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) { + return; + } + + for (const auto& box : ssplit(preset, ',')) { + const auto& vals = ssplit(box, ':'); if (vals.at(0) == "cpu") set("cpu_bottom", (vals.at(1) == "0" ? false : true)); else if (vals.at(0) == "mem") set("mem_below_net", (vals.at(1) == "0" ? false : true)); else if (vals.at(0) == "proc") set("proc_left", (vals.at(1) == "0" ? false : true)); set("graph_symbol_" + vals.at(0), vals.at(2)); } - if (not boxes.empty()) boxes.pop_back(); + if (check_boxes(boxes)) set("shown_boxes", boxes); } @@ -475,6 +486,7 @@ namespace Config { } void toggle_box(const string& box) { + auto old_boxes = current_boxes; auto box_pos = rng::find(current_boxes, box); if (box_pos == current_boxes.end()) current_boxes.push_back(box); @@ -486,6 +498,14 @@ namespace Config { for (const auto& b : current_boxes) new_boxes += b + ' '; new_boxes.pop_back(); } + + auto min_size = Term::get_min_size(new_boxes); + + if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) { + current_boxes = old_boxes; + return; + } + Config::set("shown_boxes", new_boxes); } diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 2e844a3..91a239a 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -396,7 +396,7 @@ namespace Draw { out.clear(); if (height == 1) { if (not color_gradient.empty()) - out += (last < 1 and not color_gradient.empty() ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(last)); + out += (last < 1 and not color_gradient.empty() ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(clamp(last, 0ll, 100ll))); out += graphs.at(current).at(0); } else { @@ -618,7 +618,7 @@ namespace Cpu { + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(cpu.cpu_percent.at("total").back()) - + Theme::g("cpu").at(cpu.cpu_percent.at("total").back()) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; + + Theme::g("cpu").at(clamp(cpu.cpu_percent.at("total").back(), 0ll, 100ll)) + rjust(to_string(cpu.cpu_percent.at("total").back()), 4) + Theme::c("main_fg") + '%'; if (show_temps) { const auto [temp, unit] = celsius_to(cpu.temp.at(0).back(), temp_scale); const auto& temp_color = Theme::g("temp").at(clamp(cpu.temp.at(0).back() * 100 / cpu.temp_max, 0ll, 100ll)); @@ -632,15 +632,16 @@ namespace Cpu { } catch (const std::exception& e) { throw std::runtime_error("graphs, clock, meter : " + (string)e.what()); } //? Core text and graphs - int cx = 0, cy = 1, cc = 0; + int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); + if (Shared::coreCount >= 100) core_width++; for (const auto& n : iota(0, Shared::coreCount)) { out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") - + ljust(to_string(n), (b_column_size == 0 ? 2 : 3)); + + ljust(to_string(n), core_width); if (b_column_size > 0 or extra_width > 0) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) - + Theme::g("cpu").at(cpu.core_percent.at(n).back()) + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); + + Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)) + core_graphs.at(n)(cpu.core_percent.at(n), data_same or redraw); else - out += Theme::g("cpu").at(cpu.core_percent.at(n).back()); + out += Theme::g("cpu").at(clamp(cpu.core_percent.at(n).back(), 0ll, 100ll)); out += rjust(to_string(cpu.core_percent.at(n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; if (show_temps and not hide_cores) { @@ -654,7 +655,7 @@ namespace Cpu { out += Theme::c("div_line") + Symbols::v_line; - if (++cy > ceil((double)Shared::coreCount / b_columns) and n != Shared::coreCount - 1) { + if ((++cy > ceil((double)Shared::coreCount / b_columns) or cy == b_height - 2) and n != Shared::coreCount - 1) { if (++cc >= b_columns) break; cy = 1; cx = (b_width / b_columns) * cc; } @@ -664,9 +665,9 @@ namespace Cpu { if (cy < b_height - 1 and cc <= b_columns) { string lavg_pre; int sep = 1; - if (b_column_size == 2 and got_sensors) { lavg_pre = "Load AVG:"; sep = 3; } - else if (b_column_size == 2 or (b_column_size == 1 and got_sensors)) { lavg_pre = "LAV:"; } - else if (b_column_size == 1 or (b_column_size == 0 and got_sensors)) { lavg_pre = "L"; } + if (b_column_size == 2 and show_temps) { lavg_pre = "Load AVG:"; sep = 3; } + else if (b_column_size == 2 or (b_column_size == 1 and show_temps)) { lavg_pre = "LAV:"; } + else if (b_column_size == 1 or (b_column_size == 0 and show_temps)) { lavg_pre = "L"; } string lavg; for (const auto& val : cpu.load_avg) { lavg += string(sep, ' ') + (lavg_pre.size() < 3 ? to_string((int)round(val)) : to_string(val).substr(0, 4)); @@ -860,7 +861,7 @@ 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)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(max(50ll, disk.io_activity.back())) + 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(clamp(disk.io_activity.back(), 50ll, 100ll)) + 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) { @@ -901,7 +902,7 @@ 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)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(max(50ll, disk.io_activity.back())) + 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(clamp(disk.io_activity.back(), 50ll, 100ll)) + 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; @@ -1331,11 +1332,8 @@ namespace Proc { + 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, (redraw or data_same or not alive)) + ' ' + Theme::c("title") + Fx::b + detailed.memory; - - } - //? Check bounds of current selection and view if (start > 0 and numpids <= select_max) start = 0; @@ -1349,7 +1347,7 @@ namespace Proc { //* Iteration over processes int lc = 0; for (int n=0; auto& p : plist) { - if (n++ < start or p.filtered) continue; + if (p.filtered or (proc_tree and p.tree_index == plist.size()) or n++ < start) continue; bool is_selected = (lc + 1 == selected); if (is_selected) { selected_pid = (int)p.pid; @@ -1388,11 +1386,11 @@ namespace Proc { for (int i = 0; int v : {(int)round(p.cpu_p), (int)round(p.mem * 100 / Shared::totalMem), (int)p.threads / 3}) { if (proc_gradient) { int val = (min(v, 100) + 100) - calc * 100 / select_max; - if (val < 100) colors[i++] = Theme::g("proc_color").at(val); - else colors[i++] = Theme::g("process").at(val - 100); + if (val < 100) colors[i++] = Theme::g("proc_color").at(max(0, val)); + else colors[i++] = Theme::g("process").at(clamp(val - 100, 0, 100)); } else - colors[i++] = Theme::g("process").at(min(v, 100)); + colors[i++] = Theme::g("process").at(clamp(v, 0, 100)); } c_color = colors.at(0); m_color = colors.at(1); t_color = colors.at(2); } @@ -1401,7 +1399,7 @@ namespace Proc { end = Fx::ub; } if (proc_gradient) { - g_color = Theme::g("proc").at(calc * 100 / select_max); + g_color = Theme::g("proc").at(clamp(calc * 100 / select_max, 0, 100)); } } @@ -1433,9 +1431,10 @@ namespace Proc { if (p.cpu_p < 10 or p.cpu_p >= 100) cpu_str.resize(3); string mem_str = (mem_bytes ? floating_humanizer(p.mem, true) : ""); if (not mem_bytes) { - double mem_p = (double)p.mem * 100 / Shared::totalMem; + double mem_p = clamp((double)p.mem * 100 / Shared::totalMem, 0.0, 100.0); mem_str = to_string(mem_p); - mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); + if (mem_str.size() < 4) mem_str = "0"; + else mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); mem_str += '%'; } out += (thread_size > 0 ? t_color + rjust(to_string(min(p.threads, (size_t)9999)), thread_size) + ' ' + end : "" ) @@ -1452,7 +1451,7 @@ namespace Proc { //? Draw scrollbar if needed if (numpids > select_max) { - const int scroll_pos = clamp((int)round((double)start * (select_max - 2) / (numpids - (select_max - 2))), 0, height - 5); + const int scroll_pos = clamp((int)round((double)start * select_max / (numpids - select_max)), 0, height - 5); out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + Symbols::up + Mv::to(y + height - 2, x + width - 2) + Symbols::down + Mv::to(y + 2 + scroll_pos, x + width - 2) + "█"; diff --git a/src/btop_input.cpp b/src/btop_input.cpp index f4ffd3d..2056bea 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -95,7 +95,7 @@ namespace Input { string get() { string key; while (cin.rdbuf()->in_avail() > 0 and key.size() < 100) key += cin.get(); - if (cin.rdbuf()->in_avail() > 0) cin.ignore(SSmax); + if (cin.rdbuf()->in_avail() > 0) cin.ignore(cin.rdbuf()->in_avail()); if (not key.empty()) { //? Remove escape code prefix if present if (key.substr(0, 2) == Fx::e) { @@ -351,7 +351,6 @@ 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::show(Menu::Menus::SignalChoose); diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 5793cb8..3128544 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -757,6 +757,28 @@ namespace Menu { return (redraw ? Changed : retval); } + int sizeError(const string& key) { + if (redraw) { + vector cont_vec; + cont_vec.push_back(Fx::b + Theme::g("used")[100] + "Error:" + Theme::c("main_fg") + Fx::ub); + cont_vec.push_back("Terminal size to small to" + Fx::reset); + cont_vec.push_back("display menu or box!" + Fx::reset); + + messageBox = Menu::msgBox{45, 0, cont_vec, "error"}; + Global::overlay = messageBox(); + } + + auto ret = messageBox.input(key); + if (ret == msgBox::Ok_Yes or ret == msgBox::No_Esc) { + messageBox.clear(); + return Closed; + } + else if (redraw) { + return Changed; + } + return NoChange; + } + int signalSend(const string& key) { auto& s_pid = (Config::getB("show_detailed") and Config::getI("selected_pid") == 0 ? Config::getI("detailed_pid") : Config::getI("selected_pid")); if (s_pid == 0) return Closed; @@ -1168,7 +1190,7 @@ namespace Menu { 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 " + out += Mv::to(y+6 + height - 1, 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; } //? Option name and value @@ -1286,6 +1308,7 @@ namespace Menu { //* Add menus here and update enum Menus in header const auto menuFunc = vector{ + ref(sizeError), ref(signalChoose), ref(signalSend), ref(signalReturn), @@ -1312,9 +1335,17 @@ namespace Menu { if (currentMenu < 0 or not menuMask.test(currentMenu)) { Menu::active = true; redraw = true; + if (((menuMask.test(Main) or menuMask.test(Options) or menuMask.test(Help) or menuMask.test(SignalChoose)) + and (Term::width < 80 or Term::height < 24)) + or (Term::width < 50 or Term::height < 20)) { + menuMask.reset(); + menuMask.set(SizeError); + } + for (const auto& i : iota(0, (int)menuMask.size())) { if (menuMask.test(i)) currentMenu = i; } + } auto retCode = menuFunc.at(currentMenu)(key); diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index c57dcea..b4fcab1 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -69,6 +69,7 @@ namespace Menu { //* Enum for functions in vector menuFuncs enum Menus { + SizeError, SignalChoose, SignalSend, SignalReturn, diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index e54465f..4d0f323 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -70,6 +70,7 @@ namespace Shared { void init(); extern long coreCount, page_size, clk_tck; + extern int totalMem_len; extern uint64_t totalMem; } diff --git a/src/btop_theme.cpp b/src/btop_theme.cpp index a912eb2..116e78b 100644 --- a/src/btop_theme.cpp +++ b/src/btop_theme.cpp @@ -159,7 +159,7 @@ namespace Theme { string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;"); if (hexa.size() == 2) { - int h_int = stoi(hexa, 0, 16); + int h_int = stoi(hexa, nullptr, 16); if (t_to_256) { return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m"; } else { @@ -170,14 +170,14 @@ namespace Theme { else if (hexa.size() == 6) { if (t_to_256) { return pre + to_string(truecolor_to_256( - stoi(hexa.substr(0, 2), 0, 16), - stoi(hexa.substr(2, 2), 0, 16), - stoi(hexa.substr(4, 2), 0, 16))) + "m"; + stoi(hexa.substr(0, 2), nullptr, 16), + stoi(hexa.substr(2, 2), nullptr, 16), + stoi(hexa.substr(4, 2), nullptr, 16))) + "m"; } else { return pre + - to_string(stoi(hexa.substr(0, 2), 0, 16)) + ";" + - to_string(stoi(hexa.substr(2, 2), 0, 16)) + ";" + - to_string(stoi(hexa.substr(4, 2), 0, 16)) + "m"; + to_string(stoi(hexa.substr(0, 2), nullptr, 16)) + ";" + + to_string(stoi(hexa.substr(2, 2), nullptr, 16)) + ";" + + to_string(stoi(hexa.substr(4, 2), nullptr, 16)) + "m"; } } else Logger::error("Invalid size of hex value: " + hexa); @@ -203,14 +203,14 @@ namespace Theme { for (auto& c : hexa) if (not isxdigit(c)) return array{-1, -1, -1}; if (hexa.size() == 2) { - int h_int = stoi(hexa, 0, 16); + int h_int = stoi(hexa, nullptr, 16); return array{h_int, h_int, h_int}; } else if (hexa.size() == 6) { return array{ - stoi(hexa.substr(0, 2), 0, 16), - stoi(hexa.substr(2, 2), 0, 16), - stoi(hexa.substr(4, 2), 0, 16) + stoi(hexa.substr(0, 2), nullptr, 16), + stoi(hexa.substr(2, 2), nullptr, 16), + stoi(hexa.substr(4, 2), nullptr, 16) }; } } @@ -434,4 +434,4 @@ namespace Theme { Fx::reset = Fx::reset_base + Term::fg + Term::bg; } -} \ No newline at end of file +} diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 74df467..d801fe1 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -107,7 +107,7 @@ namespace Term { initialized = (bool)isatty(STDIN_FILENO); if (initialized) { tcgetattr(STDIN_FILENO, &initial_settings); - current_tty = (string)ttyname(STDIN_FILENO); + current_tty = (ttyname(STDIN_FILENO) != NULL ? (string)ttyname(STDIN_FILENO) : "unknown"); //? Disable stream sync cin.sync_with_stdio(false); diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index cc72370..3c8b9fe 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -80,6 +80,7 @@ namespace Shared { fs::path procPath, passwd_path; uint64_t totalMem; long pageSize, clkTck, coreCount; + int totalMem_len; void init() { @@ -114,6 +115,7 @@ namespace Shared { if (meminfo.good()) { meminfo.ignore(SSmax, ':'); meminfo >> totalMem; + totalMem_len = to_string(totalMem).size(); totalMem <<= 10; } if (not meminfo.good() or totalMem == 0) @@ -1168,6 +1170,7 @@ namespace Proc { out_procs.back().get().cpu_p += p.cpu_p; out_procs.back().get().mem += p.mem; out_procs.back().get().threads += p.threads; + filter_found++; } if (collapsed and not filtering) { cur_proc.filtered = true; @@ -1201,7 +1204,7 @@ namespace Proc { //? 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)); + detailed.cpu_percent.push_back(clamp((long long)round(detailed.entry.cpu_p), 0ll, 100ll)); while (cmp_greater(detailed.cpu_percent.size(), width)) detailed.cpu_percent.pop_front(); //? Process runtime @@ -1451,7 +1454,10 @@ namespace Proc { next_x = 24; continue; case 24: //? RSS memory (can be inaccurate, but parsing smaps increases total cpu usage by ~20x) - new_proc.mem = stoull(short_str) * Shared::pageSize; + if (cmp_greater(short_str.size(), Shared::totalMem_len)) + new_proc.mem = Shared::totalMem; + else + new_proc.mem = stoull(short_str) * Shared::pageSize; } break; } @@ -1464,8 +1470,18 @@ namespace Proc { if (x-offset < 24) continue; + //? Get RSS memory from /proc/[pid]/statm if value from /proc/[pid]/stat looks wrong + if (new_proc.mem >= Shared::totalMem) { + pread.open(d.path() / "statm"); + if (not pread.good()) continue; + pread.ignore(SSmax, ' '); + pread >> new_proc.mem; + new_proc.mem *= Shared::pageSize; + pread.close(); + } + //? Process cpu usage since last update - new_proc.cpu_p = round(cmult * 1000 * (cpu_t - new_proc.cpu_t) / max((uint64_t)1, cputimes - old_cputimes)) / 10.0; + new_proc.cpu_p = clamp(round(cmult * 1000 * (cpu_t - new_proc.cpu_t) / max((uint64_t)1, cputimes - old_cputimes)) / 10.0, 0.0, 100.0 * Shared::coreCount); //? Process cumulative cpu usage since process start new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - new_proc.cpu_s); @@ -1595,8 +1611,15 @@ namespace Tools { double system_uptime() { string upstr; ifstream pread(Shared::procPath / "uptime"); - getline(pread, upstr, ' '); - pread.close(); - return stod(upstr); + if (pread.good()) { + try { + getline(pread, upstr, ' '); + pread.close(); + return stod(upstr); + } + catch (const std::invalid_argument&) {} + catch (const std::out_of_range&) {} + } + throw std::runtime_error("Failed get uptime from from " + (string)Shared::procPath + "/uptime"); } } \ No newline at end of file