btop/src/btop.cpp

788 lines
25 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>
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
#if defined(__linux__)
2021-06-10 05:47:49 +12:00
#define LINUX
2021-06-22 08:52:55 +12:00
#elif defined(__unix__) or not defined(__APPLE__) and defined(__MACH__)
2021-05-09 00:56:48 +12:00
#include <sys/param.h>
#if defined(BSD)
2021-05-20 09:21:56 +12:00
#error BSD support not yet implemented!
2021-05-09 00:56:48 +12:00
#endif
2021-06-22 08:52:55 +12:00
#elif defined(__APPLE__) and defined(__MACH__)
2021-05-09 00:56:48 +12:00
#include <TargetConditionals.h>
#if TARGET_OS_MAC == 1
2021-06-10 05:47:49 +12:00
#define OSX
2021-05-20 09:21:56 +12:00
#error OSX support not yet implemented!
2021-05-09 00:56:48 +12:00
#endif
2021-05-20 09:21:56 +12:00
#else
2021-05-24 08:25:07 +12:00
#error Platform not supported!
2021-05-09 00:56:48 +12:00
#endif
2021-05-07 06:32:03 +12:00
2021-07-21 13:17:34 +12:00
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, 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;
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;
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);
2021-08-11 06:20:33 +12:00
atomic<int> resizing (0);
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"
2021-07-21 13:17:34 +12:00
<< " --utf-foce force start even if no UTF-8 locale was detected"
<< " --debug start in debug mode with loglevel set 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
auto rez_state = ++Global::resizing;
if (rez_state > 1) return;
Global::resized = true;
Runner::stop();
auto min_size = Term::get_min_size(Config::getS("shown_boxes"));
2021-07-19 04:04:49 +12:00
while (not force) {
sleep_ms(100);
2021-08-11 06:20:33 +12:00
if (rez_state != Global::resizing) rez_state = --Global::resizing;
else if (not Term::refresh() and Term::width >= min_size[0] and Term::height >= min_size[1]) break;
}
Input::interrupt = true;
2021-08-11 06:20:33 +12:00
Global::resizing = 0;
}
//* 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-07-21 13:17:34 +12:00
if (not Global::exit_error_msg.empty()) {
2021-08-11 06:20:33 +12:00
sig = 1;
2021-07-21 13:17:34 +12:00
Logger::error(Global::exit_error_msg);
std::cerr << "ERROR: " << Global::exit_error_msg << endl;
2021-07-21 13:17:34 +12:00
}
2021-06-10 05:47:49 +12:00
Config::write();
Input::clear();
2021-06-14 09:12:11 +12:00
Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
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();
}
//? 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-06-22 08:52:55 +12:00
// if (tty_mode and letter != "█" and letter != " ") letter = "░";
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
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);
atomic<bool> do_work (false);
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
};
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;
//? ------------------------------- 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
2021-08-04 09:47:46 +12:00
pthread_sigmask(SIG_BLOCK, &mask, NULL);
//? pthread_mutex_lock to make sure this thread is a single instance 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;
}
while (not Global::quitting) {
atomic_wait(do_work, false);
do_work = false;
if (stopping or Global::resized) {
continue;
}
auto& conf = current_conf;
//? Secondary atomic lock used for signaling status to main thread
atomic_lock lck(active);
//! 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;
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_times["proc"].at(0) = time_micros();
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(std::chrono::microseconds(10)) == future_status::ready) {
try {
if (Global::debug) {
debug_times["proc"].at(1) = time_micros();
debug_times["proc"].at(0) = debug_times["proc"].at(1) - debug_times["proc"].at(0);
debug_times["total"].at(0) += debug_times["proc"].at(0);
}
output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["proc"].at(1) = time_micros() - debug_times["proc"].at(1);
debug_times["total"].at(1) += debug_times["proc"].at(1);
}
}
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_times["net"].at(0) = time_micros();
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(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["net"].at(1) = time_micros();
debug_times["net"].at(0) = debug_times["net"].at(1) - debug_times["net"].at(0);
debug_times["total"].at(0) += debug_times["net"].at(0);
}
output += Net::draw(net.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["net"].at(1) = time_micros() - debug_times["net"].at(1);
debug_times["total"].at(1) += debug_times["net"].at(1);
}
}
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_times["mem"].at(0) = time_micros();
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(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["mem"].at(1) = time_micros();
debug_times["mem"].at(0) = debug_times["mem"].at(1) - debug_times["mem"].at(0);
debug_times["total"].at(0) += debug_times["mem"].at(0);
}
output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["mem"].at(1) = time_micros() - debug_times["mem"].at(1);
debug_times["total"].at(1) += debug_times["mem"].at(1);
}
}
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_times["cpu"].at(0) = time_micros();
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(ZeroSec) == future_status::ready) {
try {
if (Global::debug) {
debug_times["cpu"].at(1) = time_micros();
debug_times["cpu"].at(0) = debug_times["cpu"].at(1) - debug_times["cpu"].at(0);
debug_times["total"].at(0) += debug_times["cpu"].at(0);
}
output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update);
if (Global::debug) {
debug_times["cpu"].at(1) = time_micros() - debug_times["cpu"].at(1);
debug_times["total"].at(1) += debug_times["cpu"].at(1);
}
}
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
: Theme::c("inactive_fg") + Fx::ub + Fx::uncolor(output + conf.clock) + conf.overlay)
<< Term::sync_end << flush;
}
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;
}
else if (box == "clock") {
if (not Global::clock.empty())
cout << Term::sync_start << Global::clock << Term::sync_end;
}
else if (box.empty() and Config::current_boxes.empty()) {
cout << Term::sync_start << Term::clear + Mv::to(10, 10) << "No boxes shown!" << Term::sync_end;
}
else {
Config::unlock();
Config::lock();
//? Setup bitmask for selected boxes instead of parsing strings
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
do_work = true;
atomic_notify(do_work);
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) {
if (not active) {
do_work = true;
atomic_notify(do_work);
sleep_ms(1);
}
else {
atomic_wait(active);
}
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-08-04 09:47:46 +12:00
sigemptyset(&Runner::mask);
sigaddset(&Runner::mask, SIGINT);
sigaddset(&Runner::mask, SIGTSTP);
sigaddset(&Runner::mask, SIGWINCH);
sigaddset(&Runner::mask, SIGTERM);
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;
}
}
2021-06-22 08:52:55 +12:00
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.\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()) {
2021-05-29 12:32:36 +12:00
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-05-29 12:32:36 +12:00
}
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
2021-06-22 08:52:55 +12:00
Logger::info("Logger set to " + 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! Use --utf-force argument to start anyway.";
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
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
//? 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) {
Global::resized = false;
2021-08-11 06:20:33 +12:00
Draw::calcSizes();
Runner::run("all", true);
atomic_wait(Runner::active);
2021-07-19 04:04:49 +12:00
}
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) {
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 (update_ms != (uint64_t)Config::getI("update_ms")) {
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();
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
}