/* Copyright 2021 Aristocratos (jakob@qvantnet.com) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. indent = tab tab-size = 4 */ #include #include #include #include #include #include #include #include #include "btop_draw.hpp" #include "btop_config.hpp" #include "btop_theme.hpp" #include "btop_shared.hpp" #include "btop_tools.hpp" #include "btop_input.hpp" #include "btop_menu.hpp" #include using std::array; using std::clamp; using std::cmp_equal; using std::cmp_greater; using std::cmp_less; using std::cmp_less_equal; using std::floor; using std::max; using std::min; using std::round; using std::to_string; using std::views::iota; using namespace Tools; using namespace std::literals; // for operator""s namespace rng = std::ranges; namespace Symbols { const string meter = "■"; const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; const std::unordered_map> graph_symbols = { { "braille_up", { " ", "⢀", "⢠", "⢰", "⢸", "⡀", "⣀", "⣠", "⣰", "⣸", "⡄", "⣄", "⣤", "⣴", "⣼", "⡆", "⣆", "⣦", "⣶", "⣾", "⡇", "⣇", "⣧", "⣷", "⣿" }}, {"braille_down", { " ", "⠈", "⠘", "⠸", "⢸", "⠁", "⠉", "⠙", "⠹", "⢹", "⠃", "⠋", "⠛", "⠻", "⢻", "⠇", "⠏", "⠟", "⠿", "⢿", "⡇", "⡏", "⡟", "⡿", "⣿" }}, {"block_up", { " ", "▗", "▗", "▐", "▐", "▖", "▄", "▄", "▟", "▟", "▖", "▄", "▄", "▟", "▟", "▌", "▙", "▙", "█", "█", "▌", "▙", "▙", "█", "█" }}, {"block_down", { " ", "▝", "▝", "▐", "▐", "▘", "▀", "▀", "▜", "▜", "▘", "▀", "▀", "▜", "▜", "▌", "▛", "▛", "█", "█", "▌", "▛", "▛", "█", "█" }}, {"tty_up", { " ", "░", "░", "▒", "▒", "░", "░", "▒", "▒", "█", "░", "▒", "▒", "▒", "█", "▒", "▒", "▒", "█", "█", "▒", "█", "█", "█", "█" }}, {"tty_down", { " ", "░", "░", "▒", "▒", "░", "░", "▒", "▒", "█", "░", "▒", "▒", "▒", "█", "▒", "▒", "▒", "█", "█", "▒", "█", "█", "█", "█" }} }; } namespace Draw { string banner_gen(int y, int x, bool centered, bool redraw) { static string banner; static size_t width = 0; if (redraw) banner.clear(); if (banner.empty()) { 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 > width) width = w; if (tty_mode) { fg = (z > 2) ? "\x1b[31m" : "\x1b[91m"; bg = (z > 2) ? "\x1b[90m" : "\x1b[37m"; } else { fg = Theme::hex_to_color(line[0], lowcolor); int bg_i = 120 - z * 12; bg = Theme::dec_to_color(bg_i, bg_i, bg_i, lowcolor); } for (size_t i = 0; i < line[1].size(); i += 3) { if (line[1][i] == ' ') { letter = Mv::r(1); i -= 2; } else letter = line[1].substr(i, 3); b_color = (letter == "█") ? fg : bg; if (b_color != oc) banner += b_color; banner += letter; oc = b_color; } if (++z < Global::Banner_src.size()) banner += Mv::l(ulen(line[1])) + Mv::d(1); } banner += Mv::r(18 - Global::Version.size()) + Theme::c("main_fg") + Fx::b + Fx::i + "v" + Global::Version + Fx::reset; } if (redraw) return ""; return (centered ? Mv::to(y, Term::width / 2 - width / 2) : Mv::to(y, x)) + banner; } TextEdit::TextEdit() {} TextEdit::TextEdit(string text, bool numeric) : numeric(numeric), text(std::move(text)) { pos = this->text.size(); upos = ulen(this->text); } bool TextEdit::command(const std::string_view key) { if (key == "left" and upos > 0) { upos--; pos = uresize(text, upos).size(); } else if (key == "right" and pos < text.size()) { upos++; pos = uresize(text, upos).size(); } else if (key == "home" and not text.empty() and pos > 0) { pos = upos = 0; } else if (key == "end" and not text.empty() and pos < text.size()) { pos = text.size(); upos = ulen(text); } else if (key == "backspace" and pos > 0) { if (pos == text.size()) { text = uresize(text, --upos); pos = text.size(); } else { const string first = uresize(text, --upos); pos = first.size(); text = first + luresize(text.substr(pos), ulen(text) - upos - 1); } } else if (key == "delete" and pos < text.size()) { const string first = uresize(text, upos + 1); text = uresize(first, ulen(first) - 1) + text.substr(first.size()); } else if (key == "space" and not numeric) { text.insert(pos++, 1, ' '); upos++; } else if (ulen(key) == 1 and text.size() < text.max_size() - 20) { if (numeric and not isint(key)) return false; if (key.size() == 1) { text.insert(pos++, 1, key.at(0)); upos++; } else { const auto first = fmt::format("{}{}", uresize(text, upos), key); text = first + text.substr(pos); upos++; pos = first.size(); } } else return false; return true; } string TextEdit::operator()(const size_t limit) { string out; size_t c_upos = upos; if (text.empty()) return Fx::ul + " " + Fx::uul; if (limit > 0 and ulen(text) + 1 > limit) { try { const size_t half = (size_t)round((double)limit / 2); string first; if (upos + half > ulen(text)) first = luresize(text.substr(0, pos), limit - (ulen(text) - upos)); else if (upos - half < 1) first = text.substr(0, pos); else first = luresize(text.substr(0, pos), half); out = first + uresize(text.substr(pos), limit - ulen(first)); c_upos = ulen(first); } catch (const std::exception& e) { Logger::error("In TextEdit::operator() : " + string{e.what()}); return ""; } } else out = text; if (c_upos == 0) return Fx::ul + uresize(out, 1) + Fx::uul + luresize(out, ulen(out) - 1); else if (c_upos == ulen(out)) return out + Fx::ul + " " + Fx::uul; else return uresize(out, c_upos) + Fx::ul + luresize(uresize(out, c_upos + 1), 1) + Fx::uul + luresize(out, ulen(out) - c_upos - 1); } void TextEdit::clear() { this->text.clear(); } string createBox( const int x, const int y, const int width, const int height, string line_color, bool fill, const std::string_view title, const std::string_view title2, const int num ) { string out; if (line_color.empty()) line_color = Theme::c("div_line"); auto tty_mode = Config::getB("tty_mode"); auto rounded = Config::getB("rounded_corners"); const string numbering = (num == 0) ? "" : Theme::c("hi_fg") + (tty_mode ? std::to_string(num) : Symbols::superscript.at(clamp(num, 0, 9))); const auto& right_up = (tty_mode or not rounded ? Symbols::right_up : Symbols::round_right_up); const auto& left_up = (tty_mode or not rounded ? Symbols::left_up : Symbols::round_left_up); const auto& right_down = (tty_mode or not rounded ? Symbols::right_down : Symbols::round_right_down); const auto& left_down = (tty_mode or not rounded ? Symbols::left_down : Symbols::round_left_down); out = Fx::reset + line_color; //? Draw horizontal lines for (const int& hpos : {y, y + height - 1}) { out += Mv::to(hpos, x) + Symbols::h_line * (width - 1); } //? Draw vertical lines and fill if enabled for (const int& hpos : iota(y + 1, y + height - 1)) { out += Mv::to(hpos, x) + Symbols::v_line + ((fill) ? string(width - 2, ' ') : Mv::r(width - 2)) + Symbols::v_line; } //? Draw corners out += Mv::to(y, x) + left_up + Mv::to(y, x + width - 1) + right_up + Mv::to(y + height - 1, x) +left_down + Mv::to(y + height - 1, x + width - 1) + right_down; //? Draw titles if defined if (not title.empty()) { out += fmt::format( "{}{}{}{}{}{}{}{}{}", Mv::to(y, x + 2), Symbols::title_left, Fx::b, numbering, Theme::c("title"), title, Fx::ub, line_color, Symbols::title_right ); } if (not title2.empty()) { out += fmt::format( "{}{}{}{}{}{}{}{}{}", Mv::to(y + height - 1, x + 2), Symbols::title_left_down, Fx::b, numbering, Theme::c("title"), title2, Fx::ub, line_color, Symbols::title_right_down ); } return out + Fx::reset + Mv::to(y + 1, x + 1); } bool update_clock(bool force) { const auto& clock_format = Config::getS("clock_format"); if (not Cpu::shown or clock_format.empty()) { if (clock_format.empty() and not Global::clock.empty()) Global::clock.clear(); return false; } static const std::unordered_map clock_custom_format = { {"/user", Tools::username()}, {"/host", Tools::hostname()}, {"/uptime", ""} }; static time_t c_time{}; static size_t clock_len{}; static string clock_str; if (auto n_time = time(nullptr); not force and n_time == c_time) return false; else { c_time = n_time; const auto new_clock = Tools::strf_time(clock_format); if (not force and new_clock == clock_str) return false; clock_str = new_clock; } auto& out = Global::clock; 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(clock_str, c_format)) { if (c_format == "/uptime") { string upstr = sec_to_dhms(system_uptime()); if (upstr.size() > 8) upstr.resize(upstr.size() - 3); clock_str = s_replace(clock_str, c_format, upstr); } else { clock_str = s_replace(clock_str, c_format, replacement); } } } clock_str = uresize(clock_str, std::max(10, width - 66 - (Term::width >= 100 and Config::getB("show_battery") and Cpu::has_battery ? 22 : 0))); out.clear(); if (clock_str.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 = clock_str.size(); } out += Mv::to(y, x+(width / 2)-(clock_len / 2)) + Fx::ub + Theme::c("cpu_box") + title_left + Theme::c("title") + Fx::b + clock_str + Theme::c("cpu_box") + Fx::ub + title_right; return true; } //* Meter class ------------------------------------------------------------------------------------------------------------> Meter::Meter() {} Meter::Meter(const int width, string color_gradient, bool invert) : width(width), color_gradient(std::move(color_gradient)), invert(invert) {} string Meter::operator()(int value) { if (width < 1) return ""; value = clamp(value, 0, 100); if (not cache.at(value).empty()) return cache.at(value); auto& out = cache.at(value); for (const int& i : iota(1, width + 1)) { int y = round((double)i * 100.0 / width); if (value >= y) out += Theme::g(color_gradient).at(invert ? 100 - y : y) + Symbols::meter; else { out += Theme::c("meter_bg") + Symbols::meter * (width + 1 - i); break; } } out += Fx::reset; return out; } //* Graph class ------------------------------------------------------------------------------------------------------------> void Graph::_create(const deque& data, int data_offset) { bool mult = (data.size() - data_offset > 1); const auto& graph_symbol = Symbols::graph_symbols.at(symbol + '_' + (invert ? "down" : "up")); array result; const float mod = (height == 1) ? 0.3 : 0.1; long long data_value = 0; if (mult and data_offset > 0) { last = data.at(data_offset - 1); if (max_value > 0) last = clamp((last + offset) * 100 / max_value, 0ll, 100ll); } //? Horizontal iteration over values in for (const int& i : iota(data_offset, (int)data.size())) { // if (tty_mode and mult and i % 2 != 0) continue; if (not tty_mode and mult) current = not current; if (i < 0) { data_value = 0; last = 0; } else { data_value = data.at(i); if (max_value > 0) data_value = clamp((data_value + offset) * 100 / max_value, 0ll, 100ll); } //? Vertical iteration over height of graph for (const int& horizon : iota(0, height)) { const int cur_high = (height > 1) ? round(100.0 * (height - horizon) / height) : 100; const int cur_low = (height > 1) ? round(100.0 * (height - (horizon + 1)) / height) : 0; //? Calculate previous + current value to fit two values in 1 braille character for (int ai = 0; const auto& value : {last, data_value}) { const int clamp_min = (no_zero and horizon == height - 1 and not (mult and i == data_offset and ai == 0)) ? 1 : 0; if (value >= cur_high) result[ai++] = 4; else if (value <= cur_low) result[ai++] = clamp_min; else { result[ai++] = clamp((int)round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod), clamp_min, 4); } } //? Generate graph symbol from 5x5 2D vector if (height == 1) { if (result.at(0) + result.at(1) == 0) graphs.at(current).at(horizon) += Mv::r(1); else { if (not color_gradient.empty()) graphs.at(current).at(horizon) += Theme::g(color_gradient).at(clamp(max(last, data_value), 0ll, 100ll)); graphs.at(current).at(horizon) += graph_symbol.at((result.at(0) * 5 + result.at(1))); } } else graphs.at(current).at(horizon) += graph_symbol.at((result.at(0) * 5 + result.at(1))); } if (mult and i >= 0) last = data_value; } last = data_value; out.clear(); if (height == 1) { //if (not color_gradient.empty()) // out += (last < 1 ? Theme::c("inactive_fg") : Theme::g(color_gradient).at(clamp(last, 0ll, 100ll))); out += graphs.at(current).at(0); } else { for (const int& i : iota(1, height + 1)) { if (i > 1) out += Mv::d(1) + Mv::l(width); if (not color_gradient.empty()) out += (invert) ? Theme::g(color_gradient).at(i * 100 / height) : Theme::g(color_gradient).at(100 - ((i - 1) * 100 / height)); out += (invert) ? graphs.at(current).at(height - i) : graphs.at(current).at(i-1); } } if (not color_gradient.empty()) out += Fx::reset; } Graph::Graph() {} Graph::Graph(int width, int height, const string& color_gradient, const deque& data, const string& symbol, bool invert, bool no_zero, long long max_value, long long offset) : width(width), height(height), color_gradient(color_gradient), invert(invert), no_zero(no_zero), offset(offset) { if (Config::getB("tty_mode") or symbol == "tty") this->symbol = "tty"; else if (symbol != "default") this->symbol = symbol; else this->symbol = Config::getS("graph_symbol"); if (this->symbol == "tty") tty_mode = true; if (max_value == 0 and offset > 0) max_value = 100; this->max_value = max_value; const int value_width = (tty_mode ? data.size() : ceil((double)data.size() / 2)); int data_offset = (value_width > width) ? data.size() - width * (tty_mode ? 1 : 2) : 0; if (not tty_mode and (data.size() - data_offset) % 2 != 0) { data_offset--; } //? Populate the two switching graph vectors and fill empty space if data size < width for (const int& i : iota(0, height * 2)) { if (tty_mode and i % 2 != current) continue; graphs[(i % 2 != 0)].push_back((value_width < width) ? ((height == 1) ? Mv::r(1) : " "s) * (width - value_width) : ""); } if (data.size() == 0) return; this->_create(data, data_offset); } string& Graph::operator()(const deque& data, bool data_same) { if (data_same) return out; //? Make room for new characters on graph if (not tty_mode) current = not current; for (const int& i : iota(0, height)) { if (height == 1 and graphs.at(current).at(i).at(1) == '[') { if (graphs.at(current).at(i).at(3) == 'C') graphs.at(current).at(i).erase(0, 4); else graphs.at(current).at(i).erase(0, graphs.at(current).at(i).find_first_of('m') + 4); } else if (graphs.at(current).at(i).at(0) == ' ') graphs.at(current).at(i).erase(0, 1); else graphs.at(current).at(i).erase(0, 3); } this->_create(data, (int)data.size() - 1); return out; } string& Graph::operator()() { return out; } //*-------------------------------------------------------------------------------------------------------------------------> } namespace Cpu { int width_p = 100, height_p = 32; int min_width = 60, min_height = 8; int x = 1, y = 1, width = 20, height; int b_columns, b_column_size; int b_x, b_y, b_width, b_height; float max_observed_pwr = 1.0f; int graph_up_height, graph_low_height; int graph_up_width, graph_low_width; int gpu_meter_width; bool shown = true, redraw = true, mid_line = false; string box; vector graphs_upper; vector graphs_lower; Draw::Meter cpu_meter; vector gpu_meters; vector core_graphs; vector temp_graphs; vector gpu_temp_graphs; vector gpu_mem_graphs; string draw(const cpu_info& cpu, const vector& gpus, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; if (force_redraw) redraw = true; bool show_temps = (Config::getB("check_temp") and got_sensors); bool show_watts = (Config::getB("show_cpu_watts") and supports_watts); auto single_graph = Config::getB("cpu_single_graph"); bool hide_cores = show_temps and (cpu_temp_only or not Config::getB("show_coretemp")); const int extra_width = (hide_cores ? max(6, 6 * b_column_size) : 0); #ifdef GPU_SUPPORT const auto& show_gpu_info = Config::getS("show_gpu_info"); const bool gpu_always = show_gpu_info == "On"; const bool gpu_auto = show_gpu_info == "Auto"; const bool show_gpu = (gpus.size() > 0 and (gpu_always or (gpu_auto and Gpu::shown < Gpu::count))); #else (void)gpus; #endif auto graph_up_field = Config::getS("cpu_graph_upper"); if (graph_up_field == "Auto" or not v_contains(Cpu::available_fields, graph_up_field)) graph_up_field = "total"; auto graph_lo_field = Config::getS("cpu_graph_lower"); if (graph_lo_field == "Auto" or not v_contains(Cpu::available_fields, graph_lo_field)) { #ifdef GPU_SUPPORT graph_lo_field = show_gpu ? "gpu-totals" : graph_up_field; #else graph_lo_field = graph_up_field; #endif } auto tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_cpu")); auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto& temp_scale = Config::getS("temp_scale"); auto cpu_bottom = Config::getB("cpu_bottom"); const string& title_left = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_left_down : Symbols::title_left); const string& title_right = Theme::c("cpu_box") + (cpu_bottom ? Symbols::title_right_down : Symbols::title_right); static int bat_pos = 0, bat_len = 0; if (safeVal(cpu.cpu_percent, "total"s).empty() or safeVal(cpu.core_percent, 0).empty() or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; if (safeVal(cpu.cpu_percent, "total"s).empty() or safeVal(cpu.core_percent, 0).empty() or (show_temps and safeVal(cpu.temp, 0).empty())) return ""; string out; out.reserve(width * height); //* Redraw elements not needed to be updated every cycle if (redraw) { mid_line = (not single_graph and graph_up_field != graph_lo_field); graph_up_height = (single_graph ? height - 2 : ceil((double)(height - 2) / 2) - (mid_line and height % 2 != 0)); graph_low_height = height - 2 - graph_up_height - mid_line; const int button_y = cpu_bottom ? y + height - 1 : y; out += box; //? Buttons on title out += Mv::to(button_y, x + 10) + title_left + Theme::c("hi_fg") + Fx::b + 'm' + Theme::c("title") + "enu" + Fx::ub + title_right; Input::mouse_mappings["m"] = {button_y, x + 11, 1, 4}; out += Mv::to(button_y, x + 16) + title_left + Theme::c("hi_fg") + Fx::b + 'p' + Theme::c("title") + "reset " + (Config::current_preset < 0 ? "*" : to_string(Config::current_preset)) + Fx::ub + title_right; Input::mouse_mappings["p"] = {button_y, x + 17, 1, 8}; const string update = to_string(Config::getI("update_ms")) + "ms"; out += Mv::to(button_y, x + width - update.size() - 8) + title_left + Fx::b + Theme::c("hi_fg") + "- " + Theme::c("title") + update + Theme::c("hi_fg") + " +" + Fx::ub + title_right; Input::mouse_mappings["-"] = {button_y, x + width - (int)update.size() - 7, 1, 2}; Input::mouse_mappings["+"] = {button_y, x + width - 5, 1, 2}; //? Graphs & meters const int graph_default_width = x + width - b_width - 3; auto init_graphs = [&](vector& graphs, const int graph_height, int& graph_width, const string& graph_field, bool invert) { #ifdef GPU_SUPPORT if (graph_field.starts_with("gpu")) { if (graph_field.find("totals") != string::npos) { graphs.resize(gpus.size()); gpu_temp_graphs.resize(gpus.size()); gpu_mem_graphs.resize(gpus.size()); gpu_meters.resize(gpus.size()); const int gpu_draw_count = gpu_always ? Gpu::count : Gpu::count - Gpu::shown; graph_width = gpu_draw_count <= 0 ? graph_default_width : graph_default_width/gpu_draw_count - gpu_draw_count + 1 + graph_default_width%gpu_draw_count; for (size_t i = 0; i < gpus.size(); i++) { if (gpu_auto and v_contains(Gpu::shown_panels, i)) continue; auto& gpu = gpus[i]; auto& graph = graphs[i]; //? GPU graphs if (gpu.supported_functions.gpu_utilization) { if (i + 1 < gpus.size()) { graph = Draw::Graph{graph_width, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true}; } else { graph = Draw::Graph{ graph_width + graph_default_width%graph_width - (int)gpus.size() + 1, graph_height, "cpu", safeVal(gpu.gpu_percent, graph_field), graph_symbol, invert, true }; } } } } else { graphs.resize(1); graph_width = graph_default_width; graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(Gpu::shared_gpu_percent, graph_field), graph_symbol, invert, true }; } } else { #endif graphs.resize(1); graph_width = graph_default_width; graphs[0] = Draw::Graph{ graph_width, graph_height, "cpu", safeVal(cpu.cpu_percent, graph_field), graph_symbol, invert, true }; #ifdef GPU_SUPPORT } #endif }; init_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field, false); if (not single_graph) init_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field, Config::getB("cpu_invert_lower")); #ifdef GPU_SUPPORT if (show_gpu and b_columns > 1) { gpu_temp_graphs.resize(gpus.size()); gpu_mem_graphs.resize(gpus.size()); gpu_meters.resize(gpus.size()); for (size_t i = 0; i < gpus.size(); i++) { if (gpu_auto and v_contains(Gpu::shown_panels, i)) continue; auto& gpu = gpus[i]; //? GPU graphs/meters auto width_left = b_width - 10 - (gpus.size() > 9 ? 2 : gpus.size() > 1 ? 1 : 0); if (gpu.supported_functions.temp_info and show_temps) { gpu_temp_graphs[i] = Draw::Graph{ 5, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23 }; width_left -= 11; } if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total and b_columns > 1) { gpu_mem_graphs[i] = Draw::Graph{ 5, 1, "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol }; width_left -= 5; } width_left -= (gpu.supported_functions.mem_used ? 5 : 0); width_left -= (gpu.supported_functions.mem_total ? 6 : 0); width_left -= (gpu.supported_functions.pwr_usage ? 6 : 0); if (gpu.supported_functions.gpu_utilization) { gpu_meters[i] = Draw::Meter{width_left, "cpu" }; } } } #endif int cpu_meter_width = b_width - (show_temps ? 23 - (b_column_size <= 1 and b_columns == 1 ? 6 : 0) : 11); if (show_watts) { cpu_meter_width -= 6; } cpu_meter = Draw::Meter{cpu_meter_width, "cpu"}; if (mid_line) { out += Mv::to(y + graph_up_height + 1, x) + Fx::ub + Theme::c("cpu_box") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * (width - b_width - 2) + Symbols::div_right + Mv::to(y + graph_up_height + 1, x + ((width - b_width) / 2) - ((graph_up_field.size() + graph_lo_field.size()) / 2) - 4) + Theme::c("main_fg") + graph_up_field + Mv::r(1) + "▲▼" + Mv::r(1) + graph_lo_field; } if (b_column_size > 0 or extra_width > 0) { core_graphs.clear(); for (const auto& core_data : cpu.core_percent) { core_graphs.emplace_back(5 * b_column_size + extra_width, 1, "cpu", core_data, graph_symbol); } } if (show_temps) { temp_graphs.clear(); temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, 0), graph_symbol, false, false, cpu.temp_max, -23); if (not hide_cores and b_column_size > 1) { for (const auto& i : iota((size_t)1, cpu.temp.size())) { temp_graphs.emplace_back(5, 1, "temp", safeVal(cpu.temp, i), graph_symbol, false, false, cpu.temp_max, -23); } } } } //? Draw battery if enabled and present if (Config::getB("show_battery") and has_battery) { static int old_percent{}; // defaults to = 0 static long old_seconds{}; // defaults to = 0 static float old_watts{}; // defaults to = 0 static string old_status; static Draw::Meter bat_meter {10, "cpu", true}; static const std::unordered_map bat_symbols = { {"charging", "▲"}, {"discharging", "▼"}, {"full", "■"}, {"unknown", "○"} }; const auto& [percent, watts, seconds, status] = current_bat; if (redraw or percent != old_percent or (watts != old_watts and Config::getB("show_battery_watts")) or seconds != old_seconds or status != old_status) { old_percent = percent; old_watts = watts; old_seconds = seconds; old_status = status; const string str_time = (seconds > 0 ? sec_to_dhms(seconds, false, true) : ""); const string str_percent = to_string(percent) + '%'; const string str_watts = (watts != -1 and Config::getB("show_battery_watts") ? fmt::format("{:.2f}", watts) + 'W' : ""); const auto& bat_symbol = bat_symbols.at((bat_symbols.contains(status) ? status : "unknown")); const int current_len = (Term::width >= 100 ? 11 : 0) + str_time.size() + str_percent.size() + str_watts.size() + to_string(Config::getI("update_ms")).size(); const int current_pos = Term::width - current_len - 17; if ((bat_pos != current_pos or bat_len != current_len) and bat_pos > 0 and not redraw) out += Mv::to(y, bat_pos) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * (bat_len + 4); bat_pos = current_pos; bat_len = current_len; out += Mv::to(y, bat_pos) + title_left + Theme::c("title") + Fx::b + "BAT" + bat_symbol + ' ' + str_percent + (Term::width >= 100 ? Fx::ub + ' ' + bat_meter(percent) + Fx::b : "") + (not str_time.empty() ? ' ' + Theme::c("title") + str_time : "") + (not str_watts.empty() ? " " + Theme::c("title") + Fx::b + str_watts : "") + Fx::ub + title_right; } } else if (bat_pos > 0) { out += Mv::to(y, bat_pos) + Fx::ub + Theme::c("cpu_box") + Symbols::h_line * (bat_len + 4); bat_pos = bat_len = 0; } try { //? Cpu/Gpu graphs out += Fx::ub + Mv::to(y + 1, x + 1); auto draw_graphs = [&](vector& graphs, const int graph_height, const int graph_width, const string& graph_field) { #ifdef GPU_SUPPORT if (graph_field.starts_with("gpu")) if (graph_field.ends_with("totals")) { int gpu_drawn = 0; for (size_t i = 0; i < gpus.size(); i++) { if (gpu_auto and v_contains(Gpu::shown_panels, i)) { continue; } try { const auto& gpu_percent = gpus[i].gpu_percent; out += graphs[i](safeVal(gpu_percent, graph_field), (data_same or redraw)); } catch (std::out_of_range& /* unused */) { continue; } if (Gpu::count - (gpu_auto ? Gpu::shown : 0) > 1) { auto i_str = to_string(i); out += Mv::l(graph_width-1) + Mv::u(graph_height/2) + (graph_width > 5 ? "GPU" : "") + i_str + Mv::d(graph_height/2) + Mv::r(graph_width - 1 - (graph_width > 5)*3 - i_str.size()); } if (++gpu_drawn < Gpu::count - (gpu_auto ? Gpu::shown : 0)) out += Theme::c("div_line") + (Symbols::v_line + Mv::l(1) + Mv::u(1))*graph_height + Mv::r(1) + Mv::d(1); } } else out += graphs[0](safeVal(Gpu::shared_gpu_percent, graph_field), (data_same or redraw)); else #else (void)graph_height; (void)graph_width; #endif out += graphs[0](safeVal(cpu.cpu_percent, graph_field), (data_same or redraw)); }; draw_graphs(graphs_upper, graph_up_height, graph_up_width, graph_up_field); if (not single_graph) { out += Mv::to(y + graph_up_height + 1 + mid_line, x + 1); draw_graphs(graphs_lower, graph_low_height, graph_low_width, graph_lo_field); } //? Uptime if (Config::getB("show_uptime")) { string upstr = sec_to_dhms(system_uptime()); if (upstr.size() > 8) { upstr.resize(upstr.size() - 3); upstr = trans(upstr); } out += Mv::to(y + (single_graph or not Config::getB("cpu_invert_lower") ? 1 : height - 2), x + 2) + Theme::c("graph_text") + "up" + Mv::r(1) + upstr; } //? Cpu clock and cpu meter if (Config::getB("show_cpu_freq") and not cpuHz.empty()) out += Mv::to(b_y, b_x + b_width - 10) + Fx::ub + Theme::c("div_line") + Symbols::h_line * (7 - cpuHz.size()) + Symbols::title_left + Fx::b + Theme::c("title") + cpuHz + Fx::ub + Theme::c("div_line") + Symbols::title_right; out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "CPU " + cpu_meter(safeVal(cpu.cpu_percent, "total"s).back()) + Theme::g("cpu").at(clamp(safeVal(cpu.cpu_percent, "total"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(cpu.cpu_percent, "total"s).back()), 4) + Theme::c("main_fg") + '%'; if (show_temps) { const auto [temp, unit] = celsius_to(safeVal(cpu.temp, 0).back(), temp_scale); const auto temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, 0).back() * 100 / cpu.temp_max, 0ll, 100ll)); if ((b_column_size > 1 or b_columns > 1) and temp_graphs.size() >= 1ll) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_color + temp_graphs.at(0)(safeVal(cpu.temp, 0), data_same or redraw); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } if (show_watts) { string cwatts = fmt::format(" {:>4.{}f}", cpu.usage_watts, cpu.usage_watts < 10.0f ? 2 : cpu.usage_watts < 100.0f ? 1 : 0); string cwatts_post = "W"; max_observed_pwr = max(max_observed_pwr, cpu.usage_watts); out += Theme::g("cached").at(clamp(cpu.usage_watts / max_observed_pwr * 100.0f, 0.0f, 100.0f)) + cwatts + Theme::c("main_fg") + cwatts_post; } out += Theme::c("div_line") + Symbols::v_line; } catch (const std::exception& e) { throw std::runtime_error("graphs, clock, meter : " + string{e.what()}); } int max_row = b_height - 3; // Subtracting one extra row for the load average (and power if enabled) int n_gpus_to_show = 0; #ifdef GPU_SUPPORT n_gpus_to_show = show_gpu ? (gpus.size() - (gpu_always ? 0 : Gpu::shown)) : 0; #endif max_row -= n_gpus_to_show; //? Core text and graphs int cx = 0, cy = 1, cc = 0, core_width = (b_column_size == 0 ? 2 : 3); if (Shared::coreCount >= 100) core_width++; for (const auto& n : iota(0, Shared::coreCount)) { out += Mv::to(b_y + cy + 1, b_x + cx + 1) + Theme::c("main_fg") + (Shared::coreCount < 100 ? Fx::b + 'C' + Fx::ub : "") + ljust(to_string(n), core_width); if ((b_column_size > 0 or extra_width > 0) and cmp_less(n, core_graphs.size())) out += Theme::c("inactive_fg") + graph_bg * (5 * b_column_size + extra_width) + Mv::l(5 * b_column_size + extra_width) + core_graphs.at(n)(safeVal(cpu.core_percent, n), data_same or redraw); out += Theme::g("cpu").at(clamp(safeVal(cpu.core_percent, n).back(), 0ll, 100ll)); out += rjust(to_string(safeVal(cpu.core_percent, n).back()), (b_column_size < 2 ? 3 : 4)) + Theme::c("main_fg") + '%'; if (show_temps and not hide_cores) { const auto [temp, unit] = celsius_to(safeVal(cpu.temp, n+1).back(), temp_scale); const auto temp_color = Theme::g("temp").at(clamp(safeVal(cpu.temp, n+1).back() * 100 / cpu.temp_max, 0ll, 100ll)); if (b_column_size > 1 and std::cmp_greater_equal(temp_graphs.size(), n)) out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + temp_graphs.at(n+1)(safeVal(cpu.temp, n+1), data_same or redraw); out += temp_color + rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; if ((++cy > ceil((double)Shared::coreCount / b_columns) or cy == max_row) and n != Shared::coreCount - 1) { if (++cc >= b_columns) break; cy = 1; cx = (b_width / b_columns) * cc; } } //? Load average if (cy < b_height - 1 and cc <= b_columns) { cy = b_height - 2 - n_gpus_to_show; string load_avg_pre = "Load avg:"; string load_avg; for (const auto& val : cpu.load_avg) { load_avg += fmt::format(" {:.2f}", val); } int len = load_avg_pre.size() + load_avg.size(); out += Mv::to(b_y + cy, b_x + 1) + string(max(b_width - len - 2, 0), ' ') + Theme::c("main_fg") + Fx::b + load_avg_pre + Fx::ub + load_avg; } #ifdef GPU_SUPPORT //? Gpu brief info if (show_gpu) { for (unsigned long i = 0; i < gpus.size(); ++i) { if (gpu_auto and v_contains(Gpu::shown_panels, i)) continue; out += Mv::to(b_y + ++cy, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU"; if (gpus.size() > 1) out += rjust(to_string(i), 1 + (gpus.size() > 9)); if (gpus[i].supported_functions.gpu_utilization) { out += ' '; if (b_columns > 1) { out += gpu_meters[i](safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()) + Theme::g("cpu").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)); } out += rjust(to_string(safeVal(gpus[i].gpu_percent, "gpu-totals"s).back()), 3) + Theme::c("main_fg") + '%'; if (b_columns == 1) out += ' '; } if (gpus[i].supported_functions.mem_used and gpus[i].supported_functions.mem_total and b_columns > 1) { out += ' ' + Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + Theme::g("used").at(safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s).back()) + gpu_mem_graphs[i](safeVal(gpus[i].gpu_percent, "gpu-vram-totals"s), data_same or redraw); } if (gpus[i].supported_functions.mem_used) { out += Theme::c("main_fg") + rjust(floating_humanizer(gpus[i].mem_used, true), 5); } if (gpus[i].supported_functions.mem_total) { out += Theme::c("inactive_fg") + '/' + Theme::c("main_fg") + ljust(floating_humanizer(gpus[i].mem_total, true), 4); } if (show_temps and gpus[i].supported_functions.temp_info) { const auto [temp, unit] = celsius_to(gpus[i].temp.back(), temp_scale); out += ' '; if (b_columns > 1) out += Theme::c("inactive_fg") + graph_bg * 5 + Mv::l(5) + Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)) + gpu_temp_graphs[i](gpus[i].temp, data_same or redraw); else out += Theme::g("temp").at(clamp(gpus[i].temp.back() * 100 / gpus[i].temp_max, 0ll, 100ll)); out += rjust(to_string(temp), 3) + Theme::c("main_fg") + unit; } if (gpus[i].supported_functions.pwr_usage) { out += ' ' + Theme::g("cached").at(clamp(safeVal(gpus[i].gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + fmt::format("{:>4.{}f}", gpus[i].pwr_usage / 1000.0, gpus[i].pwr_usage < 10'000 ? 2 : gpus[i].pwr_usage < 100'000 ? 1 : 0) + Theme::c("main_fg") + 'W'; } if (cy > b_height - 1) break; } } #endif redraw = false; return out + Fx::reset; } } #ifdef GPU_SUPPORT namespace Gpu { int width_p = 100, height_p = 32; int min_width = 41, min_height = 11; int width = 41, height; vector x_vec = {}, y_vec = {}, b_height_vec = {}; int b_width; vector b_x_vec = {}, b_y_vec = {}; vector redraw = {}; int shown = 0; int count = 0; vector shown_panels = {}; int graph_up_height; vector graph_upper_vec = {}, graph_lower_vec = {}; vector temp_graph_vec = {}; vector mem_used_graph_vec = {}, mem_util_graph_vec = {}; vector gpu_meter_vec = {}; vector pwr_meter_vec = {}; vector enc_meter_vec = {}; vector box = {}; string draw(const gpu_info& gpu, unsigned long index, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; auto& b_x = b_x_vec[index]; auto& b_y = b_y_vec[index]; auto& x = x_vec[index]; auto& y = y_vec[index]; auto& graph_upper = graph_upper_vec[index]; auto& graph_lower = graph_lower_vec[index]; auto& temp_graph = temp_graph_vec[index]; auto& mem_used_graph = mem_used_graph_vec[index]; auto& mem_util_graph = mem_util_graph_vec[index]; auto& gpu_meter = gpu_meter_vec[index]; auto& pwr_meter = pwr_meter_vec[index]; auto& enc_meter = enc_meter_vec[index]; if (force_redraw) redraw[index] = true; bool show_temps = gpu.supported_functions.temp_info and (Config::getB("check_temp")); auto tty_mode = Config::getB("tty_mode"); auto& temp_scale = Config::getS("temp_scale"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_gpu")); auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto single_graph = !Config::getB("gpu_mirror_graph"); string out; out.reserve(width * height); //* Redraw elements not needed to be updated every cycle if (redraw[index]) { graph_up_height = single_graph ? height - 2 : ceil((double)(height - 2) / 2); const int graph_low_height = height - 2 - graph_up_height; out += box[index]; if (gpu.supported_functions.gpu_utilization) { graph_upper = Draw::Graph{x + width - b_width - 3, graph_up_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, false, true}; // TODO cpu -> gpu if (not single_graph) { graph_lower = Draw::Graph{ x + width - b_width - 3, graph_low_height, "cpu", safeVal(gpu.gpu_percent, "gpu-totals"s), graph_symbol, Config::getB("cpu_invert_lower"), true }; } gpu_meter = Draw::Meter{b_width - (show_temps ? 25 : 12), "cpu"}; } if (gpu.supported_functions.temp_info) temp_graph = Draw::Graph{6, 1, "temp", gpu.temp, graph_symbol, false, false, gpu.temp_max, -23}; if (gpu.supported_functions.pwr_usage) pwr_meter = Draw::Meter{b_width - (gpu.supported_functions.pwr_state and gpu.pwr_state != 32 ? 25 : 12), "cached"}; if (gpu.supported_functions.mem_utilization) mem_util_graph = Draw::Graph{b_width/2 - 1, 2, "free", gpu.mem_utilization_percent, graph_symbol, 0, 0, 100, 4}; // offset so the graph isn't empty at 0-5% utilization if (gpu.supported_functions.mem_used and gpu.supported_functions.mem_total) mem_used_graph = Draw::Graph{b_width/2 - 2, 2 + 2*(gpu.supported_functions.mem_utilization), "used", safeVal(gpu.gpu_percent, "gpu-vram-totals"s), graph_symbol}; if (gpu.supported_functions.encoder_utilization) enc_meter = Draw::Meter{b_width/2 - 10, "cpu"}; } //* General GPU info //? Gpu graph, meter & clock speed if (gpu.supported_functions.gpu_utilization) { out += Fx::ub + Mv::to(y + 1, x + 1) + graph_upper(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); if (not single_graph) out += Mv::to(y + graph_up_height + 1, x + 1) + graph_lower(safeVal(gpu.gpu_percent, "gpu-totals"s), (data_same or redraw[index])); out += Mv::to(b_y + 1, b_x + 1) + Theme::c("main_fg") + Fx::b + "GPU " + gpu_meter(safeVal(gpu.gpu_percent, "gpu-totals"s).back()) + Theme::g("cpu").at(clamp(safeVal(gpu.gpu_percent, "gpu-totals"s).back(), 0ll, 100ll)) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-totals"s).back()), 5) + Theme::c("main_fg") + '%'; //? Temperature graph, I assume the device supports utilization if it supports temperature if (show_temps) { const auto [temp, unit] = celsius_to(gpu.temp.back(), temp_scale); out += ' ' + Theme::c("inactive_fg") + graph_bg * 6 + Mv::l(6) + Theme::g("temp").at(clamp(gpu.temp.back() * 100 / gpu.temp_max, 0ll, 100ll)) + temp_graph(gpu.temp, data_same or redraw[index]); out += rjust(to_string(temp), 4) + Theme::c("main_fg") + unit; } out += Theme::c("div_line") + Symbols::v_line; } if (gpu.supported_functions.gpu_clock) { string clock_speed_string = to_string(gpu.gpu_clock_speed); out += Mv::to(b_y, b_x + b_width - 12) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " MHz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; } //? Power usage meter, power state if (gpu.supported_functions.pwr_usage) { out += Mv::to(b_y + 2, b_x + 1) + Theme::c("main_fg") + Fx::b + "PWR " + pwr_meter(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back()) + Theme::g("cached").at(clamp(safeVal(gpu.gpu_percent, "gpu-pwr-totals"s).back(), 0ll, 100ll)) + fmt::format("{:>5.{}f}", gpu.pwr_usage / 1000.0, gpu.pwr_usage < 10'000 ? 2 : gpu.pwr_usage < 100'000 ? 1 : 0) + Theme::c("main_fg") + 'W'; if (gpu.supported_functions.pwr_state and gpu.pwr_state != 32) // NVML_PSTATE_UNKNOWN; unsupported or non-nvidia card out += std::string(" P-state: ") + (gpu.pwr_state > 9 ? "" : " ") + 'P' + Theme::g("cached").at(clamp(gpu.pwr_state, 0ll, 100ll)) + to_string(gpu.pwr_state); } //? Encode and Decode meters bool drawnEncDec = gpu.supported_functions.encoder_utilization and gpu.supported_functions.decoder_utilization; if (drawnEncDec) { out += Mv::to(b_y + 3, b_x +1) + Theme::c("main_fg") + Fx::b + "ENC " + enc_meter(gpu.encoder_utilization) + Theme::g("cpu").at(clamp(gpu.encoder_utilization, 0ll, 100ll)) + rjust(to_string(gpu.encoder_utilization), 4) + Theme::c("main_fg") + '%' + Theme::c("div_line") + Symbols::v_line + Theme::c("main_fg") + Fx::b + "DEC " + enc_meter(gpu.decoder_utilization) + Theme::g("cpu").at(clamp(gpu.decoder_utilization, 0ll, 100ll)) + rjust(to_string(gpu.decoder_utilization), 4) + Theme::c("main_fg") + '%'; } if (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) { out += Mv::to(b_y + (drawnEncDec ? 4 : 3), b_x); if (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) { string used_memory_string = floating_humanizer(gpu.mem_used); auto offset = (gpu.supported_functions.mem_total or gpu.supported_functions.mem_used) * (1 + 2*(gpu.supported_functions.mem_total and gpu.supported_functions.mem_used) + 2*gpu.supported_functions.mem_utilization); //? Used graph, memory section header, total vram out += Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Fx::b + Theme::c("title") + "vram" + Theme::c("div_line") + Fx::ub + Symbols::title_right + Symbols::h_line*(b_width/2-8) + Symbols::div_up + Mv::d(offset)+Mv::l(1) + Symbols::div_down + Mv::l(1)+Mv::u(1) + (Symbols::v_line + Mv::l(1)+Mv::u(1))*(offset-1) + Symbols::div_up + Symbols::h_line + Theme::c("title") + "Used:" + Theme::c("div_line") + Symbols::h_line*(b_width/2+b_width%2-9-used_memory_string.size()) + Theme::c("title") + used_memory_string + Theme::c("div_line") + Symbols::h_line + Symbols::div_right + Mv::d(1) + Mv::l(b_width/2-1) + mem_used_graph(safeVal(gpu.gpu_percent, "gpu-vram-totals"s), (data_same or redraw[index])) + Mv::l(b_width-3) + Mv::u(1+2*gpu.supported_functions.mem_utilization) + Theme::c("main_fg") + Fx::b + "Total:" + rjust(floating_humanizer(gpu.mem_total), b_width/2-9) + Fx::ub + Mv::r(3) + rjust(to_string(safeVal(gpu.gpu_percent, "gpu-vram-totals"s).back()), 3) + '%'; //? Memory utilization if (gpu.supported_functions.mem_utilization) out += Mv::l(b_width/2+6) + Mv::d(1) + Theme::c("div_line") + Symbols::div_left+Symbols::h_line + Theme::c("title") + "Utilization:" + Theme::c("div_line") + Symbols::h_line*(b_width/2-14) + Symbols::div_right + Mv::l(b_width/2) + Mv::d(1) + mem_util_graph(gpu.mem_utilization_percent, (data_same or redraw[index])) + Mv::l(b_width/2-1) + Mv::u(1) + rjust(to_string(gpu.mem_utilization_percent.back()), 3) + '%'; //? Memory clock speed if (gpu.supported_functions.mem_clock) { string clock_speed_string = to_string(gpu.mem_clock_speed); out += Mv::to(b_y + (drawnEncDec ? 4 : 3), b_x + b_width/2 - 11) + Theme::c("div_line") + Symbols::h_line*(5-clock_speed_string.size()) + Symbols::title_left + Fx::b + Theme::c("title") + clock_speed_string + " MHz" + Fx::ub + Theme::c("div_line") + Symbols::title_right; } } else { out += Theme::c("main_fg") + Mv::r(1); if (gpu.supported_functions.mem_total) out += "VRAM total:" + rjust(floating_humanizer(gpu.mem_total), b_width/(1 + gpu.supported_functions.mem_clock)-14); else out += "VRAM usage:" + rjust(floating_humanizer(gpu.mem_used), b_width/(1 + gpu.supported_functions.mem_clock)-14); if (gpu.supported_functions.mem_clock) out += " VRAM clock:" + rjust(to_string(gpu.mem_clock_speed) + " MHz", b_width/2-13); } } //? Processes section header //out += Mv::to(b_y+8, b_x) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line + Symbols::title_left + Theme::c("main_fg") + Fx::b + "gpu-proc" + Fx::ub + Theme::c("div_line") // + Symbols::title_right + Symbols::h_line*(b_width/2-12) + Symbols::div_down + Symbols::h_line*(b_width/2-2) + Symbols::div_right; //? PCIe link throughput if (gpu.supported_functions.pcie_txrx and Config::getB("nvml_measure_pcie_speeds")) { string tx_string = floating_humanizer(gpu.pcie_tx, 0, 1, 0, 1); string rx_string = floating_humanizer(gpu.pcie_rx, 0, 1, 0, 1); out += Mv::to(b_y + b_height_vec[index] - 1, b_x+2) + Theme::c("div_line") + Symbols::title_left_down + Theme::c("title") + Fx::b + "TX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2-9-tx_string.size()) + Symbols::title_left_down + Theme::c("title") + Fx::b + tx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + (gpu.supported_functions.mem_total and gpu.supported_functions.mem_used ? Symbols::div_down : Symbols::h_line) + Symbols::title_left_down + Theme::c("title") + Fx::b + "RX:" + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::h_line*(b_width/2+b_width%2-9-rx_string.size()) + Symbols::title_left_down + Theme::c("title") + Fx::b + rx_string + Fx::ub + Theme::c("div_line") + Symbols::title_right_down + Symbols::round_right_down; } redraw[index] = false; return out + Fx::reset; } } #endif namespace Mem { int width_p = 45, height_p = 36; int min_width = 36, min_height = 10; int x = 1, y, width = 20, height; int mem_width, disks_width, divider, item_height, mem_size, mem_meter, graph_height, disk_meter; int disks_io_h = 0; int disks_io_half = 0; bool shown = true, redraw = true; string box; std::unordered_map mem_meters; std::unordered_map mem_graphs; std::unordered_map disk_meters_used; std::unordered_map disk_meters_free; std::unordered_map io_graphs; string draw(const mem_info& mem, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; if (force_redraw) redraw = true; auto show_swap = Config::getB("show_swap"); auto swap_disk = Config::getB("swap_disk"); auto show_disks = Config::getB("show_disks"); auto show_io_stat = Config::getB("show_io_stat"); auto io_mode = Config::getB("io_mode"); auto io_graph_combined = Config::getB("io_graph_combined"); auto use_graphs = Config::getB("mem_graphs"); auto tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_mem")); auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto totalMem = Mem::get_totalMem(); string out; out.reserve(height * width); //* Redraw elements not needed to be updated every cycle if (redraw) { out += box; mem_meters.clear(); mem_graphs.clear(); disk_meters_free.clear(); disk_meters_used.clear(); io_graphs.clear(); //? Mem graphs and meters for (const auto& name : mem_names) { if (use_graphs) mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name, safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name}; } if (show_swap and has_swap) { for (const auto& name : swap_names) { if (use_graphs) mem_graphs[name] = Draw::Graph{mem_meter, graph_height, name.substr(5), safeVal(mem.percent, name), graph_symbol}; else mem_meters[name] = Draw::Meter{mem_meter, name.substr(5)}; } } //? Disk meters and io graphs if (show_disks) { if (show_io_stat or io_mode) { std::unordered_map custom_speeds; int half_height = 0; if (io_mode) { disks_io_h = max((int)floor((double)(height - 2 - (disk_ios * 2)) / max(1, disk_ios)), (io_graph_combined ? 1 : 2)); half_height = ceil((double)disks_io_h / 2); if (not Config::getS("io_graph_speeds").empty()) { auto split = ssplit(Config::getS("io_graph_speeds")); for (const auto& entry : split) { auto vals = ssplit(entry, ':'); if (vals.size() == 2 and mem.disks.contains(vals.at(0)) and isint(vals.at(1))) try { custom_speeds[vals.at(0)] = std::stoi(vals.at(1)); } catch (const std::out_of_range&) { continue; } } } } for (const auto& [name, disk] : mem.disks) { if (disk.io_read.empty()) continue; io_graphs[name + "_activity"] = Draw::Graph{disks_width - 6, 1, "available", disk.io_activity, graph_symbol}; if (io_mode) { //? Create one combined graph for IO read/write if enabled long long speed = (custom_speeds.contains(name) ? custom_speeds.at(name) : 100) << 20; if (io_graph_combined) { deque combined(disk.io_read.size(), 0); rng::transform(disk.io_read, disk.io_write, combined.begin(), std::plus()); io_graphs[name] = Draw::Graph{ disks_width, disks_io_h, "available", combined, graph_symbol, false, true, speed}; } else { io_graphs[name + "_read"] = Draw::Graph{ disks_width, half_height, "free", disk.io_read, graph_symbol, false, true, speed}; io_graphs[name + "_write"] = Draw::Graph{ disks_width, disks_io_h - half_height, "used", disk.io_write, graph_symbol, true, true, speed}; } } } } for (int i = 0; const auto& [name, ignored] : mem.disks) { if (i * 2 > height - 2) break; disk_meters_used[name] = Draw::Meter{disk_meter, "used"}; if (cmp_less_equal(mem.disks.size() * 3, height - 1)) disk_meters_free[name] = Draw::Meter{disk_meter, "free"}; } out += Mv::to(y, x + width - 6) + Fx::ub + Theme::c("mem_box") + Symbols::title_left + (io_mode ? Fx::b : "") + Theme::c("hi_fg") + 'i' + Theme::c("title") + 'o' + Fx::ub + Theme::c("mem_box") + Symbols::title_right; Input::mouse_mappings["i"] = {y, x + width - 5, 1, 2}; } } //? Mem and swap int cx = 1, cy = 1; string divider = (graph_height > 0 ? Mv::l(2) + Theme::c("mem_box") + Symbols::div_left + Theme::c("div_line") + Symbols::h_line * (mem_width - 1) + (show_disks ? "" : Theme::c("mem_box")) + Symbols::div_right + Mv::l(mem_width - 1) + Theme::c("main_fg") : ""); string up = (graph_height >= 2 ? Mv::l(mem_width - 2) + Mv::u(graph_height - 1) : ""); bool big_mem = mem_width > 21; out += Mv::to(y + 1, x + 2) + Theme::c("title") + Fx::b + "Total:" + rjust(floating_humanizer(totalMem), mem_width - 9) + Fx::ub + Theme::c("main_fg"); vector comb_names (mem_names.begin(), mem_names.end()); if (show_swap and has_swap and not swap_disk) comb_names.insert(comb_names.end(), swap_names.begin(), swap_names.end()); for (const auto& name : comb_names) { if (cy > height - 4) break; string title; if (name == "swap_used") { if (cy > height - 5) break; if (height - cy > 6) { if (graph_height > 0) out += Mv::to(y+1+cy, x+1+cx) + divider; cy += 1; } out += Mv::to(y+1+cy, x+1+cx) + Theme::c("title") + Fx::b + "Swap:" + rjust(floating_humanizer(safeVal(mem.stats, "swap_total"s)), mem_width - 8) + Theme::c("main_fg") + Fx::ub; cy += 1; title = "Used"; } else if (name == "swap_free") title = "Free"; if (title.empty()) title = capitalize(name); const string humanized = floating_humanizer(safeVal(mem.stats, name)); const int offset = max(0, divider.empty() ? 9 - (int)humanized.size() : 0); const string graphics = ( use_graphs and mem_graphs.contains(name) ? mem_graphs.at(name)(safeVal(mem.percent, name), redraw or data_same) : mem_meters.contains(name) ? mem_meters.at(name)(safeVal(mem.percent, name).back()) : ""); if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + title.substr(0, big_mem ? 10 : 5) + ":" + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + (divider.empty() ? Mv::l(offset) + string(" ") * offset + humanized : trans(humanized)) + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(safeVal(mem.percent, name).back()) + "%", 4); cy += (graph_height == 0 ? 2 : graph_height + 1); } else { out += Mv::to(y+1+cy, x+1+cx) + ljust(title, (mem_size > 1 ? 5 : 1)) + (graph_height >= 2 ? "" : " ") + graphics + Theme::c("title") + rjust(humanized, (mem_size > 1 ? 9 : 7)); cy += (graph_height == 0 ? 1 : graph_height); } } if (graph_height > 0 and cy < height - 2) out += Mv::to(y+1+cy, x+1+cx) + divider; //? Disks if (show_disks) { const auto& disks = mem.disks; cx = mem_width; cy = 0; bool big_disk = disks_width >= 25; divider = Mv::l(1) + Theme::c("div_line") + Symbols::div_left + Symbols::h_line * disks_width + Theme::c("mem_box") + Fx::ub + Symbols::div_right + Mv::l(disks_width); const string hu_div = Theme::c("div_line") + Symbols::h_line + Theme::c("main_fg"); if (io_mode) { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; const auto disk = safeVal(disks, mount); if (disk.io_read.empty()) continue; const string total = floating_humanizer(disk.total, not big_disk); out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - total.size()) + trans(total) + Fx::ub; if (big_disk) { const string used_percent = to_string(disk.used_percent); out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)used_percent.size() / 2) - 1) + hu_div + used_percent + '%' + hu_div; } if (io_graphs.contains(mount + "_activity")) { out += Mv::to(y+2+cy++, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); } if (++cy > height - 3) break; if (io_graph_combined) { if (not io_graphs.contains(mount)) continue; auto comb_val = disk.io_read.back() + disk.io_write.back(); const string humanized = (disk.io_write.back() > 0 ? "▼"s : ""s) + (disk.io_read.back() > 0 ? "▲"s : ""s) + (comb_val > 0 ? Mv::r(1) + floating_humanizer(comb_val, true) : "RW"); if (disks_io_h == 1) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' '); out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount)({comb_val}, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + Theme::c("main_fg") + humanized; cy += disks_io_h; } else { if (not io_graphs.contains(mount + "_read") or not io_graphs.contains(mount + "_write")) continue; const string human_read = (disk.io_read.back() > 0 ? "▲" + floating_humanizer(disk.io_read.back(), true) : "R"); const string human_write = (disk.io_write.back() > 0 ? "▼" + floating_humanizer(disk.io_write.back(), true) : "W"); if (disks_io_h <= 3) out += Mv::to(y+1+cy, x+1+cx) + string(5, ' ') + Mv::to(y+cy + disks_io_h, x+1+cx) + string(5, ' '); out += Mv::to(y+1+cy, x+1+cx) + io_graphs.at(mount + "_read")(disk.io_read, redraw or data_same) + Mv::l(disks_width) + Mv::d(1) + io_graphs.at(mount + "_write")(disk.io_write, redraw or data_same) + Mv::to(y+1+cy, x+1+cx) + human_read + Mv::to(y+cy + disks_io_h, x+1+cx) + human_write; cy += disks_io_h; } } } else { for (const auto& mount : mem.disks_order) { if (not disks.contains(mount)) continue; if (cy > height - 3) break; const auto disk = safeVal(disks, mount); if (disk.name.empty() or not disk_meters_used.contains(mount)) continue; auto comb_val = (not disk.io_read.empty() ? disk.io_read.back() + disk.io_write.back() : 0ll); const string human_io = (comb_val > 0 ? (disk.io_write.back() > 0 and big_disk ? "▼"s : ""s) + (disk.io_read.back() > 0 and big_disk ? "▲"s : ""s) + floating_humanizer(comb_val, true) : ""); const string human_total = floating_humanizer(disk.total, not big_disk); const string human_used = floating_humanizer(disk.used, not big_disk); const string human_free = floating_humanizer(disk.free, not big_disk); out += Mv::to(y+1+cy, x+1+cx) + divider + Theme::c("title") + Fx::b + uresize(disk.name, disks_width - 8) + Mv::to(y+1+cy, x+cx + disks_width - human_total.size()) + trans(human_total) + Fx::ub + Theme::c("main_fg"); if (big_disk and not human_io.empty()) out += Mv::to(y+1+cy, x+1+cx + round((double)disks_width / 2) - round((double)human_io.size() / 2) - 1) + hu_div + human_io + hu_div; if (++cy > height - 3) break; if (show_io_stat and io_graphs.contains(mount + "_activity")) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " IO% " : " IO " + Mv::l(2)) + Theme::c("inactive_fg") + graph_bg * (disks_width - 6) + Theme::g("available").at(clamp(disk.io_activity.back(), 50ll, 100ll)) + Mv::l(disks_width - 6) + io_graphs.at(mount + "_activity")(disk.io_activity, redraw or data_same) + Theme::c("main_fg"); if (not big_disk) out += Mv::to(y+1+cy, x+cx+1) + Theme::c("main_fg") + human_io; if (++cy > height - 3) break; } out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Used:" + rjust(to_string(disk.used_percent) + '%', 4) : "U") + ' ' + disk_meters_used.at(mount)(disk.used_percent) + rjust(human_used, (big_disk ? 9 : 5)); if (++cy > height - 3) break; if (disk_meters_free.contains(mount) and cmp_less_equal(disks.size() * 3 + (show_io_stat ? disk_ios : 0), height - 1)) { out += Mv::to(y+1+cy, x+1+cx) + (big_disk ? " Free:" + rjust(to_string(disk.free_percent) + '%', 4) : "F") + ' ' + disk_meters_free.at(mount)(disk.free_percent) + rjust(human_free, (big_disk ? 9 : 5)); cy++; if (cmp_less_equal(disks.size() * 4 + (show_io_stat ? disk_ios : 0), height - 1)) cy++; } } } if (cy < height - 2) out += Mv::to(y+1+cy, x+1+cx) + divider; } redraw = false; return out + Fx::reset; } } namespace Net { int width_p = 45, height_p = 32; int min_width = 36, min_height = 6; int x = 1, y, width = 20, height; int b_x, b_y, b_width, b_height, d_graph_height, u_graph_height; bool shown = true, redraw = true; const int MAX_IFNAMSIZ = 15; string old_ip; std::unordered_map graphs; string box; string draw(const net_info& net, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; if (force_redraw) redraw = true; auto net_sync = Config::getB("net_sync"); auto net_auto = Config::getB("net_auto"); auto tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_net")); string ip_addr = (net.ipv4.empty() ? net.ipv6 : net.ipv4); if (old_ip != ip_addr) { old_ip = ip_addr; redraw = true; } string out; out.reserve(width * height); const string title_left = Theme::c("net_box") + Fx::ub + Symbols::title_left; const string title_right = Theme::c("net_box") + Fx::ub + Symbols::title_right; const int i_size = min((int)selected_iface.size(), MAX_IFNAMSIZ); const long long down_max = (net_auto ? safeVal(graph_max, "download"s) : ((long long)(Config::getI("net_download")) << 20) / 8); const long long up_max = (net_auto ? safeVal(graph_max, "upload"s) : ((long long)(Config::getI("net_upload")) << 20) / 8); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; //? Graphs graphs.clear(); if (safeVal(net.bandwidth, "download"s).empty() or safeVal(net.bandwidth, "upload"s).empty()) return out + Fx::reset; graphs["download"] = Draw::Graph{ width - b_width - 2, u_graph_height, "download", net.bandwidth.at("download"), graph_symbol, false, true, down_max}; graphs["upload"] = Draw::Graph{ width - b_width - 2, d_graph_height, "upload", net.bandwidth.at("upload"), graph_symbol, true, true, up_max}; //? Interface selector and buttons out += Mv::to(y, x+width - i_size - 9) + title_left + Fx::b + Theme::c("hi_fg") + Symbols::left + "b " + Theme::c("title") + uresize(selected_iface, MAX_IFNAMSIZ) + Theme::c("hi_fg") + " n" + Symbols::right + title_right + Mv::to(y, x+width - i_size - 15) + title_left + Theme::c("hi_fg") + (safeVal(net.stat, "download"s).offset + safeVal(net.stat, "upload"s).offset > 0 ? Fx::b : "") + 'z' + Theme::c("title") + "ero" + title_right; Input::mouse_mappings["b"] = {y, x+width - i_size - 8, 1, 3}; Input::mouse_mappings["n"] = {y, x+width - 6, 1, 3}; Input::mouse_mappings["z"] = {y, x+width - i_size - 14, 1, 4}; if (width - i_size - 20 > 6) { out += Mv::to(y, x+width - i_size - 21) + title_left + Theme::c("hi_fg") + (net_auto ? Fx::b : "") + 'a' + Theme::c("title") + "uto" + title_right; Input::mouse_mappings["a"] = {y, x+width - i_size - 20, 1, 4}; } if (width - i_size - 20 > 13) { out += Mv::to(y, x+width - i_size - 27) + title_left + Theme::c("title") + (net_sync ? Fx::b : "") + 's' + Theme::c("hi_fg") + 'y' + Theme::c("title") + "nc" + title_right; Input::mouse_mappings["y"] = {y, x+width - i_size - 26, 1, 4}; } } //? IP or device address if (not ip_addr.empty() and cmp_greater(width - i_size - 36, ip_addr.size())) { out += Mv::to(y, x + 8) + title_left + Theme::c("title") + Fx::b + ip_addr + title_right; } //? Graphs and stats int cy = 0; for (const string dir : {"download", "upload"}) { out += Mv::to(y+1 + (dir == "upload" ? u_graph_height : 0), x + 1) + graphs.at(dir)(safeVal(net.bandwidth, dir), redraw or data_same or not net.connected) + Mv::to(y+1 + (dir == "upload" ? height - 3: 0), x + 1) + Fx::ub + Theme::c("graph_text") + floating_humanizer((dir == "upload" ? up_max : down_max), true); const string speed = floating_humanizer(safeVal(net.stat, dir).speed, false, 0, false, true); const string speed_bits = (b_width >= 20 ? floating_humanizer(safeVal(net.stat, dir).speed, false, 0, true, true) : ""); const string top = floating_humanizer(safeVal(net.stat, dir).top, false, 0, true, true); const string total = floating_humanizer(safeVal(net.stat, dir).total); const string symbol = (dir == "upload" ? "▲" : "▼"); out += Mv::to(b_y+1+cy, b_x+1) + Fx::ub + Theme::c("main_fg") + symbol + ' ' + ljust(speed, 10) + (b_width >= 20 ? rjust('(' + speed_bits + ')', 13) : ""); cy += (b_height == 5 ? 2 : 1); if (b_height >= 8) { out += Mv::to(b_y+1+cy, b_x+1) + symbol + ' ' + "Top: " + rjust('(' + top, (b_width >= 20 ? 17 : 9)) + ')'; cy++; } if (b_height >= 6) { out += Mv::to(b_y+1+cy, b_x+1) + symbol + ' ' + "Total: " + rjust(total, (b_width >= 20 ? 16 : 8)); cy += (b_height > 6 and b_height % 2 ? 2 : 1); } } redraw = false; return out + Fx::reset; } } namespace Proc { int width_p = 55, height_p = 68; int min_width = 44, min_height = 16; int x, y, width = 20, height; int start, selected, select_max; bool shown = true, redraw = true; int selected_pid = 0, selected_depth = 0; string selected_name; std::unordered_map p_graphs; std::unordered_map p_wide_cmd; std::unordered_map p_counters; int counter = 0; Draw::TextEdit filter; Draw::Graph detailed_cpu_graph; Draw::Graph detailed_mem_graph; int user_size, thread_size, prog_size, cmd_size, tree_size; int dgraph_x, dgraph_width, d_width, d_x, d_y; string box; int selection(const std::string_view cmd_key) { auto start = Config::getI("proc_start"); auto selected = Config::getI("proc_selected"); auto last_selected = Config::getI("proc_last_selected"); const int select_max = (Config::getB("show_detailed") ? Proc::select_max - 8 : Proc::select_max); auto vim_keys = Config::getB("vim_keys"); int numpids = Proc::numpids; if ((cmd_key == "up" or (vim_keys and cmd_key == "k")) and selected > 0) { if (start > 0 and selected == 1) start--; else selected--; if (Config::getI("proc_last_selected") > 0) Config::set("proc_last_selected", 0); } else if (cmd_key == "mouse_scroll_up" and start > 0) { start = max(0, start - 3); } else if (cmd_key == "mouse_scroll_down" and start < numpids - select_max) { start = min(numpids - select_max, start + 3); } else if (cmd_key == "down" or (vim_keys and cmd_key == "j")) { if (start < numpids - select_max and selected == select_max) start++; else if (selected == 0 and last_selected > 0) { selected = last_selected; Config::set("proc_last_selected", 0); } else selected++; } else if (cmd_key == "page_up") { if (selected > 0 and start == 0) selected = 0; else start = max(0, start - select_max); } else if (cmd_key == "page_down") { if (selected > 0 and start >= numpids - select_max) selected = select_max; else start = clamp(start + select_max, 0, max(0, numpids - select_max)); } else if (cmd_key == "home" or (vim_keys and cmd_key == "g")) { start = 0; if (selected > 0) selected = 1; } else if (cmd_key == "end" or (vim_keys and cmd_key == "G")) { start = max(0, numpids - select_max); if (selected > 0) selected = select_max; } else if (cmd_key.starts_with("mousey")) { int mouse_y = std::atoi(cmd_key.substr(6).data()); start = clamp((int)round((double)mouse_y * (numpids - select_max - 2) / (select_max - 2)), 0, max(0, numpids - select_max)); } bool changed = false; if (start != Config::getI("proc_start")) { Config::set("proc_start", start); changed = true; } if (selected != Config::getI("proc_selected")) { Config::set("proc_selected", selected); changed = true; } return (not changed ? -1 : selected); } string draw(const vector& plist, bool force_redraw, bool data_same) { if (Runner::stopping) return ""; auto proc_tree = Config::getB("proc_tree"); bool show_detailed = (Config::getB("show_detailed") and cmp_equal(Proc::detailed.last_pid, Config::getI("detailed_pid"))); bool proc_gradient = (Config::getB("proc_gradient") and not Config::getB("lowcolor") and Theme::gradients.contains("proc")); auto proc_colors = Config::getB("proc_colors"); auto tty_mode = Config::getB("tty_mode"); auto& graph_symbol = (tty_mode ? "tty" : Config::getS("graph_symbol_proc")); auto& graph_bg = Symbols::graph_symbols.at((graph_symbol == "default" ? Config::getS("graph_symbol") + "_up" : graph_symbol + "_up")).at(6); auto mem_bytes = Config::getB("proc_mem_bytes"); auto vim_keys = Config::getB("vim_keys"); auto show_graphs = Config::getB("proc_cpu_graphs"); start = Config::getI("proc_start"); selected = Config::getI("proc_selected"); const int y = show_detailed ? Proc::y + 8 : Proc::y; const int height = show_detailed ? Proc::height - 8 : Proc::height; const int select_max = show_detailed ? Proc::select_max - 8 : Proc::select_max; auto totalMem = Mem::get_totalMem(); int numpids = Proc::numpids; if (force_redraw) redraw = true; string out; out.reserve(width * height); //* Redraw elements not needed to be updated every cycle if (redraw) { out = box; const string title_left = Theme::c("proc_box") + Symbols::title_left; const string title_right = Theme::c("proc_box") + Symbols::title_right; const string title_left_down = Theme::c("proc_box") + Symbols::title_left_down; const string title_right_down = Theme::c("proc_box") + Symbols::title_right_down; for (const auto& key : {"T", "K", "S", "enter"}) if (Input::mouse_mappings.contains(key)) Input::mouse_mappings.erase(key); //? Adapt sizes of text fields user_size = (width < 75 ? 5 : 10); thread_size = (width < 75 ? - 1 : 4); prog_size = (width > 70 ? 16 : ( width > 55 ? 8 : width - user_size - thread_size - 33)); cmd_size = (width > 55 ? width - prog_size - user_size - thread_size - 33 : -1); tree_size = width - user_size - thread_size - 23; if (not show_graphs) { cmd_size += 5; tree_size += 5; } //? Detailed box if (show_detailed) { bool alive = detailed.status != "Dead"; dgraph_x = x; dgraph_width = max(width / 3, width - 121); d_width = width - dgraph_width - 1; d_x = x + dgraph_width + 1; d_y = Proc::y; //? Create cpu and mem graphs if process is alive if (alive) { detailed_cpu_graph = Draw::Graph{dgraph_width - 1, 7, "cpu", detailed.cpu_percent, graph_symbol, false, true}; detailed_mem_graph = Draw::Graph{d_width / 3, 1, "", detailed.mem_bytes, graph_symbol, false, false, detailed.first_mem}; } //? Draw structure of details box const string pid_str = to_string(detailed.entry.pid); out += Mv::to(y, x) + Theme::c("proc_box") + Symbols::div_left + Symbols::h_line + title_left + Theme::c("hi_fg") + Fx::b + (tty_mode ? "4" : Symbols::superscript.at(4)) + Theme::c("title") + "proc" + Fx::ub + title_right + Symbols::h_line * (width - 10) + Symbols::div_right + Mv::to(d_y, dgraph_x + 2) + title_left + Fx::b + Theme::c("title") + pid_str + Fx::ub + title_right + title_left + Fx::b + Theme::c("title") + uresize(detailed.entry.name, dgraph_width - pid_str.size() - 7, true) + Fx::ub + title_right; out += Mv::to(d_y, d_x - 1) + Theme::c("proc_box") + Symbols::div_up + Mv::to(y, d_x - 1) + Symbols::div_down + Theme::c("div_line"); for (const int& i : iota(1, 8)) out += Mv::to(d_y + i, d_x - 1) + Symbols::v_line; const string t_color = (not alive or selected > 0 ? Theme::c("inactive_fg") : Theme::c("title")); const string hi_color = (not alive or selected > 0 ? t_color : Theme::c("hi_fg")); const string hide = (selected > 0 ? t_color + "hide " : Theme::c("title") + "hide " + Theme::c("hi_fg")); int mouse_x = d_x + 2; out += Mv::to(d_y, d_x + 1); if (width > 55) { out += Fx::ub + title_left + hi_color + Fx::b + 't' + t_color + "erminate" + Fx::ub + title_right; if (alive and selected == 0) Input::mouse_mappings["t"] = {d_y, mouse_x, 1, 9}; mouse_x += 11; } out += title_left + hi_color + Fx::b + (vim_keys ? 'K' : 'k') + t_color + "ill" + Fx::ub + title_right + title_left + hi_color + Fx::b + 's' + t_color + "ignals" + Fx::ub + title_right + Mv::to(d_y, d_x + d_width - 10) + title_left + t_color + Fx::b + hide + Symbols::enter + Fx::ub + title_right; if (alive and selected == 0) { Input::mouse_mappings["k"] = {d_y, mouse_x, 1, 4}; mouse_x += 6; Input::mouse_mappings["s"] = {d_y, mouse_x, 1, 7}; } if (selected == 0) Input::mouse_mappings["enter"] = {d_y, d_x + d_width - 9, 1, 6}; //? Labels const int item_fit = floor((double)(d_width - 2) / 10); const int item_width = floor((double)(d_width - 2) / min(item_fit, 8)); out += Mv::to(d_y + 1, d_x + 1) + Fx::b + Theme::c("title") + cjust("Status:", item_width) + cjust("Elapsed:", item_width); if (item_fit >= 3) out += cjust("IO/R:", item_width); if (item_fit >= 4) out += cjust("IO/W:", item_width); if (item_fit >= 5) out += cjust("Parent:", item_width); if (item_fit >= 6) out += cjust("User:", item_width); if (item_fit >= 7) out += cjust("Threads:", item_width); if (item_fit >= 8) out += cjust("Nice:", item_width); //? Command line for (int i = 0; const auto& l : {'C', 'M', 'D'}) out += Mv::to(d_y + 5 + i++, d_x + 1) + l; out += Theme::c("main_fg") + Fx::ub; const auto san_cmd = replace_ascii_control(detailed.entry.cmd); const int cmd_size = ulen(san_cmd, true); for (int num_lines = min(3, (int)ceil((double)cmd_size / (d_width - 5))), i = 0; i < num_lines; i++) { out += Mv::to(d_y + 5 + (num_lines == 1 ? 1 : i), d_x + 3) + cjust(luresize(san_cmd, cmd_size - (d_width - 5) * i, true), d_width - 5, true, true); } } //? Filter auto filtering = Config::getB("proc_filtering"); // ? filter(20) : Config::getS("proc_filter")) const auto filter_text = (filtering) ? filter(max(6, width - 58)) : uresize(Config::getS("proc_filter"), max(6, width - 58)); out += Mv::to(y, x+9) + title_left + (not filter_text.empty() ? Fx::b : "") + Theme::c("hi_fg") + 'f' + Theme::c("title") + (not filter_text.empty() ? ' ' + filter_text : "ilter") + (not filtering and not filter_text.empty() ? Theme::c("hi_fg") + " del" : "") + (filtering ? Theme::c("hi_fg") + ' ' + Symbols::enter : "") + Fx::ub + title_right; if (not filtering) { int f_len = (filter_text.empty() ? 6 : ulen(filter_text) + 2); Input::mouse_mappings["f"] = {y, x + 10, 1, f_len}; if (filter_text.empty() and Input::mouse_mappings.contains("delete")) Input::mouse_mappings.erase("delete"); else if (not filter_text.empty()) Input::mouse_mappings["delete"] = {y, x + 11 + f_len, 1, 3}; } //? per-core, reverse, tree and sorting const auto& sorting = Config::getS("proc_sorting"); const int sort_len = sorting.size(); const int sort_pos = x + width - sort_len - 8; if (width > 55 + sort_len) { out += Mv::to(y, sort_pos - 25) + title_left + (Config::getB("proc_per_core") ? Fx::b : "") + Theme::c("title") + "per-" + Theme::c("hi_fg") + 'c' + Theme::c("title") + "ore" + Fx::ub + title_right; Input::mouse_mappings["c"] = {y, sort_pos - 24, 1, 8}; } if (width > 45 + sort_len) { out += Mv::to(y, sort_pos - 15) + title_left + (Config::getB("proc_reversed") ? Fx::b : "") + Theme::c("hi_fg") + 'r' + Theme::c("title") + "everse" + Fx::ub + title_right; Input::mouse_mappings["r"] = {y, sort_pos - 14, 1, 7}; } if (width > 35 + sort_len) { out += Mv::to(y, sort_pos - 6) + title_left + (Config::getB("proc_tree") ? Fx::b : "") + Theme::c("title") + "tre" + Theme::c("hi_fg") + 'e' + Fx::ub + title_right; Input::mouse_mappings["e"] = {y, sort_pos - 5, 1, 4}; } out += Mv::to(y, sort_pos) + title_left + Fx::b + Theme::c("hi_fg") + Symbols::left + " " + Theme::c("title") + sorting + " " + Theme::c("hi_fg") + Symbols::right + Fx::ub + title_right; Input::mouse_mappings["left"] = {y, sort_pos + 1, 1, 2}; Input::mouse_mappings["right"] = {y, sort_pos + sort_len + 3, 1, 2}; //? select, info and signal buttons const string down_button = (selected == select_max and start == numpids - select_max ? Theme::c("inactive_fg") : Theme::c("hi_fg")) + Symbols::down; const string t_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("title")); const string hi_color = (selected == 0 ? Theme::c("inactive_fg") : Theme::c("hi_fg")); int mouse_x = x + 14; out += Mv::to(y + height - 1, x + 1) + title_left_down + Fx::b + hi_color + Symbols::up + Theme::c("title") + " select " + down_button + Fx::ub + title_right_down + title_left_down + Fx::b + t_color + "info " + hi_color + Symbols::enter + Fx::ub + title_right_down; if (selected > 0) Input::mouse_mappings["enter"] = {y + height - 1, mouse_x, 1, 6}; mouse_x += 8; if (width > 60) { out += title_left_down + Fx::b + hi_color + 't' + t_color + "erminate" + Fx::ub + title_right_down; if (selected > 0) Input::mouse_mappings["t"] = {y + height - 1, mouse_x, 1, 9}; mouse_x += 11; } if (width > 55) { out += title_left_down + Fx::b + hi_color + (vim_keys ? 'K' : 'k') + t_color + "ill" + Fx::ub + title_right_down; if (selected > 0) Input::mouse_mappings["k"] = {y + height - 1, mouse_x, 1, 4}; mouse_x += 6; } out += title_left_down + Fx::b + hi_color + 's' + t_color + "ignals" + Fx::ub + title_right_down; if (selected > 0) Input::mouse_mappings["s"] = {y + height - 1, mouse_x, 1, 7}; //? Labels for fields in list if (not proc_tree) out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b + rjust("Pid:", 8) + ' ' + ljust("Program:", prog_size) + ' ' + (cmd_size > 0 ? ljust("Command:", cmd_size) : "") + ' '; else out += Mv::to(y+1, x+1) + Theme::c("title") + Fx::b + ljust("Tree:", tree_size) + ' '; out += (thread_size > 0 ? Mv::l(4) + "Threads: " : "") + ljust("User:", user_size) + ' ' + rjust((mem_bytes ? "MemB" : "Mem%"), 5) + ' ' + rjust("Cpu%", (show_graphs ? 10 : 5)) + Fx::ub; } //* End of redraw block //? Draw details box if shown if (show_detailed) { bool alive = detailed.status != "Dead"; const int item_fit = floor((double)(d_width - 2) / 10); const int item_width = floor((double)(d_width - 2) / min(item_fit, 8)); //? Graph part of box string cpu_str = (alive ? fmt::format("{:.2f}", detailed.entry.cpu_p) : ""); if (alive) { cpu_str.resize(4); if (cpu_str.ends_with('.')) { cpu_str.pop_back(); cpu_str.pop_back(); } } out += Mv::to(d_y + 1, dgraph_x + 1) + Fx::ub + detailed_cpu_graph(detailed.cpu_percent, (redraw or data_same or not alive)) + Mv::to(d_y + 1, dgraph_x + 1) + Theme::c("title") + Fx::b + rjust(cpu_str, 4) + "%"; for (int i = 0; const auto& l : {'C', 'P', 'U'}) out += Mv::to(d_y + 3 + i++, dgraph_x + 1) + l; //? Info part of box const string stat_color = (not alive ? Theme::c("inactive_fg") : (detailed.status == "Running" ? Theme::c("proc_misc") : Theme::c("main_fg"))); out += Mv::to(d_y + 2, d_x + 1) + stat_color + Fx::ub + cjust(detailed.status, item_width) + Theme::c("main_fg") + cjust(detailed.elapsed, item_width); if (item_fit >= 3) out += cjust(detailed.io_read, item_width); if (item_fit >= 4) out += cjust(detailed.io_write, item_width); if (item_fit >= 5) out += cjust(detailed.parent, item_width, true); if (item_fit >= 6) out += cjust(detailed.entry.user, item_width, true); if (item_fit >= 7) out += cjust(to_string(detailed.entry.threads), item_width); if (item_fit >= 8) out += cjust(to_string(detailed.entry.p_nice), item_width); const double mem_p = detailed.mem_bytes.back() * 100.0 / totalMem; string mem_str = fmt::format("{:.2f}", mem_p); mem_str.resize(4); if (mem_str.ends_with('.')) mem_str.pop_back(); out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + rjust(mem_str, 4) + "% ", (d_width / 3) - 2) + Theme::c("inactive_fg") + Fx::ub + graph_bg * (d_width / 3) + Mv::l(d_width / 3) + Theme::c("proc_misc") + detailed_mem_graph(detailed.mem_bytes, (redraw or data_same or not alive)) + ' ' + Theme::c("title") + Fx::b + detailed.memory; } //? Check bounds of current selection and view if (start > 0 and numpids <= select_max) start = 0; if (start > numpids - select_max) start = max(0, numpids - select_max); if (selected > select_max) selected = select_max; if (selected > numpids) selected = numpids; //* Iteration over processes int lc = 0; for (int n=0; auto& p : plist) { if (p.filtered or (proc_tree and p.tree_index == plist.size()) or n++ < start) continue; bool is_selected = (lc + 1 == selected); if (is_selected) { selected_pid = (int)p.pid; selected_name = p.name; selected_depth = p.depth; } //? Update graphs for processes with above 0.0% cpu usage, delete if below 0.1% 10x times bool has_graph = show_graphs ? p_counters.contains(p.pid) : false; if (show_graphs and ((p.cpu_p > 0 and not has_graph) or (not data_same and has_graph))) { if (not has_graph) { p_graphs[p.pid] = Draw::Graph{5, 1, "", {}, graph_symbol}; p_counters[p.pid] = 0; } else if (p.cpu_p < 0.1 and ++p_counters[p.pid] >= 10) { if (p_graphs.contains(p.pid)) p_graphs.erase(p.pid); p_counters.erase(p.pid); } else p_counters[p.pid] = 0; } out += Fx::reset; //? Set correct gradient colors if enabled string c_color, m_color, t_color, g_color, end; if (is_selected) { c_color = m_color = t_color = g_color = Fx::b; end = Fx::ub; out += Theme::c("selected_bg") + Theme::c("selected_fg") + Fx::b; } else { int calc = (selected > lc) ? selected - lc : lc - selected; if (proc_colors) { end = Theme::c("main_fg") + Fx::ub; array colors; for (int i = 0; int v : {(int)round(p.cpu_p), (int)round(p.mem * 100 / totalMem), (int)p.threads / 3}) { if (proc_gradient) { int val = (min(v, 100) + 100) - calc * 100 / select_max; if (val < 100) colors[i++] = Theme::g("proc_color").at(max(0, val)); else colors[i++] = Theme::g("process").at(clamp(val - 100, 0, 100)); } else colors[i++] = Theme::g("process").at(clamp(v, 0, 100)); } c_color = colors.at(0); m_color = colors.at(1); t_color = colors.at(2); } else { c_color = m_color = t_color = Fx::b; end = Fx::ub; } if (proc_gradient) { g_color = Theme::g("proc").at(clamp(calc * 100 / select_max, 0, 100)); } } const auto san_cmd = replace_ascii_control(p.cmd); if (not p_wide_cmd.contains(p.pid)) p_wide_cmd[p.pid] = ulen(san_cmd) != ulen(san_cmd, true); //? Normal view line if (not proc_tree) { out += Mv::to(y+2+lc, x+1) + g_color + rjust(to_string(p.pid), 8) + ' ' + c_color + ljust(p.name, prog_size, true) + ' ' + end + (cmd_size > 0 ? g_color + ljust(san_cmd, cmd_size, true, p_wide_cmd[p.pid]) + Mv::to(y+2+lc, x+11+prog_size+cmd_size) + ' ' : ""); } //? Tree view line else { const string prefix_pid = p.prefix + to_string(p.pid); int width_left = tree_size; out += Mv::to(y+2+lc, x+1) + g_color + uresize(prefix_pid, width_left) + ' '; width_left -= ulen(prefix_pid); if (width_left > 0) { out += c_color + uresize(p.name, width_left - 1) + end + ' '; width_left -= (ulen(p.name) + 1); } if (width_left > 7) { const string_view cmd = width_left > 40 ? rtrim(san_cmd) : p.short_cmd; if (not cmd.empty() and cmd != p.name) { out += g_color + '(' + uresize(string{cmd}, width_left - 3, p_wide_cmd[p.pid]) + ") "; width_left -= (ulen(string{cmd}, true) + 3); } } out += string(max(0, width_left), ' ') + Mv::to(y+2+lc, x+2+tree_size); } //? Common end of line string cpu_str = fmt::format("{:.2f}", p.cpu_p); if (p.cpu_p < 10 or (p.cpu_p >= 100 and p.cpu_p < 1000)) cpu_str.resize(3); else if (p.cpu_p >= 10'000) { cpu_str = fmt::format("{:.2f}", p.cpu_p / 1000); cpu_str.resize(3); if (cpu_str.ends_with('.')) cpu_str.pop_back(); cpu_str += "k"; } string mem_str = (mem_bytes ? floating_humanizer(p.mem, true) : ""); if (not mem_bytes) { double mem_p = clamp((double)p.mem * 100 / totalMem, 0.0, 100.0); mem_str = mem_p < 0.01 ? "0" : fmt::format("{:.1f}", mem_p); if (mem_str.size() > 3) mem_str.resize(3); if (mem_str.ends_with('.')) mem_str.pop_back(); mem_str += '%'; } // Shorten process thread representation when larger than 5 digits: 10000 -> 10K ... const std::string proc_threads_string = [&] { if (p.threads > 9999) { return std::to_string(p.threads / 1000) + 'K'; } else { return std::to_string(p.threads); } }(); out += (thread_size > 0 ? t_color + rjust(proc_threads_string, thread_size) + ' ' + end : "" ) + g_color + ljust((cmp_greater(p.user.size(), user_size) ? p.user.substr(0, user_size - 1) + '+' : p.user), user_size) + ' ' + m_color + rjust(mem_str, 5) + end + ' ' + (is_selected ? "" : Theme::c("inactive_fg")) + (show_graphs ? graph_bg * 5: "") + (p_graphs.contains(p.pid) ? Mv::l(5) + c_color + p_graphs.at(p.pid)({(p.cpu_p >= 0.1 and p.cpu_p < 5 ? 5ll : (long long)round(p.cpu_p))}, data_same) : "") + end + ' ' + c_color + rjust(cpu_str, 4) + " " + end; if (lc++ > height - 5) break; } out += Fx::reset; while (lc++ < height - 5) out += Mv::to(y+lc+1, x+1) + string(width - 2, ' '); //? Draw scrollbar if needed if (numpids > select_max) { const int scroll_pos = clamp((int)round((double)start * select_max / (numpids - select_max)), 0, height - 5); out += Mv::to(y + 1, x + width - 2) + Fx::b + Theme::c("main_fg") + Symbols::up + Mv::to(y + height - 2, x + width - 2) + Symbols::down; for (int i = y + 2; i < y + height - 2; i++) { out += Mv::to(i, x + width - 2) + ((i == y + 2 + scroll_pos) ? "█" : " "); } } //? Current selection and number of processes string location = to_string(start + selected) + '/' + to_string(numpids); string loc_clear = Symbols::h_line * max((size_t)0, 9 - location.size()); out += Mv::to(y + height - 1, x+width - 3 - max(9, (int)location.size())) + Fx::ub + Theme::c("proc_box") + loc_clear + Symbols::title_left_down + Theme::c("title") + Fx::b + location + Fx::ub + Theme::c("proc_box") + Symbols::title_right_down; //? Clear out left over graphs from dead processes at a regular interval if (not data_same and ++counter >= 100) { counter = 0; std::erase_if(p_graphs, [&](const auto& pair) { return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); }); std::erase_if(p_counters, [&](const auto& pair) { return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); }); std::erase_if(p_wide_cmd, [&](const auto& pair) { return rng::find(plist, pair.first, &proc_info::pid) == plist.end(); }); } if (selected == 0 and selected_pid != 0) { selected_pid = 0; selected_name.clear(); } redraw = false; return out + Fx::reset; } } namespace Draw { void calcSizes() { atomic_wait(Runner::active); Config::unlock(); auto boxes = Config::getS("shown_boxes"); auto cpu_bottom = Config::getB("cpu_bottom"); auto mem_below_net = Config::getB("mem_below_net"); auto proc_left = Config::getB("proc_left"); Cpu::box.clear(); Mem::box.clear(); Net::box.clear(); Proc::box.clear(); Global::clock.clear(); Global::overlay.clear(); Runner::pause_output = false; Runner::redraw = true; Proc::p_counters.clear(); Proc::p_graphs.clear(); if (Menu::active) Menu::redraw = true; Input::mouse_mappings.clear(); Cpu::x = Mem::x = Net::x = Proc::x = 1; Cpu::y = Mem::y = Net::y = Proc::y = 1; Cpu::width = Mem::width = Net::width = Proc::width = 0; Cpu::height = Mem::height = Net::height = Proc::height = 0; Cpu::redraw = Mem::redraw = Net::redraw = Proc::redraw = true; Cpu::shown = s_contains(boxes, "cpu"); #ifdef GPU_SUPPORT Gpu::box.clear(); Gpu::width = 0; Gpu::shown_panels.clear(); if (Gpu::count > 0) { std::istringstream iss(boxes, std::istringstream::in); string current; while (iss >> current) { if (current.starts_with("gpu")) Gpu::shown_panels.push_back(current.back()-'0'); } } Gpu::shown = Gpu::shown_panels.size(); #endif Mem::shown = s_contains(boxes, "mem"); Net::shown = s_contains(boxes, "net"); Proc::shown = s_contains(boxes, "proc"); //* Calculate and draw cpu box outlines if (Cpu::shown) { using namespace Cpu; #ifdef GPU_SUPPORT int gpus_extra_height = Config::getS("show_gpu_info") == "On" ? Gpu::count : Config::getS("show_gpu_info") == "Auto" ? Gpu::count - Gpu::shown : 0; #endif const bool show_temp = (Config::getB("check_temp") and got_sensors); width = round((double)Term::width * width_p / 100); #ifdef GPU_SUPPORT if (Gpu::shown != 0 and not (Mem::shown or Net::shown or Proc::shown)) { height = Term::height - Gpu::min_height*Gpu::shown - gpus_extra_height; } else { height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p/(Gpu::shown+1) + (Gpu::shown != 0)*5) / 100)); } if (height <= Term::height-gpus_extra_height) height += gpus_extra_height; #else height = max(8, (int)ceil((double)Term::height * (trim(boxes) == "cpu" ? 100 : height_p) / 100)); #endif x = 1; y = cpu_bottom ? Term::height - height + 1 : 1; #ifdef GPU_SUPPORT b_columns = max(2, (int)ceil((double)(Shared::coreCount + 1) / (height - gpus_extra_height - 5))); #else b_columns = max(1, (int)ceil((double)(Shared::coreCount + 1) / (height - 5))); #endif if (b_columns * (21 + 12 * show_temp) < width - (width / 3)) { b_column_size = 2; b_width = (21 + 12 * show_temp) * b_columns - (b_columns - 1); } else if (b_columns * (15 + 6 * show_temp) < width - (width / 3)) { b_column_size = 1; b_width = (15 + 6 * show_temp) * b_columns - (b_columns - 1); } else if (b_columns * (8 + 6 * show_temp) < width - (width / 3)) { b_column_size = 0; } else { b_columns = (width - width / 3) / (8 + 6 * show_temp); b_column_size = 0; } if (b_column_size == 0) b_width = (8 + 6 * show_temp) * b_columns + 1; #ifdef GPU_SUPPORT //gpus_extra_height = max(0, gpus_extra_height - 1); b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4 + gpus_extra_height); #else b_height = min(height - 2, (int)ceil((double)Shared::coreCount / b_columns) + 4); #endif b_x = x + width - b_width - 1; b_y = y + ceil((double)(height - 2) / 2) - ceil((double)b_height / 2) + 1; box = createBox(x, y, width, height, Theme::c("cpu_box"), true, (cpu_bottom ? "" : "cpu"), (cpu_bottom ? "cpu" : ""), 1); auto& custom = Config::getS("custom_cpu_name"); static const bool hasCpuHz = not Cpu::get_cpuHz().empty(); const string cpu_title = uresize( (custom.empty() ? Cpu::cpuName : custom), b_width - (Config::getB("show_cpu_freq") and hasCpuHz ? 14 : 4) ); box += createBox(b_x, b_y, b_width, b_height, "", false, cpu_title); } #ifdef GPU_SUPPORT //* Calculate and draw gpu box outlines if (Gpu::shown != 0) { using namespace Gpu; x_vec.resize(shown); y_vec.resize(shown); b_x_vec.resize(shown); b_y_vec.resize(shown); b_height_vec.resize(shown); box.resize(shown); graph_upper_vec.resize(shown); graph_lower_vec.resize(shown); temp_graph_vec.resize(shown); mem_used_graph_vec.resize(shown); mem_util_graph_vec.resize(shown); gpu_meter_vec.resize(shown); pwr_meter_vec.resize(shown); enc_meter_vec.resize(shown); redraw.resize(shown); for (auto i = 0; i < shown; ++i) { redraw[i] = true; width = Term::width; if (Cpu::shown) if (not (Mem::shown or Net::shown or Proc::shown)) height = min_height; else height = Cpu::height; else if (not (Mem::shown or Net::shown or Proc::shown)) height = Term::height/Gpu::shown + (i == 0)*(Term::height%Gpu::shown); else height = max(min_height, (int)ceil((double)Term::height * height_p/Gpu::shown / 100)); height += (height+Cpu::height == Term::height-1); x_vec[i] = 1; y_vec[i] = 1 + i*height + (not Config::getB("cpu_bottom"))*Cpu::shown*Cpu::height; box[i] = createBox(x_vec[i], y_vec[i], width, height, Theme::c("cpu_box"), true, std::string("gpu") + (char)(shown_panels[i]+'0'), "", (shown_panels[i]+5)%10); // TODO gpu_box b_height_vec[i] = 2 + gpu_b_height_offsets[shown_panels[i]]; b_width = clamp(width/2, min_width, 65); //? Main statistics box b_x_vec[i] = x_vec[i] + width - b_width - 1; b_y_vec[i] = y_vec[i] + ceil((double)(height - 2) / 2) - ceil((double)(b_height_vec[i]) / 2) + 1; string name = Config::getS(std::string("custom_gpu_name") + (char)(shown_panels[i]+'0')); if (name.empty()) name = gpu_names[shown_panels[i]]; box[i] += createBox(b_x_vec[i], b_y_vec[i], b_width, b_height_vec[i], "", false, name.substr(0, b_width-5)); } } #endif //* Calculate and draw mem box outlines if (Mem::shown) { using namespace Mem; auto show_disks = Config::getB("show_disks"); auto swap_disk = Config::getB("swap_disk"); auto mem_graphs = Config::getB("mem_graphs"); width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); #ifdef GPU_SUPPORT height = ceil((double)Term::height * (100 - Net::height_p * Net::shown*4 / ((Gpu::shown != 0 and Cpu::shown) + 4)) / 100) - Cpu::height - Gpu::height*Gpu::shown; #else height = ceil((double)Term::height * (100 - Cpu::height_p * Cpu::shown - Net::height_p * Net::shown) / 100) + 1; #endif x = (proc_left and Proc::shown) ? Term::width - width + 1: 1; if (mem_below_net and Net::shown) #ifdef GPU_SUPPORT y = Term::height - height + 1 - (cpu_bottom ? Cpu::height + Gpu::height*Gpu::shown : 0); else y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; #else y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); else y = cpu_bottom ? 1 : Cpu::height + 1; #endif if (show_disks) { mem_width = ceil((double)(width - 3) / 2); mem_width += mem_width % 2; disks_width = width - mem_width - 2; divider = x + mem_width; } else mem_width = width - 1; item_height = has_swap and not swap_disk ? 6 : 4; if (height - (has_swap and not swap_disk ? 3 : 2) > 2 * item_height) mem_size = 3; else if (mem_width > 25) mem_size = 2; else mem_size = 1; mem_meter = max(0, mem_width - (mem_size > 2 ? 7 : 17)); if (mem_size == 1) mem_meter += 6; if (mem_graphs) { graph_height = max(1, (int)round((double)((height - (has_swap and not swap_disk ? 2 : 1)) - (mem_size == 3 ? 2 : 1) * item_height) / item_height)); if (graph_height > 1) mem_meter += 6; } else graph_height = 0; if (show_disks) { disk_meter = max(-14, width - mem_width - 23); if (disks_width < 25) disk_meter += 14; } box = createBox(x, y, width, height, Theme::c("mem_box"), true, "mem", "", 2); box += Mv::to(y, (show_disks ? divider + 2 : x + width - 9)) + Theme::c("mem_box") + Symbols::title_left + (show_disks ? Fx::b : "") + Theme::c("hi_fg") + 'd' + Theme::c("title") + "isks" + Fx::ub + Theme::c("mem_box") + Symbols::title_right; Input::mouse_mappings["d"] = {y, (show_disks ? divider + 3 : x + width - 8), 1, 5}; if (show_disks) { box += Mv::to(y, divider) + Symbols::div_up + Mv::to(y + height - 1, divider) + Symbols::div_down + Theme::c("div_line"); for (auto i : iota(1, height - 1)) box += Mv::to(y + i, divider) + Symbols::v_line; } } //* Calculate and draw net box outlines if (Net::shown) { using namespace Net; width = round((double)Term::width * (Proc::shown ? width_p : 100) / 100); #ifdef GPU_SUPPORT height = Term::height - Cpu::height - Gpu::height*Gpu::shown - Mem::height; #else height = Term::height - Cpu::height - Mem::height; #endif x = (proc_left and Proc::shown) ? Term::width - width + 1 : 1; if (mem_below_net and Mem::shown) #ifdef GPU_SUPPORT y = cpu_bottom ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; #else y = cpu_bottom ? 1 : Cpu::height + 1; #endif else y = Term::height - height + 1 - (cpu_bottom ? Cpu::height : 0); b_width = (width > 45) ? 27 : 19; b_height = (height > 10) ? 9 : height - 2; b_x = x + width - b_width - 1; b_y = y + ((height - 2) / 2) - b_height / 2 + 1; d_graph_height = round((double)(height - 2) / 2); u_graph_height = height - 2 - d_graph_height; box = createBox(x, y, width, height, Theme::c("net_box"), true, "net", "", 3); box += createBox(b_x, b_y, b_width, b_height, "", false, "download", "upload"); } //* Calculate and draw proc box outlines if (Proc::shown) { using namespace Proc; width = Term::width - (Mem::shown ? Mem::width : (Net::shown ? Net::width : 0)); #ifdef GPU_SUPPORT height = Term::height - Cpu::height - Gpu::height*Gpu::shown; #else height = Term::height - Cpu::height; #endif x = proc_left ? 1 : Term::width - width + 1; #ifdef GPU_SUPPORT y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + Gpu::height*Gpu::shown + 1; #else y = (cpu_bottom and Cpu::shown) ? 1 : Cpu::height + 1; #endif select_max = height - 3; box = createBox(x, y, width, height, Theme::c("proc_box"), true, "proc", "", 4); } } }