diff --git a/btop.cpp b/btop.cpp index 310887a..b7c428f 100644 --- a/btop.cpp +++ b/btop.cpp @@ -40,6 +40,7 @@ namespace Global { {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; const std::string Version = "0.0.20"; + int coreCount; } #include @@ -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 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; diff --git a/src/btop_config.h b/src/btop_config.h index df131ed..86f50a4 100644 --- a/src/btop_config.h +++ b/src/btop_config.h @@ -112,23 +112,29 @@ namespace Config { } //* Set config value to bool - void setB(string name, bool value){ + void set(string name, bool value){ bools.at(name) = value; changed = true; } //* Set config value to int - void setI(string name, int value){ + void set(string name, int value){ ints.at(name) = value; changed = true; } //* Set config value to string - 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; } diff --git a/src/btop_draw.h b/src/btop_draw.h index 7d56f11..6736eab 100644 --- a/src/btop_draw.h +++ b/src/btop_draw.h @@ -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; diff --git a/src/btop_input.h b/src/btop_input.h index 2dc98f4..9ce1f5a 100644 --- a/src/btop_input.h +++ b/src/btop_input.h @@ -81,10 +81,10 @@ namespace Input { //* Poll keyboard & mouse input for 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; } diff --git a/src/btop_linux.h b/src/btop_linux.h index 2854b9b..6c533e4 100644 --- a/src/btop_linux.h +++ b/src/btop_linux.h @@ -46,24 +46,18 @@ namespace fs = std::filesystem; using namespace Tools; const auto SSmax = std::numeric_limits::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 stop (false); atomic 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 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(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((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 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; diff --git a/src/btop_theme.h b/src/btop_theme.h index c354cc0..98c7885 100644 --- a/src/btop_theme.h +++ b/src/btop_theme.h @@ -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 c_gradient; - string wname; bool t_to_256 = !Config::getB("truecolor"); - array, 3> rgb_arr; - array, 101> dec_arr; for (auto& [name, source_arr] : rgbs) { if (!name.ends_with("_start")) continue; + array, 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, 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) { diff --git a/src/btop_tools.h b/src/btop_tools.h index 5d9eb59..8bc8639 100644 --- a/src/btop_tools.h +++ b/src/btop_tools.h @@ -93,19 +93,19 @@ namespace Fx { //* Collection of escape codes and functions for cursor manipulation namespace Mv { //* Move cursor to , - 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 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 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 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 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 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); }