Optimizations and fixes

This commit is contained in:
aristocratos 2021-06-05 01:41:24 +02:00
parent ce34cbb8d0
commit 43e3c4fa87
7 changed files with 170 additions and 176 deletions

View file

@ -40,6 +40,7 @@ namespace Global {
{"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
};
const std::string Version = "0.0.20";
int coreCount;
}
#include <btop_tools.h>
@ -127,14 +128,14 @@ void _exit_handler() { clean_quit(-1); }
//? Generate the btop++ banner
void banner_gen() {
size_t z = 0, w = 0;
size_t z = 0;
string b_color, bg, fg, oc, letter;
bool truecolor = Config::getB("truecolor");
int bg_i;
Global::banner.clear();
Global::banner_width = 0;
for (auto line: Global::Banner_src) {
if ( (w = ulen(line[1])) > Global::banner_width) Global::banner_width = w;
if (auto w = ulen(line[1]); w > Global::banner_width) Global::banner_width = w;
fg = Theme::hex_to_color(line[0], !truecolor);
bg_i = 120-z*12;
bg = Theme::dec_to_color(bg_i, bg_i, bg_i, !truecolor);
@ -179,12 +180,8 @@ int main(int argc, char **argv){
std::atexit(_exit_handler);
#if defined(LINUX)
//? Linux paths init
Global::proc_path = (fs::is_directory(fs::path("/proc")) && access("/proc", R_OK) != -1) ? "/proc" : "";
if (Global::proc_path.empty()) {
cout << "ERROR: Proc filesystem not found or no permission to read from it!" << endl;
exit(1);
}
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();
@ -199,21 +196,18 @@ int main(int argc, char **argv){
}
}
if (!Config::conf_dir.empty()) {
std::error_code ec;
if (!fs::is_directory(Config::conf_dir) && !fs::create_directories(Config::conf_dir, ec)) {
if (std::error_code ec; !fs::is_directory(Config::conf_dir) && !fs::create_directories(Config::conf_dir, ec)) {
cout << "WARNING: Could not create or access btop config directory. Logging and config saving disabled." << endl;
cout << "Make sure your $HOME environment variable is correctly set to fix this." << endl;
}
else {
std::error_code ec;
Config::conf_file = Config::conf_dir / "btop.conf";
Logger::logfile = Config::conf_dir / "btop.log";
Theme::user_theme_dir = Config::conf_dir / "themes";
if (!fs::exists(Theme::user_theme_dir) && !fs::create_directory(Theme::user_theme_dir, ec)) Theme::user_theme_dir.clear();
}
}
if (!Global::self_path.empty()) {
std::error_code ec;
if (std::error_code ec; !Global::self_path.empty()) {
Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
if (ec || access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
}
@ -245,6 +239,11 @@ int main(int argc, char **argv){
clean_quit(1);
}
#if defined(LINUX)
//? Linux init
Proc::init();
#endif
//? Read config file if present
Config::load();
// Config::setB("truecolor", false);
@ -261,7 +260,7 @@ int main(int argc, char **argv){
//* ------------------------------------------------ TESTING ------------------------------------------------------
Global::debuginit = true;
Global::debuginit = false;
// cout << Theme("main_bg") << Term::clear << flush;
// bool thread_test = false;
@ -436,7 +435,6 @@ int main(int argc, char **argv){
auto timestamp = time_ms();
Proc::init();
@ -444,7 +442,7 @@ int main(int argc, char **argv){
string ostring;
uint64_t tsl, timestamp2, rcount = 0;
list<uint64_t> avgtimes = {0};
uint timer = 1000;
uint timer = 2000;
bool filtering = false;
bool reversing = false;
int sortint = Proc::sort_map["cpu lazy"];
@ -461,25 +459,28 @@ int main(int argc, char **argv){
string pbox = Draw::createBox({.x = 0, .y = 10, .width = Term::width, .height = Term::height - 16, .line_color = Theme::c("proc_box"), .title = "testbox", .title2 = "below", .fill = false, .num = 7});
pbox += rjust("Pid:", 8) + " " + ljust("Program:", 16) + " " + ljust("Command:", Term::width - 69) + " Threads: " +
ljust("User:", 10) + " " + rjust("MemB", 5) + " " + rjust("Cpu%", 14) + "\n";
ljust("User:", 10) + " " + rjust("MemB", 5) + " " + rjust("Cpu%", 14) + "\n" + Mv::save;
while (key != "q") {
timestamp = time_micros();
tsl = time_ms() + timer;
auto plist = Proc::collect(Proc::sort_array[sortint], reversing, filter);
auto plist = Proc::collect(Proc::sort_array[sortint], reversing, filter, Config::getB("proc_per_core"));
timestamp2 = time_micros();
timestamp = timestamp2 - timestamp;
ostring.clear();
lc = 0;
filter_cur = (filtering) ? Fx::bl + "" + Fx::reset : "";
ostring = Mv::save + Mv::u(2) + Mv::r(20) + trans(rjust("Filter: " + filter + filter_cur + string(Term::width / 3, ' ') +
"Sorting: " + string(Proc::sort_array[sortint]), Term::width - 25, true, filtering)) + Mv::restore;
ostring = Mv::u(2) + Mv::l(Term::width) + Mv::r(12)
+ trans("Filter: " + filter + (filtering ? Fx::bl + "" + Fx::reset : "")) + Mv::l(Term::width)
+ trans(rjust("Per core: " + (Config::getB("proc_per_core") ? "On "s : "Off"s) + " Sorting: "
+ string(Proc::sort_array[sortint]), Term::width - 3))
+ Mv::restore;
for (auto& p : plist){
ostring += Mv::r(1) + greyscale[lc] + rjust(to_string(p.pid), 8) + " " + ljust(p.name, 16) + " " + ljust(p.cmd, Term::width - 66, true) + " " +
rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ');
ostring += (p.cpu_p > 100) ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4);
ostring += "\n";
ostring += Mv::r(1) + greyscale[lc] + rjust(to_string(p.pid), 8) + " " + ljust(p.name, 16) + " " + ljust(p.cmd, Term::width - 66, true) + " "
+ rjust(to_string(p.threads), 5) + " " + ljust(p.user, 10) + " " + rjust(floating_humanizer(p.mem, true), 5) + string(11, ' ')
+ (p.cpu_p < 10 || p.cpu_p >= 100 ? rjust(to_string(p.cpu_p), 3) + " " : rjust(to_string(p.cpu_p), 4))
+ "\n";
if (lc++ > Term::height - 21) break;
}
@ -511,6 +512,7 @@ int main(int argc, char **argv){
else if (key == "right") { if (++sortint > (int)Proc::sort_array.size() - 1) sortint = 0; }
else if (key == "f") filtering = true;
else if (key == "r") reversing = !reversing;
else if (key == "c") Config::flip("proc_per_core");
else if (key == "delete") filter.clear();
else continue;
break;

View file

@ -112,23 +112,29 @@ namespace Config {
}
//* Set config value <name> to bool <value>
void setB(string name, bool value){
void set(string name, bool value){
bools.at(name) = value;
changed = true;
}
//* Set config value <name> to int <value>
void setI(string name, int value){
void set(string name, int value){
ints.at(name) = value;
changed = true;
}
//* Set config value <name> to string <value>
void setS(string name, string value){
void set(string name, string value){
strings.at(name) = value;
changed = true;
}
//* Flip config bool value
void flip(string name){
bools.at(name) = !bools.at(name);
changed = true;
}
void load(){
if (conf_file.empty()) return;
}

View file

@ -91,21 +91,21 @@ namespace Draw {
//* Draw horizontal lines
for (uint hpos : {c.y, c.y + c.height - 1}){
out += Mv::to(hpos, c.x) + Symbols::h_line * (c.width - 1);
out += Mv::to(hpos, c.x) + Symbols::h_line * (c.width);
}
//* Draw vertical lines and fill if enabled
for (uint hpos : iota(c.y + 1, c.y + c.height - 1)){
out += Mv::to(hpos, c.x) + Symbols::v_line +
((c.fill) ? string(c.width - 2, ' ') : Mv::r(c.width - 2)) +
((c.fill) ? string(c.width - 1, ' ') : Mv::r(c.width - 1)) +
Symbols::v_line;
}
//* Draw corners
out += Mv::to(c.y, c.x) + Symbols::left_up +
Mv::to(c.y, c.x + c.width - 1) + Symbols::right_up +
Mv::to(c.y, c.x + c.width) + Symbols::right_up +
Mv::to(c.y + c.height - 1, c.x) + Symbols::left_down +
Mv::to(c.y + c.height - 1, c.x + c.width - 1) + Symbols::right_down;
Mv::to(c.y + c.height - 1, c.x + c.width) + Symbols::right_down;
//* Draw titles if defined
if (!c.title.empty()){
@ -117,7 +117,7 @@ namespace Draw {
Fx::ub + lcolor + Symbols::title_right;
}
return out + Fx::reset + Mv::to(c.y + 1, c.x + 1);
return out + Fx::reset + Mv::to(c.y + 1, c.x + 2);
}
//* Class holding a percentage meter
@ -203,7 +203,7 @@ namespace Draw {
ai++;
}
//? Generate braille symbol from 5x5 2D vector
graphs[current][horizon] += (height == 1 && result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[result[0] * 5 + result[1]];
graphs[current][horizon] += (height == 1 && result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])];
}
if (mult && i > data_offset) last = data_value;

View file

@ -81,10 +81,10 @@ namespace Input {
//* Poll keyboard & mouse input for <timeout> ms and return input availabilty as a bool
bool poll(int timeout=0){
if (timeout < 1) return cin.rdbuf()->in_avail() > 0;
int timer = 0;
while (timer++ * 10 <= timeout) {
while (timeout > 0) {
if (cin.rdbuf()->in_avail() > 0) return true;
sleep_ms( (timer * 10 <= timeout) ? 10 : timeout % 10);
sleep_ms(timeout < 10 ? timeout : 10);
timeout -= 10;
}
return false;
}

View file

@ -46,24 +46,18 @@ namespace fs = std::filesystem;
using namespace Tools;
const auto SSmax = std::numeric_limits<streamsize>::max();
namespace Global {
fs::path proc_path;
}
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
double system_uptime(){
string upstr;
ifstream pread("/proc/uptime");
getline(pread, upstr, ' ');
pread.close();
return stod(upstr);
namespace Tools {
double system_uptime(){
string upstr;
ifstream pread("/proc/uptime");
getline(pread, upstr, ' ');
pread.close();
return stod(upstr);
}
}
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
namespace Proc {
namespace {
uint64_t tstamp;
@ -81,6 +75,8 @@ namespace Proc {
}
fs::path proc_path;
uint64_t old_cputimes = 0;
size_t numpids = 500;
atomic<bool> stop (false);
atomic<bool> running (false);
@ -99,36 +95,27 @@ namespace Proc {
//* proc_info: pid, name, cmd, threads, user, mem, cpu_p, cpu_c, state, cpu_n, p_nice, ppid
struct proc_info {
uint pid;
string name, cmd;
size_t threads;
string user;
uint64_t mem;
double cpu_p, cpu_c;
char state;
int cpu_n, p_nice;
uint ppid;
string name = "", cmd = "";
size_t threads = 0;
string user = "";
uint64_t mem = 0;
double cpu_p = 0.0, cpu_c = 0.0;
char state = '0';
int cpu_n = 0, p_nice = 0;
uint ppid = 0;
};
//* Collects process information from /proc and returns a vector of proc_info structs
auto collect(string sorting="pid", bool reverse=false, string filter=""){
auto collect(string sorting="pid", bool reverse=false, string filter="", bool per_core=true){
running.store(true);
uint pid, ppid;
uint64_t cpu_t, rss_mem;
double cpu, cpu_s;
bool new_cache;
char state;
int cpu_n, p_nice;
size_t threads, s_pos, c_pos, s_count;
ifstream pread;
string pid_str, name, cmd, attr, user, instr, uid, status, tmpstr;
auto since_last = time_ms() - tstamp;
if (since_last < 1) since_last = 1;
auto uptime = system_uptime();
auto sortint = (sort_map.contains(sorting)) ? sort_map[sorting] : 7;
vector<proc_info> procs;
procs.reserve((numpids + 10));
numpids = 0;
int cmult = (per_core) ? Global::coreCount : 1;
//* Update uid_user map if /etc/passwd changed since last run
if (!passwd_path.empty() && fs::last_write_time(passwd_path) != passwd_time) {
@ -148,8 +135,18 @@ namespace Proc {
pread.close();
}
//* Iterate over all pids in /proc and get relevant values
for (auto& d: fs::directory_iterator(Global::proc_path)){
//* Get cpu total times from /proc/stat
uint64_t cputimes = 0;
pread.open(proc_path / "stat");
if (pread.good()) {
pread.ignore(SSmax, ' ');
for (uint64_t times; pread >> times; cputimes += times);
pread.close();
}
else return procs;
//* Iterate over all pids in /proc
for (auto& d: fs::directory_iterator(proc_path)){
if (pread.is_open()) pread.close();
if (stop.load()) {
procs.clear();
@ -157,17 +154,16 @@ namespace Proc {
stop.store(false);
return procs;
}
pid_str = d.path().filename();
cpu = 0.0; cpu_s = 0.0; cpu_t = 0; cpu_n = 0;
rss_mem = 0; threads = 0; state = '0'; ppid = 0; p_nice = 0;
new_cache = false;
string pid_str = d.path().filename();
bool new_cache = false;
if (d.is_directory() && isdigit(pid_str[0])) {
numpids++;
pid = stoul(pid_str);
proc_info new_proc (stoul(pid_str));
//* Cache program name, command and username
if (!cache.contains(pid)) {
name.clear(); cmd.clear(); user.clear();
if (!cache.contains(new_proc.pid)) {
string name, cmd, user;
new_cache = true;
pread.open(d.path() / "comm");
if (pread.good()) {
@ -178,6 +174,7 @@ namespace Proc {
pread.open(d.path() / "cmdline");
if (pread.good()) {
string tmpstr = "";
while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " ";
pread.close();
if (!cmd.empty()) cmd.pop_back();
@ -186,10 +183,11 @@ namespace Proc {
pread.open(d.path() / "status");
if (pread.good()) {
uid.clear();
string uid;
while (!pread.eof()){
getline(pread, status, ':');
if (status == "Uid") {
string line;
getline(pread, line, ':');
if (line == "Uid") {
pread.ignore();
getline(pread, uid, '\t');
break;
@ -201,25 +199,30 @@ namespace Proc {
user = (!uid.empty() && uid_user.contains(uid)) ? uid_user.at(uid) : uid;
}
else continue;
cache[pid] = {name, cmd, user};
cache[new_proc.pid] = {name, cmd, user};
}
//* Match filter if defined
if (!filter.empty()
&& pid_str.find(filter) == string::npos
&& cache[pid].name.find(filter) == string::npos
&& cache[pid].cmd.find(filter) == string::npos
&& cache[pid].user.find(filter) == string::npos) {
if (new_cache) cache.erase(pid);
&& cache[new_proc.pid].name.find(filter) == string::npos
&& cache[new_proc.pid].cmd.find(filter) == string::npos
&& cache[new_proc.pid].user.find(filter) == string::npos) {
if (new_cache) cache.erase(new_proc.pid);
continue;
}
new_proc.name = cache[new_proc.pid].name;
new_proc.cmd = cache[new_proc.pid].cmd;
new_proc.user = cache[new_proc.pid].user;
//* Parse /proc/[pid]/stat
pread.open(d.path() / "stat");
if (pread.good()) {
string instr;
getline(pread, instr);
pread.close();
s_pos = 0; c_pos = 0; s_count = 0;
size_t s_pos = 0, c_pos = 0, s_count = 0;
uint64_t cpu_t = 0;
//? Skip pid and comm field and find comm fields closing ')'
s_pos = instr.find_last_of(')') + 2;
@ -231,11 +234,11 @@ namespace Proc {
switch (s_count) {
case 0: { //? Process state
state = instr[s_pos];
new_proc.state = instr[s_pos];
break;
}
case 1: { //? Process parent pid
ppid = stoul(instr.substr(s_pos, c_pos - s_pos));
new_proc.ppid = stoul(instr.substr(s_pos, c_pos - s_pos));
break;
}
case 11: { //? Process utime
@ -247,22 +250,19 @@ namespace Proc {
break;
}
case 16: { //? Process nice value
p_nice = stoi(instr.substr(s_pos, c_pos - s_pos));
new_proc.p_nice = stoi(instr.substr(s_pos, c_pos - s_pos));
break;
}
case 17: { //? Process number of threads
threads = stoul(instr.substr(s_pos, c_pos - s_pos));
new_proc.threads = stoul(instr.substr(s_pos, c_pos - s_pos));
break;
}
case 19: { //? Cache cpu times and cpu seconds
if (new_cache) {
cache[pid].cpu_t = cpu_t;
cache[pid].cpu_s = stoull(instr.substr(s_pos, c_pos - s_pos));
};
case 19: { //? Cache cpu seconds
if (new_cache) cache[new_proc.pid].cpu_s = stoull(instr.substr(s_pos, c_pos - s_pos));
break;
}
case 36: { //? CPU number last executed on
cpu_n = stoi(instr.substr(s_pos, c_pos - s_pos));
new_proc.cpu_n = stoi(instr.substr(s_pos, c_pos - s_pos));
break;
}
}
@ -271,14 +271,14 @@ namespace Proc {
if (s_count < 19) continue;
//? Process cpu usage since last update, 100'000 because (100 percent * 1000 milliseconds) for correct conversion
cpu = static_cast<double>(100000 * (cpu_t - cache[pid].cpu_t) / since_last) / clk_tck;
//? Process cpu usage since last update
new_proc.cpu_p = round(cmult * 1000 * (cpu_t - cache[new_proc.pid].cpu_t) / (cputimes - old_cputimes)) / 10.0;
//? Process cumulative cpu usage since process start
cpu_s = static_cast<double>((cpu_t / clk_tck) / (uptime - (cache[pid].cpu_s / clk_tck)));
new_proc.cpu_c = ((double)cpu_t / clk_tck) / (uptime - (cache[new_proc.pid].cpu_s / clk_tck));
//? Update cache with latest cpu times
cache[pid].cpu_t = cpu_t;
cache[new_proc.pid].cpu_t = cpu_t;
}
else continue;
@ -286,13 +286,13 @@ namespace Proc {
pread.open(d.path() / "statm");
if (pread.good()) {
pread.ignore(SSmax, ' ');
pread >> rss_mem;
pread >> new_proc.mem;
pread.close();
rss_mem *= page_size;
new_proc.mem *= page_size;
}
//* Create proc_info
procs.emplace_back(pid, cache[pid].name, cache[pid].cmd, threads, cache[pid].user, rss_mem, cpu, cpu_s, state, cpu_n, p_nice, ppid);
procs.push_back(new_proc);
}
}
@ -329,12 +329,14 @@ namespace Proc {
unordered_flat_map<uint, p_cache> r_cache;
r_cache.reserve(procs.size());
counter = 0;
Logger::debug("Cleared proc cache");
if (filter.empty()) {
for (auto& p : procs) r_cache[p.pid] = cache[p.pid];
cache.swap(r_cache);
}
else cache.clear();
}
old_cputimes = cputimes;
tstamp = time_ms();
running.store(false);
return procs;
@ -343,6 +345,15 @@ namespace Proc {
//* Initialize needed variables for collect
void init(){
tstamp = time_ms();
proc_path = (fs::is_directory(fs::path("/proc")) && 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);
cout << "ERROR: " << errmsg << endl;
exit(1);
}
passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path;
if (passwd_path.empty()) Logger::warning("Could not read /etc/passwd, will show UID instead of username.");
@ -352,7 +363,6 @@ namespace Proc {
Logger::warning("Could not get system page size. Defaulting to 4096, processes memory usage might be incorrect.");
}
clk_tck = sysconf(_SC_CLK_TCK);
if (clk_tck <= 0) {
clk_tck = 100;

View file

@ -85,7 +85,7 @@ namespace Theme {
namespace {
//* Convert 24-bit colors to 256 colors using 6x6x6 color cube
int truecolor_to_256(uint r, uint g, uint b){
int truecolor_to_256(int r, int g, int b){
if (round((double)r / 11) == round((double)g / 11) && round((double)g / 11) == round((double)b / 11)) {
return 232 + round((double)r / 11);
} else {
@ -105,12 +105,10 @@ namespace Theme {
Logger::error("Invalid hex value: " + hexa);
return "";
}
depth = (depth == "fg") ? "38" : "48";
string pre = Fx::e + depth + ";";
pre += (t_to_256) ? "5;" : "2;";
string pre = Fx::e + (depth == "fg" ? "38" : "48") + ";" + (t_to_256 ? "5;" : "2;");
if (hexa.size() == 2){
uint h_int = stoi(hexa, 0, 16);
int h_int = stoi(hexa, 0, 16);
if (t_to_256){
return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m";
} else {
@ -133,7 +131,7 @@ namespace Theme {
}
else Logger::error("Invalid size of hex value: " + hexa);
}
else Logger::error("Hex value missing." + hexa);
else Logger::error("Hex value missing: " + hexa);
return "";
}
@ -141,13 +139,11 @@ namespace Theme {
//* 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(uint r, uint g, uint b, bool t_to_256=false, string depth="fg"){
depth = (depth == "fg") ? "38" : "48";
string pre = Fx::e + depth + ";";
pre += (t_to_256) ? "5;" : "2;";
r = min(r, 255u);
g = min(g, 255u);
b = min(b, 255u);
string dec_to_color(int r, int g, int b, bool t_to_256=false, string depth="fg"){
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);
b = std::clamp(b, 0, 255);
if (t_to_256) return pre + to_string(truecolor_to_256(r, g, b)) + "m";
else return pre + to_string(r) + ";" + to_string(g) + ";" + to_string(b) + "m";
}
@ -229,15 +225,13 @@ namespace Theme {
void generateGradients(){
gradients.clear();
array<string, 101> c_gradient;
string wname;
bool t_to_256 = !Config::getB("truecolor");
array<array<int, 3>, 3> rgb_arr;
array<array<int, 3>, 101> dec_arr;
for (auto& [name, source_arr] : rgbs) {
if (!name.ends_with("_start")) continue;
array<array<int, 3>, 101> dec_arr;
dec_arr[0][0] = -1;
wname = rtrim(name, "_start");
rgb_arr = {source_arr, rgbs[wname + "_mid"], rgbs[wname + "_end"]};
string wname = rtrim(name, "_start");
array<array<int, 3>, 3> rgb_arr = {source_arr, rgbs[wname + "_mid"], rgbs[wname + "_end"]};
//? Only start iteration if gradient has a _end color value defined
if (rgb_arr[2][0] >= 0) {

View file

@ -93,19 +93,19 @@ namespace Fx {
//* Collection of escape codes and functions for cursor manipulation
namespace Mv {
//* Move cursor to <line>, <column>
const string to(int line, int col){ return Fx::e + to_string(line) + ";" + to_string(col) + "f";}
string to(int line, int col){ return Fx::e + to_string(line) + ";" + to_string(col) + "f";}
//* Move cursor right <x> columns
const string r(int x){ return Fx::e + to_string(x) + "C";}
string r(int x){ return Fx::e + to_string(x) + "C";}
//* Move cursor left <x> columns
const string l(int x){ return Fx::e + to_string(x) + "D";}
string l(int x){ return Fx::e + to_string(x) + "D";}
//* Move cursor up x lines
const string u(int x){ return Fx::e + to_string(x) + "A";}
string u(int x){ return Fx::e + to_string(x) + "A";}
//* Move cursor down x lines
const string d(int x) { return Fx::e + to_string(x) + "B";}
string d(int x) { return Fx::e + to_string(x) + "B";}
//* Save cursor position
const string save = Fx::e + "s";
@ -135,18 +135,19 @@ namespace Term {
return 0 == tcsetattr(STDIN_FILENO, TCSANOW, &settings);
}
//* Refresh variables holding current terminal width and height and return true if resized
bool refresh(){
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
resized = (width != w.ws_col || height != w.ws_row) ? true : false;
width = w.ws_col;
height = w.ws_row;
return resized;
//* Toggle need for return key when reading input
bool linebuffered(bool on=true){
struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ICANON;
else settings.c_lflag &= ~(ICANON);
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
if (on) setlinebuf(stdin);
else setbuf(stdin, NULL);
return true;
}
}
//* Hide terminal cursor
const string hide_cursor = Fx::e + "?25l";
@ -180,20 +181,18 @@ namespace Term {
//* Disable direct mouse reporting
const string mouse_direct_off = Fx::e + "?1003l";
//* Toggle need for return key when reading input
bool linebuffered(bool on=true){
struct termios settings;
if (tcgetattr(STDIN_FILENO, &settings)) return false;
if (on) settings.c_lflag |= ICANON;
else settings.c_lflag &= ~(ICANON);
if (tcsetattr(STDIN_FILENO, TCSANOW, &settings)) return false;
if (on) setlinebuf(stdin);
else setbuf(stdin, NULL);
return true;
//* Refresh variables holding current terminal width and height and return true if resized
bool refresh(){
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
if (width != w.ws_col || height != w.ws_row) {
width = w.ws_col;
height = w.ws_row;
resized = true;
}
return resized;
}
//* Check for a valid tty, save terminal options and set new options
bool init(){
if (!initialized){
@ -408,34 +407,16 @@ namespace Tools {
return out;
}
//* Repeat string <str> <n> number of times
string repeat(string str, const size_t n){
if (n == 0){
str.clear();
str.shrink_to_fit();
return str;
} else if (n == 1 || str.empty()){
return str;
}
const auto period = str.size();
if (period == 1){
str.append(n - 1, str.front());
return str;
}
str.reserve(period * n);
size_t m = 2;
for (; m < n; m *= 2) str += str;
str.append(str.c_str(), (n - (m / 2)) * period);
return str;
}
//* String gets passed to repeat function
//* Add std::string operator "*" : Repeat string <str> <n> number of times
std::string operator*(string str, size_t n){
return repeat(std::move(str), n);
string out;
out.reserve(str.size() * n);
while (n-- > 0) out += str;
return out;
}
//* Return current time in <strf> format
std::string strf_time(std::string strf){
string strf_time(string strf){
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::tm bt {};
@ -468,11 +449,11 @@ namespace Logger {
if (loglevel < level || logfile.empty()) return;
busy.wait(true); busy.store(true);
std::error_code ec;
if (fs::file_size(logfile, ec) > 1024 << 10) {
if (fs::file_size(logfile, ec) > 1024 << 10 && !ec) {
auto old_log = logfile;
old_log += ".1";
if (fs::exists(old_log)) fs::remove(old_log, ec);
fs::rename(logfile, old_log, ec);
if (!ec) fs::rename(logfile, old_log, ec);
}
if (!ec) {
std::ofstream lwrite(logfile, std::ios::app);
@ -480,6 +461,7 @@ namespace Logger {
lwrite << Tools::strf_time(tdf) << log_levels[level] << ": " << msg << "\n";
lwrite.close();
}
else logfile.clear();
busy.store(false);
}