summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoraristocratos <gnmjpl@gmail.com>2021-06-19 14:57:27 +0200
committeraristocratos <gnmjpl@gmail.com>2021-06-19 14:57:27 +0200
commitd459d088a0dfb216fc6827a940d8da3f61066ecd (patch)
treed56fd230fa7445b543f388166d3042c8fef28871 /src
parentba481d042cf340542bf642283a25a1faf5ec421b (diff)
File reorganization and more efficient build
Diffstat (limited to 'src')
-rw-r--r--src/btop.cpp651
-rw-r--r--src/btop_config.cpp337
-rw-r--r--src/btop_config.h285
-rw-r--r--src/btop_draw.cpp281
-rw-r--r--src/btop_draw.h222
-rw-r--r--src/btop_input.h9
-rw-r--r--src/btop_linux.cpp411
-rw-r--r--src/btop_linux.h392
-rw-r--r--src/btop_menu.h7
-rw-r--r--src/btop_theme.cpp288
-rw-r--r--src/btop_theme.h262
-rw-r--r--src/btop_tools.cpp432
-rw-r--r--src/btop_tools.h457
13 files changed, 2561 insertions, 1473 deletions
diff --git a/src/btop.cpp b/src/btop.cpp
new file mode 100644
index 0000000..8ed0a60
--- /dev/null
+++ b/src/btop.cpp
@@ -0,0 +1,651 @@
+/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+indent = tab
+tab-size = 4
+*/
+
+
+#include <string>
+#include <array>
+#include <list>
+#include <vector>
+#include <csignal>
+#include <thread>
+#include <future>
+#include <atomic>
+#include <numeric>
+#include <ranges>
+#include <filesystem>
+#include <unistd.h>
+#include <robin_hood.h>
+#include <cmath>
+
+#include <btop_tools.h>
+#include <btop_config.h>
+#include <btop_input.h>
+#include <btop_theme.h>
+#include <btop_draw.h>
+#include <btop_menu.h>
+
+#if defined(__linux__)
+ #define LINUX
+ #include <btop_linux.h>
+#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__)
+ #include <sys/param.h>
+ #if defined(BSD)
+ // #include <btop_bsd.h>
+ #error BSD support not yet implemented!
+ #endif
+#elif defined(__APPLE__) && defined(__MACH__)
+ #include <TargetConditionals.h>
+ #if TARGET_OS_MAC == 1
+ #define OSX
+ // #include <btop_osx.h>
+ #error OSX support not yet implemented!
+ #endif
+#else
+ #error Platform not supported!
+#endif
+
+namespace Global {
+ const std::vector<std::array<std::string, 2>> Banner_src = {
+ {"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
+ {"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
+ {"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
+ {"#9A1919", "██╔══██╗ ██║ ██║ ██║██╔═══╝ ╚═██╔═╝╚═██╔═╝"},
+ {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
+ {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
+ };
+ std::string Version = "0.0.21";
+ int coreCount;
+}
+
+using std::string, std::vector, std::array, robin_hood::unordered_flat_map, std::atomic, std::endl, std::cout, std::views::iota, std::list, std::accumulate;
+using std::flush, std::endl, std::future, std::string_literals::operator""s, std::future_status, std::to_string, std::round;
+namespace fs = std::filesystem;
+namespace rng = std::ranges;
+using namespace Tools;
+
+
+namespace Global {
+ string banner;
+ size_t banner_width = 0;
+
+ fs::path self_path;
+
+ bool debuginit = false;
+ bool debug = false;
+
+ uint64_t start_time;
+
+ bool quitting = false;
+
+ bool arg_tty = false;
+}
+
+
+//* A simple argument parser
+void argumentParser(int argc, char **argv){
+ string argument;
+ for(int i = 1; i < argc; i++) {
+ argument = argv[i];
+ if (argument == "-v" || argument == "--version") {
+ cout << "btop version: " << Global::Version << endl;
+ exit(0);
+ }
+ else if (argument == "-h" || argument == "--help") {
+ cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n"
+ << "optional arguments:\n"
+ << " -h, --help show this help message and exit\n"
+ << " -v, --version show version info and exit\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"
+ << " --debug start with loglevel set to DEBUG, overriding value set in config\n"
+ << endl;
+ exit(0);
+ }
+ else if (argument == "--debug")
+ Global::debug = true;
+ else if (argument == "-t" || argument == "--tty_on") {
+ Config::set("tty_mode", true);
+ Global::arg_tty = true;
+ }
+ else if (argument == "+t" || argument == "--tty_off") {
+ Config::set("tty_mode", false);
+ Global::arg_tty = true;
+ }
+ else {
+ cout << " Unknown argument: " << argument << "\n" <<
+ " Use -h or --help for help." << endl;
+ exit(1);
+ }
+ }
+}
+
+void clean_quit(int sig){
+ if (Global::quitting) return;
+ if (Term::initialized) {
+ Term::restore();
+ if (!Global::debuginit) cout << Term::normal_screen << Term::show_cursor << flush;
+ }
+ Global::quitting = true;
+ Config::write();
+ Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
+ if (sig != -1) exit(sig);
+}
+
+void sleep_now(){
+ if (Term::initialized) {
+ Term::restore();
+ if (!Global::debuginit) cout << Term::normal_screen << Term::show_cursor << flush;
+ }
+ std::raise(SIGSTOP);
+}
+
+void resume_now(){
+ Term::init();
+ if (!Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << flush;
+}
+
+void _exit_handler() { clean_quit(-1); }
+
+void _signal_handler(int sig) {
+ switch (sig) {
+ case SIGINT:
+ clean_quit(0);
+ break;
+ case SIGTSTP:
+ sleep_now();
+ break;
+ case SIGCONT:
+ resume_now();
+ break;
+ }
+}
+
+//? Generate the btop++ banner
+void banner_gen() {
+ size_t z = 0;
+ string b_color, bg, fg, oc, letter;
+ bool truecolor = Config::getB("truecolor");
+ int bg_i;
+ Global::banner.clear();
+ Global::banner_width = 0;
+ auto tty_mode = (Config::getB("tty_mode"));
+ for (auto line: Global::Banner_src) {
+ if (auto w = ulen(line[1]); w > Global::banner_width) Global::banner_width = w;
+ fg = Theme::hex_to_color(line[0], !truecolor);
+ bg_i = 120-z*12;
+ bg = Theme::dec_to_color(bg_i, bg_i, bg_i, !truecolor);
+ for (size_t i = 0; i < line[1].size(); i += 3) {
+ if (line[1][i] == ' ') {
+ letter = ' ';
+ i -= 2;
+ }
+ else
+ letter = line[1].substr(i, 3);
+
+ if (tty_mode && letter != "█" && letter != " ") letter = "░";
+ b_color = (letter == "█") ? fg : bg;
+ if (b_color != oc) Global::banner += b_color;
+ Global::banner += letter;
+ oc = b_color;
+ }
+ if (++z < Global::Banner_src.size()) Global::banner += Mv::l(ulen(line[1])) + Mv::d(1);
+ }
+ Global::banner += Mv::r(18 - Global::Version.size()) + Fx::i + Theme::dec_to_color(0,0,0, !truecolor, "bg") +
+ Theme::dec_to_color(150, 150, 150, !truecolor) + "v" + Global::Version + Fx::ui;
+}
+
+//* Threading test function
+// string my_worker(int x){
+// for (int i = 0; i < 100 + (x * 100); i++){
+// sleep_ms(10);
+// if (Global::stop_all.load()) return "Thread stopped! x=" + to_string(x);
+// }
+// return "Thread done! x=" + to_string(x);
+// }
+
+
+//? --------------------------------------------- Main starts here! ---------------------------------------------------
+int main(int argc, char **argv){
+
+ //? Init
+
+ Global::start_time = time_s();
+
+ cout.setf(std::ios::boolalpha);
+ if (argc > 1) argumentParser(argc, argv);
+
+ std::atexit(_exit_handler);
+ std::at_quick_exit(_exit_handler);
+ std::signal(SIGINT, _signal_handler);
+ std::signal(SIGTSTP, _signal_handler);
+ std::signal(SIGCONT, _signal_handler);
+
+ //? Linux init
+ #if defined(LINUX)
+ Global::coreCount = sysconf(_SC_NPROCESSORS_ONLN);
+ if (Global::coreCount < 1) Global::coreCount = 1;
+
+ {
+ std::error_code ec;
+ Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
+ }
+ #endif
+
+ //? Setup paths for config, log and themes
+ for (auto env : {"XDG_CONFIG_HOME", "HOME"}) {
+ if (getenv(env) != NULL && access(getenv(env), W_OK) != -1) {
+ Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
+ break;
+ }
+ }
+ if (!Config::conf_dir.empty()) {
+ if (std::error_code ec; !fs::is_directory(Config::conf_dir) && !fs::create_directories(Config::conf_dir, ec)) {
+ cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled." << endl;
+ cout << "Make sure your $HOME environment variable is correctly set to fix this." << endl;
+ }
+ else {
+ Config::conf_file = Config::conf_dir / "btop.conf";
+ Logger::logfile = Config::conf_dir / "btop.log";
+ Theme::user_theme_dir = Config::conf_dir / "themes";
+ if (!fs::exists(Theme::user_theme_dir) && !fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear();
+ }
+ }
+ if (std::error_code ec; !Global::self_path.empty()) {
+ Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
+ if (ec || access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
+ }
+
+ if (Theme::theme_dir.empty()) {
+ for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) {
+ if (fs::exists(fs::path(theme_path)) && access(theme_path, R_OK) != -1) {
+ Theme::theme_dir = fs::path(theme_path);
+ break;
+ }
+ }
+ }
+
+ //? Config init
+ { vector<string> load_errors;
+ Config::load(Config::conf_file, load_errors);
+
+ if (Global::debug) Logger::set("DEBUG");
+ else Logger::set(Config::getS("log_level"));
+
+ Logger::debug("Logger set to DEBUG");
+
+ for (auto& err_str : load_errors) Logger::warning(err_str);
+ }
+
+ if (!string(getenv("LANG")).ends_with("UTF-8") && !string(getenv("LANG")).ends_with("utf-8")) {
+ string err_msg = "No UTF-8 locale was detected! Symbols might not look as intended.\n"
+ "Make sure your $LANG evironment variable is set and with a UTF-8 locale.";
+ Logger::warning(err_msg);
+ cout << "WARNING: " << err_msg << endl;
+ }
+
+ //? Initialize terminal and set options
+ if (!Term::init()) {
+ string err_msg = "No tty detected!\nbtop++ needs an interactive shell to run.";
+ Logger::error(err_msg);
+ cout << "ERROR: " << err_msg << endl;
+ clean_quit(1);
+ }
+
+ Logger::debug("Running on " + Term::current_tty);
+ if (!Global::arg_tty && Config::getB("force_tty")) {
+ Config::set("tty_mode", true);
+ Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols");
+ }
+ else if (!Global::arg_tty && Term::current_tty.starts_with("/dev/tty")) {
+ Config::set("tty_mode", true);
+ Logger::info("Real tty detected, setting 16 color mode and using tty friendly graph symbols");
+ }
+
+
+ #if defined(LINUX)
+ //? Linux init
+ Proc::init();
+ #endif
+
+
+ // Config::set("truecolor", false);
+
+ auto thts = time_ms();
+
+ //? Generate the theme
+ Theme::set(Theme::Default_theme);
+
+ //? Create the btop++ banner
+ banner_gen();
+
+
+ //* ------------------------------------------------ TESTING ------------------------------------------------------
+
+
+ Global::debuginit = true;
+
+ // cout << Theme("main_bg") << Term::clear << flush;
+ // bool thread_test = false;
+
+ if (!Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << flush;
+
+ cout << Theme::c("main_fg") << Theme::c("main_bg") << Term::clear << endl;
+
+ cout << Mv::r(Term::width / 2 - Global::banner_width / 2) << Global::banner << endl;
+ // cout << string(Term::width - 1, '-') << endl;
+ size_t blen = (Term::width > 200) ? 200 : Term::width;
+ if (Term::width > 203) cout << Mv::r(Term::width / 2 - blen / 2) << flush;
+ int ill = 0;
+ for (int i : iota(0, (int)blen)){
+ ill = (i <= (int)blen / 2) ? i : ill - 1;
+ cout << Theme::g("used")[ill] << Symbols::h_line;
+ }
+ cout << Fx::reset << endl;
+
+ //* Test theme
+ if (false) {
+ cout << "Theme generation took " << time_ms() - thts << "ms" << endl;
+
+ cout << "Colors:" << endl;
+ uint i = 0;
+ for(auto& item : Theme::test_colors()) {
+ cout << rjust(item.first, 15) << ":" << item.second << "■"s * 10 << Fx::reset << " ";
+ // << Theme::dec(item.first)[0] << ":" << Theme::dec(item.first)[1] << ":" << Theme::dec(item.first)[2] << ;
+ if (++i == 4) {
+ i = 0;
+ cout << endl;
+ }
+ }
+ cout << Fx::reset << endl;
+
+
+ cout << "Gradients:";
+ for (auto& [name, cvec] : Theme::test_gradients()) {
+ cout << endl << rjust(name + ":", 10);
+ for (auto& color : cvec) {
+ cout << color << "■";
+ }
+
+ cout << Fx::reset << endl;
+ }
+
+
+ exit(0);
+ }
+
+
+ if (false) {
+ Draw::Meter kmeter;
+ kmeter(Term::width - 2, "cpu", false);
+ cout << kmeter(25) << endl;
+ cout << kmeter(0) << endl;
+ cout << kmeter(50) << endl;
+ cout << kmeter(100) << endl;
+ cout << kmeter(50) << endl;
+ exit(0);
+ }
+
+ if (false) {
+ cout << fs::absolute(fs::current_path() / "..") << endl;
+ cout << Global::self_path << endl;
+ cout << Theme::theme_dir << endl;
+ cout << Config::conf_dir << endl;
+ exit(0);
+ }
+
+ if (false) {
+
+ vector<long long> mydata;
+ for (long long i = 0; i <= 100; i++) mydata.push_back(i);
+ for (long long i = 100; i >= 0; i--) mydata.push_back(i);
+ // mydata.push_back(0);
+ // mydata.push_back(0);
+ mydata.push_back(50);
+
+
+ // for (long long i = 0; i <= 100; i++) mydata.push_back(i);
+ // for (long long i = 100; i >= 0; i--) mydata.push_back(i);
+
+ Draw::Graph kgraph {};
+ Draw::Graph kgraph2 {};
+ Draw::Graph kgraph3 {};
+
+ cout << Draw::createBox({.x = 5, .y = 10, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "braille", .fill = false, .num = 1}) << Mv::save;
+ cout << Draw::createBox({.x = 5, .y = 23, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "block", .fill = false, .num = 2});
+ cout << Draw::createBox({.x = 5, .y = 36, .width = Term::width - 10, .height = 12, .line_color = Theme::c("proc_box"), .title = "tty", .fill = false, .num = 3}) << flush;
+ // Draw::Meter kmeter {};
+ // Draw::Graph kgraph2 {};
+ // Draw::Graph kgraph3 {};
+
+ auto kts = time_micros();
+ kgraph(Term::width - 12, 10, "cpu", mydata, "braille", false, false);
+ kgraph2(Term::width - 12, 10, "cpu", mydata, "block", false, false);
+ kgraph3(Term::width - 12, 10, "cpu", mydata, "tty", false, false);
+
+ // kmeter(Term::width - 12, "process");
+ // cout << Mv::save << kgraph(mydata) << "\n\nInit took " << time_micros() - kts << " μs. " << endl;
+
+ // exit(0);
+ // kgraph2(Term::width, 10, "process", mydata, true, false);
+ // kgraph3(Term::width, 1, "process", mydata, false, false);
+ // cout << kgraph() << endl;
+ // cout << kgraph2() << endl;
+ // exit(0);
+
+ cout << Mv::restore << kgraph(mydata, true)
+ << Mv::restore << Mv::d(13) << kgraph2(mydata, true)
+ << Mv::restore << Mv::d(26) << kgraph3(mydata, true) << endl
+ << Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
+ // cout << Mv::save << kgraph(mydata, true) << "\n" << kgraph2(mydata, true) << "\n" << kgraph3(mydata, true) << "\n" << kmeter(mydata.back()) << "\n\nInit took " << time_micros() - kts << " μs. " << endl;
+ // sleep_ms(1000);
+ // mydata.push_back(50);
+ // cout << Mv::restore << kgraph(mydata) << "\n" << kgraph2(mydata) << "\n\nInit took " << time_micros() - kts << " μs. " << endl;
+ // exit(0);
+
+ // long long y = 0;
+ // bool flip = false;
+ list<uint64_t> ktavg;
+ while (true) {
+ mydata.back() = std::rand() % 101;
+ // mydata.back() = y;
+ kts = time_micros();
+ // cout << Mv::restore << " "s * Term::width << "\n" << " "s * Term::width << endl;
+ cout << Mv::restore << kgraph(mydata)
+ << Mv::restore << Mv::d(13) << kgraph2(mydata)
+ << Mv::restore << Mv::d(26) << kgraph3(mydata)
+ << endl;
+ // cout << Mv::restore << kgraph(mydata) << "\n" << kgraph2(mydata) << "\n" << " "s * Term::width << Mv::l(Term::width) << kgraph3(mydata) << "\n" << kmeter(mydata.back()) << endl;
+ ktavg.push_front(time_micros() - kts);
+ if (ktavg.size() > 100) ktavg.pop_back();
+ cout << Mv::d(1) << "Time: " << ktavg.front() << " μs. Avg: " << accumulate(ktavg.begin(), ktavg.end(), 0) / ktavg.size() << " μs. " << flush;
+ // if (flip) y--;
+ // else y++;
+ // if (y == 100 || y == 0) flip = !flip;
+ if (Input::poll()) {
+ if (Input::get() == "space") Input::wait(true);
+ else break;
+ }
+ sleep_ms(100);
+ }
+ Input::get();
+
+ exit(0);
+
+ }
+
+
+ if (false) {
+ cout << Config::getS("log_level") << endl;
+
+ vector<string> vv = {"hej", "vad", "du"};
+ vector<int> vy;
+
+ cout << v_contains(vv, "vad"s) << endl;
+ cout << v_index(vv, "hej"s) << endl;
+ cout << v_index(vv, "du"s) << endl;
+ cout << v_index(vv, "kodkod"s) << endl;
+ cout << v_index(vy, 4) << endl;
+
+
+ exit(0);
+ }
+
+
+ // if (thread_test){
+
+ // unordered_flat_map<int, future<string>> runners;
+ // unordered_flat_map<int, string> outputs;
+
+ // for (int i : iota(0, 10)){
+ // runners[i] = async(my_worker, i);
+ // }
+ // // uint i = 0;
+ // while (outputs.size() < 10){
+
+ // for (int i : iota(0, 10)){
+ // if (runners[i].valid() && runners[i].wait_for(std::chrono::milliseconds(10)) == future_status::ready) {
+ // outputs[i] = runners[i].get();
+ // cout << "Thread " << i << " : " << outputs[i] << endl;
+ // }
+ // }
+
+ // // if (++i >= 10) i = 0;
+ // if (outputs.size() >= 8) Global::stop_all.store(true);
+ // }
+ // }
+
+
+
+ cout << "Up for " << sec_to_dhms(round(system_uptime())) << endl;
+
+
+//*------>>>>>> Proc testing
+
+
+ auto timestamp = time_ms();
+
+
+
+ uint lc;
+ string ostring;
+ uint64_t tsl, timestamp2, rcount = 0;
+ list<uint64_t> avgtimes = {0};
+ uint timer = 2000;
+ bool filtering = false;
+ vector<string> greyscale;
+ string filter;
+ string filter_cur;
+ string key;
+
+ int xc;
+ for (uint i : iota(0, (int)Term::height - 19)){
+ xc = 230 - i * 150 / (Term::height - 20);
+ greyscale.push_back(Theme::dec_to_color(xc, xc, xc));
+ }
+
+ string pbox = Draw::createBox({.x = 1, .y = 10, .width = Term::width, .height = Term::height - 16, .line_color = Theme::c("proc_box"), .title = "testbox", .title2 = "below", .fill = false, .num = 7});
+ pbox += Mv::r(1) + Theme::c("title") + Fx::b + rjust("Pid:", 8) + " " + ljust("Program:", 16) + " " + ljust("Command:", Term::width - 70) + " Threads: " +
+ ljust("User:", 10) + " " + rjust("MemB", 5) + " " + rjust("Cpu%", 14) + "\n" + Fx::reset + Mv::save;
+
+ while (key != "q") {
+ timestamp = time_micros();
+ tsl = time_ms() + timer;
+ auto plist = Proc::collect();
+ timestamp2 = time_micros();
+ timestamp = timestamp2 - timestamp;
+ ostring.clear();
+ lc = 0;
+
+ ostring = Mv::u(2) + Mv::l(Term::width) + Mv::r(12)
+ + trans("Filter: " + filter + (filtering ? Fx::bl + "█" + Fx::reset : "")) + Mv::l(Term::width)
+ + trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: "
+ + string(Config::getS("proc_sorting")), Term::width - 3))
+ + Mv::restore;
+
+ for (auto& p : plist){
+ if (!Config::getB("proc_tree")) {
+ ostring += Mv::r(1) + greyscale[lc] + rjust(to_string(p.pid), 8) + " " + ljust(p.name, 16) + " " + ljust(p.cmd, Term::width - 66, true) + " "
+ + rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ')
+ + (p.cpu_p < 10 || p.cpu_p >= 100 ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4))
+ + "\n";
+ }
+ else {
+ string cmd_cond;
+ if (!p.cmd.empty()) {
+ cmd_cond = p.cmd.substr(0, std::min(p.cmd.find(' '), p.cmd.size()));
+ cmd_cond = cmd_cond.substr(std::min(cmd_cond.find_last_of('/') + 1, cmd_cond.size()));
+ }
+ ostring += Mv::r(1) + greyscale[lc] + ljust(p.prefix + to_string(p.pid) + " " + p.name + " " + (!cmd_cond.empty() && cmd_cond != p.name ? "(" + cmd_cond + ")" : ""), Term::width - 40, true) + " "
+ + rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ')
+ + (p.cpu_p < 10 || p.cpu_p >= 100 ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4))
+ + "\n";
+ }
+ if (lc++ > Term::height - 21) break;
+ }
+
+ while (lc++ < Term::height - 19) ostring += Mv::r(1) + string(Term::width - 2, ' ') + "\n";
+
+
+
+ if (rcount > 0) avgtimes.push_front(timestamp);
+ if (avgtimes.size() > 10) avgtimes.pop_back();
+ cout << pbox << ostring << Fx::reset << "\n" << endl;
+ cout << Mv::to(Term::height - 4, 1) << "Processes call took: " << rjust(to_string(timestamp), 5) << " μs. Average: " <<
+ rjust(to_string(accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size()), 5) << " μs of " << avgtimes.size() <<
+ " samples. Drawing took: " << time_micros() - timestamp2 << " μs.\nNumber of processes: " << Proc::numpids << ". Run count: " <<
+ ++rcount << ". Time: " << strf_time("%X ") << endl;
+
+ while (time_ms() < tsl) {
+ if (Input::poll(tsl - time_ms())) key = Input::get();
+ else { key.clear() ; continue; }
+ if (filtering) {
+ if (key == "enter") filtering = false;
+ else if (key == "backspace") {if (!filter.empty()) filter = uresize(filter, ulen(filter) - 1);}
+ else if (key == "space") filter.push_back(' ');
+ else if (ulen(key) == 1 ) filter.append(key);
+ else { key.clear(); continue; }
+ if (filter != Config::getS("proc_filter")) Config::set("proc_filter", filter);
+ break;
+ }
+ else if (key == "q") break;
+ else if (key == "left") {
+ int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting"));
+ if (--cur_i < 0) cur_i = Proc::sort_vector.size() - 1;
+ Config::set("proc_sorting", Proc::sort_vector.at(cur_i));
+ }
+ else if (key == "right") {
+ int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting"));
+ if (++cur_i > (int)Proc::sort_vector.size() - 1) cur_i = 0;
+ Config::set("proc_sorting", Proc::sort_vector.at(cur_i));
+ }
+ else if (key == "f") filtering = true;
+ else if (key == "t") Config::flip("proc_tree");
+ else if (key == "r") Config::flip("proc_reversed");
+ else if (key == "c") Config::flip("proc_per_core");
+ else if (key == "delete") { filter.clear(); Config::set("proc_filter", filter); }
+ else continue;
+ break;
+ }
+ }
+
+ // cout << "Found " << plist.size() << " pids\n" << endl;
+
+//*-----<<<<<
+
+ return 0;
+}
diff --git a/src/btop_config.cpp b/src/btop_config.cpp
new file mode 100644
index 0000000..c250a78
--- /dev/null
+++ b/src/btop_config.cpp
@@ -0,0 +1,337 @@
+/* Copyright 2021 Aristocratos (jakob@qvantnet.com)
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+indent = tab
+tab-size = 4
+*/
+
+#include <array>
+#include <robin_hood.h>
+#include <ranges>
+#include <atomic>
+#include <fstream>
+
+#include <btop_config.h>
+#include <btop_tools.h>
+
+using robin_hood::unordered_flat_map, std::map, std::array, std::atomic;
+namespace fs = std::filesystem;
+namespace rng = std::ranges;
+using namespace Tools;
+
+//* Functions and variables for reading and writing the btop config file
+namespace Config {
+ namespace {
+ atomic<bool> locked (false);
+ atomic<bool> writelock (false);
+ bool write_new;
+
+ vector<array<string, 2>> descriptions = {
+ {"color_theme", "#* Color theme, looks for a .theme file in \"/usr/[local/]share/bpytop/themes\" and \"~/.config/bpytop/themes\", \"Default\" for builtin default theme.\n"
+ "#* Prefix name by a plus sign (+) for a theme located in user themes folder, i.e. color_theme=\"+monokai\"." },
+ {"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
+ {"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
+ {"graph_symbol", "#* Default symbols to use for graph creation, \"braille\", \"block\" or \"tty\".\n"
+ "#* \"braille\" offers the highest resolution but might not be included in all fonts.\n"
+ "#* \"block\" has half the resolution of braille but uses more common characters.\n"
+ "#* \"tty\" uses only 3 different symbols but will work with most fonts and should work in a real TTY.\n"
+ "#* Note that \"tty\" only has half the horizontal resolution of the other two, so will show a shorter historical view."},
+ {"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
+ {"graph_symbol_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
+ {"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
+ {"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
+ {"force_tty", "#* Set to true to true to force tty mode regardless if a real tty has been detected or not."},
+ {"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
+ {"update_ms", "#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs."},
+ {"proc_update_mult", "#* Processes update multiplier, sets how often the process list is updated as a multiplier of \"update_ms\".\n"
+ "#* Set to 2 or higher to greatly decrease bpytop cpu usage. (Only integers)."},
+ {"proc_sorting", "#* Processes sorting, \"pid\" \"program\" \"arguments\" \"threads\" \"user\" \"memory\" \"cpu lazy\" \"cpu responsive\",\n"
+ "#* \"cpu lazy\" updates top process over time, \"cpu responsive\" updates top process directly."},
+ {"proc_reversed", "#* Reverse sorting order, True or False."},
+ {"proc_tree", "#* Show processes as a tree."},
+ {"proc_colors", "#* Use the cpu graph colors in the process list."},
+ {"proc_gradient", "#* Use a darkening gradient in the process list."},
+ {"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."},
+ {"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
+ {"cpu_graph_upper", "#* Sets the CPU stat shown in upper half of the CPU graph, \"total\" is always available.\n"
+ "#* Select from a list of detected attributes from the options menu."},
+ {"cpu_graph_lower", "#* Sets the CPU stat shown in lower half of the CPU graph, \"total\" is always available.\n"
+ "#* Select from a list of detected attributes from the options menu."},
+ {"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
+ {"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
+ {"show_uptime", "#* Shows the system uptime in the CPU box."},
+ {"check_temp", "#* Show cpu temperature."},
+ {"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
+ {"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
+ {"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
+ {"show_cpu_freq", "#* Show CPU frequency."},
+ {"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
+ {"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
+ {"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
+ {"disks_filter", "#* Optional filter for shown disks, should be full path of a mountpoint, separate multiple values with a comma \",\".\n"
+ "#* Begin line with \"exclude=\" to change to exclude filter, otherwise defaults to \"most include\" filter. Example: disks_filter=\"exclude=/boot, /home/user\"."},
+ {"mem_graphs", "#* Show graphs instead of meters for memory values."},
+ {"show_swap", "#* If swap memory should be shown in memory box."},
+ {"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
+ {"show_disks", "#* If mem box should be split to also show disks info."},
+ {"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
+ {"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
+ {"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
+ {"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
+ {"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 \"device:speed\" separate disks with a comma \",\".\n"
+ "#* Example: \"/dev/sda:100, /dev/sdb:20\"."},
+ {"net_download", "#* Set fixed values for network graphs, default \"10M\" = 10 Mibibytes, possible units \"K\", \"M\", \"G\", append with \"bit\" for bits instead of bytes, i.e \"100mbit\"."},
+ {"net_upload", ""},
+ {"net_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
+ {"net_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
+ {"net_color_fixed", "#* If the network graphs color gradient should scale to bandwidth usage or auto scale, bandwidth usage is based on \"net_download\" and \"net_upload\" values."},
+ {"net_iface", "#* Starts with the Network Interface specified here."},
+ {"show_battery", "#* Show battery stats in top right if battery is present."},
+ {"log_level", "#* Set loglevel for \"~/.config/bpytop/error.log\" levels are: \"ERROR\" \"WARNING\" \"INFO\" \"DEBUG\".\n"
+ "#* The level set includes all lower levels, i.e. \"DEBUG\" will show all logging info."}
+ };
+
+ unordered_flat_map<string, string> strings = {
+ {"color_theme", "Default"},
+ {"shown_boxes", "cpu mem net proc"},
+ {"graph_symbol", "braille"},
+ {"graph_symbol_cpu", "default"},
+ {"graph_symbol_mem", "default"},
+ {"graph_symbol_net", "default"},
+ {"graph_symbol_proc", "default"},
+ {"proc_sorting", "cpu lazy"},
+ {"cpu_graph_upper", "total"},
+ {"cpu_graph_lower", "total"},
+ {"cpu_sensor", "Auto"},
+ {"temp_scale", "celsius"},
+ {"draw_clock", "%X"},
+ {"custom_cpu_name", ""},
+ {"disks_filter", ""},
+ {"io_graph_speeds", ""},
+ {"net_download", "10M"},
+ {"net_upload", "10M"},
+ {"net_iface", ""},
+ {"log_level", "WARNING"},
+ {"proc_filter", ""}
+ };
+ unordered_flat_map<string, string> stringsTmp;
+
+ unordered_flat_map<string, bool> bools = {
+ {"theme_background", true},
+ {"truecolor", true},
+ {"proc_reversed", false},
+ {"proc_tree", false},
+ {"proc_colors", true},
+ {"proc_gradient", true},
+ {"proc_per_core", false},
+ {"proc_mem_bytes", true},
+ {"cpu_invert_lower", true},
+ {"cpu_single_graph", false},
+ {"show_uptime", true},
+ {"check_temp", true},
+ {"show_coretemp", true},
+ {"show_cpu_freq", true},
+ {"background_update", true},
+ {"mem_graphs", true},
+ {"show_swap", true},
+ {"swap_disk", true},
+ {"show_disks", true},
+ {"only_physical", true},
+ {"use_fstab", false},
+ {"show_io_stat", true},
+ {"io_mode", false},
+ {"io_graph_combined", false},
+ {"net_color_fixed", false},
+ {"net_auto", true},
+ {"net_sync", false},
+ {"show_battery", true},
+ {"tty_mode", false},
+ {"force_tty", false},
+ };
+ unordered_flat_map<string, bool> boolsTmp;
+
+ unordered_flat_map<string, int> ints = {
+ {"update_ms", 2000},
+ {"proc_update_mult", 2},
+ };
+ unordered_flat_map<string, int> intsTmp;
+
+ bool _locked(const string& name){
+ atomic_wait(writelock);
+ if (!write_new && rng::find_if(descriptions, [&name](const auto& a){ return a.at(0) == name; }) != descriptions.end())
+ write_new = true;
+ return locked.load();
+ }
+ }
+
+ fs::path conf_dir;
+ fs::path conf_file;
+
+ vector<string> valid_graph_symbols = { "braille", "block", "tty" };
+
+ //* Return bool config value <name>
+ const bool& getB(string name){
+ return bools.at(name);
+ }
+
+ //* Return integer config value <name>
+ const int& getI(string name){
+ return ints.at(name);
+ }
+
+ //* Return string config value <name>
+ const string& getS(string name){
+ return strings.at(name);
+ }
+
+ //* Set config value <name> to bool <value>
+ void set(string name, bool value){
+ if (_locked(name)) boolsTmp.insert_or_assign(name, value);
+ else bools.at(name) = value;
+ }
+
+ //* Set config value <name> to int <value>
+ void set(string name, int value){
+ if (_locked(name)) intsTmp.insert_or_assign(name, value);
+ ints.at(name) = value;
+ }
+
+ //*