btop/src/btop.cpp

921 lines
29 KiB
C++
Raw Normal View History

2021-05-07 06:32:03 +12:00
/* 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
*/
2021-06-10 05:47:49 +12:00
#include <csignal>
2021-08-04 09:47:46 +12:00
#include <pthread.h>
2021-08-11 06:20:33 +12:00
#include <thread>
#include <future>
#include <bitset>
2021-05-20 09:21:56 +12:00
#include <numeric>
#include <ranges>
2021-05-22 12:13:56 +12:00
#include <unistd.h>
#include <cmath>
2021-06-20 08:48:31 +12:00
#include <iostream>
#include <exception>
#include <tuple>
#include <regex>
#include <chrono>
2021-05-24 08:25:07 +12:00
2021-06-20 10:49:13 +12:00
#include <btop_shared.hpp>
2021-06-20 08:48:31 +12:00
#include <btop_tools.hpp>
#include <btop_config.hpp>
#include <btop_input.hpp>
#include <btop_theme.hpp>
#include <btop_draw.hpp>
#include <btop_menu.hpp>
2021-05-09 00:56:48 +12:00
using std::string, std::string_view, std::vector, std::atomic, std::endl, std::cout, std::min, std::flush, std::endl;
using std::string_literals::operator""s, std::to_string, std::future, std::async, std::bitset, std::future_status;
2021-07-21 13:17:34 +12:00
namespace fs = std::filesystem;
namespace rng = std::ranges;
using namespace Tools;
using namespace std::chrono_literals;
2021-07-21 13:17:34 +12:00
namespace Global {
2021-07-21 13:17:34 +12:00
const vector<array<string, 2>> Banner_src = {
{"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
{"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
{"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
{"#9A1919", "██╔══██╗ ██║ ██║ ██║██╔═══╝ ╚═██╔═╝╚═██╔═╝"},
{"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
};
const string Version = "0.5.0";
2021-05-07 06:32:03 +12:00
2021-07-21 13:17:34 +12:00
int coreCount;
2021-05-15 04:54:37 +12:00
string banner;
2021-06-03 07:33:26 +12:00
size_t banner_width = 0;
2021-07-21 13:17:34 +12:00
string overlay;
string clock;
string bg_black = "\x1b[0;40m";
string fg_white = "\x1b[1;97m";
string fg_green = "\x1b[1;92m";
string fg_red = "\x1b[0;91m";
fs::path self_path;
string exit_error_msg;
atomic<bool> thread_exception (false);
2021-05-29 12:32:36 +12:00
bool debuginit = false;
bool debug = false;
2021-07-21 13:17:34 +12:00
bool utf_force = false;
2021-05-29 12:32:36 +12:00
uint64_t start_time;
atomic<bool> resized (false);
atomic<bool> quitting (false);
bool arg_tty = false;
2021-06-22 08:52:55 +12:00
bool arg_low_color = false;
2021-05-09 00:56:48 +12:00
}
2021-05-07 06:32:03 +12:00
2021-05-09 00:56:48 +12:00
//* A simple argument parser
2021-07-21 13:17:34 +12:00
void argumentParser(const int& argc, char **argv) {
2021-05-08 12:38:51 +12:00
for(int i = 1; i < argc; i++) {
const string argument = argv[i];
if (is_in(argument, "-h", "--help")) {
cout << "usage: btop [-h] [-v] [-/+t] [--debug]\n\n"
<< "optional arguments:\n"
2021-06-22 08:52:55 +12:00
<< " -h, --help show this help message and exit\n"
<< " -v, --version show version info and exit\n"
<< " -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\n"
<< " --debug start in DEBUG mode: shows microsecond timer for information collect\n"
<< " and screen draw functions and sets loglevel to DEBUG\n"
<< endl;
2021-05-08 12:38:51 +12:00
exit(0);
2021-05-29 12:32:36 +12:00
}
if (is_in(argument, "-v", "--version")) {
2021-07-21 13:17:34 +12:00
cout << "btop version: " << Global::Version << endl;
exit(0);
}
else if (is_in(argument, "-lc", "--low-color")) {
2021-07-21 13:17:34 +12:00
Global::arg_low_color = true;
}
else if (is_in(argument, "-t", "--tty_on")) {
Config::set("tty_mode", true);
Global::arg_tty = true;
}
else if (is_in(argument, "+t", "--tty_off")) {
Config::set("tty_mode", false);
Global::arg_tty = true;
}
2021-07-21 13:17:34 +12:00
else if (argument == "--utf-force")
Global::utf_force = true;
else if (argument == "--debug")
Global::debug = true;
2021-05-29 12:32:36 +12:00
else {
2021-05-08 12:38:51 +12:00
cout << " Unknown argument: " << argument << "\n" <<
" Use -h or --help for help." << endl;
exit(1);
}
}
}
2021-07-19 04:04:49 +12:00
//* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true
void term_resize(bool force) {
2021-08-11 06:20:33 +12:00
if (auto refreshed = Term::refresh(); refreshed or force) {
2021-07-19 04:04:49 +12:00
if (force and refreshed) force = false;
}
else return;
2021-08-11 06:20:33 +12:00
Global::resized = true;
Runner::stop();
auto boxes = Config::getS("shown_boxes");
auto min_size = Term::get_min_size(boxes);
while (not force or (Term::width < min_size.at(0) or Term::height < min_size.at(1))) {
sleep_ms(100);
if (Term::width < min_size.at(0) or Term::height < min_size.at(1)) {
cout << Term::clear << Global::bg_black << Global::fg_white << Mv::to((Term::height / 2) - 2, (Term::width / 2) - 11)
<< "Terminal size too small:" << Mv::to((Term::height / 2) - 1, (Term::width / 2) - 10)
<< " Width = " << (Term::width < min_size.at(1) ? Global::fg_red : Global::fg_green) << Term::width
<< Global::fg_white << " Height = " << (Term::height < min_size.at(0) ? Global::fg_red : Global::fg_green) << Term::height
<< Mv::to((Term::height / 2) + 1, (Term::width / 2) - 12) << Global::fg_white
<< "Needed for current config:" << Mv::to((Term::height / 2) + 2, (Term::width / 2) - 10)
<< "Width = " << min_size.at(0) << " Height = " << min_size.at(1) << flush;
while (not Term::refresh() and not Input::poll()) sleep_ms(10);
if (Input::poll() and Input::get() == "q") exit(0);
min_size = Term::get_min_size(boxes);
}
else if (not Term::refresh()) break;
}
Input::interrupt = true;
}
//* Exit handler; stops threads, restores terminal and saves config changes
2021-08-11 06:20:33 +12:00
void clean_quit(int sig) {
2021-06-03 07:33:26 +12:00
if (Global::quitting) return;
Global::quitting = true;
Runner::stop();
if (pthread_join(Runner::runner_id, NULL) != 0) {
Logger::error("Failed to join _runner thread!");
}
2021-08-11 06:20:33 +12:00
2021-06-10 05:47:49 +12:00
Config::write();
Input::clear();
2021-08-04 09:47:46 +12:00
2021-08-11 06:20:33 +12:00
//? Wait for any remaining Tools::atomic_lock destructors to finish for max 1000ms
for (int i = 0; Tools::active_locks > 0 and i < 100; i++) {
sleep_ms(10);
}
if (Term::initialized) {
Term::restore();
}
if (not Global::exit_error_msg.empty()) {
sig = 1;
Logger::error(Global::exit_error_msg);
std::cerr << Global::fg_red << "ERROR: " << Global::fg_white << Global::exit_error_msg << Fx::reset << endl;
}
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
2021-08-11 06:20:33 +12:00
//? Assume error if still not cleaned up and call quick_exit to avoid a segfault from Tools::atomic_lock destructor
if (Tools::active_locks > 0) {
quick_exit((sig != -1 ? sig : 0));
}
2021-08-04 09:47:46 +12:00
2021-06-10 05:47:49 +12:00
if (sig != -1) exit(sig);
}
//* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
2021-07-21 13:17:34 +12:00
void _sleep() {
Runner::stop();
Term::restore();
2021-06-10 05:47:49 +12:00
std::raise(SIGSTOP);
}
//* Handler for SIGCONT; re-initialize terminal and force a resize event
2021-07-21 13:17:34 +12:00
void _resume() {
2021-06-10 05:47:49 +12:00
Term::init();
2021-07-19 04:04:49 +12:00
term_resize(true);
2021-06-03 07:33:26 +12:00
}
void _exit_handler() {
clean_quit(-1);
}
2021-06-03 07:33:26 +12:00
2021-07-21 13:17:34 +12:00
void _signal_handler(const int sig) {
2021-06-10 05:47:49 +12:00
switch (sig) {
case SIGINT:
clean_quit(0);
break;
case SIGTSTP:
_sleep();
2021-06-10 05:47:49 +12:00
break;
case SIGCONT:
_resume();
break;
case SIGWINCH:
2021-07-19 04:04:49 +12:00
term_resize();
2021-06-10 05:47:49 +12:00
break;
}
}
2021-07-21 13:17:34 +12:00
//* Generate the btop++ banner
2021-06-03 07:33:26 +12:00
void banner_gen() {
Global::banner.clear();
Global::banner_width = 0;
2021-07-21 13:17:34 +12:00
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;
2021-06-22 08:52:55 +12:00
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);
2021-07-21 13:17:34 +12:00
int bg_i = 120 - z * 12;
2021-06-22 08:52:55 +12:00
bg = Theme::dec_to_color(bg_i, bg_i, bg_i, lowcolor);
}
for (size_t i = 0; i < line[1].size(); i += 3) {
if (line[1][i] == ' ') {
2021-05-15 04:54:37 +12:00
letter = ' ';
i -= 2;
2021-05-07 06:32:03 +12:00
}
else
letter = line[1].substr(i, 3);
2021-05-15 04:54:37 +12:00
b_color = (letter == "") ? fg : bg;
2021-06-03 07:33:26 +12:00
if (b_color != oc) Global::banner += b_color;
Global::banner += letter;
2021-05-15 04:54:37 +12:00
oc = b_color;
2021-05-07 06:32:03 +12:00
}
2021-06-03 07:33:26 +12:00
if (++z < Global::Banner_src.size()) Global::banner += Mv::l(ulen(line[1])) + Mv::d(1);
2021-05-07 06:32:03 +12:00
}
2021-06-22 08:52:55 +12:00
Global::banner += Mv::r(18 - Global::Version.size())
+ (tty_mode ? "\x1b[0;40;37m" : Theme::dec_to_color(0,0,0, lowcolor, "bg")
+ Theme::dec_to_color(150, 150, 150, lowcolor))
+ Fx::i + "v" + Global::Version + Fx::ui;
2021-05-15 04:54:37 +12:00
}
2021-05-07 06:32:03 +12:00
bool update_clock() {
const auto& clock_format = Config::getS("clock_format");
if (not Cpu::shown or clock_format.empty()) return false;
static const unordered_flat_map<string, string> clock_custom_format = {
{"/user", Tools::username()},
{"/host", Tools::hostname()},
{"/uptime", ""}
};
static time_t c_time = 0;
static size_t clock_len = 0;
static string old_clock;
string new_clock;
if (auto n_time = time(NULL); n_time == c_time)
return false;
else {
c_time = n_time;
new_clock = Tools::strf_time(clock_format);
if (new_clock == old_clock) return false;
old_clock = new_clock;
}
auto& out = Global::clock;
const auto& cpu_bottom = Config::getB("cpu_bottom");
const auto& x = Cpu::x;
const auto y = (cpu_bottom ? Cpu::y + Cpu::height - 1 : Cpu::y);
const auto& width = Cpu::width;
const auto& title_left = (cpu_bottom ? Symbols::title_left_down : Symbols::title_left);
const auto& title_right = (cpu_bottom ? Symbols::title_right_down : Symbols::title_right);
for (const auto& [c_format, replacement] : clock_custom_format) {
if (s_contains(new_clock, c_format)) {
if (c_format == "/uptime") {
string upstr = sec_to_dhms(system_uptime());
if (upstr.size() > 8) upstr.resize(upstr.size() - 3);
new_clock = s_replace(new_clock, c_format, upstr);
}
else {
new_clock = s_replace(new_clock, c_format, replacement);
}
}
}
new_clock = uresize(new_clock, std::max(0, width - 56));
out.clear();
if (new_clock.size() != clock_len) {
if (not Global::resized and clock_len > 0) out = Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * clock_len;
clock_len = new_clock.size();
}
out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left
+ Theme::c("title") + Fx::b + new_clock + Theme::c("cpu_box") + Fx::ub + title_right;
return true;
}
2021-07-21 13:17:34 +12:00
//* Manages secondary thread for collection and drawing of boxes
namespace Runner {
atomic<bool> active (false);
atomic<bool> stopping (false);
2021-08-11 06:20:33 +12:00
atomic<bool> waiting (false);
//* Setup semaphore for triggering thread to do work
#if __GNUC__ < 11
#include <semaphore.h>
sem_t do_work;
inline void thread_sem_init() { sem_init(&do_work, 0, 0); }
inline void thread_wait() { sem_wait(&do_work); }
inline void thread_trigger() { sem_post(&do_work); }
#else
#include <semaphore>
std::binary_semaphore do_work(0);
inline void thread_sem_init() { ; }
inline void thread_wait() { do_work.acquire(); }
inline void thread_trigger() { do_work.release(); }
#endif
//* RAII wrapper for pthread_mutex locking
class thread_lock {
pthread_mutex_t& pt_mutex;
public:
int status;
thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) { status = pthread_mutex_lock(&pt_mutex); }
~thread_lock() { if (status == 0) pthread_mutex_unlock(&pt_mutex); }
};
string output;
2021-08-04 09:47:46 +12:00
sigset_t mask;
pthread_t runner_id;
2021-08-11 06:20:33 +12:00
pthread_mutex_t mtx;
const unordered_flat_map<string, uint_fast8_t> box_bits = {
{"proc", 0b0000'0001},
{"net", 0b0000'0100},
{"mem", 0b0001'0000},
{"cpu", 0b0100'0000},
};
enum bit_pos {
proc_present, proc_running,
net_present, net_running,
mem_present, mem_running,
cpu_present, cpu_running
};
enum debug_actions {
collect_begin,
draw_begin,
draw_done
};
enum debug_array {
collect,
draw
};
const uint_fast8_t proc_done = 0b0000'0011;
const uint_fast8_t net_done = 0b0000'1100;
const uint_fast8_t mem_done = 0b0011'0000;
const uint_fast8_t cpu_done = 0b1100'0000;
string debug_bg;
unordered_flat_map<string, array<uint64_t, 2>> debug_times;
2021-08-04 09:47:46 +12:00
struct runner_conf {
bitset<8> box_mask;
bool no_update;
bool force_redraw;
string overlay;
string clock;
2021-08-04 09:47:46 +12:00
};
struct runner_conf current_conf;
void debug_timer(const char* name, const int action) {
switch (action) {
case collect_begin:
debug_times[name].at(collect) = time_micros();
return;
case draw_begin:
debug_times[name].at(draw) = time_micros();
debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect);
debug_times["total"].at(collect) += debug_times[name].at(collect);
return;
case draw_done:
debug_times[name].at(draw) = time_micros() - debug_times[name].at(draw);
debug_times["total"].at(draw) += debug_times[name].at(draw);
return;
}
}
//? ------------------------------- Secondary thread: async launcher and drawing ----------------------------------
void * _runner(void * _) {
(void)_;
//? Block all signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTSTP);
sigaddset(&mask, SIGWINCH);
sigaddset(&mask, SIGTERM);
2021-08-04 09:47:46 +12:00
pthread_sigmask(SIG_BLOCK, &mask, NULL);
//? pthread_mutex_lock to lock thread and monitor health from main thread
thread_lock pt_lck(mtx);
if (pt_lck.status != 0) {
Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status);
Global::thread_exception = true;
Input::interrupt = true;
stopping = true;
}
//* ----------------------------------------------- THREAD LOOP -----------------------------------------------
while (not Global::quitting) {
thread_wait();
if (stopping or Global::resized) {
continue;
}
//? Atomic lock used for blocking non-thread safe actions in main thread
atomic_lock lck(active);
auto& conf = current_conf;
//! DEBUG stats
if (Global::debug) {
debug_times.clear();
debug_times["total"] = {0, 0};
}
output.clear();
//* Start collection functions for all boxes in async threads and draw in this thread when finished
//? Starting order below based on mean time to finish
try {
future<Cpu::cpu_info&> cpu;
future<Mem::mem_info&> mem;
future<Net::net_info&> net;
future<vector<Proc::proc_info>&> proc;
//? Loop until all box flags present in bitmask have been zeroed
while (conf.box_mask.count() > 0) {
if (stopping) break;
//? PROC
if (conf.box_mask.test(proc_present)) {
if (not conf.box_mask.test(proc_running)) {
if (Global::debug) debug_timer("proc", collect_begin);
//? Start async collect
proc = async(Proc::collect, conf.no_update);
conf.box_mask.set(proc_running);
}
else if (not proc.valid())
throw std::runtime_error("Proc::collect() future not valid.");
else if (proc.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("proc", draw_begin);
//? Draw box
output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update);
if (Global::debug) debug_timer("proc", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Proc:: -> " + (string)e.what());
}
conf.box_mask ^= proc_done;
}
}
//? NET
if (conf.box_mask.test(net_present)) {
if (not conf.box_mask.test(net_running)) {
if (Global::debug) debug_timer("net", collect_begin);
//? Start async collect
net = async(Net::collect, conf.no_update);
conf.box_mask.set(net_running);
}
else if (not net.valid())
throw std::runtime_error("Net::collect() future not valid.");
else if (net.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("net", draw_begin);
//? Draw box
output += Net::draw(net.get(), conf.force_redraw, conf.no_update);
if (Global::debug) debug_timer("net", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Net:: -> " + (string)e.what());
}
conf.box_mask ^= net_done;
}
}
//? MEM
if (conf.box_mask.test(mem_present)) {
if (not conf.box_mask.test(mem_running)) {
if (Global::debug) debug_timer("mem", collect_begin);
//? Start async collect
mem = async(Mem::collect, conf.no_update);
conf.box_mask.set(mem_running);
}
else if (not mem.valid())
throw std::runtime_error("Mem::collect() future not valid.");
else if (mem.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("mem", draw_begin);
//? Draw box
output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update);
if (Global::debug) debug_timer("mem", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Mem:: -> " + (string)e.what());
}
conf.box_mask ^= mem_done;
}
}
//? CPU
if (conf.box_mask.test(cpu_present)) {
if (not conf.box_mask.test(cpu_running)) {
if (Global::debug) debug_timer("cpu", collect_begin);
//? Start async collect
cpu = async(Cpu::collect, conf.no_update);
conf.box_mask.set(cpu_running);
}
else if (not cpu.valid())
throw std::runtime_error("Cpu::collect() future not valid.");
else if (cpu.wait_for(10us) == future_status::ready) {
try {
if (Global::debug) debug_timer("cpu", draw_begin);
//? Draw box
output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update);
if (Global::debug) debug_timer("cpu", draw_done);
}
catch (const std::exception& e) {
throw std::runtime_error("Cpu:: -> " + (string)e.what());
}
conf.box_mask ^= cpu_done;
}
}
}
}
catch (const std::exception& e) {
Global::exit_error_msg = "Exception in runner thread -> " + (string)e.what();
Global::thread_exception = true;
Input::interrupt = true;
stopping = true;
}
if (stopping) {
continue;
}
//! DEBUG stats -->
if (Global::debug) {
output += debug_bg + Theme::c("title") + Fx::b + ljust(" Box", 9) + ljust("Collect μs", 12, true) + ljust("Draw μs", 9, true) + Theme::c("main_fg") + Fx::ub;
for (const string name : {"cpu", "mem", "net", "proc", "total"}) {
if (not debug_times.contains(name)) debug_times[name] = {0,0};
const auto& [time_collect, time_draw] = debug_times.at(name);
if (name == "total") output += Fx::b;
output += Mv::l(29) + Mv::d(1) + ljust(name, 8) + ljust(to_string(time_collect), 12) + ljust(to_string(time_draw), 9);
}
}
2021-07-21 13:17:34 +12:00
//? If overlay isn't empty, print output without color and then print overlay on top
cout << Term::sync_start << (conf.overlay.empty()
? output + conf.clock
: Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output + conf.clock) + conf.overlay)
<< Term::sync_end << flush;
}
//* ----------------------------------------------- THREAD LOOP -----------------------------------------------
2021-07-21 13:17:34 +12:00
2021-08-04 09:47:46 +12:00
pthread_exit(NULL);
}
2021-08-11 06:20:33 +12:00
//? ------------------------------------------ Secondary thread end -----------------------------------------------
//* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values
void run(const string& box, const bool no_update, const bool force_redraw) {
2021-08-11 06:20:33 +12:00
atomic_lock lck(waiting);
atomic_wait(active);
2021-08-11 06:20:33 +12:00
if (stopping or Global::resized) return;
if (box == "overlay") {
cout << Term::sync_start << Global::overlay << Term::sync_end << flush;
}
else if (box == "clock") {
cout << Term::sync_start << Global::clock << Term::sync_end << flush;
}
else if (Config::current_boxes.empty()) {
cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end << flush;
}
else {
Config::unlock();
Config::lock();
//? Setup bitmask for selected boxes instead of parsing strings in _runner thread loop
bitset<8> box_mask;
for (const auto& box : (box == "all" ? Config::current_boxes : vector{box})) {
box_mask |= box_bits.at(box);
}
current_conf = {box_mask, no_update, force_redraw, Global::overlay, Global::clock};
2021-08-04 09:47:46 +12:00
thread_trigger();
2021-08-11 06:20:33 +12:00
//? Wait for _runner thread to be active before returning
2021-08-11 06:20:33 +12:00
for (int i = 0; not active and i < 10; i++) sleep_ms(1);
}
}
//* Stops any work being done in runner thread and checks for thread errors
void stop() {
stopping = true;
2021-08-11 06:20:33 +12:00
int ret = pthread_mutex_trylock(&mtx);
if (ret != EBUSY and not Global::quitting) {
2021-08-11 06:20:33 +12:00
if (active) active = false;
Global::exit_error_msg = "Runner thread died unexpectedly!";
exit(1);
}
else if (ret == EBUSY) {
atomic_wait(active);
thread_trigger();
sleep_ms(1);
2021-08-11 06:20:33 +12:00
}
stopping = false;
}
}
2021-05-10 08:25:41 +12:00
2021-05-09 00:56:48 +12:00
2021-07-21 13:17:34 +12:00
//* --------------------------------------------- Main starts here! ---------------------------------------------------
int main(int argc, char **argv) {
2021-05-07 06:32:03 +12:00
2021-07-21 13:17:34 +12:00
//? ------------------------------------------------ INIT ---------------------------------------------------------
2021-05-07 06:32:03 +12:00
2021-05-29 12:32:36 +12:00
Global::start_time = time_s();
2021-07-21 13:17:34 +12:00
//? Call argument parser if launched with arguments
2021-05-07 06:32:03 +12:00
if (argc > 1) argumentParser(argc, argv);
2021-07-21 13:17:34 +12:00
//? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
2021-05-29 12:32:36 +12:00
std::atexit(_exit_handler);
2021-06-10 05:47:49 +12:00
std::signal(SIGINT, _signal_handler);
std::signal(SIGTSTP, _signal_handler);
std::signal(SIGCONT, _signal_handler);
std::signal(SIGWINCH, _signal_handler);
2021-05-29 12:32:36 +12:00
2021-07-21 13:17:34 +12:00
//? Setup paths for config, log and user themes
for (const auto& env : {"XDG_CONFIG_HOME", "HOME"}) {
2021-06-22 08:52:55 +12:00
if (getenv(env) != NULL and access(getenv(env), W_OK) != -1) {
2021-05-29 12:32:36 +12:00
Config::conf_dir = fs::path(getenv(env)) / (((string)env == "HOME") ? ".config/btop" : "btop");
break;
}
}
if (Config::conf_dir.empty()) {
cout << "WARNING: Could not get path user HOME folder.\n"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
}
else {
2021-06-22 08:52:55 +12:00
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.\n"
<< "Make sure $XDG_CONFIG_HOME or $HOME environment variables is correctly set to fix this." << endl;
}
else {
2021-05-29 12:32:36 +12:00
Config::conf_file = Config::conf_dir / "btop.conf";
Logger::logfile = Config::conf_dir / "btop.log";
Theme::user_theme_dir = Config::conf_dir / "themes";
2021-06-22 08:52:55 +12:00
if (not fs::exists(Theme::user_theme_dir) and not fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear();
}
}
2021-07-21 13:17:34 +12:00
//? Try to find global btop theme path relative to binary path
#if defined(__linux__)
{ std::error_code ec;
Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
}
#endif
2021-06-22 08:52:55 +12:00
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();
}
2021-07-21 13:17:34 +12:00
//? If relative path failed, check two most common absolute paths
2021-05-29 12:32:36 +12:00
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) {
2021-05-29 12:32:36 +12:00
Theme::theme_dir = fs::path(theme_path);
break;
}
}
}
2021-06-14 09:12:11 +12:00
//? Config init
{ vector<string> load_warnings;
Config::load(Config::conf_file, load_warnings);
2021-06-13 04:49:27 +12:00
if (Config::current_boxes.empty()) Config::check_boxes(Config::getS("shown_boxes"));
2021-06-22 08:52:55 +12:00
Config::set("lowcolor", (Global::arg_low_color ? true : not Config::getB("truecolor")));
if (Global::debug) {
Logger::set("DEBUG");
Logger::debug("Starting in DEBUG mode!");
}
else Logger::set(Config::getS("log_level"));
2021-06-13 04:49:27 +12:00
Logger::info("Logger set to " + (Global::debug ? "DEBUG" : Config::getS("log_level")));
2021-06-13 04:49:27 +12:00
for (const auto& err_str : load_warnings) Logger::warning(err_str);
2021-06-13 04:49:27 +12:00
}
2021-05-24 08:25:07 +12:00
2021-07-21 13:17:34 +12:00
//? 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!\nUse --utf-force argument to force start if you're sure your terminal can handle it.";
exit(1);
2021-07-21 13:17:34 +12:00
}
else
Logger::debug("Setting LC_ALL=" + (string)std::setlocale(LC_ALL, NULL));
2021-05-24 08:25:07 +12:00
}
2021-05-09 10:18:51 +12:00
//? Initialize terminal and set options
2021-06-22 08:52:55 +12:00
if (not Term::init()) {
Global::exit_error_msg = "No tty detected!\nbtop++ needs an interactive shell to run.";
exit(1);
2021-05-07 06:32:03 +12:00
}
2021-06-22 08:52:55 +12:00
Logger::info("Running on " + Term::current_tty);
if (not Global::arg_tty and Config::getB("force_tty")) {
Config::set("tty_mode", true);
Logger::info("Forcing tty mode: setting 16 color mode and using tty friendly graph symbols");
}
2021-06-22 08:52:55 +12:00
else if (not Global::arg_tty and Term::current_tty.starts_with("/dev/tty")) {
Config::set("tty_mode", true);
Logger::info("Real tty detected: setting 16 color mode and using tty friendly graph symbols");
}
2021-07-21 13:17:34 +12:00
//? Platform dependent init and error check
try {
Shared::init();
}
catch (const std::exception& e) {
Global::exit_error_msg = "Exception in Shared::init() -> " + (string)e.what();
exit(1);
}
2021-06-26 09:58:19 +12:00
2021-07-21 13:17:34 +12:00
//? Update list of available themes and generate the selected theme
2021-06-26 09:58:19 +12:00
Theme::updateThemes();
Theme::setTheme();
2021-05-09 10:18:51 +12:00
//? Start runner thread
Runner::thread_sem_init();
if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) {
Global::exit_error_msg = "Failed to create _runner thread!";
exit(1);
}
if (Global::debug) {
Runner::debug_bg = Draw::createBox(2, 2, 32, 8, "", true, "debug");
}
2021-05-09 10:18:51 +12:00
//? Create the btop++ banner
2021-06-03 07:33:26 +12:00
banner_gen();
2021-05-09 10:18:51 +12:00
2021-07-21 13:17:34 +12:00
//? Calculate sizes of all boxes
Draw::calcSizes();
2021-05-08 12:38:51 +12:00
{
const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes"));
if (Term::height < y or Term::width < x) {
Global::exit_error_msg = "Terminal size to small for current config.\n(WxH) Current: " + to_string(Term::width)
+ 'x' + to_string(Term::height) + " | Needed: " + to_string(x) + 'x' + to_string(y)
+ "\nResize terminal or disable some boxes in config file to fix this.";
exit(1);
}
}
//? Print out box outlines
2021-07-21 13:17:34 +12:00
cout << Term::sync_start << Cpu::box << Mem::box << Net::box << Proc::box << Term::sync_end << flush;
2021-05-24 08:25:07 +12:00
2021-07-21 13:17:34 +12:00
//? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
2021-05-10 08:25:41 +12:00
2021-07-21 13:17:34 +12:00
uint64_t update_ms = Config::getI("update_ms");
auto future_time = time_ms();
try {
2021-07-19 04:04:49 +12:00
while (not true not_eq not false) {
2021-07-21 13:17:34 +12:00
//? Check for exceptions in secondary thread and exit with fail signal if true
2021-08-11 06:20:33 +12:00
if (Global::thread_exception) exit(1);
2021-07-19 04:04:49 +12:00
//? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
term_resize();
2021-05-28 08:29:36 +12:00
2021-08-11 06:20:33 +12:00
//? Trigger secondary thread to redraw if terminal has been resized
2021-07-19 04:04:49 +12:00
if (Global::resized) {
2021-08-11 06:20:33 +12:00
Draw::calcSizes();
update_clock();
Global::resized = false;
if (Menu::active) Menu::process();
else Runner::run("all", true, true);
2021-08-11 06:20:33 +12:00
atomic_wait(Runner::active);
2021-07-19 04:04:49 +12:00
}
//? Update clock if needed
if (update_clock() and not Menu::active) {
Runner::run("clock");
}
2021-07-21 13:17:34 +12:00
//? Start secondary collect & draw thread at the interval set by <update_ms> config value
if (time_ms() >= future_time and not Global::resized) {
2021-07-19 04:04:49 +12:00
Runner::run("all");
2021-07-21 13:17:34 +12:00
update_ms = Config::getI("update_ms");
2021-07-19 04:04:49 +12:00
future_time = time_ms() + update_ms;
2021-06-14 09:12:11 +12:00
}
2021-07-21 13:17:34 +12:00
//? Loop over input polling and input action processing
2021-08-11 06:20:33 +12:00
for (auto current_time = time_ms(); current_time < future_time; current_time = time_ms()) {
2021-07-21 13:17:34 +12:00
2021-08-04 09:47:46 +12:00
//? Check for external clock changes and for changes to the update timer
if (std::cmp_not_equal(update_ms, Config::getI("update_ms"))) {
2021-08-04 09:47:46 +12:00
update_ms = Config::getI("update_ms");
future_time = time_ms() + update_ms;
}
else if (future_time - current_time > update_ms)
2021-07-19 04:04:49 +12:00
future_time = current_time;
2021-07-21 13:17:34 +12:00
//? Poll for input and process any input detected
else if (Input::poll(min(1000ul, future_time - current_time))) {
if (not Runner::active) Config::unlock();
if (Menu::active) Menu::process(Input::get());
else Input::process(Input::get());
2021-07-21 13:17:34 +12:00
}
//? Break the loop at 1000ms intervals or if input polling was interrupted
else break;
2021-06-14 09:12:11 +12:00
}
2021-07-19 04:04:49 +12:00
}
}
catch (std::exception& e) {
Global::exit_error_msg = "Exception in main loop -> " + (string)e.what();
exit(1);
}
2021-05-11 09:46:41 +12:00
2021-05-07 06:32:03 +12:00
}