mirror of
https://github.com/aristocratos/btop.git
synced 2024-05-14 17:33:11 +12:00
Added collect for battery
This commit is contained in:
parent
8583a8a2ef
commit
650df9ac39
|
@ -0,0 +1,3 @@
|
|||
## v0.9.0
|
||||
|
||||
* Test release
|
8
Makefile
8
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)
|
||||
|
|
33
src/btop.cpp
33
src/btop.cpp
|
@ -239,6 +239,7 @@ namespace Runner {
|
|||
atomic<bool> active (false);
|
||||
atomic<bool> stopping (false);
|
||||
atomic<bool> waiting (false);
|
||||
atomic<bool> 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);
|
||||
|
|
|
@ -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).";
|
||||
|
|
|
@ -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<string, string> 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<long long> combined(disk.io_read.size(), 0);
|
||||
rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus<long long>());
|
||||
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<string, 4> 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")) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -26,8 +26,9 @@ tab-size = 4
|
|||
#include <robin_hood.h>
|
||||
#include <array>
|
||||
#include <ifaddrs.h>
|
||||
#include <tuple>
|
||||
|
||||
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<bool> active;
|
||||
extern atomic<bool> reading;
|
||||
extern atomic<bool> stopping;
|
||||
extern atomic<bool> 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<string> available_fields;
|
||||
extern vector<string> available_sensors;
|
||||
extern tuple<int, long, string> current_bat;
|
||||
|
||||
struct cpu_info {
|
||||
unordered_flat_map<string, deque<long long>> cpu_percent = {
|
||||
|
@ -105,6 +108,13 @@ namespace Cpu {
|
|||
|
||||
//* Draw contents of cpu box using <cpu> 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<int, int>;
|
||||
extern unordered_flat_map<int, int> core_mapping;
|
||||
|
||||
//* Get battery info from /sys
|
||||
auto get_battery() -> tuple<int, long, string>;
|
||||
}
|
||||
|
||||
namespace Mem {
|
||||
|
@ -128,10 +138,12 @@ namespace Mem {
|
|||
};
|
||||
|
||||
struct mem_info {
|
||||
unordered_flat_map<string, uint64_t> stats = {{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0},
|
||||
{"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}};
|
||||
unordered_flat_map<string, deque<long long>> percent = {{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}},
|
||||
{"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}};
|
||||
unordered_flat_map<string, uint64_t> stats =
|
||||
{{"used", 0}, {"available", 0}, {"cached", 0}, {"free", 0},
|
||||
{"swap_total", 0}, {"swap_used", 0}, {"swap_free", 0}};
|
||||
unordered_flat_map<string, deque<long long>> percent =
|
||||
{{"used", {}}, {"available", {}}, {"cached", {}}, {"free", {}},
|
||||
{"swap_total", {}}, {"swap_used", {}}, {"swap_free", {}}};
|
||||
unordered_flat_map<string, disk_info> disks;
|
||||
vector<string> disks_order;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<int, int>;
|
||||
|
||||
struct Sensor {
|
||||
fs::path path;
|
||||
string label;
|
||||
|
@ -150,6 +147,8 @@ namespace Shared {
|
|||
namespace Cpu {
|
||||
string cpuName;
|
||||
string cpuHz;
|
||||
bool has_battery = true;
|
||||
tuple<int, long, string> current_bat;
|
||||
|
||||
const array<string, 10> 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<int, int> {
|
||||
unordered_flat_map<int, int> 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<int, long, string> {
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue