This commit is contained in:
aristocratos 2021-07-21 03:17:34 +02:00
parent 77ef41daea
commit 5d4e2ce182
16 changed files with 804 additions and 779 deletions

View File

@ -20,14 +20,34 @@
* Tab size: 4
## Optimization
* Alternative operators `and`, `or` and `not`.
* Avoid writing to disk if possible.
* Opening curly braces `{` at the end of the same line as the statement/condition.
* Make sure variables/vectors/maps/classes etc. are cleaned up if not reused.
## General guidelines
* Compare cpu and memory usage with and without your code and look for alternatives if they cause a noticeable negative impact.
* Don't force a programming style. Use object oriented, functional, data oriented, etc., where it's suitable.
For questions contact Aristocratos at admin@qvantnet.com
* Use [RAII](https://en.cppreference.com/w/cpp/language/raii).
* Make use of the standard algorithms library, (re)watch [C++ Seasoning](https://www.youtube.com/watch?v=W2tWOdzgXHA) and [105 STL Algorithms](https://www.youtube.com/watch?v=bFSnXNIsK4A) for inspiration.
* Make use of the included [robin_hood unordered map & set](https://github.com/martinus/robin-hood-hashing)
* Do not add includes if the same functionality can be achieved using the already included libraries.
* Use descriptive names for variables.
* Use comments if not very obvious what your code is doing.
* Add comments as labels for what's currently happening in bigger sections of code for better readability.
* Avoid writing to disk.
* If using the logger functions, be sensible, only call it if something of importance has changed.
* Benchmark your code and look for alternatives if they cause a noticeable negative impact.
For questions open a new discussion thread or send a mail to jakob@qvantnet.com
For proposing changes to this document create a [new issue](https://github.com/aristocratos/btop/issues/new/choose).

View File

@ -16,19 +16,11 @@ 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 <iostream>
#include <exception>
@ -58,8 +50,14 @@ tab-size = 4
#error Platform not supported!
#endif
using std::string, std::string_view, std::vector, std::array, std::atomic, std::endl, std::cout, std::min;
using std::flush, std::endl, std::string_literals::operator""s, std::to_string;
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
namespace Global {
const std::vector<std::array<std::string, 2>> Banner_src = {
const vector<array<string, 2>> Banner_src = {
{"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
{"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
{"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
@ -67,19 +65,12 @@ namespace Global {
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
};
const std::string Version = "0.0.30";
const string Version = "0.0.30";
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;
string overlay;
string exit_error_msg;
atomic<bool> thread_exception (false);
@ -88,6 +79,7 @@ namespace Global {
bool debuginit = false;
bool debug = false;
bool utf_force = false;
uint64_t start_time;
@ -100,15 +92,10 @@ namespace Global {
//* A simple argument parser
void argumentParser(int argc, char **argv){
string argument;
void argumentParser(const int& argc, char **argv) {
for(int i = 1; i < argc; i++) {
argument = argv[i];
if (argument == "-v" or argument == "--version") {
cout << "btop version: " << Global::Version << endl;
exit(0);
}
else if (argument == "-h" or argument == "--help") {
string argument = argv[i];
if (argument == "-h" or argument == "--help") {
cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n"
<< "optional arguments:\n"
<< " -h, --help show this help message and exit\n"
@ -116,12 +103,18 @@ void argumentParser(int argc, char **argv){
<< " -lc, --low-color disable truecolor, converts 24-bit colors to 256-color\n"
<< " -t, --tty_on force (ON) tty mode, max 16 colors and tty friendly graph symbols\n"
<< " +t, --tty_off force (OFF) tty mode\n"
<< " --utf-foce force start even if no UTF-8 locale was detected"
<< " --debug start with loglevel set to DEBUG, overriding value set in config\n"
<< endl;
exit(0);
}
else if (argument == "--debug")
Global::debug = true;
if (argument == "-v" or argument == "--version") {
cout << "btop version: " << Global::Version << endl;
exit(0);
}
else if (argument == "-lc" or argument == "--low-color") {
Global::arg_low_color = true;
}
else if (argument == "-t" or argument == "--tty_on") {
Config::set("tty_mode", true);
Global::arg_tty = true;
@ -130,9 +123,10 @@ void argumentParser(int argc, char **argv){
Config::set("tty_mode", false);
Global::arg_tty = true;
}
else if (argument == "-lc" or argument == "--low-color") {
Global::arg_low_color = true;
}
else if (argument == "--utf-force")
Global::utf_force = true;
else if (argument == "--debug")
Global::debug = true;
else {
cout << " Unknown argument: " << argument << "\n" <<
" Use -h or --help for help." << endl;
@ -142,7 +136,7 @@ void argumentParser(int argc, char **argv){
}
//* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true
void term_resize(bool force=false){
void term_resize(bool force=false) {
if (auto refreshed = Term::refresh() or force) {
if (force and refreshed) force = false;
Global::resized = true;
@ -160,7 +154,7 @@ void term_resize(bool force=false){
}
//* Exit handler; stops threads, restores terminal and saves config changes
void clean_quit(int sig){
void clean_quit(const int sig) {
if (Global::quitting) return;
Global::quitting = true;
Runner::stop();
@ -168,14 +162,17 @@ void clean_quit(int sig){
Term::restore();
if (not Global::debuginit) cout << Term::normal_screen << Term::show_cursor << flush;
}
if (not Global::exit_error_msg.empty()) {
Logger::error(Global::exit_error_msg);
cout << "ERROR: " << Global::exit_error_msg << endl;
}
Config::write();
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
if (not Global::exit_error_msg.empty()) cout << Global::exit_error_msg << endl;
if (sig != -1) exit(sig);
}
//* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
void _sleep(){
void _sleep() {
Runner::stop();
if (Term::initialized) {
Term::restore();
@ -185,7 +182,7 @@ void _sleep(){
}
//* Handler for SIGCONT; re-initialize terminal and force a resize event
void _resume(){
void _resume() {
Term::init();
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << flush;
term_resize(true);
@ -195,7 +192,7 @@ void _exit_handler() {
clean_quit(-1);
}
void _signal_handler(int sig) {
void _signal_handler(const int sig) {
switch (sig) {
case SIGINT:
clean_quit(0);
@ -212,24 +209,22 @@ void _signal_handler(int sig) {
}
}
//? Generate the btop++ banner
//* Generate the btop++ banner
void banner_gen() {
size_t z = 0;
string b_color, bg, fg, oc, letter;
auto& lowcolor = Config::getB("lowcolor");
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;
string b_color, bg, fg, oc, letter;
auto& lowcolor = Config::getB("lowcolor");
auto& tty_mode = Config::getB("tty_mode");
for (size_t z = 0; const auto& line : Global::Banner_src) {
if (const auto w = ulen(line[1]); w > Global::banner_width) Global::banner_width = w;
if (tty_mode) {
fg = (z > 2) ? "\x1b[31m" : "\x1b[91m";
bg = (z > 2) ? "\x1b[90m" : "\x1b[37m";
}
else {
fg = Theme::hex_to_color(line[0], lowcolor);
bg_i = 120 - z * 12;
int bg_i = 120 - z * 12;
bg = Theme::dec_to_color(bg_i, bg_i, bg_i, lowcolor);
}
for (size_t i = 0; i < line[1].size(); i += 3) {
@ -253,34 +248,33 @@ void banner_gen() {
+ Fx::i + "v" + Global::Version + Fx::ui;
}
//? Manages secondary thread for collection and drawing of boxes
//* Manages secondary thread for collection and drawing of boxes
namespace Runner {
atomic<bool> active (false);
atomic<bool> stopping (false);
atomic<bool> has_output (false);
atomic<uint64_t> time_spent (0);
string output;
string overlay;
//* Secondary thread; run collect, draw and print out
void _runner(const vector<string> boxes, const bool no_update, const bool force_redraw, const bool interrupt) {
auto timestamp = time_micros();
string out;
out.reserve(output.size());
output.clear();
for (auto& box : boxes) {
for (const auto& box : boxes) {
if (stopping) break;
try {
if (box == "cpu") {
out += Cpu::draw(Cpu::collect(no_update), force_redraw);
output += Cpu::draw(Cpu::collect(no_update), force_redraw);
}
else if (box == "mem") {
out += Mem::draw(Mem::collect(no_update), force_redraw);
output += Mem::draw(Mem::collect(no_update), force_redraw);
}
else if (box == "net") {
out += Net::draw(Net::collect(no_update), force_redraw);
output += Net::draw(Net::collect(no_update), force_redraw);
}
else if (box == "proc") {
out += Proc::draw(Proc::collect(no_update), force_redraw);
output += Proc::draw(Proc::collect(no_update), force_redraw);
}
}
catch (const std::exception& e) {
@ -289,7 +283,6 @@ namespace Runner {
Global::exit_error_msg = "Exception in runner thread -> "
+ fname + "::draw(" + fname + "::collect(no_update=" + (no_update ? "true" : "false")
+ "), force_redraw=" + (force_redraw ? "true" : "false") + ") : " + (string)e.what();
Logger::error(Global::exit_error_msg);
Global::thread_exception = true;
Input::interrupt = true;
stopping = true;
@ -303,31 +296,39 @@ namespace Runner {
return;
}
if (out.empty()) {
out += "No boxes shown!";
if (output.empty()) {
output += "No boxes shown!";
}
output.swap(out);
time_spent = time_micros() - timestamp;
has_output = true;
//? If overlay isn't empty, print output without color and effects and then print overlay over
cout << Term::sync_start << (overlay.empty() ? output : Theme::c("inactive_fg") + Fx::uncolor(output) + overlay) << Term::sync_end << flush;
//! DEBUG stats -->
cout << Fx::reset << Mv::to(2, 2) << "Runner took: " << rjust(to_string(time_micros() - timestamp), 5) << " μs. " << flush;
if (interrupt) Input::interrupt = true;
active = false;
active.notify_one();
active.notify_all();
}
void run(const string box, const bool no_update, const bool force_redraw, const bool input_interrupt) {
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values, box="all" for all boxes
void run(const string& box, const bool no_update, const bool force_redraw, const bool input_interrupt) {
active.wait(true);
Config::unlock();
if (stopping) return;
active = true;
Config::lock();
vector<string> boxes;
if (box == "all") boxes = Config::current_boxes;
else boxes.push_back(box);
std::thread run_thread(_runner, boxes, no_update, force_redraw, input_interrupt);
if (not Global::overlay.empty())
overlay = Global::overlay;
else if (not overlay.empty())
overlay.clear();
std::thread run_thread(_runner, (box == "all" ? Config::current_boxes : vector{box}), no_update, force_redraw, input_interrupt);
run_thread.detach();
}
//* Stops any secondary thread running and unlocks config
void stop() {
stopping = true;
active.wait(true);
@ -335,25 +336,20 @@ namespace Runner {
stopping = false;
}
string get_output() {
active.wait(true);
Config::unlock();
has_output = false;
return output;
}
}
//? --------------------------------------------- Main starts here! ---------------------------------------------------
int main(int argc, char **argv){
//* --------------------------------------------- Main starts here! ---------------------------------------------------
int main(int argc, char **argv) {
//? Init
//? ------------------------------------------------ INIT ---------------------------------------------------------
Global::start_time = time_s();
cout.setf(std::ios::boolalpha);
//? Call argument parser if launched with arguments
if (argc > 1) argumentParser(argc, argv);
//? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
std::atexit(_exit_handler);
std::at_quick_exit(_exit_handler);
std::signal(SIGINT, _signal_handler);
@ -365,14 +361,15 @@ int main(int argc, char **argv){
#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();
{ 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"}) {
//? Setup paths for config, log and user themes
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) {
Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
break;
@ -381,7 +378,7 @@ int main(int argc, char **argv){
if (not Config::conf_dir.empty()) {
if (std::error_code ec; not fs::is_directory(Config::conf_dir) and not 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;
cout << "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
}
else {
Config::conf_file = Config::conf_dir / "btop.conf";
@ -390,11 +387,12 @@ int main(int argc, char **argv){
if (not fs::exists(Theme::user_theme_dir) and not fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear();
}
}
//? Try to find global btop theme path relative to binary path
if (std::error_code ec; not Global::self_path.empty()) {
Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
if (ec or not fs::is_directory(Theme::theme_dir) or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
}
//? If relative path failed, check two most common absolute paths
if (Theme::theme_dir.empty()) {
for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) {
if (fs::is_directory(fs::path(theme_path)) and access(theme_path, R_OK) != -1) {
@ -415,14 +413,36 @@ int main(int argc, char **argv){
Logger::info("Logger set to " + Config::getS("log_level"));
for (auto& err_str : load_errors) Logger::warning(err_str);
for (const auto& err_str : load_errors) Logger::warning(err_str);
}
if (not string(getenv("LANG")).ends_with("UTF-8") and not 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;
//? Try to find and set a UTF-8 locale
if (bool found = false; not str_to_upper((string)std::setlocale(LC_ALL, NULL)).ends_with("UTF-8")) {
if (const string lang = (string)getenv("LANG"); str_to_upper(lang).ends_with("UTF-8")) {
found = true;
std::setlocale(LC_ALL, lang.c_str());
}
else if (const string loc = std::locale("").name(); not loc.empty()) {
try {
for (auto& l : ssplit(loc, ';')) {
if (str_to_upper(l).ends_with("UTF-8")) {
found = true;
std::setlocale(LC_ALL, l.substr(l.find('=') + 1).c_str());
break;
}
}
}
catch (const std::out_of_range&) { found = false; }
}
if (not found and Global::utf_force)
Logger::warning("No UTF-8 locale detected! Forcing start with --utf-force argument.");
else if (not found) {
Global::exit_error_msg = "No UTF-8 locale detected! Use --utf-force argument to start anyway.";
clean_quit(1);
}
else
Logger::debug("Setting LC_ALL=" + (string)std::setlocale(LC_ALL, NULL));
}
//? Initialize terminal and set options
@ -443,52 +463,54 @@ int main(int argc, char **argv){
Logger::info("Real tty detected, setting 16 color mode and using tty friendly graph symbols");
}
//? Platform init and error check
//? Platform dependent init and error check
Shared::init();
// Config::set("truecolor", false);
auto thts = time_micros();
//? Update theme list and generate the theme
//? Update list of available themes and generate the selected theme
Theme::updateThemes();
Theme::setTheme();
//? Create the btop++ banner
banner_gen();
//? Calculate sizes of all boxes
Draw::calcSizes();
//? Start first collection and drawing run
Runner::run();
Global::debuginit = false; //! Debug -- remove
//? Switch to alternative terminal buffer, hide cursor, and print out box outlines
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor;
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
//* ------------------------------------------------ TESTING ------------------------------------------------------
Global::debuginit = false;
if (false) {
Draw::calcSizes();
if (not Global::debuginit) cout << Term::alt_screen << Term::hide_cursor << Term::clear << endl;
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
cout << "Current: " << std::setlocale(LC_ALL, NULL) << endl;
exit(0);
}
//* Test theme
if (false) {
string key;
bool no_redraw = false;
Config::unlock();
auto theme_index = v_index(Theme::themes, Config::getS("color_theme"));
uint64_t timer = 0;
while (key != "q") {
key.clear();
if (not no_redraw) {
cout << "\nTheme generation of " << fs::path(Config::getS("color_theme")).filename().replace_extension("") << " took " << time_micros() - thts << "μs" << endl;
cout << "Colors:" << endl;
cout << Fx::reset << Term::clear << "Theme: " << Config::getS("color_theme") << ". Generation took " << timer << " μs." << endl;
size_t i = 0;
for(auto& item : Theme::test_colors()) {
for(const auto& item : Theme::colors) {
cout << rjust(item.first, 15) << ":" << item.second << ""s * 10 << Fx::reset << " ";
if (++i == 4) {
i = 0;
@ -499,7 +521,7 @@ int main(int argc, char **argv){
cout << "Gradients:";
for (auto& [name, cvec] : Theme::test_gradients()) {
for (const auto& [name, cvec] : Theme::gradients) {
cout << endl << rjust(name + ":", 10);
for (auto& color : cvec) {
cout << color << "";
@ -512,7 +534,6 @@ int main(int argc, char **argv){
no_redraw = true;
key = Input::wait();
if (key.empty()) continue;
thts = time_micros();
if (key == "right") {
if (theme_index == Theme::themes.size() - 1) theme_index = 0;
else theme_index++;
@ -524,7 +545,9 @@ int main(int argc, char **argv){
else continue;
no_redraw = false;
Config::set("color_theme", Theme::themes.at(theme_index));
timer = time_micros();
Theme::setTheme();
timer = time_micros() - timer;
}
@ -558,7 +581,6 @@ int main(int argc, char **argv){
<< Mv::restore << Mv::d(26) << kgraph3(mydata, true) << '\n'
<< Mv::d(1) << "Init took " << time_micros() - kts << " μs. " << endl;
list<uint64_t> ktavg;
for (;;) {
mydata.back() = std::rand() % 101;
kts = time_micros();
@ -566,9 +588,7 @@ int main(int argc, char **argv){
<< Mv::restore << Mv::d(13) << kgraph2(mydata)
<< Mv::restore << Mv::d(26) << kgraph3(mydata)
<< Term::sync_end << 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;
cout << Mv::d(1) << "Time: " << time_micros() - kts << " μs. " << flush;
if (Input::poll()) {
if (Input::get() == "space") Input::wait();
else break;
@ -583,15 +603,15 @@ int main(int argc, char **argv){
//* ------------------------------------------------ MAIN LOOP ----------------------------------------------------
//? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
uint64_t future_time = time_ms(), rcount = 0;
list<uint64_t> avgtimes;
uint64_t update_ms = Config::getI("update_ms");
auto future_time = time_ms();
try {
while (not true not_eq not false) {
//? Check for exceptions in secondary thread and exit with fail signal if true
if (Global::thread_exception) clean_quit(1);
uint64_t update_ms = Config::getI("update_ms");
//? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
term_resize();
@ -604,31 +624,28 @@ int main(int argc, char **argv){
Runner::run("all", true, true);
}
//? Print out any available output from secondary thread
if (Runner::has_output) {
cout << Term::sync_start << Runner::get_output() << Term::sync_end << flush;
//! DEBUG stats -->
avgtimes.push_front(Runner::time_spent);
if (avgtimes.size() > 30) avgtimes.pop_back();
cout << Fx::reset << Mv::to(2, 2) << "Runner took: " << rjust(to_string(avgtimes.front()), 5) << " μs. Average: " <<
rjust(to_string(accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size()), 5) << " μs of " << avgtimes.size() <<
" samples. Run count: " << ++rcount << ". " << flush;
//! <--
}
//? Start collect & draw thread at the interval set by <update_ms> config value
//? Start secondary collect & draw thread at the interval set by <update_ms> config value
if (time_ms() >= future_time) {
Runner::run("all");
update_ms = Config::getI("update_ms");
future_time = time_ms() + update_ms;
}
//? Loop over input polling and input action processing and check for external clock changes
//? Loop over input polling and input action processing
for (auto current_time = time_ms(); current_time < future_time and not Global::resized; current_time = time_ms()) {
//? Check for external clock changes to avoid a timer bugs
if (future_time - current_time > update_ms)
future_time = current_time;
else if (Input::poll(future_time - current_time))
//? Poll for input and process any input detected
else if (Input::poll(min(1000ul, future_time - current_time))) {
if (not Runner::active)
Config::unlock();
Input::process(Input::get());
}
//? Break the loop at 1000ms intervals or if input polling was interrupted
else
break;
}
@ -637,7 +654,6 @@ int main(int argc, char **argv){
}
catch (std::exception& e) {
Global::exit_error_msg = "Exception in main loop -> " + (string)e.what();
Logger::error(Global::exit_error_msg);
clean_quit(1);
}

View File

@ -17,7 +17,6 @@ tab-size = 4
*/
#include <array>
#include <robin_hood.h>
#include <ranges>
#include <atomic>
#include <fstream>
@ -26,215 +25,214 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
using robin_hood::unordered_flat_map, std::map, std::array, std::atomic;
using 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", "#* Full path to a bashtop/bpytop/btop++ formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes."},
atomic<bool> locked (false);
atomic<bool> writelock (false);
bool write_new;
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
const vector<array<string, 2>> descriptions = {
{"color_theme", "#* Full path to a bashtop/bpytop/btop++ formatted \".theme\" file, \"Default\" and \"TTY\" for builtin themes."},
{"truecolor", "#* Sets if 24-bit truecolor should be used, will convert 24-bit colors to 256 color (6x6x6 color cube) if false."},
{"theme_background", "#* If the theme set background should be shown, set to False if you want terminal background transparency."},
{"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n"
"#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
{"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."},
{"force_tty", "#* Set to true to force tty mode regardless if a real tty has been detected or not.\n"
"#* Will force 16-color mode and TTY theme, set all graph symbols to \"tty\" and swap out other non tty friendly symbols."},
{"graph_symbol_cpu", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
{"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_mem", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
{"graph_symbol_cpu", "# 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_mem", "# 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\"."},
{"graph_symbol_net", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
{"graph_symbol_proc", "# Graph symbol to use for graphs in cpu box, \"default\", \"braille\", \"block\" or \"tty\"."},
{"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."},
{"shown_boxes", "#* Manually set which boxes to show. Available values are \"cpu mem net proc\", separate values with whitespace."},
{"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)."},
{"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_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_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_reversed", "#* Reverse sorting order, True or False."},
{"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_tree", "#* Show processes as a tree."},
{"proc_reversed", "#* Reverse sorting order, True or False."},
{"proc_colors", "#* Use the cpu graph colors in the process list."},
{"proc_tree", "#* Show processes as a tree."},
{"proc_gradient", "#* Use a darkening gradient in the process list."},
{"proc_colors", "#* Use the cpu graph colors 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_gradient", "#* Use a darkening gradient in the process list."},
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
{"proc_per_core", "#* If process cpu usage should be of the core it's running on or usage of the total available cpu power."},
{"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."},
{"proc_mem_bytes", "#* Show process memory as bytes instead of percent."},
{"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_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_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
{"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_single_graph", "#* Set to True to completely disable the lower CPU graph."},
{"cpu_invert_lower", "#* Toggles if the lower CPU graph should be inverted."},
{"show_uptime", "#* Shows the system uptime in the CPU box."},
{"cpu_single_graph", "#* Set to True to completely disable the lower CPU graph."},
{"check_temp", "#* Show cpu temperature."},
{"show_uptime", "#* Shows the system uptime in the CPU box."},
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
{"check_temp", "#* Show cpu temperature."},
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
{"cpu_sensor", "#* Which sensor to use for cpu temperature, use options menu to select from list of available sensors."},
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
{"show_coretemp", "#* Show temperatures for cpu cores also if check_temp is True and sensors has been found."},
{"show_cpu_freq", "#* Show CPU frequency."},
{"temp_scale", "#* Which temperature scale to use, available values: \"celsius\", \"fahrenheit\", \"kelvin\" and \"rankine\"."},
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
{"show_cpu_freq", "#* Show CPU frequency."},
{"background_update", "#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort."},
{"draw_clock", "#* Draw a clock at top of screen, formatting according to strftime, empty string to disable."},
{"custom_cpu_name", "#* Custom cpu model name, 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."},
{"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\"."},
{"custom_cpu_name", "#* Custom cpu model name, empty string to disable."},
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
{"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\"."},
{"show_swap", "#* If swap memory should be shown in memory box."},
{"mem_graphs", "#* Show graphs instead of meters for memory values."},
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
{"show_swap", "#* If swap memory should be shown in memory box."},
{"show_disks", "#* If mem box should be split to also show disks info."},
{"swap_disk", "#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk."},
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
{"show_disks", "#* If mem box should be split to also show disks info."},
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
{"only_physical", "#* Filter out non physical disks. Set this to False to include network disks, RAM disks and similar."},
{"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
{"use_fstab", "#* Read disks list from /etc/fstab. This also disables only_physical."},
{"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
{"show_io_stat", "#* Toggles if io stats should be shown in regular disk usage view."},
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
{"io_mode", "#* Toggles io mode for disks, showing only big graphs for disk read/write speeds."},
{"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\"."},
{"io_graph_combined", "#* Set to True to show combined read/write io graphs in io mode."},
{"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\"."},
{"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_upload", ""},
{"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_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
{"net_upload", ""},
{"net_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
{"net_auto", "#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest."},
{"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_sync", "#* Sync the scaling for download and upload to whichever currently has the highest scale."},
{"net_iface", "#* Starts with the Network Interface specified here."},
{"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."},
{"show_battery", "#* Show battery stats in top right if battery is present."},
{"net_iface", "#* Starts with the Network Interface specified here."},
{"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."}
};
{"show_battery", "#* Show battery stats in top right if battery is present."},
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", ""},
{"proc_command", ""},
};
unordered_flat_map<string, string> stringsTmp;
{"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, 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},
{"lowcolor", false},
{"show_detailed", false},
{"proc_filtering", false},
};
unordered_flat_map<string, bool> boolsTmp;
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", ""},
{"proc_command", ""},
};
unordered_flat_map<string, string> stringsTmp;
unordered_flat_map<string, int> ints = {
{"update_ms", 2000},
{"proc_update_mult", 2},
{"detailed_pid", 0},
{"selected_pid", 0},
{"proc_start", 0},
{"proc_selected", 0},
};
unordered_flat_map<string, int> intsTmp;
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},
{"lowcolor", false},
{"show_detailed", false},
{"proc_filtering", false},
};
unordered_flat_map<string, bool> boolsTmp;
vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
unordered_flat_map<string, int> ints = {
{"update_ms", 2000},
{"proc_update_mult", 2},
{"detailed_pid", 0},
{"selected_pid", 0},
{"proc_start", 0},
{"proc_selected", 0},
};
unordered_flat_map<string, int> intsTmp;
bool _locked(const string& name){
writelock.wait(true);
if (not write_new and rng::find_if(descriptions, [&name](const auto& a){ return a.at(0) == name; }) != descriptions.end())
write_new = true;
return locked.load();
}
vector<string> valid_boxes = { "cpu", "mem", "net", "proc" };
bool _locked(const string& name) {
writelock.wait(true);
if (not write_new and 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;
@ -242,36 +240,22 @@ namespace Config {
vector<string> current_boxes;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
const bool& getB(const string& name){
return bools.at(name);
}
const int& getI(const string& name){
return ints.at(name);
}
const string& getS(const string& name){
return strings.at(name);
}
void set(string name, bool value){
void set(string name, bool value) {
if (_locked(name)) boolsTmp.insert_or_assign(name, value);
else bools.at(name) = value;
}
void set(string name, int value){
void set(string name, int value) {
if (_locked(name)) intsTmp.insert_or_assign(name, value);
ints.at(name) = value;
}
void set(string name, string value){
void set(string name, string value) {
if (_locked(name)) stringsTmp.insert_or_assign(name, value);
else strings.at(name) = value;
}
void flip(string name){
void flip(string name) {
if (_locked(name)) {
if (boolsTmp.contains(name)) boolsTmp.at(name) = not boolsTmp.at(name);
else boolsTmp.insert_or_assign(name, (not bools.at(name)));
@ -279,42 +263,49 @@ namespace Config {
else bools.at(name) = not bools.at(name);
}
void lock(){
void lock() {
writelock.wait(true);
locked = true;
}
void unlock(){
void unlock() {
if (not locked) return;
writelock.wait(true);
writelock = true;
if (Proc::shown) {
ints.at("selected_pid") = Proc::selected_pid;
ints.at("proc_start") = Proc::start;
ints.at("proc_selected") = Proc::selected;
}
try {
if (Proc::shown) {
ints.at("selected_pid") = Proc::selected_pid;
ints.at("proc_start") = Proc::start;
ints.at("proc_selected") = Proc::selected;
}
for (auto& item : stringsTmp){
strings.at(item.first) = item.second;
}
stringsTmp.clear();
for (auto& item : stringsTmp) {
strings.at(item.first) = item.second;
}
stringsTmp.clear();
for (auto& item : intsTmp){
ints.at(item.first) = item.second;
}
intsTmp.clear();
for (auto& item : intsTmp) {
ints.at(item.first) = item.second;
}
intsTmp.clear();
for (auto& item : boolsTmp){
bools.at(item.first) = item.second;
for (auto& item : boolsTmp) {
bools.at(item.first) = item.second;
}
boolsTmp.clear();
}
catch (const std::exception& e) {
Global::exit_error_msg = "Exception during Config::unlock() : " + (string)e.what();
clean_quit(1);
}
boolsTmp.clear();
locked = false;
writelock = false;
writelock.notify_all();
}
bool check_boxes(string boxes){
bool check_boxes(string boxes) {
auto new_boxes = ssplit(boxes);
for (auto& box : new_boxes) {
if (not v_contains(valid_boxes, box)) return false;
@ -323,7 +314,7 @@ namespace Config {
return true;
}
void load(fs::path conf_file, vector<string>& load_errors){
void load(fs::path conf_file, vector<string>& load_errors) {
if (conf_file.empty())
return;
else if (not fs::exists(conf_file)) {
@ -392,7 +383,7 @@ namespace Config {
}
}
void write(){
void write() {
if (conf_file.empty() or not write_new) return;
Logger::debug("Writing new config file");
std::ofstream cwrite(conf_file, std::ios::trunc);

View File

@ -20,9 +20,10 @@ tab-size = 4
#include <string>
#include <vector>
#include <robin_hood.h>
#include <filesystem>
using std::string, std::vector;
using std::string, std::vector, robin_hood::unordered_flat_map;
//* Functions and variables for reading and writing the btop config file
namespace Config {
@ -30,7 +31,11 @@ namespace Config {
extern std::filesystem::path conf_dir;
extern std::filesystem::path conf_file;
extern const vector<string> valid_graph_symbols;
extern unordered_flat_map<string, string> strings;
extern unordered_flat_map<string, bool> bools;
extern unordered_flat_map<string, int> ints;
const vector<string> valid_graph_symbols = { "braille", "block", "tty" };
extern vector<string> current_boxes;
@ -38,13 +43,13 @@ namespace Config {
bool check_boxes(string boxes);
//* Return bool for config key <name>
const bool& getB(const string& name);
inline const bool& getB(const string& name) { return bools.at(name); }
//* Return integer for config key <name>
const int& getI(const string& name);
inline const int& getI(const string& name) { return ints.at(name); }
//* Return string for config key <name>
const string& getS(const string& name);
inline const string& getS(const string& name) { return strings.at(name); }
//* Set config key <name> to bool <value>
void set(string name, bool value);
@ -58,7 +63,7 @@ namespace Config {
//* Flip config key bool <name>
void flip(string name);
//* Wait if locked then lock config and cache changes until unlock
//* Lock config and cache changes until unlocked
void lock();
//* Unlock config and write any cached values to config

View File

@ -17,8 +17,6 @@ tab-size = 4
*/
#include <array>
#include <map>
#include <ranges>
#include <algorithm>
#include <cmath>
@ -32,7 +30,6 @@ tab-size = 4
using std::round, std::views::iota, std::string_literals::operator""s, std::clamp, std::array, std::floor, std::max, std::min,
std::to_string;
namespace rng = std::ranges;
using namespace Tools;
namespace Symbols {
@ -100,7 +97,7 @@ namespace Symbols {
namespace Draw {
string createBox(int x, int y, int width, int height, string line_color, bool fill, string title, string title2, int num){
string createBox(int x, int y, int width, int height, string line_color, bool fill, string title, string title2, int num) {
string out;
string lcolor = (line_color.empty()) ? Theme::c("div_line") : line_color;
string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (Config::getB("tty_mode") ? std::to_string(num) : Symbols::superscript[num]);
@ -108,12 +105,12 @@ namespace Draw {
out = Fx::reset + lcolor;
//? Draw horizontal lines
for (int hpos : {y, y + height - 1}){
for (int hpos : {y, y + height - 1}) {
out += Mv::to(hpos, x) + Symbols::h_line * (width - 1);
}
//? Draw vertical lines and fill if enabled
for (int hpos : iota(y + 1, y + height - 1)){
for (int hpos : iota(y + 1, y + height - 1)) {
out += Mv::to(hpos, x) + Symbols::v_line +
((fill) ? string(width - 2, ' ') : Mv::r(width - 2)) +
Symbols::v_line;
@ -126,11 +123,11 @@ namespace Draw {
Mv::to(y + height - 1, x + width - 1) + Symbols::right_down;
//? Draw titles if defined
if (not title.empty()){
if (not title.empty()) {
out += Mv::to(y, x + 2) + Symbols::title_left + Fx::b + numbering + Theme::c("title") + title +
Fx::ub + lcolor + Symbols::title_right;
}
if (not title2.empty()){
if (not title2.empty()) {
out += Mv::to(y + height - 1, x + 2) + Symbols::title_left + Theme::c("title") + title2 +
Fx::ub + lcolor + Symbols::title_right;
}
@ -186,7 +183,7 @@ namespace Draw {
else data_value = data[i];
if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll);
//? Vertical iteration over height of graph
for (int horizon : iota(0, height)){
for (int horizon : iota(0, height)) {
int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100;
int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0;
//? Calculate previous + current value to fit two values in 1 braille character
@ -338,7 +335,7 @@ namespace Proc {
string box;
void selection(string cmd_key) {
void selection(const string& cmd_key) {
auto start = Config::getI("proc_start");
auto selected = Config::getI("proc_selected");
int numpids = Proc::numpids;
@ -371,7 +368,7 @@ namespace Proc {
Config::set("proc_selected", selected);
}
string draw(const vector<proc_info>& plist, bool force_redraw){
string draw(const vector<proc_info>& plist, bool force_redraw) {
auto& filter = Config::getS("proc_filter");
auto& filtering = Config::getB("proc_filtering");
auto& proc_tree = Config::getB("proc_tree");
@ -390,7 +387,7 @@ namespace Proc {
redraw = false;
out = box;
out += Mv::to(y, x) + Mv::r(12)
+ trans("Filter: " + filter + (filtering ? Fx::bl + "" + Fx::reset : " "))
+ trans("Filter: " + filter + (filtering ? Fx::bl + ""s + Fx::reset : " "))
+ trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: "
+ string(Config::getS("proc_sorting")), width - 23 - ulen(filter)));
@ -420,7 +417,7 @@ namespace Proc {
//* Iteration over processes
int lc = 0;
for (int n=0; auto& p : plist){
for (int n=0; auto& p : plist) {
if (n++ < start) continue;
bool is_selected = (lc + 1 == selected);
if (is_selected) selected_pid = (int)p.pid;
@ -463,8 +460,8 @@ namespace Proc {
if (not proc_tree) {
out += Mv::to(y+2+lc, x+1)
+ g_color + rjust(to_string(p.pid), 8) + ' '
+ c_color + ljust(p.name, (width < 70 ? width - 46 : 16)) + end
+ (width >= 70 ? g_color + ljust(p.cmd, width - 62, true) : "");
+ c_color + ljust(p.name, (width < 70 ? width - 46 : 15)) + end
+ (width >= 70 ? g_color + ' ' + ljust(p.cmd, width - 62, true) : "");
}
//? Tree view line
else {
@ -509,7 +506,7 @@ namespace Proc {
}
namespace Draw {
void calcSizes(){
void calcSizes() {
auto& boxes = Config::getS("shown_boxes");
Cpu::box.clear();

View File

@ -22,24 +22,8 @@ tab-size = 4
#include <vector>
#include <robin_hood.h>
#include <deque>
#include <atomic>
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque, std::atomic;
namespace Symbols {
extern const string h_line;
extern const string v_line;
extern const string left_up;
extern const string right_up;
extern const string left_down;
extern const string right_down;
extern const string title_left;
extern const string title_right;
extern const string div_up;
extern const string div_down;
}
//! See btop_shared.hpp for draw & misc functions for boxes
using std::string, std::vector, robin_hood::unordered_flat_map, std::deque;
namespace Draw {
@ -60,7 +44,7 @@ namespace Draw {
string operator()(int value);
};
//* Class holding a graph
//* Class holding a percentage graph
class Graph {
string out, color_gradient, symbol = "default";
int width = 0, height = 0;

View File

@ -72,7 +72,7 @@ namespace Input {
string last = "";
bool poll(int timeout){
bool poll(int timeout) {
if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
while (timeout > 0) {
if (cin.rdbuf()->in_avail() > 0) return true;
@ -83,10 +83,10 @@ namespace Input {
return false;
}
string get(){
string get() {
string key;
while (cin.rdbuf()->in_avail() > 0 and key.size() < 100) key += cin.get();
if (not key.empty()){
if (not key.empty()) {
if (key.substr(0,2) == Fx::e) key.erase(0, 1);
if (Key_escapes.contains(key)) key = Key_escapes.at(key);
else if (ulen(key) > 1) key = "";
@ -95,7 +95,7 @@ namespace Input {
return key;
}
string wait(){
string wait() {
while (cin.rdbuf()->in_avail() < 1) {
if (interrupt) { interrupt = false; return ""; }
sleep_ms(10);
@ -103,11 +103,11 @@ namespace Input {
return get();
}
void clear(){
void clear() {
last.clear();
}
void process(const string key){
void process(const string key) {
if (key.empty()) return;
try {
auto& filtering = Config::getB("proc_filtering");
@ -120,28 +120,58 @@ namespace Input {
bool keep_going = false;
if (filtering) {
string filter = Config::getS("proc_filter");
if (key == "enter") Config::set("proc_filtering", false);
else if (key == "backspace" and not filter.empty()) filter = uresize(filter, ulen(filter) - 1);
else if (key == "space") filter.push_back(' ');
else if (ulen(key) == 1) filter.append(key);
else return;
if (key == "enter")
Config::set("proc_filtering", false);
else if (key == "backspace" and not filter.empty())
filter = uresize(filter, ulen(filter) - 1);
else if (key == "space")
filter.push_back(' ');
else if (ulen(key) == 1)
filter.append(key);
else
return;
Config::set("proc_filter", filter);
}
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;
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;
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") { Config::flip("proc_filtering"); recollect = false; }
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" and not Config::getS("proc_filter").empty()) Config::set("proc_filter", ""s);
else if (key == "f") {
Config::flip("proc_filtering");
recollect = false;
}
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" and not Config::getS("proc_filter").empty())
Config::set("proc_filter", ""s);
else if (key == "ö") {
if (Global::overlay.empty())
Global::overlay = Mv::to(Term::height / 2, Term::width / 2) + "\x1b[1;32mTESTING";
else
Global::overlay.clear();
Runner::run("all", true, true);
}
else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) {
Proc::selection(key);
recollect = false;

View File

@ -18,25 +18,16 @@ tab-size = 4
#if defined(__linux__)
#include <string>
#include <vector>
#include <atomic>
#include <fstream>
#include <filesystem>
#include <ranges>
#include <list>
#include <cmath>
#include <iostream>
#include <cmath>
#include <unistd.h>
#include <btop_shared.hpp>
#include <btop_config.hpp>
#include <btop_tools.hpp>
using std::string, std::vector, std::ifstream, std::atomic, std::numeric_limits, std::streamsize,
std::round, std::string_literals::operator""s;
namespace fs = std::filesystem;
@ -46,7 +37,7 @@ using namespace Tools;
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
namespace Tools {
double system_uptime(){
double system_uptime() {
string upstr;
ifstream pread("/proc/uptime");
getline(pread, upstr, ' ');
@ -63,13 +54,11 @@ namespace Shared {
long page_size;
long clk_tck;
void init(){
void init() {
proc_path = (fs::is_directory(fs::path("/proc")) and access("/proc", R_OK) != -1) ? "/proc" : "";
if (proc_path.empty()) {
string errmsg = "Proc filesystem not found or no permission to read from it!";
Logger::error(errmsg);
std::cout << "ERROR: " << errmsg << std::endl;
exit(1);
Global::exit_error_msg = "Proc filesystem not found or no permission to read from it!";
clean_quit(1);
}
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path;
@ -84,7 +73,7 @@ namespace Shared {
clk_tck = sysconf(_SC_CLK_TCK);
if (clk_tck <= 0) {
clk_tck = 100;
Logger::warning("Could not get system clocks per second. Defaulting to 100, processes cpu usage might be incorrect.");
Logger::warning("Could not get system clock ticks per second. Defaulting to 100, processes cpu usage might be incorrect.");
}
}
@ -171,7 +160,7 @@ namespace Proc {
detail_container detailed;
//* Generate process tree list
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false){
void _tree_gen(const proc_info& cur_proc, const vector<proc_info>& in_procs, vector<proc_info>& out_procs, int cur_depth, const bool collapsed, const string& filter, bool found=false) {
if (Runner::stopping) return;
auto cur_pos = out_procs.size();
bool filtering = false;
@ -193,12 +182,13 @@ namespace Proc {
if (not collapsed and not filtering) {
out_procs.push_back(cur_proc);
if (auto& cmdline = cache.at(cur_proc.pid).cmd; not cmdline.empty() and not cmdline.starts_with("(")) {
cmdline = cmdline.substr(0, std::min(cmdline.find(' '), cmdline.size()));
cmdline = cmdline.substr(std::min(cmdline.find_last_of('/') + 1, cmdline.size()));
if (cmdline == cur_proc.name)
std::string_view cmd_view = cmdline;
cmd_view = cmd_view.substr(0, std::min(cmd_view.find(' '), cmd_view.size()));
cmd_view = cmd_view.substr(std::min(cmd_view.find_last_of('/') + 1, cmd_view.size()));
if (cmd_view == cur_proc.name)
cmdline.clear();
else
cmdline = '(' + cmdline + ')';
cmdline = '(' + (string)cmd_view + ')';
out_procs.back().cmd = cmdline;
}
}
@ -222,7 +212,7 @@ namespace Proc {
}
//* Get detailed info for selected process
void _collect_details(size_t pid, uint64_t uptime, vector<proc_info>& procs, bool is_filtered){
void _collect_details(const size_t pid, const uint64_t uptime, vector<proc_info>& procs, const bool is_filtered) {
fs::path pid_path = Shared::proc_path / std::to_string(pid);
if (pid != detailed.last_pid) {
@ -274,8 +264,8 @@ namespace Proc {
else
detailed.memory = floating_humanizer(rss, false, 1);
}
catch (std::invalid_argument const&) {}
catch (std::out_of_range const&) {}
catch (const std::invalid_argument&) {}
catch (const std::out_of_range&) {}
}
d_read.close();
}
@ -302,8 +292,8 @@ namespace Proc {
d_read.ignore(SSmax, '\n');
}
}
catch (std::invalid_argument const&) {}
catch (std::out_of_range const&) {}
catch (const std::invalid_argument&) {}
catch (const std::out_of_range&) {}
}
d_read.close();
}
@ -313,27 +303,27 @@ namespace Proc {
}
//* Collects and sorts process information from /proc
vector<proc_info>& collect(const bool return_last){
vector<proc_info>& collect(const bool return_last) {
if (return_last) return current_procs;
auto& sorting = Config::getS("proc_sorting");
auto reverse = Config::getB("proc_reversed");
auto& filter = Config::getS("proc_filter");
auto per_core = Config::getB("proc_per_core");
auto tree = Config::getB("proc_tree");
const auto& sorting = Config::getS("proc_sorting");
const auto& reverse = Config::getB("proc_reversed");
const auto& filter = Config::getS("proc_filter");
const auto& per_core = Config::getB("proc_per_core");
const auto& tree = Config::getB("proc_tree");
if (tree_state != tree) {
cache.clear();
tree_state = tree;
}
auto show_detailed = Config::getB("show_detailed");
size_t detailed_pid = Config::getI("detailed_pid");
const auto& show_detailed = Config::getB("show_detailed");
const size_t detailed_pid = Config::getI("detailed_pid");
ifstream pread;
string long_string;
string short_str;
double uptime = system_uptime();
const double uptime = system_uptime();
vector<proc_info> procs;
procs.reserve(reserve_pids + 10);
int npids = 0;
int cmult = (per_core) ? Global::coreCount : 1;
const int cmult = (per_core) ? Global::coreCount : 1;
bool got_detailed = false;
bool detailed_filtered = false;
@ -344,7 +334,7 @@ namespace Proc {
uid_user.clear();
pread.open(Shared::passwd_path);
if (pread.good()) {
while (not pread.eof()){
while (not pread.eof()) {
getline(pread, r_user, ':');
pread.ignore(SSmax, ':');
getline(pread, r_uid, ':');
@ -366,12 +356,12 @@ namespace Proc {
else return current_procs;
//* Iterate over all pids in /proc
for (auto& d: fs::directory_iterator(Shared::proc_path)){
for (const auto& d: fs::directory_iterator(Shared::proc_path)) {
if (Runner::stopping)
return current_procs;
if (pread.is_open()) pread.close();
string pid_str = d.path().filename();
const string pid_str = d.path().filename();
if (not isdigit(pid_str[0])) continue;
npids++;
@ -386,7 +376,6 @@ namespace Proc {
pread.close();
size_t name_offset = rng::count(name, ' ');
pread.open(d.path() / "cmdline");
if (not pread.good()) continue;
long_string.clear();
@ -394,12 +383,11 @@ namespace Proc {
pread.close();
if (not cmd.empty()) cmd.pop_back();
pread.open(d.path() / "status");
if (not pread.good()) continue;
string uid;
string line;
while (not pread.eof()){
while (not pread.eof()) {
getline(pread, line, ':');
if (line == "Uid") {
pread.ignore();
@ -533,7 +521,7 @@ namespace Proc {
}
//* Sort processes
auto cmp = [&reverse](const auto &a, const auto &b) { return (reverse ? a < b : a > b); };
const auto cmp = [&reverse](const auto &a, const auto &b) { return (reverse ? a < b : a > b); };
switch (v_index(sort_vector, sorting)) {
case 0: { rng::sort(procs, cmp, &proc_info::pid); break; }
case 1: { rng::sort(procs, cmp, &proc_info::name); break; }
@ -548,15 +536,17 @@ namespace Proc {
//* When sorting with "cpu lazy" push processes over threshold cpu usage to the front regardless of cumulative usage
if (not tree and not reverse and sorting == "cpu lazy") {
double max = 10.0, target = 30.0;
for (size_t i = 0, offset = 0; i < procs.size(); i++) {
for (size_t i = 0, x = 0, offset = 0; i < procs.size(); i++) {
if (i <= 5 and procs[i].cpu_p > max)
max = procs[i].cpu_p;
else if (i == 6)
target = (max > 30.0) ? max : 10.0;
if (i == offset and procs[i].cpu_p > 30.0)
offset++;
else if (procs[i].cpu_p > target)
else if (procs[i].cpu_p > target) {
rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
if (++x > 10) break;
}
}
}
@ -569,12 +559,12 @@ namespace Proc {
rng::stable_sort(procs, rng::less{}, &proc_info::ppid);
//? Start recursive iteration over processes with the lowest shared parent pids
for (auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
for (const auto& p : rng::equal_range(procs, procs.at(0).ppid, rng::less{}, &proc_info::ppid)) {
_tree_gen(p, procs, tree_procs, 0, cache.at(p.pid).collapsed, filter);
}
if (Runner::stopping) return current_procs;
procs.swap(tree_procs);
procs = std::move(tree_procs);
}
@ -583,17 +573,17 @@ namespace Proc {
counter = 0;
unordered_flat_map<size_t, p_cache> r_cache;
r_cache.reserve(procs.size());
rng::for_each(procs, [&r_cache](const auto &p){
rng::for_each(procs, [&r_cache](const auto &p) {
if (cache.contains(p.pid))
r_cache[p.pid] = cache.at(p.pid);
});
cache.swap(r_cache);
cache = std::move(r_cache);
}
old_cputimes = cputimes;
numpids = (int)procs.size();
current_procs.swap(procs);
reserve_pids = npids;
current_procs = std::move(procs);
return current_procs;
}
}

75
src/btop_menu.cpp Normal file
View File

@ -0,0 +1,75 @@
/* 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 <vector>
#include <deque>
#include <robin_hood.h>
#include <array>
#include <btop_menu.hpp>
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_theme.hpp>
#include <btop_draw.hpp>
using std::vector, std::deque, robin_hood::unordered_flat_map, std::array;
namespace Menu {
atomic<bool> active (false);
string output;
const unordered_flat_map<string, unordered_flat_map<string, vector<string>>> menus = {
{ "options", {
{ "normal", {
"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
"│ │├─┘ │ ││ ││││└─┐",
"└─┘┴ ┴ ┴└─┘┘└┘└─┘"
} },
{ "selected", {
"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝"
} }
} },
{ "help", {
{ "normal", {
"┬ ┬┌─┐┬ ┌─┐",
"├─┤├┤ │ ├─┘",
"┴ ┴└─┘┴─┘┴ "
} },
{ "selected", {
"╦ ╦╔═╗╦ ╔═╗",
"╠═╣║╣ ║ ╠═╝",
"╩ ╩╚═╝╩═╝╩ "
} }
} },
{ "quit", {
{ "normal", {
"┌─┐ ┬ ┬ ┬┌┬┐",
"│─┼┐│ │ │ │ ",
"└─┘└└─┘ ┴ ┴ "
} },
{ "selected", {
"╔═╗ ╦ ╦ ╦╔╦╗ ",
"║═╬╗║ ║ ║ ║ ",
"╚═╝╚╚═╝ ╩ ╩ "
} }
} }
};
}

View File

@ -19,56 +19,14 @@ tab-size = 4
#pragma once
#include <string>
#include <vector>
#include <array>
#include <atomic>
#include <robin_hood.h>
using std::string, std::vector, std::unordered_map, std::array, std::atomic, robin_hood::unordered_flat_map;
using std::string, std::atomic;
namespace Menu {
atomic<bool> active(false);
const unordered_flat_map<string, unordered_map<string, vector<string>>> Menus = {
{ "options", {
{ "normal", {
"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐",
"│ │├─┘ │ ││ ││││└─┐",
"└─┘┴ ┴ ┴└─┘┘└┘└─┘"
} },
{ "selected", {
"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗",
"║ ║╠═╝ ║ ║║ ║║║║╚═╗",
"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝"
} }
} },
{ "help", {
{ "normal", {
"┬ ┬┌─┐┬ ┌─┐",
"├─┤├┤ │ ├─┘",
"┴ ┴└─┘┴─┘┴ "
} },
{ "selected", {
"╦ ╦╔═╗╦ ╔═╗",
"╠═╣║╣ ║ ╠═╝",
"╩ ╩╚═╝╩═╝╩ "
} }
} },
{ "quit", {
{ "normal", {
"┌─┐ ┬ ┬ ┬┌┬┐",
"│─┼┐│ │ │ │ ",
"└─┘└└─┘ ┴ ┴ "
} },
{ "selected", {
"╔═╗ ╦ ╦ ╦╔╦╗ ",
"║═╬╗║ ║ ║ ║ ",
"╚═╝╚╚═╝ ╩ ╩ "
} }
} }
};
extern atomic<bool> active;
extern string output;
}

View File

@ -28,7 +28,8 @@ tab-size = 4
using std::string, std::vector, std::deque, robin_hood::unordered_flat_map, std::atomic, std::array;
void clean_quit(int sig=-1);
void clean_quit(const int sig=-1);
void banner_gen();
namespace Global {
extern const string Version;
@ -37,14 +38,16 @@ namespace Global {
extern int coreCount;
extern string banner;
extern atomic<bool> resized;
extern string overlay;
}
namespace Runner {
extern atomic<bool> active;
extern atomic<bool> reading;
extern atomic<bool> stopping;
void run(const string box="", const bool no_update=false, const bool force_redraw=false, const bool input_interrupt=true);
void run(const string& box="", const bool no_update=false, const bool force_redraw=false, const bool input_interrupt=true);
void stop();
}
@ -162,7 +165,7 @@ namespace Proc {
vector<proc_info>& collect(const bool return_last=false);
//* Update current selection and view
void selection(string cmd_key);
void selection(const string& cmd_key);
//* Draw contents of proc box using <plist> as data source
string draw(const vector<proc_info>& plist, bool force_redraw=false);

View File

@ -16,10 +16,8 @@ indent = tab
tab-size = 4
*/
#include <cmath>
#include <vector>
#include <unordered_map>
#include <ranges>
#include <algorithm>
#include <fstream>
@ -28,17 +26,23 @@ tab-size = 4
#include <btop_config.hpp>
#include <btop_theme.hpp>
using std::round, std::vector, robin_hood::unordered_flat_map, std::stoi, std::views::iota, std::array,
using std::round, std::vector, std::stoi, std::views::iota,
std::clamp, std::max, std::min, std::ceil, std::to_string;
using namespace Tools;
namespace rng = std::ranges;
namespace fs = std::filesystem;
string Term::fg, Term::bg;
string Fx::reset = reset_base;
namespace Theme {
fs::path theme_dir;
fs::path user_theme_dir;
vector<string> themes;
unordered_flat_map<string, string> colors;
unordered_flat_map<string, array<int, 3>> rgbs;
unordered_flat_map<string, array<string, 101>> gradients;
const unordered_flat_map<string, string> Default_theme = {
{ "main_bg", "#00" },
@ -131,18 +135,21 @@ namespace Theme {
};
namespace {
//* Convert 24-bit colors to 256 colors using 6x6x6 color cube
int truecolor_to_256(int r, int g, int b){
if (round((double)r / 11) == round((double)g / 11) and round((double)g / 11) == round((double)b / 11)) {
return 232 + round((double)r / 11);
} else {
//* Convert 24-bit colors to 256 colors
int truecolor_to_256(const int& r, const int& g, const int& b) {
//? Use upper 232-255 greyscale values if the downscaled red, green and blue are the same value
if (int red = round((double)r / 11); red == round((double)g / 11) and round((double)g / 11) == round((double)b / 11)) {
return 232 + red;
}
//? Else use 6x6x6 color cube to calculate approximate colors
else {
return round((double)r / 51) * 36 + round((double)g / 51) * 6 + round((double)b / 51) + 16;
}
}
}
string hex_to_color(string hexa, bool t_to_256, string depth){
if (hexa.size() > 1){
string hex_to_color(string hexa, const bool& t_to_256, const string& depth) {
if (hexa.size() > 1) {
hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) {
Logger::error("Invalid hex value: " + hexa);
@ -150,17 +157,17 @@ namespace Theme {
}
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
if (hexa.size() == 2){
if (hexa.size() == 2) {
int h_int = stoi(hexa, 0, 16);
if (t_to_256){
if (t_to_256) {
return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m";
} else {
string h_str = to_string(h_int);
return pre + h_str + ";" + h_str + ";" + h_str + "m";
}
}
else if (hexa.size() == 6){
if (t_to_256){
else if (hexa.size() == 6) {
if (t_to_256) {
return pre + to_string(truecolor_to_256(
stoi(hexa.substr(0, 2), 0, 16),
stoi(hexa.substr(2, 2), 0, 16),
@ -178,7 +185,7 @@ namespace Theme {
return "";
}
string dec_to_color(int r, int g, int b, bool t_to_256, string depth){
string dec_to_color(int r, int g, int b, const bool& t_to_256, const string& depth) {
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
r = std::clamp(r, 0, 255);
g = std::clamp(g, 0, 255);
@ -188,21 +195,17 @@ namespace Theme {
}
namespace {
unordered_flat_map<string, string> colors;
unordered_flat_map<string, array<int, 3>> rgbs;
unordered_flat_map<string, array<string, 101>> gradients;
//* Convert hex color to a array of decimals
array<int, 3> hex_to_dec(string hexa){
if (hexa.size() > 1){
array<int, 3> hex_to_dec(string hexa) {
if (hexa.size() > 1) {
hexa.erase(0, 1);
for (auto& c : hexa) if (not isxdigit(c)) return array<int, 3>{-1, -1, -1};
if (hexa.size() == 2){
if (hexa.size() == 2) {
int h_int = stoi(hexa, 0, 16);
return array<int, 3>{h_int, h_int, h_int};
}
else if (hexa.size() == 6){
else if (hexa.size() == 6) {
return array<int, 3>{
stoi(hexa.substr(0, 2), 0, 16),
stoi(hexa.substr(2, 2), 0, 16),
@ -214,12 +217,12 @@ namespace Theme {
}
//* Generate colors and rgb decimal vectors for the theme
void generateColors(unordered_flat_map<string, string> source){
void generateColors(const unordered_flat_map<string, string>& source) {
vector<string> t_rgb;
string depth;
bool t_to_256 = Config::getB("lowcolor");
const bool& t_to_256 = Config::getB("lowcolor");
colors.clear(); rgbs.clear();
for (auto& [name, color] : Default_theme) {
for (const auto& [name, color] : Default_theme) {
if (name == "main_bg" and not Config::getB("theme_background")) {
colors[name] = "\x1b[49m";
rgbs[name] = {-1, -1, -1};
@ -252,70 +255,102 @@ namespace Theme {
}
}
}
if (colors[name].empty()) {
if (not colors.contains(name) and not is_in(name, "meter_bg", "process_start", "process_mid", "process_end", "graph_text")) {
Logger::debug("Missing color value for \"" + name + "\". Using value from default.");
colors[name] = hex_to_color(color, t_to_256, depth);
rgbs[name] = array<int, 3>{-1, -1, -1};
rgbs[name] = hex_to_dec(color);
}
}
//? Set fallback values for optional colors not defined in theme file
if (not colors.contains("meter_bg")) {
colors["meter_bg"] = colors.at("inactive_fg");
rgbs["meter_bg"] = rgbs.at("inactive_fg");
}
if (not colors.contains("process_start")) {
colors["process_start"] = colors.at("cpu_start");
colors["process_mid"] = colors.at("cpu_mid");
colors["process_end"] = colors.at("cpu_end");
rgbs["process_start"] = rgbs.at("cpu_start");
rgbs["process_mid"] = rgbs.at("cpu_mid");
rgbs["process_end"] = rgbs.at("cpu_end");
}
if (not colors.contains("graph_text")) {
colors["graph_text"] = colors.at("inactive_fg");
rgbs["graph_text"] = rgbs.at("inactive_fg");
}
}
//* Generate color gradients from two or three colors, 101 values indexed 0-100
void generateGradients(){
void generateGradients() {
gradients.clear();
array<string, 101> c_gradient;
bool t_to_256 = Config::getB("lowcolor");
rgbs.insert({
{"proc_start", rgbs["main_fg"]}, {"proc_mid", {-1, -1, -1}}, {"proc_end", rgbs["inactive_fg"]},
{"proc_color_start", rgbs["inactive_fg"]}, {"proc_color_mid", {-1, -1, -1}}, {"proc_color_end", rgbs["process_start"]}
});
for (auto& [name, source_arr] : rgbs) {
const bool& t_to_256 = Config::getB("lowcolor");
//? Insert values for processes greyscale gradient and processes color gradient
rgbs.insert({ { "proc_start", rgbs["main_fg"] },
{ "proc_mid", {-1, -1, -1} },
{ "proc_end", rgbs["inactive_fg"] },
{ "proc_color_start", rgbs["inactive_fg"] },
{ "proc_color_mid", {-1, -1, -1} },
{ "proc_color_end", rgbs["process_start"] },
});
for (const auto& [name, source_arr] : rgbs) {
if (not name.ends_with("_start")) continue;
array<array<int, 3>, 101> dec_arr;
dec_arr[0][0] = -1;
string wname = rtrim(name, "_start");
array<array<int, 3>, 3> rgb_array = {source_arr, rgbs[wname + "_mid"], rgbs[wname + "_end"]};
const string color_name = rtrim(name, "_start");
//? Only start iteration if gradient has a _end color value defined
if (rgb_array[2][0] >= 0) {
//? input_colors[start,mid,end][red,green,blue]
const array<array<int, 3>, 3> input_colors = {
source_arr,
rgbs[color_name + "_mid"],
rgbs[color_name + "_end"]
};
//? Split iteration in two passes of 50 + 51 instead of 101 if gradient has _start, _mid and _end values defined
int cur_range = (rgb_array[1][0] >= 0) ? 50 : 100;
for (int rgb : iota(0, 3)){
//? output_colors[red,green,blue][0-100]
array<array<int, 3>, 101> output_colors;
output_colors[0][0] = -1;
//? Only start iteration if gradient has an end color defined
if (input_colors[2][0] >= 0) {
//? Split iteration in two passes of 50 + 51 instead of one pass of 101 if gradient has start, mid and end values defined
int current_range = (input_colors[1][0] >= 0) ? 50 : 100;
for (const int& rgb : iota(0, 3)) {
int start = 0, offset = 0;
int end = (cur_range == 50) ? 1 : 2;
for (int i : iota(0, 101)) {
dec_arr[i][rgb] = rgb_array[start][rgb] + (i - offset) * (rgb_array[end][rgb] - rgb_array[start][rgb]) / cur_range;
int end = (current_range == 50) ? 1 : 2;
for (const int& i : iota(0, 101)) {
output_colors[i][rgb] = input_colors[start][rgb] + (i - offset) * (input_colors[end][rgb] - input_colors[start][rgb]) / current_range;
//? Switch source arrays from _start/_mid to _mid/_end at 50 passes if _mid is defined
if (i == cur_range) { ++start; ++end; offset = 50;}
//? Switch source arrays from start->mid to mid->end at 50 passes if mid is defined
if (i == current_range) { ++start; ++end; offset = 50; }
}
}
}
if (dec_arr[0][0] != -1) {
int y = 0;
for (auto& arr : dec_arr) c_gradient[y++] = dec_to_color(arr[0], arr[1], arr[2], t_to_256);
//? Generate color escape codes for the generated rgb decimals
array<string, 101> color_gradient;
if (output_colors[0][0] != -1) {
for (int y = 0; const auto& [red, green, blue] : output_colors)
color_gradient[y++] = dec_to_color(red, green, blue, t_to_256);
}
else {
//? If only _start was defined fill array with _start color
c_gradient.fill(colors[name]);
//? If only start was defined fill array with start color
color_gradient.fill(colors[name]);
}
gradients[wname].swap(c_gradient);
gradients[color_name] = std::move(color_gradient);
}
}
//* Set colors and generate gradients for the TTY theme
void generateTTYColors(){
void generateTTYColors() {
rgbs.clear();
gradients.clear();
colors = TTY_theme;
for (auto& c : colors) {
for (const auto& c : colors) {
if (not c.first.ends_with("_start")) continue;
string base_name = rtrim(c.first, "_start");
const string base_name = rtrim(c.first, "_start");
string section = "_start";
int split = colors.at(base_name + "_mid").empty() ? 50 : 33;
for (int i : iota(0, 101)) {
for (const int& i : iota(0, 101)) {
gradients[base_name][i] = colors.at(base_name + section);
if (i == split) {
section = (split == 33) ? "_mid" : "_end";
@ -326,9 +361,9 @@ namespace Theme {
}
//* Load a .theme file from disk
auto loadFile(string filename){
auto loadFile(const string& filename) {
unordered_flat_map<string, string> theme_out;
fs::path filepath = filename;
const fs::path filepath = filename;
if (not fs::exists(filepath))
return Default_theme;
@ -354,21 +389,20 @@ namespace Theme {
theme_out[name] = value;
}
themefile.close();
return theme_out;
}
return Default_theme;
}
}
void updateThemes(){
void updateThemes() {
themes.clear();
themes.push_back("Default");
themes.push_back("TTY");
for (const auto& path : { theme_dir, user_theme_dir } ) {
if (path.empty()) continue;
for (auto& file : fs::directory_iterator(path)){
for (auto& file : fs::directory_iterator(path)) {
if (file.path().extension() == ".theme" and access(file.path().c_str(), R_OK) != -1) {
themes.push_back(file.path().c_str());
}
@ -377,7 +411,7 @@ namespace Theme {
}
void setTheme(){
void setTheme() {
string theme = Config::getS("color_theme");
if (theme == "TTY" or Config::getB("tty_mode"))
generateTTYColors();
@ -390,26 +424,4 @@ namespace Theme {
Fx::reset = Fx::reset_base + Term::fg + Term::bg;
}
//* Return escape code for color <name>
const string& c(string name){
return colors.at(name);
}
//* Return array of escape codes for color gradient <name>
const array<string, 101>& g(string name){
return gradients.at(name);
}
//* Return array of red, green and blue in decimal for color <name>
const std::array<int, 3>& dec(string name){
return rgbs.at(name);
}
unordered_flat_map<string, string>& test_colors(){
return colors;
}
unordered_flat_map<string, std::array<string, 101>>& test_gradients(){
return gradients;
}
}

View File

@ -20,9 +20,10 @@ tab-size = 4
#include <string>
#include <robin_hood.h>
#include <array>
#include <filesystem>
using std::string;
using std::string, robin_hood::unordered_flat_map, std::array;
namespace Theme {
extern std::filesystem::path theme_dir;
@ -35,13 +36,13 @@ namespace Theme {
//* Args hexa: ["#000000"-"#ffffff"] for color, ["#00"-"#ff"] for greyscale
//* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color
string hex_to_color(string hexa, bool t_to_256=false, string depth="fg");
string hex_to_color(string hexa, const bool& t_to_256=false, const string& depth="fg");
//* Generate escape sequence for 24-bit or 256 color and return as a string
//* Args r: [0-255], g: [0-255], b: [0-255]
//* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color
string dec_to_color(int r, int g, int b, bool t_to_256=false, string depth="fg");
string dec_to_color(int r, int g, int b, const bool& t_to_256=false, const string& depth="fg");
//* Update list of paths for available themes
void updateThemes();
@ -49,17 +50,17 @@ namespace Theme {
//* Set current theme from current "color_theme" value in config
void setTheme();
extern unordered_flat_map<string, string> colors;
extern unordered_flat_map<string, array<int, 3>> rgbs;
extern unordered_flat_map<string, array<string, 101>> gradients;
//* Return escape code for color <name>
const string& c(string name);
inline const string& c(const string& name) { return colors.at(name); }
//* Return array of escape codes for color gradient <name>
const std::array<string, 101>& g(string name);
inline const array<string, 101>& g(string name) { return gradients.at(name); }
//* Return array of red, green and blue in decimal for color <name>
const std::array<int, 3>& dec(string name);
//? Testing
robin_hood::unordered_flat_map<string, string>& test_colors();
robin_hood::unordered_flat_map<string, std::array<string, 101>>& test_gradients();
inline const std::array<int, 3>& dec(string name) { return rgbs.at(name); }
}

View File

@ -32,74 +32,25 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
using std::string_view, std::array, std::regex, std::max, std::to_string, std::cin, robin_hood::unordered_flat_map;
using std::string_view, std::array, std::max, std::to_string, std::cin, robin_hood::unordered_flat_map;
namespace fs = std::filesystem;
namespace rng = std::ranges;
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
//* Collection of escape codes for text style and formatting
namespace Fx {
const string e = "\x1b[";
const string b = e + "1m";
const string ub = e + "22m";
const string d = e + "2m";
const string ud = e + "22m";
const string i = e + "3m";
const string ui = e + "23m";
const string ul = e + "4m";
const string uul = e + "24m";
const string bl = e + "5m";
const string ubl = e + "25m";
const string s = e + "9m";
const string us = e + "29m";
const string reset_base = e + "0m";
string reset = reset_base;
const regex escape_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m|f|s|u|C|D|A|B){1}");
const regex color_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m){1}");
}
//* Collection of escape codes and functions for cursor manipulation
namespace Mv {
const string to(int line, int col){ return Fx::e + to_string(line) + ";" + to_string(col) + "f";}
const string r(int x){ return Fx::e + to_string(x) + "C";}
const string l(int x){ return Fx::e + to_string(x) + "D";}
const string u(int x){ return Fx::e + to_string(x) + "A";}
const string d(int x) { return Fx::e + to_string(x) + "B";}
const string save = Fx::e + "s";
const string restore = Fx::e + "u";
}
//* Collection of escape codes and functions for terminal manipulation
namespace Term {
atomic<bool> initialized = false;
atomic<int> width = 0;
atomic<int> height = 0;
string fg, bg, current_tty;
const string hide_cursor = Fx::e + "?25l";
const string show_cursor = Fx::e + "?25h";
const string alt_screen = Fx::e + "?1049h";
const string normal_screen = Fx::e + "?1049l";
const string clear = Fx::e + "2J" + Fx::e + "0;0f";
const string clear_end = Fx::e + "0J";
const string clear_begin = Fx::e + "1J";
const string mouse_on = Fx::e + "?1002h" + Fx::e + "?1015h" + Fx::e + "?1006h";
const string mouse_off = Fx::e + "?1002l";
const string mouse_direct_on = Fx::e + "?1003h";
const string mouse_direct_off = Fx::e + "?1003l";
const string sync_start = Fx::e + "?2026h";
const string sync_end = Fx::e + "?2026l";
string current_tty;
namespace {
struct termios initial_settings;
//* Toggle terminal input echo
bool echo(bool on=true){
bool echo(bool on=true) {
struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ECHO;
@ -108,7 +59,7 @@ namespace Term {
}
//* Toggle need for return key when reading input
bool linebuffered(bool on=true){
bool linebuffered(bool on=true) {
struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ICANON;
@ -120,7 +71,7 @@ namespace Term {
}
}
bool refresh(){
bool refresh() {
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (width != w.ws_col or height != w.ws_row) {
@ -131,8 +82,8 @@ namespace Term {
return false;
}
bool init(){
if (not initialized){
bool init() {
if (not initialized) {
initialized = (bool)isatty(STDIN_FILENO);
if (initialized) {
tcgetattr(STDIN_FILENO, &initial_settings);
@ -148,7 +99,7 @@ namespace Term {
return initialized;
}
void restore(){
void restore() {
if (initialized) {
echo(true);
linebuffered(true);
@ -162,7 +113,7 @@ namespace Term {
namespace Tools {
string uresize(string str, const size_t len){
string uresize(string str, const size_t len) {
if (len < 1) return "";
for (size_t x = 0, i = 0; i < str.size(); i++) {
if ((static_cast<unsigned char>(str.at(i)) & 0xC0) != 0x80) x++;
@ -175,19 +126,19 @@ namespace Tools {
return str;
}
string ltrim(const string& str, const string& t_str){
string ltrim(const string& str, const string& t_str) {
string_view str_v = str;
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
return (string)str_v;
}
string rtrim(const string& str, const string& t_str){
string rtrim(const string& str, const string& t_str) {
string_view str_v = str;
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size());
return (string)str_v;
}
vector<string> ssplit(const string& str, const char& delim){
vector<string> ssplit(const string& str, const char& delim) {
vector<string> out;
for (const auto& s : str | rng::views::split(delim)
| rng::views::transform([](auto &&rng) {
@ -198,7 +149,7 @@ namespace Tools {
return out;
}
string ljust(string str, const size_t x, const bool utf, const bool limit){
string ljust(string str, const size_t x, const bool utf, const bool limit) {
if (utf) {
if (limit and ulen(str) > x) str = uresize(str, x);
return str + string(max((int)(x - ulen(str)), 0), ' ');
@ -209,7 +160,7 @@ namespace Tools {
}
}
string rjust(string str, const size_t x, const bool utf, const bool limit){
string rjust(string str, const size_t x, const bool utf, const bool limit) {
if (utf) {
if (limit and ulen(str) > x) str = uresize(str, x);
return string(max((int)(x - ulen(str)), 0), ' ') + str;
@ -220,12 +171,11 @@ namespace Tools {
}
}
string trans(const string& str){
size_t pos;
string trans(const string& str) {
string_view oldstr = str;
string newstr;
newstr.reserve(str.size());
while ((pos = oldstr.find(' ')) != string::npos){
for (size_t pos; (pos = oldstr.find(' ')) != string::npos;) {
newstr.append(oldstr.substr(0, pos));
size_t x = 0;
while (pos + x < oldstr.size() and oldstr.at(pos + x) == ' ') x++;
@ -235,34 +185,29 @@ namespace Tools {
return (newstr.empty()) ? str : newstr + (string)oldstr;
}
string sec_to_dhms(size_t sec){
string out;
size_t d, h, m;
d = sec / (3600 * 24);
sec %= 3600 * 24;
h = sec / 3600;
sec %= 3600;
m = sec / 60;
sec %= 60;
if (d>0) out = to_string(d) + "d ";
out += ((h<10) ? "0" : "") + to_string(h) + ":";
out += ((m<10) ? "0" : "") + to_string(m) + ":";
out += ((sec<10) ? "0" : "") + to_string(sec);
string sec_to_dhms(size_t seconds) {
size_t days = seconds / 86400; seconds %= 86400;
size_t hours = seconds / 3600; seconds %= 3600;
size_t minutes = seconds / 60; seconds %= 60;
string out = (days > 0 ? to_string(days) + "d " : "")
+ (hours < 10 ? "0" : "") + to_string(hours) + ":"
+ (minutes < 10 ? "0" : "") + to_string(minutes) + ":"
+ (seconds < 10 ? "0" : "") + to_string(seconds);
return out;
}
string floating_humanizer(uint64_t value, bool shorten, size_t start, bool bit, bool per_second){
string floating_humanizer(uint64_t value, const bool shorten, size_t start, const bool bit, const bool per_second) {
string out;
size_t mult = (bit) ? 8 : 1;
const size_t mult = (bit) ? 8 : 1;
static const array<string, 11> Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"};
static const array<string, 11> Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"};
auto& units = (bit) ? Units_bit : Units_byte;
const auto& units = (bit) ? Units_bit : Units_byte;
value *= 100 * mult;
while (value >= 102400){
while (value >= 102400) {
value >>= 10;
if (value < 100){
if (value < 100) {
out = to_string(value);
break;
}
@ -274,7 +219,7 @@ namespace Tools {
else if (out.size() == 3 and start > 0) out.insert(1, ".");
else if (out.size() >= 2) out.resize(out.size() - 2);
}
if (shorten){
if (shorten) {
if (out.find('.') != string::npos) out = to_string((int)round(stof(out)));
if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;}
out.push_back(units[start][0]);
@ -285,16 +230,15 @@ namespace Tools {
return out;
}
std::string operator*(string str, size_t n){
if (n == 0) return "";
str.reserve(str.size() * n);
for (string org_str = str; n > 1; n--) str.append(org_str);
return str;
std::string operator*(const string& str, size_t n) {
string new_str;
new_str.reserve(str.size() * n);
for (; n > 0; n--) new_str.append(str);
return new_str;
}
string strf_time(const string& strf){
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
string strf_time(const string& strf) {
auto in_time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm bt {};
std::stringstream ss;
ss << std::put_time(localtime_r(&in_time_t, &bt), strf.c_str());
@ -308,25 +252,17 @@ namespace Logger {
namespace {
std::atomic<bool> busy (false);
bool first = true;
string tdf = "%Y/%m/%d (%T) | ";
const string tdf = "%Y/%m/%d (%T) | ";
}
const vector<string> log_levels = {
"DISABLED",
"ERROR",
"WARNING",
"INFO",
"DEBUG",
};
size_t loglevel;
fs::path logfile;
void set(const string level){
void set(const string& level) {
loglevel = v_index(log_levels, level);
}
void log_write(const size_t level, const string& msg){
void log_write(const size_t level, const string& msg) {
if (loglevel < level or logfile.empty()) return;
busy.wait(true);
busy = true;
@ -341,7 +277,6 @@ namespace Logger {
std::ofstream lwrite(logfile, std::ios::app);
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";}
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
lwrite.close();
}
else logfile.clear();
busy = false;

View File

@ -28,67 +28,65 @@ tab-size = 4
#include <thread>
using std::string, std::vector, std::atomic;
using std::string, std::vector, std::atomic, std::to_string, std::regex;
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
//* Collection of escape codes for text style and formatting
namespace Fx {
extern const string e; //* Escape sequence start
extern const string b; //* Bold on/off
extern const string ub; //* Bold off
extern const string d; //* Dark on
extern const string ud; //* Dark off
extern const string i; //* Italic on
extern const string ui; //* Italic off
extern const string ul; //* Underline on
extern const string uul; //* Underline off
extern const string bl; //* Blink on
extern const string ubl; //* Blink off
extern const string s; //* Strike/crossed-out on
extern const string us; //* Strike/crossed-out on/off
const string e = "\x1b["; //* Escape sequence start
const string b = e + "1m"; //* Bold on/off
const string ub = e + "22m"; //* Bold off
const string d = e + "2m"; //* Dark on
const string ud = e + "22m"; //* Dark off
const string i = e + "3m"; //* Italic on
const string ui = e + "23m"; //* Italic off
const string ul = e + "4m"; //* Underline on
const string uul = e + "24m"; //* Underline off
const string bl = e + "5m"; //* Blink on
const string ubl = e + "25m"; //* Blink off
const string s = e + "9m"; //* Strike/crossed-out on
const string us = e + "29m"; //* Strike/crossed-out on/off
//* Reset foreground/background color and text effects
extern const string reset_base;
const string reset_base = e + "0m";
//* Reset text effects and restore theme foregrund and background color
extern string reset;
//* Regex for matching color, style and curse move escape sequences
extern const std::regex escape_regex;
const regex escape_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m|f|s|u|C|D|A|B){1}");
//* Regex for matching only color and style escape sequences
extern const std::regex color_regex;
const regex color_regex("\033\\[\\d+;?\\d?;?\\d*;?\\d*;?\\d*(m){1}");
//* Return a string with all colors and text styling removed
inline string uncolor(const string& s){
return regex_replace(s, color_regex, "");
};
inline string uncolor(const string& s) { return regex_replace(s, color_regex, ""); }
}
//* Collection of escape codes and functions for cursor manipulation
namespace Mv {
//* Move cursor to <line>, <column>
const string to(int line, int col);
inline string to(const int& line, const int& col) { return Fx::e + to_string(line) + ';' + to_string(col) + 'f'; }
//* Move cursor right <x> columns
const string r(int x);
inline string r(const int& x) { return Fx::e + to_string(x) + 'C'; }
//* Move cursor left <x> columns
const string l(int x);
inline string l(const int& x) { return Fx::e + to_string(x) + 'D'; }
//* Move cursor up x lines
const string u(int x);
inline string u(const int& x) { return Fx::e + to_string(x) + 'A'; }
//* Move cursor down x lines
const string d(int x);
inline string d(const int& x) { return Fx::e + to_string(x) + 'B'; }
//* Save cursor position
extern const string save;
const string save = Fx::e + "s";
//* Restore saved cursor postion
extern const string restore;
const string restore = Fx::e + "u";
}
//* Collection of escape codes and functions for terminal manipulation
@ -98,44 +96,19 @@ namespace Term {
extern atomic<int> height;
extern string fg, bg, current_tty;
//* Hide terminal cursor
extern const string hide_cursor;
//* Show terminal cursor
extern const string show_cursor;
//* Switch to alternate screen
extern const string alt_screen;
//* Switch to normal screen
extern const string normal_screen;
//* Clear screen and set cursor to position 0,0
extern const string clear;
//* Clear from cursor to end of screen
extern const string clear_end;
//* Clear from cursor to beginning of screen
extern const string clear_begin;
//* Enable reporting of mouse position on click and release
extern const string mouse_on;
//* Disable mouse reporting
extern const string mouse_off;
//* Enable reporting of mouse position at any movement
extern const string mouse_direct_on;
//* Disable direct mouse reporting
extern const string mouse_direct_off;
//* Escape sequence for start of synchronized output
extern const string sync_start;
//* Escape sequence for end of synchronized output
extern const string sync_end;
const string hide_cursor = Fx::e + "?25l";
const string show_cursor = Fx::e + "?25h";
const string alt_screen = Fx::e + "?1049h";
const string normal_screen = Fx::e + "?1049l";
const string clear = Fx::e + "2J" + Fx::e + "0;0f";
const string clear_end = Fx::e + "0J";
const string clear_begin = Fx::e + "1J";
const string mouse_on = Fx::e + "?1002h" + Fx::e + "?1015h" + Fx::e + "?1006h"; //? Enable reporting of mouse position on click and release
const string mouse_off = Fx::e + "?1002l";
const string mouse_direct_on = Fx::e + "?1003h"; //? Enable reporting of mouse position at any movement
const string mouse_direct_off = Fx::e + "?1003l";
const string sync_start = Fx::e + "?2026h"; //? Start of terminal synchronized output
const string sync_end = Fx::e + "?2026l"; //? End of terminal synchronized output
//* Returns true if terminal has been resized and updates width and height
bool refresh();
@ -153,7 +126,7 @@ namespace Tools {
constexpr auto SSmax = std::numeric_limits<std::streamsize>::max();
//* Return number of UTF8 characters in a string
inline size_t ulen(const string& str){
inline size_t ulen(const string& str) {
return std::ranges::count_if(str, [](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
};
@ -161,14 +134,14 @@ namespace Tools {
string uresize(const string str, const size_t len);
//* Return <str> with only uppercase characters
inline string str_to_upper(string str){
std::ranges::for_each(str, [](auto& c){ c = ::toupper(c); } );
inline string str_to_upper(string str) {
std::ranges::for_each(str, [](auto& c) { c = ::toupper(c); } );
return str;
}
//* Return <str> with only lowercase characters
inline string str_to_lower(string str){
std::ranges::for_each(str, [](char& c){ c = ::tolower(c); } );
inline string str_to_lower(string str) {
std::ranges::for_each(str, [](char& c) { c = ::tolower(c); } );
return str;
}
@ -197,32 +170,32 @@ namespace Tools {
}
//* Return current time since epoch in seconds
inline uint64_t time_s(){
inline uint64_t time_s() {
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
//* Return current time since epoch in milliseconds
inline uint64_t time_ms(){
inline uint64_t time_ms() {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
//* Return current time since epoch in microseconds
inline uint64_t time_micros(){
inline uint64_t time_micros() {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
//* Check if a string is a valid bool value
inline bool isbool(const string& str){
inline bool isbool(const string& str) {
return (str == "true") or (str == "false") or (str == "True") or (str == "False");
}
//* Convert string to bool, returning any value not equal to "true" or "True" as false
inline bool stobool(const string& str){
inline bool stobool(const string& str) {
return (str == "true" or str == "True");
}
//* Check if a string is a valid integer value
inline bool isint(const string& str){
inline bool isint(const string& str) {
return all_of(str.begin() + (str[0] == '-' ? 1 : 0), str.end(), ::isdigit);
}
@ -233,7 +206,7 @@ namespace Tools {
string rtrim(const string& str, const string& t_str = " ");
//* Left/right-trim <t_str> from <str> and return new string
inline string trim(const string& str, const string& t_str = " "){
inline string trim(const string& str, const string& t_str = " ") {
return ltrim(rtrim(str, t_str), t_str);
};
@ -252,17 +225,17 @@ namespace Tools {
//* Replace whitespaces " " with escape code for move right
string trans(const string& str);
//* Convert seconds to format "Xd HH:MM:SS" and return string
string sec_to_dhms(size_t sec);
//* Convert seconds to format "<days>d <hours>:<minutes>:<seconds>" and return string
string sec_to_dhms(size_t seconds);
//* Scales up in steps of 1024 to highest possible unit and returns string with unit suffixed
//* Scales up in steps of 1024 to highest positive value unit and returns string with unit suffixed
//* bit=True or defaults to bytes
//* start=int to set 1024 multiplier starting unit
//* short=True always returns 0 decimals and shortens unit to 1 character
string floating_humanizer(uint64_t value, bool shorten=false, size_t start=0, bool bit=false, bool per_second=false);
string floating_humanizer(uint64_t value, const bool shorten=false, size_t start=0, const bool bit=false, const bool per_second=false);
//* Add std::string operator "*" : Repeat string <str> <n> number of times
std::string operator*(string str, size_t n);
//* Add std::string operator * : Repeat string <str> <n> number of times
std::string operator*(const string& str, size_t n);
//* Return current time in <strf> format
string strf_time(const string& strf);
@ -271,14 +244,22 @@ namespace Tools {
//* Simple logging implementation
namespace Logger {
extern const vector<string> log_levels;
const vector<string> log_levels = {
"DISABLED",
"ERROR",
"WARNING",
"INFO",
"DEBUG",
};
extern std::filesystem::path logfile;
void set(const string level); //* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
//* Set log level, valid arguments: "DISABLED", "ERROR", "WARNING", "INFO" and "DEBUG"
void set(const string& level);
void log_write(const size_t level, const string& msg);
inline void error(const string msg){ log_write(1, msg); }
inline void warning(const string msg){ log_write(2, msg); }
inline void info(const string msg){ log_write(3, msg); }
inline void debug(const string msg){ log_write(4, msg); }
inline void error(const string msg) { log_write(1, msg); }
inline void warning(const string msg) { log_write(2, msg); }
inline void info(const string msg) { log_write(3, msg); }
inline void debug(const string msg) { log_write(4, msg); }
}

27
src/menu.hpp Normal file
View File

@ -0,0 +1,27 @@
/* 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
*/
#pragma once
#include <atomic>
using std::atomic;
namespace Menu {
extern atomic<bool> active;
}