From 650df9ac39d014517696c4b639ddc4a83b0569f9 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Fri, 17 Sep 2021 14:25:54 +0200 Subject: [PATCH] Added collect for battery --- CHANGELOG.md | 3 + Makefile | 8 +- src/btop.cpp | 33 ++++++-- src/btop_config.cpp | 7 +- src/btop_draw.cpp | 66 ++++++++++----- src/btop_draw.hpp | 2 +- src/btop_input.cpp | 26 ++---- src/btop_menu.cpp | 47 +++++++++-- src/btop_menu.hpp | 3 + src/btop_shared.hpp | 24 ++++-- src/btop_tools.cpp | 2 +- src/linux/btop_collect.cpp | 169 ++++++++++++++++++++++++++++++++----- 12 files changed, 302 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..19d960a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## v0.9.0 + +* Test release \ No newline at end of file diff --git a/Makefile b/Makefile index 5dbd42a..fda7136 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -#* Btop++ makefile v1.0 +#* Btop++ makefile v1.2 BANNER = \n \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m████████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗ \033[38;5;196m██████\033[38;5;240m╗\n \033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗╚══\033[38;5;160m██\033[38;5;239m╔══╝\033[38;5;160m██\033[38;5;239m╔═══\033[38;5;160m██\033[38;5;239m╗\033[38;5;160m██\033[38;5;239m╔══\033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗ \033[38;5;160m██\033[38;5;239m╗\n \033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║ \033[38;5;124m██\033[38;5;238m║\033[38;5;124m██████\033[38;5;238m╔╝ \033[38;5;124m██████\033[38;5;238m╗\033[38;5;124m██████\033[38;5;238m╗\n \033[38;5;88m██\033[38;5;237m╔══\033[38;5;88m██\033[38;5;237m╗ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║ \033[38;5;88m██\033[38;5;237m║\033[38;5;88m██\033[38;5;237m╔═══╝ ╚═\033[38;5;88m██\033[38;5;237m╔═╝╚═\033[38;5;88m██\033[38;5;237m╔═╝\n \033[38;5;52m██████\033[38;5;236m╔╝ \033[38;5;52m██\033[38;5;236m║ ╚\033[38;5;52m██████\033[38;5;236m╔╝\033[38;5;52m██\033[38;5;236m║ ╚═╝ ╚═╝\n \033[38;5;235m╚═════╝ ╚═╝ ╚═════╝ ╚═╝ \033[1;3;38;5;240mMakefile v1.2\033[0m @@ -16,7 +16,7 @@ ifeq ($(ARCH),unknown) ARCH := $(shell uname -m || echo unknown) endif ifeq ($(ARCH),x86_64) - ADDFLAGS := -fcf-protection + override ADDFLAGS += -fcf-protection endif #? Make sure PLATFORM Darwin is OSX and not Darwin @@ -72,9 +72,9 @@ OBJEXT := o #? Flags, Libraries and Includes override REQFLAGS := -std=c++20 -WARNFLAGS := -Wall -Wextra -pedantic -pedantic-errors -Wfatal-errors +WARNFLAGS := -Wall -Wextra -pedantic OPTFLAGS ?= -O2 -ftree-loop-vectorize -flto=$(THREADS) -LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection $(ADDFLAGS) +LDCXXFLAGS := -pthread -D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector -fstack-clash-protection -static $(ADDFLAGS) override CXXFLAGS += $(REQFLAGS) $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) override LDFLAGS += $(LDCXXFLAGS) $(OPTFLAGS) $(WARNFLAGS) INC := -I$(INCDIR) -I$(SRCDIR) diff --git a/src/btop.cpp b/src/btop.cpp index bd1a41e..42336dd 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -239,6 +239,7 @@ namespace Runner { atomic active (false); atomic stopping (false); atomic waiting (false); + atomic redraw (false); //* Setup semaphore for triggering thread to do work #if __GNUC__ < 11 @@ -265,6 +266,7 @@ namespace Runner { }; string output; + string empty_bg; bool pause_output = false; sigset_t mask; pthread_t runner_id; @@ -358,14 +360,14 @@ namespace Runner { continue; } - //? Atomic lock used for blocking non-thread safe actions in main thread + //? Atomic lock used for blocking non thread-safe actions in main thread atomic_lock lck(active); auto& conf = current_conf; //! DEBUG stats if (Global::debug) { - if (debug_bg.empty()) Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug"); + if (debug_bg.empty() or redraw) Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug"); debug_times.clear(); debug_times["total"] = {0, 0}; } @@ -508,8 +510,28 @@ namespace Runner { continue; } + if (redraw or conf.force_redraw) { + empty_bg.clear(); + redraw = false; + } + if (not pause_output) output += conf.clock; if (not conf.overlay.empty() and not conf.background_update) pause_output = true; + if (output.empty() and not pause_output) { + if (empty_bg.empty()) { + const int x = Term::width / 2 - 10, y = Term::height / 2 - 10; + output += Term::clear; + empty_bg += Draw::banner_gen(y, 0, true) + + Mv::to(y+6, x) + Theme::c("title") + Fx::b + "No boxes shown!" + + Mv::to(y+8, x) + Theme::c("hi_fg") + "1" + Theme::c("main_fg") + " | Show CPU box" + + Mv::to(y+9, x) + Theme::c("hi_fg") + "2" + Theme::c("main_fg") + " | Show MEM box" + + Mv::to(y+10, x) + Theme::c("hi_fg") + "3" + Theme::c("main_fg") + " | Show NET box" + + Mv::to(y+11, x) + Theme::c("hi_fg") + "4" + Theme::c("main_fg") + " | Show PROC box" + + Mv::to(y+12, x-2) + Theme::c("hi_fg") + "esc" + Theme::c("main_fg") + " | Show menu" + + Mv::to(y+13, x) + Theme::c("hi_fg") + "q" + Theme::c("main_fg") + " | Quit"; + } + output += empty_bg; + } //! DEBUG stats --> if (Global::debug and not Menu::active) { @@ -546,14 +568,11 @@ namespace Runner { else if (box == "clock") { cout << Term::sync_start << Global::clock << Term::sync_end << flush; } - else if (Config::current_boxes.empty()) { - cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end << flush; - } else { Config::unlock(); Config::lock(); - //? Setup bitmask for selected boxes instead of parsing strings in _runner thread loop + //? Setup bitmask for selected boxes and pass to _runner thread bitset<8> box_mask; for (const auto& box : (box == "all" ? Config::current_boxes : vector{box})) { box_mask |= box_bits.at(box); @@ -767,7 +786,7 @@ int main(int argc, char **argv) { //? Trigger secondary thread to redraw if terminal has been resized if (Global::resized) { Draw::calcSizes(); - Draw::update_clock(); + Draw::update_clock(true); Global::resized = false; if (Menu::active) Menu::process(); else Runner::run("all", true, true); diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 9773e01..aeec5b7 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -109,6 +109,7 @@ namespace Config { {"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."}, {"cpu_core_map", "#* Set a custom mapping between core and coretemp, can be needed on certain cpus to get correct temperature for correct core.\n" + "#* Use lm-sensors or similar to see which cores are reporting temperatures on your machine.\n" "#* Format \"x:y\" x=core with wrong temp, y=core with correct temp, use space as separator between multiple entries.\n" "#* Example: \"4:0 5:1 6:3\""}, @@ -146,7 +147,7 @@ namespace Config { {"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."}, - {"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (10 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n" + {"io_graph_speeds", "#* Set the top speed for the io graphs in MiB/s (100 by default), use format \"mountpoint:speed\" separate disks with whitespace \" \".\n" "#* Example: \"/mnt/media:100 /:20 /boot:1\"."}, {"net_download", "#* Set fixed values for network graphs in Mebibits. Is only used if net_auto is also set to False."}, @@ -276,6 +277,10 @@ namespace Config { validError = "Value out of range!"; return false; } + catch (const std::exception& e) { + validError = (string)e.what(); + return false; + } if (name == "update_ms" and i_value < 100) validError = "Config value update_ms set too low (<100)."; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 1bd8fa8..148b7e5 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -261,7 +261,7 @@ namespace Draw { return out + Fx::reset + Mv::to(y + 1, x + 1); } - bool update_clock() { + bool update_clock(bool force) { 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(); @@ -275,16 +275,15 @@ namespace Draw { }; static time_t c_time = 0; static size_t clock_len = 0; - static string old_clock; - string new_clock; + static string clock_str; - if (auto n_time = time(NULL); n_time == c_time) + if (auto n_time = time(NULL); not force and 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; + const auto new_clock = Tools::strf_time(clock_format); + if (not force and new_clock == clock_str) return false; + clock_str = new_clock; } auto& out = Global::clock; @@ -297,29 +296,30 @@ namespace Draw { for (const auto& [c_format, replacement] : clock_custom_format) { - if (s_contains(new_clock, c_format)) { + if (s_contains(clock_str, 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); + clock_str = s_replace(clock_str, c_format, upstr); } else { - new_clock = s_replace(new_clock, c_format, replacement); + clock_str = s_replace(clock_str, c_format, replacement); } } } - new_clock = uresize(new_clock, std::max(0, width - 56)); + clock_str = uresize(clock_str, 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(); + if (clock_str.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 = clock_str.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; + + Theme::c("title") + Fx::b + clock_str + Theme::c("cpu_box") + Fx::ub + title_right; return true; } @@ -488,7 +488,7 @@ namespace Cpu { string out; out.reserve(width * height); //* Redraw elements not needed to be updated every cycle - if (redraw or force_redraw) { + if (redraw) { auto& cpu_bottom = Config::getB("cpu_bottom"); mid_line = (not single_graph and graph_up_field != graph_lo_field); graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0 ? 1 : 0)); @@ -533,8 +533,31 @@ namespace Cpu { } } } - } + + //? Draw battery if enabled and present + if (Config::getB("show_battery") and has_battery) { + static int old_percent = 0; + static long old_seconds = 0; + static string old_status; + static Draw::Meter bat_meter {10, "cpu", true}; + static const unordered_flat_map bat_symbols = { + {"charging", "▲"}, + {"discharging", "▼"}, + {"full", "■"}, + {"unknown", "○"} + }; + + const auto& [percent, seconds, status] = current_bat; + if (redraw or percent != old_percent or seconds != old_seconds or status != old_status) { + old_percent = percent; + old_seconds = seconds; + old_status = status; + const string bat_time = (seconds > 0 ? to_string(seconds / 3600) + ':' + to_string((seconds % 3600) / 60) : ""); + const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown")); + } + } + try { //? Cpu graphs out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(cpu.cpu_percent.at(graph_up_field), (data_same or redraw)); @@ -693,7 +716,10 @@ namespace Mem { for (const auto& entry : split) { auto vals = ssplit(entry); if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1))) - custom_speeds[vals.at(0)] = std::stoi(vals.at(1)); + try { + custom_speeds[vals.at(0)] = std::stoi(vals.at(1)); + } + catch (const std::out_of_range&) { continue; } } } } @@ -705,7 +731,7 @@ namespace Mem { if (io_mode) { //? Create one combined graph for IO read/write if enabled - long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 10) << 20; + long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 100) << 20; if (io_graph_combined) { deque combined(disk.io_read.size(), 0); rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus()); @@ -1442,7 +1468,7 @@ namespace Draw { Global::clock.clear(); Global::overlay.clear(); Runner::pause_output = false; - Runner::debug_bg.clear(); + Runner::redraw = true; Proc::p_counters.clear(); Proc::p_graphs.clear(); if (Menu::active) Menu::redraw = true; diff --git a/src/btop_draw.hpp b/src/btop_draw.hpp index 0e1e6a0..203eed1 100644 --- a/src/btop_draw.hpp +++ b/src/btop_draw.hpp @@ -77,7 +77,7 @@ namespace Draw { //* 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(); + bool update_clock(bool force=false); //* Class holding a percentage meter class Meter { diff --git a/src/btop_input.cpp b/src/btop_input.cpp index cf3a0d2..167735f 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -190,25 +190,24 @@ namespace Input { exit(0); } else if (is_in(key, "escape", "m")) { - Menu::menuMask.set(Menu::Main); - Menu::process(); + Menu::show(Menu::Menus::Main); return; } else if (is_in(key, "F1", "h")) { - Menu::menuMask.set(Menu::Help); - Menu::process(); + Menu::show(Menu::Menus::Help); return; } else if (is_in(key, "F2", "o")) { - Menu::menuMask.set(Menu::Options); - Menu::process(); + Menu::show(Menu::Menus::Options); return; } else if (is_in(key, "1", "2", "3", "4")) { atomic_wait(Runner::active); static const array boxes = {"cpu", "mem", "net", "proc"}; Config::toggle_box(boxes.at(std::stoi(key) - 1)); - term_resize(true); + Draw::calcSizes(); + Runner::run("all", false, true); + return; } else keep_going = true; @@ -270,11 +269,6 @@ namespace Input { else if (key == "delete" and not Config::getS("proc_filter").empty()) Config::set("proc_filter", ""s); - else if (key == "ö") { - Menu::menuMask.set(Menu::Menus::SignalSend); - Menu::process(); - return; - } else if (key.starts_with("mouse_")) { redraw = false; const auto& [col, line] = mouse_pos; @@ -339,18 +333,14 @@ namespace Input { else if (is_in(key, "t", "k") and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) { 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::SignalSend); - Menu::signalToSend = (key == "t" ? SIGTERM : SIGKILL); - Menu::process(); + Menu::show(Menu::Menus::SignalSend, (key == "t" ? SIGTERM : SIGKILL)); 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); - Menu::signalToSend = -1; - Menu::process(); + Menu::show(Menu::Menus::SignalChoose); return; } else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) { diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 756c48b..37e9df5 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -317,6 +317,9 @@ namespace Menu { "Can be needed on certain cpus to get correct", "temperature for correct core.", "", + "Use lm-sensors or similar to see which cores", + "are reporting temperatures on your machine.", + "", "Format: \"X:Y\"", "X=core with wrong temp.", "Y=core with correct temp.", @@ -376,7 +379,7 @@ namespace Menu { "Toggle IO activity graphs.", "", "Show small IO graphs that for disk activity", - "percentage when not in IO mode.", + "(disk busy time) when not in IO mode.", "", "True or False."}, {"io_mode", @@ -397,10 +400,10 @@ namespace Menu { "", "Manually set which speed in MiB/s that", "equals 100 percent in the io graphs.", - "(10 MiB/s by default).", + "(100 MiB/s by default).", "", - "Format: \"device:speed\" seperate disks with a", - "comma \",\".", + "Format: \"device:speed\" seperate disks with", + "whitespace \" \".", "", "Example: \"/dev/sda:100, /dev/sdb:20\"."}, {"show_swap", @@ -477,6 +480,7 @@ namespace Menu { "Network Interface.", "", "Manually set the starting Network Interface.", + "", "Will otherwise automatically choose the NIC", "with the highest total download since boot."}, }, @@ -928,6 +932,7 @@ namespace Menu { bool screen_redraw = false; bool theme_refresh = false; + //? Draw background if needed else process input if (redraw) { mouse_mappings.clear(); selPred.reset(); @@ -960,7 +965,15 @@ namespace Menu { 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; + if (is_in(option, "shown_boxes", "custom_cpu_name")) screen_redraw = true; + else if (option == "clock_format") { + Draw::update_clock(true); + screen_redraw = true; + } + else if (option == "cpu_core_map") { + atomic_wait(Runner::active); + Cpu::core_mapping = Cpu::get_core_mapping(); + } } else if (selPred.test(isInt) and Config::intValid(option, editor.text)) { Config::set(option, stoi(editor.text)); @@ -1060,6 +1073,9 @@ namespace Menu { } else if (is_in(option, "rounded_corners", "theme_background")) theme_refresh = true; + else if (option == "background_update") { + Runner::pause_output = false; + } } else if (selPred.test(isBrowseable)) { auto& optList = optionsList.at(option).get(); @@ -1071,6 +1087,10 @@ namespace Menu { if (option == "color_theme") theme_refresh = true; + else if (option == "log_level") { + Logger::set(optList.at(i)); + Logger::info("Logger set to " + optList.at(i)); + } else if (is_in(option, "proc_sorting", "cpu_sensor") or option.starts_with("graph_symbol") or option.starts_with("cpu_graph_")) screen_redraw = true; } @@ -1081,6 +1101,7 @@ namespace Menu { retval = NoChange; } + //? Draw the menu if (retval == Changed) { Config::unlock(); auto& out = Global::overlay; @@ -1093,6 +1114,7 @@ namespace Menu { selected = select_max; } + //? Get variable properties for currently selected option if (selPred.none() or last_sel != (selected_cat << 8) + selected) { selPred.reset(); last_sel = (selected_cat << 8) + selected; @@ -1113,6 +1135,7 @@ namespace Menu { selPred.set(isEditable); } + //? Category buttons 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 @@ -1127,6 +1150,7 @@ namespace Menu { 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; } + //? Option name and value 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]; @@ -1149,6 +1173,7 @@ namespace Menu { 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); } + //? Description of selected option 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; @@ -1175,9 +1200,11 @@ namespace Menu { optionsMenu(""); } if (screen_redraw) { - auto overlay_bkp = Global::overlay; + auto overlay_bkp = move(Global::overlay); + auto clock_bkp = move(Global::clock); Draw::calcSizes(); - Global::overlay = overlay_bkp; + Global::overlay = move(overlay_bkp); + Global::clock = move(clock_bkp); recollect = true; } if (recollect) { @@ -1291,4 +1318,10 @@ namespace Menu { process(); } } + + void show(int menu, int signal) { + menuMask.set(menu); + signalToSend = signal; + process(); + } } diff --git a/src/btop_menu.hpp b/src/btop_menu.hpp index de47912..c57dcea 100644 --- a/src/btop_menu.hpp +++ b/src/btop_menu.hpp @@ -80,4 +80,7 @@ namespace Menu { //* Handles redirection of input for menu functions and handles return codes void process(string key=""); + //* Show a menu from enum Menu::Menus + void show(int menu, int signal=-1); + } diff --git a/src/btop_shared.hpp b/src/btop_shared.hpp index a340746..e54465f 100644 --- a/src/btop_shared.hpp +++ b/src/btop_shared.hpp @@ -26,8 +26,9 @@ tab-size = 4 #include #include #include +#include -using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array; +using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array, std::tuple; void term_resize(bool force=false); void banner_gen(); @@ -49,6 +50,7 @@ namespace Runner { extern atomic active; extern atomic reading; extern atomic stopping; + extern atomic redraw; extern pthread_t runner_id; extern bool pause_output; extern string debug_bg; @@ -75,10 +77,11 @@ namespace Shared { namespace Cpu { extern string box; extern int x, y, width, height, min_width, min_height; - extern bool shown, redraw, got_sensors, cpu_temp_only; + extern bool shown, redraw, got_sensors, cpu_temp_only, has_battery; extern string cpuName, cpuHz; extern vector available_fields; extern vector available_sensors; + extern tuple current_bat; struct cpu_info { unordered_flat_map> cpu_percent = { @@ -105,6 +108,13 @@ namespace Cpu { //* Draw contents of cpu box using as source string draw(const cpu_info& cpu, const bool force_redraw=false, const bool data_same=false); + + //* Parse /proc/cpu info for mapping of core ids + auto get_core_mapping() -> unordered_flat_map; + extern unordered_flat_map core_mapping; + + //* Get battery info from /sys + auto get_battery() -> tuple; } namespace Mem { @@ -128,10 +138,12 @@ namespace Mem { }; struct mem_info { - unordered_flat_map stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, - {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; - unordered_flat_map> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, - {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; + unordered_flat_map stats = + {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0}, + {"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}}; + unordered_flat_map> percent = + {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}}, + {"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}}; unordered_flat_map disks; vector disks_order; }; diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index 27e0286..378ac54 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -338,7 +338,7 @@ namespace Tools { for (string readstr; getline(file, readstr); out += readstr); } catch (const std::exception& e) { - throw std::runtime_error("Exception when reading " + (string)path + " : " + e.what()); + throw std::runtime_error("readfile() : Exception when reading " + (string)path + " : " + e.what()); } return (out.empty() ? fallback : out); } diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index abc14aa..9387456 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -57,9 +57,6 @@ namespace Cpu { //* Search /proc/cpuinfo for a cpu name string get_cpuName(); - //* Parse /proc/cpu info for mapping of core ids - auto get_core_mapping() -> unordered_flat_map; - struct Sensor { fs::path path; string label; @@ -150,6 +147,8 @@ namespace Shared { namespace Cpu { string cpuName; string cpuHz; + bool has_battery = true; + tuple current_bat; const array time_names = {"user", "nice", "system", "idle", "iowait", "irq", "softirq", "steal", "guest", "guest_nice"}; @@ -175,8 +174,25 @@ namespace Cpu { for (string instr; getline(cpuinfo, instr, ':') and not instr.starts_with("model name");) cpuinfo.ignore(SSmax, '\n'); if (cpuinfo.bad()) return name; - cpuinfo.ignore(1); - getline(cpuinfo, name); + else if (not cpuinfo.eof()) { + cpuinfo.ignore(1); + getline(cpuinfo, name); + } + else if (fs::exists("/sys/devices")) { + for (const auto& d : fs::directory_iterator("/sys/devices")) { + if (string(d.path().filename()).starts_with("arm")) { + name = d.path().filename(); + break; + } + } + if (not name.empty()) { + auto name_vec = ssplit(name, '_'); + if (name_vec.size() < 2) return capitalize(name); + else return capitalize(name_vec.at(1)) + (name_vec.size() > 2 ? ' ' + capitalize(name_vec.at(2)) : ""); + } + + } + auto name_vec = ssplit(name); if ((s_contains(name, "Xeon"s) or v_contains(name_vec, "Duo"s)) and v_contains(name_vec, "CPU"s)) { @@ -300,7 +316,7 @@ namespace Cpu { } catch (...) {} - if (not got_coretemp) cpu_temp_only = true; + if (not got_coretemp or core_sensors.empty()) cpu_temp_only = true; if (cpu_sensor.empty() and not found_sensors.empty()) { for (const auto& [name, sensor] : found_sensors) { if (s_contains(str_to_lower(name), "cpu")) { @@ -397,11 +413,12 @@ namespace Cpu { auto get_core_mapping() -> unordered_flat_map { unordered_flat_map core_map; + if (cpu_temp_only) return core_map; //? Try to get core mapping from /proc/cpuinfo ifstream cpuinfo(Shared::procPath / "cpuinfo"); if (cpuinfo.good()) { - int cpu, core; + int cpu, core, n = 0; for (string instr; cpuinfo >> instr;) { if (instr == "processor") { cpuinfo.ignore(SSmax, ':'); @@ -410,22 +427,31 @@ namespace Cpu { else if (instr.starts_with("core")) { cpuinfo.ignore(SSmax, ':'); cpuinfo >> core; - core_map[cpu] = core; + if (std::cmp_greater_equal(core, core_sensors.size())) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[cpu] = n++; + } + else + core_map[cpu] = core; } cpuinfo.ignore(SSmax, '\n'); } } - //? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely map 0-0 1-1 2-2 etc. + //? If core mapping from cpuinfo was incomplete try to guess remainder, if missing completely, map 0-0 1-1 2-2 etc. if (cmp_less(core_map.size(), Shared::coreCount)) { if (Shared::coreCount % 2 == 0 and (long)core_map.size() == Shared::coreCount / 2) { - for (int i = 0; i < Shared::coreCount / 2; i++) - core_map[Shared::coreCount / 2 + i] = i; + for (int i = 0, n = 0; i < Shared::coreCount / 2; i++) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[Shared::coreCount / 2 + i] = n++; + } } else { core_map.clear(); - for (int i = 0; i < Shared::coreCount; i++) - core_map[i] = i; + for (int i = 0, n = 0; i < Shared::coreCount; i++) { + if (std::cmp_greater_equal(n, core_sensors.size())) n = 0; + core_map[i] = n++; + } } } @@ -438,7 +464,7 @@ namespace Cpu { if (vals.size() != 2) continue; int change_id = std::stoi(vals.at(0)); int new_id = std::stoi(vals.at(1)); - if (not core_map.contains(change_id) or new_id >= Shared::coreCount) continue; + if (not core_map.contains(change_id) or cmp_greater(new_id, core_sensors.size())) continue; core_map.at(change_id) = new_id; } } @@ -448,6 +474,100 @@ namespace Cpu { return core_map; } + auto get_battery() -> tuple { + if (not has_battery) return {0, 0, ""}; + static fs::path bat_dir, energy_now_path, energy_full_path, power_now_path, status_path, online_path; + static bool use_energy = true; + + //? Get paths to needed files and check for valid values on first run + if (bat_dir.empty() and has_battery) { + if (fs::exists("/sys/class/power_supply")) { + for (const auto& d : fs::directory_iterator("/sys/class/power_supply")) { + if (const string dir_name = d.path().filename(); d.is_directory() and (dir_name.starts_with("BAT") or s_contains(str_to_lower(dir_name), "battery"))) { + bat_dir = d.path(); + break; + } + } + } + if (bat_dir.empty()) { + has_battery = false; + return {0, 0, ""}; + } + else { + if (fs::exists(bat_dir / "energy_now")) energy_now_path = bat_dir / "energy_now"; + else if (fs::exists(bat_dir / "charge_now")) energy_now_path = bat_dir / "charge_now"; + else use_energy = false; + + if (fs::exists(bat_dir / "energy_full")) energy_full_path = bat_dir / "energy_full"; + else if (fs::exists(bat_dir / "charge_full")) energy_full_path = bat_dir / "charge_full"; + else use_energy = false; + + if (not use_energy and not fs::exists(bat_dir / "capacity")) { + has_battery = false; + return {0, 0, ""}; + } + + if (fs::exists(bat_dir / "power_now")) power_now_path = bat_dir / "power_now"; + else if (fs::exists(bat_dir / "current_now")) power_now_path = bat_dir / "current_now"; + + if (fs::exists(bat_dir / "AC0/online")) online_path = bat_dir / "AC0/online"; + else if (fs::exists(bat_dir / "AC/online")) online_path = bat_dir / "AC/online"; + } + } + + int percent = -1; + long seconds = -1; + + //? Try to get battery percentage + if (use_energy) { + try { + percent = round(100.0 * stoll(readfile(energy_now_path, "-1")) / stoll(readfile(energy_full_path, "1"))); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + if (percent < 0) { + try { + percent = stoll(readfile(bat_dir / "capacity", "-1")); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + if (percent < 0) { + has_battery = false; + return {0, 0, ""}; + } + + //? Get charging/discharging status + string status = str_to_lower(readfile(bat_dir / "status", "unknown")); + if (status == "unknown" and not online_path.empty()) { + const auto online = readfile(online_path, "0"); + if (online == "1" and percent < 100) status = "charging"; + else if (online == "1") status = "full"; + else status = "discharging"; + } + + //? Get seconds to empty + if (not is_in(status, "charging", "full")) { + if (use_energy and not power_now_path.empty()) { + try { + seconds = round((double)stoll(readfile(energy_now_path, "0")) / stoll(readfile(power_now_path, "1")) * 3600); + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + if (seconds < 0 and fs::exists(bat_dir / "time_to_empty")) { + try { + seconds = stoll(readfile(bat_dir / "time_to_empty", "0")) * 60; + } + catch (const std::invalid_argument&) { } + catch (const std::out_of_range&) { } + } + } + + return {percent, seconds, status}; + } + auto collect(const bool no_update) -> cpu_info& { if (Runner::stopping or (no_update and not current_cpu.cpu_percent.at("total").empty())) return current_cpu; auto& cpu = current_cpu; @@ -535,6 +655,9 @@ namespace Cpu { if (Config::getB("check_temp") and got_sensors) update_sensors(); + if (Config::getB("show_battery") and has_battery) + current_bat = get_battery(); + return cpu; } } @@ -717,6 +840,7 @@ namespace Mem { else it++; } + if (found.size() != last_found.size()) redraw = true; last_found = std::move(found); } else @@ -1346,7 +1470,7 @@ namespace Proc { //? Process cumulative cpu usage since process start new_proc.cpu_c = (double)cpu_t / max(1.0, (uptime * Shared::clkTck) - new_proc.cpu_s); - //? Update cache with latest cpu times + //? Update cached value with latest cpu times new_proc.cpu_t = cpu_t; if (show_detailed and not got_detailed and new_proc.pid == detailed_pid) { @@ -1374,14 +1498,14 @@ namespace Proc { //* Sort processes if (sorted_change or not no_update) { switch (v_index(sort_vector, sorting)) { - case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break; - case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break; - case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break; - case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break; + case 0: rng::sort(current_procs, rng::greater{}, &proc_info::pid); break; + case 1: rng::sort(current_procs, rng::greater{}, &proc_info::name); break; + case 2: rng::sort(current_procs, rng::greater{}, &proc_info::cmd); break; + case 3: rng::sort(current_procs, rng::greater{}, &proc_info::threads); break; case 4: rng::sort(current_procs, rng::greater{}, &proc_info::user); break; - case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break; - case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; - case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; + case 5: rng::sort(current_procs, rng::greater{}, &proc_info::mem); break; + case 6: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_p); break; + case 7: rng::sort(current_procs, rng::greater{}, &proc_info::cpu_c); break; } if (reverse) rng::reverse(current_procs); @@ -1403,7 +1527,6 @@ namespace Proc { } } - //* Match filter if defined if (should_filter) { filter_found = 0;