From 88a2528ca3a2390f2c94c7f1a18ba982a5e5f2b2 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sat, 16 Oct 2021 19:34:10 +0200 Subject: [PATCH] Merge changes from main --- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- CHANGELOG.md | 26 +++ src/btop.cpp | 279 ++++++++++++--------------- src/btop_config.cpp | 15 +- src/btop_draw.cpp | 29 +-- src/btop_input.cpp | 16 +- src/btop_menu.cpp | 30 ++- src/btop_tools.cpp | 26 ++- src/btop_tools.hpp | 16 +- src/linux/btop_collect.cpp | 8 +- 10 files changed, 247 insertions(+), 202 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fd64412..de86eaf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -44,10 +44,12 @@ contents of `~/.config/btop/btop.log` If btop++ is crashing at start the following steps could be helpful: +(Extra helpful if compiled with `make OPTFLAGS="-O0 -g"`) + 1. run `gdb btop` 2. `r` to run, wait for crash and press enter -3. `bt` to get backtrace +3. `thread apply all bt` to get backtrace for all threads 4. Copy and paste the backtrace here: diff --git a/CHANGELOG.md b/CHANGELOG.md index c101021..69f1507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +## v1.0.16 + +* Fixed: atomic_wait() and atomic_lock{} use cpu pause instructions instead of thread sleep + +* Fixed: Swapped from atomic bool spinlocks to mutexes to fix rare deadlock + +* Added: Continuous Build workflow for OSX branch, by @ShrirajHegde + +* Changed: Reverted thread mutex lock to atomic bool with wait and timeout + +* Changed: Removed unnecessary async threads in Runner thread + +* Added: Try to restart secondary thread in case of stall and additional error checks for ifstream in Proc::collect() + +* Fixed: change [k]ill to [K]ill when enabling vim keys, by @jlopezcur + +## v1.0.15 + +* Fixed: Extra "root" partition when running in snap + +* Changed: Limit atomic_wait() to 1000ms to fix rare stall + +* Fixed: Removed unneeded lock in Runner::run() + +* Added: Toggle in options for enabling directional vim keys "h,j,k,l" + ## v1.0.14 * Changed: Total system memory is checked at every update instead of once at start diff --git a/src/btop.cpp b/src/btop.cpp index 793f40f..2d5e7a9 100644 --- a/src/btop.cpp +++ b/src/btop.cpp @@ -19,8 +19,6 @@ tab-size = 4 #include #include #include -#include -#include #include #include #include @@ -30,6 +28,7 @@ tab-size = 4 #include #include #include +#include #include #include @@ -40,7 +39,7 @@ tab-size = 4 #include using std::string, std::string_view, std::vector, std::atomic, std::endl, std::cout, std::min, std::flush, std::endl; -using std::string_literals::operator""s, std::to_string, std::future, std::async, std::bitset, std::future_status; +using std::string_literals::operator""s, std::to_string, std::mutex, std::lock_guard; namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; @@ -55,7 +54,7 @@ namespace Global { {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"}, {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"}, }; - const string Version = "1.0.14"; + const string Version = "1.0.16"; int coreCount; string overlay; @@ -79,7 +78,6 @@ namespace Global { uint64_t start_time; atomic resized (false); - atomic resizing (false); atomic quitting (false); atomic _runner_started (false); @@ -150,8 +148,8 @@ void argumentParser(const int& argc, char **argv) { //* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true void term_resize(bool force) { - if (Global::resizing) return; - atomic_lock lck(Global::resizing); + static mutex resizing; + lock_guard lock(resizing); if (auto refreshed = Term::refresh(true); refreshed or force) { if (force and refreshed) force = false; } @@ -200,8 +198,20 @@ void clean_quit(int sig) { if (Global::quitting) return; Global::quitting = true; Runner::stop(); - if (Global::_runner_started and pthread_join(Runner::runner_id, NULL) != 0) { - Logger::error("Failed to join _runner thread!"); + if (Global::_runner_started) { + #ifdef __APPLE__ + if (pthread_join(Runner::runner_id, NULL) != 0) { + Logger::error("Failed to join _runner thread!"); + pthread_cancel(Runner::runner_id); + } + #else + struct timespec ts; + ts.tv_sec = 5; + if (pthread_timedjoin_np(Runner::runner_id, NULL, &ts) != 0) { + Logger::error("Failed to join _runner thread!"); + pthread_cancel(Runner::runner_id); + } + #endif } Config::write(); @@ -224,13 +234,11 @@ void clean_quit(int sig) { Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time)); //? Assume error if still not cleaned up and call quick_exit to avoid a segfault from Tools::atomic_lock destructor +#ifndef __APPLE__ if (Tools::active_locks > 0) { -#ifdef __APPLE__ - exit((sig != -1 ? sig : 0)); -#else quick_exit((sig != -1 ? sig : 0)); -#endif } +#endif if (sig != -1) exit(sig); } @@ -307,20 +315,6 @@ namespace Runner { pthread_t runner_id; pthread_mutex_t mtx; - const unordered_flat_map box_bits = { - {"proc", 0b0000'0001}, - {"net", 0b0000'0100}, - {"mem", 0b0001'0000}, - {"cpu", 0b0100'0000}, - }; - - enum bit_pos { - proc_present, proc_running, - net_present, net_running, - mem_present, mem_running, - cpu_present, cpu_running - }; - enum debug_actions { collect_begin, draw_begin, @@ -332,16 +326,11 @@ namespace Runner { draw }; - const uint_fast8_t proc_done = 0b0000'0011; - const uint_fast8_t net_done = 0b0000'1100; - const uint_fast8_t mem_done = 0b0011'0000; - const uint_fast8_t cpu_done = 0b1100'0000; - string debug_bg; unordered_flat_map> debug_times; struct runner_conf { - bitset<8> box_mask; + vector boxes; bool no_update; bool force_redraw; bool background_update; @@ -391,7 +380,15 @@ namespace Runner { //* ----------------------------------------------- THREAD LOOP ----------------------------------------------- while (not Global::quitting) { thread_wait(); + atomic_wait_for(active, true, 5000); + if (active) { + Global::exit_error_msg = "Runner thread failed to get active lock!"; + Global::thread_exception = true; + Input::interrupt = true; + stopping = true; + } if (stopping or Global::resized) { + sleep_ms(1); continue; } @@ -409,128 +406,86 @@ namespace Runner { output.clear(); - //* Start collection functions for all boxes in async threads and draw in this thread when finished - //? Starting order below based on mean time to finish + //* Run collection and draw functions for all boxes try { - future cpu; - future mem; - future net; - future&> proc; + //? PROC + if (v_contains(conf.boxes, "proc")) { + try { + if (Global::debug) debug_timer("proc", collect_begin); - //? Loop until all box flags present in bitmask have been zeroed - while (conf.box_mask.count() > 0) { - if (stopping) break; + //? Start collect + auto proc = Proc::collect(conf.no_update); - //? PROC - if (conf.box_mask.test(proc_present)) { - if (not conf.box_mask.test(proc_running)) { - if (Global::debug) debug_timer("proc", collect_begin); + if (Global::debug) debug_timer("proc", draw_begin); - //? Start async collect - proc = async(Proc::collect, conf.no_update); - conf.box_mask.set(proc_running); - } - else if (not proc.valid()) - throw std::runtime_error("Proc::collect() future not valid."); + //? Draw box + if (not pause_output) output += Proc::draw(proc, conf.force_redraw, conf.no_update); - else if (proc.wait_for(10us) == future_status::ready) { - try { - if (Global::debug) debug_timer("proc", draw_begin); - - //? Draw box - if (not pause_output) output += Proc::draw(proc.get(), conf.force_redraw, conf.no_update); - - if (Global::debug) debug_timer("proc", draw_done); - } - catch (const std::exception& e) { - throw std::runtime_error("Proc:: -> " + (string)e.what()); - } - conf.box_mask ^= proc_done; - } + if (Global::debug) debug_timer("proc", draw_done); } - - //? NET - if (conf.box_mask.test(net_present)) { - if (not conf.box_mask.test(net_running)) { - if (Global::debug) debug_timer("net", collect_begin); - - //? Start async collect - net = async(Net::collect, conf.no_update); - conf.box_mask.set(net_running); - } - else if (not net.valid()) - throw std::runtime_error("Net::collect() future not valid."); - - else if (net.wait_for(10us) == future_status::ready) { - try { - if (Global::debug) debug_timer("net", draw_begin); - - //? Draw box - if (not pause_output) output += Net::draw(net.get(), conf.force_redraw, conf.no_update); - - if (Global::debug) debug_timer("net", draw_done); - } - catch (const std::exception& e) { - throw std::runtime_error("Net:: -> " + (string)e.what()); - } - conf.box_mask ^= net_done; - } + catch (const std::exception& e) { + throw std::runtime_error("Proc:: -> " + (string)e.what()); } + } - //? MEM - if (conf.box_mask.test(mem_present)) { - if (not conf.box_mask.test(mem_running)) { - if (Global::debug) debug_timer("mem", collect_begin); - //? Start async collect - mem = async(Mem::collect, conf.no_update); - conf.box_mask.set(mem_running); - } - else if (not mem.valid()) - throw std::runtime_error("Mem::collect() future not valid."); + //? NET + if (v_contains(conf.boxes, "net")) { + try { + if (Global::debug) debug_timer("net", collect_begin); - else if (mem.wait_for(10us) == future_status::ready) { - try { - if (Global::debug) debug_timer("mem", draw_begin); + //? Start collect + auto net = Net::collect(conf.no_update); - //? Draw box - if (not pause_output) output += Mem::draw(mem.get(), conf.force_redraw, conf.no_update); + if (Global::debug) debug_timer("net", draw_begin); - if (Global::debug) debug_timer("mem", draw_done); - } - catch (const std::exception& e) { - throw std::runtime_error("Mem:: -> " + (string)e.what()); - } - conf.box_mask ^= mem_done; - } + //? Draw box + if (not pause_output) output += Net::draw(net, conf.force_redraw, conf.no_update); + + if (Global::debug) debug_timer("net", draw_done); } + catch (const std::exception& e) { + throw std::runtime_error("Net:: -> " + (string)e.what()); + } + } - //? CPU - if (conf.box_mask.test(cpu_present)) { - if (not conf.box_mask.test(cpu_running)) { - if (Global::debug) debug_timer("cpu", collect_begin); + //? MEM + if (v_contains(conf.boxes, "mem")) { + try { + if (Global::debug) debug_timer("mem", collect_begin); - //? Start async collect - cpu = async(Cpu::collect, conf.no_update); - conf.box_mask.set(cpu_running); - } - else if (not cpu.valid()) - throw std::runtime_error("Cpu::collect() future not valid."); + //? Start collect + auto mem = Mem::collect(conf.no_update); - else if (cpu.wait_for(10us) == future_status::ready) { - try { - if (Global::debug) debug_timer("cpu", draw_begin); + if (Global::debug) debug_timer("mem", draw_begin); - //? Draw box - if (not pause_output) output += Cpu::draw(cpu.get(), conf.force_redraw, conf.no_update); + //? Draw box + if (not pause_output) output += Mem::draw(mem, conf.force_redraw, conf.no_update); - if (Global::debug) debug_timer("cpu", draw_done); - } - catch (const std::exception& e) { - throw std::runtime_error("Cpu:: -> " + (string)e.what()); - } - conf.box_mask ^= cpu_done; - } + if (Global::debug) debug_timer("mem", draw_done); + } + catch (const std::exception& e) { + throw std::runtime_error("Mem:: -> " + (string)e.what()); + } + } + + //? CPU + if (v_contains(conf.boxes, "cpu")) { + try { + if (Global::debug) debug_timer("cpu", collect_begin); + + //? Start collect + auto cpu = Cpu::collect(conf.no_update); + + if (Global::debug) debug_timer("cpu", draw_begin); + + //? Draw box + if (not pause_output) output += Cpu::draw(cpu, conf.force_redraw, conf.no_update); + + if (Global::debug) debug_timer("cpu", draw_done); + } + catch (const std::exception& e) { + throw std::runtime_error("Cpu:: -> " + (string)e.what()); } } } @@ -593,8 +548,17 @@ namespace Runner { //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values void run(const string& box, const bool no_update, const bool force_redraw) { - atomic_lock lck(waiting); - atomic_wait(active); + atomic_wait_for(active, true, 5000); + if (active) { + Logger::error("Stall in Runner thread, restarting!"); + active = false; + // exit(1); + pthread_cancel(Runner::runner_id); + if (pthread_create(&Runner::runner_id, NULL, &Runner::_runner, NULL) != 0) { + Global::exit_error_msg = "Failed to re-create _runner thread!"; + exit(1); + } + } if (stopping or Global::resized) return; if (box == "overlay") { @@ -607,21 +571,21 @@ namespace Runner { Config::unlock(); Config::lock(); - //? Setup bitmask for selected boxes and pass to _runner thread - bitset<8> box_mask; - for (const auto& box : (box == "all" ? Config::current_boxes : vector{box})) { - box_mask |= box_bits.at(box); - } - - current_conf = {box_mask, no_update, force_redraw, (not Config::getB("tty_mode") and Config::getB("background_update")), Global::overlay, Global::clock}; + current_conf = { + (box == "all" ? Config::current_boxes : vector{box}), + no_update, force_redraw, + (not Config::getB("tty_mode") and Config::getB("background_update")), + Global::overlay, + Global::clock + }; if (Menu::active and not current_conf.background_update) Global::overlay.clear(); thread_trigger(); - - //? Wait for _runner thread to be active before returning - for (int i = 0; not active and i < 10; i++) sleep_ms(1); + atomic_wait_for(active, false, 10); } + + } //* Stops any work being done in runner thread and checks for thread errors @@ -634,9 +598,20 @@ namespace Runner { exit(1); } else if (ret == EBUSY) { - atomic_wait(active); + atomic_wait_for(active, true, 5000); + if (active) { + active = false; + if (Global::quitting) { + return; + } + else { + Global::exit_error_msg = "No response from Runner thread, quitting!"; + exit(1); + } + } thread_trigger(); - sleep_ms(1); + atomic_wait_for(active, false, 100); + atomic_wait_for(active, true, 100); } stopping = false; } @@ -864,7 +839,7 @@ int main(int argc, char **argv) { Global::resized = false; if (Menu::active) Menu::process(); else Runner::run("all", true, true); - atomic_wait(Runner::active); + atomic_wait_for(Runner::active, true, 1000); } //? Update clock if needed diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 2a1bbc0..4ca2ac1 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -21,12 +21,13 @@ tab-size = 4 #include #include #include +#include #include #include #include -using std::array, std::atomic, std::string_view, std::string_literals::operator""s; +using std::array, std::atomic, std::string_view, std::string_literals::operator""s, std::mutex, std::lock_guard; namespace fs = std::filesystem; namespace rng = std::ranges; using namespace Tools; @@ -35,7 +36,7 @@ using namespace Tools; namespace Config { atomic locked (false); - atomic writelock (false); + mutex writelock; bool write_new; const vector> descriptions = { @@ -54,6 +55,9 @@ namespace Config { "#* Use withespace \" \" as seprator between different presets.\n" "#* Example: \"cpu:0:default,mem:0:tty,proc:1:default cpu:0:braille,proc:0:tty\""}, + {"vim_keys", "#* Set to True to enable \"h,j,k,l\" keys for directional control in lists.\n" + "#* Conflicting keys for h:\"help\" and k:\"kill\" is accessible while holding shift."}, + {"rounded_corners", "#* Rounded corners on boxes, is ignored if TTY mode is ON."}, {"graph_symbol", "#* Default symbols to use for graph creation, \"braille\", \"block\" or \"tty\".\n" @@ -231,6 +235,7 @@ namespace Config { {"net_auto", true}, {"net_sync", false}, {"show_battery", true}, + {"vim_keys", false}, {"tty_mode", false}, {"force_tty", false}, {"lowcolor", false}, @@ -252,7 +257,7 @@ namespace Config { unordered_flat_map intsTmp; bool _locked(const string& name) { - atomic_wait(writelock); + lock_guard lock(writelock); if (not write_new and rng::find_if(descriptions, [&name](const auto& a) { return a.at(0) == name; }) != descriptions.end()) write_new = true; return locked.load(); @@ -330,7 +335,7 @@ namespace Config { } void lock() { - atomic_wait(writelock); + lock_guard lock(writelock); locked = true; } @@ -444,7 +449,7 @@ namespace Config { void unlock() { if (not locked) return; atomic_wait(Runner::active); - atomic_lock lck(writelock); + lock_guard lock(writelock); try { if (Proc::shown) { ints.at("selected_pid") = Proc::selected_pid; diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index 609a336..bc64dc9 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -815,7 +815,7 @@ namespace Mem { if (title.empty()) title = capitalize(name); const string humanized = floating_humanizer(mem.stats.at(name)); const string graphics = (use_graphs ? mem_graphs.at(name)(mem.percent.at(name), redraw or data_same) : mem_meters.at(name)(mem.percent.at(name).back())); - if (mem_size > 2 && mem.percent.at(name).size() > 0) { + if (mem_size > 2) { out += Mv::to(y+1+cy, x+1+cx) + divider + ljust(title, 4, false, false, not big_mem) + ljust(":", (big_mem ? 1 : 6)) + Mv::to(y+1+cy, x+cx + mem_width - 2 - humanized.size()) + trans(humanized) + Mv::to(y+2+cy, x+cx + (graph_height >= 2 ? 0 : 1)) + graphics + up + rjust(to_string(mem.percent.at(name).back()) + "%", 4); @@ -1039,9 +1039,10 @@ namespace Proc { 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" and selected > 0) { + 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); @@ -1052,7 +1053,7 @@ namespace Proc { else if (cmd_key == "mouse_scroll_down" and start < numpids - select_max) { start = min(numpids - select_max, start + 3); } - else if (cmd_key == "down") { + 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; @@ -1103,6 +1104,7 @@ namespace Proc { 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"); start = Config::getI("proc_start"); selected = Config::getI("proc_selected"); const int y = show_detailed ? Proc::y + 8 : Proc::y; @@ -1167,7 +1169,7 @@ namespace Proc { 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 + 'k' + t_color + "ill" + Fx::ub + title_right + 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) { @@ -1260,7 +1262,7 @@ namespace Proc { mouse_x += 11; } if (width > 55) { - out += title_left_down + Fx::b + hi_color + 'k' + t_color + "ill" + Fx::ub + title_right_down; + 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; } @@ -1313,15 +1315,14 @@ namespace Proc { 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); - if (detailed.mem_bytes.size() > 0) { - const double mem_p = (double)detailed.mem_bytes.back() * 100 / totalMem; - string mem_str = to_string(mem_p); - mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); - out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + mem_str + "% ", (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; - } + + const double mem_p = (double)detailed.mem_bytes.back() * 100 / totalMem; + string mem_str = to_string(mem_p); + mem_str.resize((mem_p < 10 or mem_p >= 100 ? 3 : 4)); + out += Mv::to(d_y + 4, d_x + 1) + Theme::c("title") + Fx::b + rjust((item_fit > 4 ? "Memory: " : "M:") + mem_str + "% ", (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 diff --git a/src/btop_input.cpp b/src/btop_input.cpp index 6e4fc02..7d6be33 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -184,7 +184,9 @@ namespace Input { if (key.empty()) return; try { auto& filtering = Config::getB("proc_filtering"); - + auto& vim_keys = Config::getB("vim_keys"); + auto help_key = (vim_keys ? "H" : "h"); + auto kill_key = (vim_keys ? "K" : "k"); //? Global input actions if (not filtering) { bool keep_going = false; @@ -195,7 +197,7 @@ namespace Input { Menu::show(Menu::Menus::Main); return; } - else if (is_in(key, "F1", "h")) { + else if (is_in(key, "F1", help_key)) { Menu::show(Menu::Menus::Help); return; } @@ -254,13 +256,13 @@ namespace Input { else return; } - else if (key == "left") { + else if (key == "left" or (vim_keys and key == "h")) { int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting")); if (--cur_i < 0) cur_i = Proc::sort_vector.size() - 1; Config::set("proc_sorting", Proc::sort_vector.at(cur_i)); } - else if (key == "right") { + else if (key == "right" or (vim_keys and key == "l")) { int cur_i = v_index(Proc::sort_vector, Config::getS("proc_sorting")); if (std::cmp_greater(++cur_i, Proc::sort_vector.size() - 1)) cur_i = 0; @@ -346,7 +348,7 @@ namespace Input { if (key == "-" or key == "space") Proc::collapse = pid; no_update = false; } - else if (is_in(key, "t", "k") and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) { + else if (is_in(key, "t", kill_key) and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) { atomic_wait(Runner::active); if (Config::getB("show_detailed") and Config::getI("proc_selected") == 0 and Proc::detailed.status == "Dead") return; Menu::show(Menu::Menus::SignalSend, (key == "t" ? SIGTERM : SIGKILL)); @@ -358,7 +360,7 @@ namespace Input { Menu::show(Menu::Menus::SignalChoose); return; } - else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end")) { + else if (is_in(key, "up", "down", "page_up", "page_down", "home", "end") or (vim_keys and is_in(key, "j", "k"))) { proc_mouse_scroll: redraw = false; auto old_selected = Config::getI("proc_selected"); @@ -485,4 +487,4 @@ namespace Input { } } -} +} \ No newline at end of file diff --git a/src/btop_menu.cpp b/src/btop_menu.cpp index 3128544..6c9eb8d 100644 --- a/src/btop_menu.cpp +++ b/src/btop_menu.cpp @@ -177,6 +177,15 @@ namespace Menu { "Will force 16-color mode and TTY theme,", "set all graph symbols to \"tty\" and swap", "out other non tty friendly symbols."}, + {"vim_keys", + "Enable vim keys.", + "Set to True to enable \"h,j,k,l\" keys for", + "directional control in lists.", + "", + "Conflicting keys for", + "h (help) and k (kill)", + "is accessible while holding shift."}, + {"presets", "Define presets for the layout of the boxes.", "", @@ -695,7 +704,7 @@ namespace Menu { else if (key == "backspace" and selected_signal != -1) { selected_signal = (selected_signal < 10 ? -1 : selected_signal / 10); } - else if (key == "up" and selected_signal != 16) { + else if (is_in(key, "up", "k") and selected_signal != 16) { if (selected_signal == 1) selected_signal = 31; else if (selected_signal < 6) selected_signal += 25; else { @@ -704,7 +713,7 @@ namespace Menu { if (selected_signal <= 16 and offset) selected_signal--; } } - else if (key == "down") { + else if (is_in(key, "down", "j")) { if (selected_signal == 31) selected_signal = 1; else if (selected_signal < 1 or selected_signal == 16) selected_signal = 1; else if (selected_signal > 26) selected_signal -= 25; @@ -715,11 +724,11 @@ namespace Menu { if (selected_signal > 31) selected_signal = 31; } } - else if (key == "left" and selected_signal > 0 and selected_signal != 16) { + else if (is_in(key, "left", "h") and selected_signal > 0 and selected_signal != 16) { if (--selected_signal < 1) selected_signal = 31; else if (selected_signal == 16) selected_signal--; } - else if (key == "right" and selected_signal <= 31 and selected_signal != 16) { + else if (is_in(key, "right", "l") and selected_signal <= 31 and selected_signal != 16) { if (++selected_signal > 31) selected_signal = 1; else if (selected_signal == 16) selected_signal++; } @@ -903,10 +912,10 @@ namespace Menu { exit(0); } } - else if (is_in(key, "down", "tab", "mouse_scroll_down")) { + else if (is_in(key, "down", "tab", "mouse_scroll_down", "j")) { if (++selected > 2) selected = 0; } - else if (is_in(key, "up", "shift_tab", "mouse_scroll_up")) { + else if (is_in(key, "up", "shift_tab", "mouse_scroll_up", "k")) { if (--selected < 0) selected = 2; } else { @@ -956,6 +965,7 @@ namespace Menu { {"cpu_sensor", std::cref(Cpu::available_sensors)} }; auto& tty_mode = Config::getB("tty_mode"); + auto& vim_keys = Config::getB("vim_keys"); if (max_items == 0) { for (const auto& cat : categories) { if ((int)cat.size() > max_items) max_items = cat.size(); @@ -1054,14 +1064,14 @@ namespace Menu { else if (is_in(key, "escape", "q", "o", "backspace")) { return Closed; } - else if (is_in(key, "down", "mouse_scroll_down")) { + else if (is_in(key, "down", "mouse_scroll_down") or (vim_keys and key == "j")) { if (++selected > select_max or selected >= item_height) { if (page < pages - 1) page++; else if (pages > 1) page = 0; selected = 0; } } - else if (is_in(key, "up", "mouse_scroll_up")) { + else if (is_in(key, "up", "mouse_scroll_up") or (vim_keys and key == "k")) { if (--selected < 0) { if (page > 0) page--; else if (pages > 1) page = pages - 1; @@ -1089,12 +1099,12 @@ namespace Menu { selected_cat = key.back() - '0' - 1; page = selected = 0; } - else if (is_in(key, "left", "right")) { + else if (is_in(key, "left", "right") or (vim_keys and is_in(key, "h", "l"))) { const auto& option = categories[selected_cat][item_height * page + selected][0]; if (selPred.test(isInt)) { const int mod = (option == "update_ms" ? 100 : 1); long value = Config::getI(option); - if (key == "right") value += mod; + if (key == "right" or (vim_keys and key == "l")) value += mod; else value -= mod; if (Config::intValid(option, to_string(value))) diff --git a/src/btop_tools.cpp b/src/btop_tools.cpp index bb75f2f..72b7e90 100644 --- a/src/btop_tools.cpp +++ b/src/btop_tools.cpp @@ -24,6 +24,7 @@ tab-size = 4 #include #include #include +#include #include #include @@ -33,7 +34,8 @@ tab-size = 4 #include #include -using std::string_view, std::max, std::floor, std::to_string, std::cin, std::cout, std::flush, robin_hood::unordered_flat_map; +using std::string_view, std::max, std::floor, std::to_string, std::cin, std::cout, std::flush, robin_hood::unordered_flat_map, + std::mutex, std::lock_guard; namespace fs = std::filesystem; namespace rng = std::ranges; @@ -321,9 +323,18 @@ namespace Tools { return ss.str(); } + void atomic_wait(const atomic& atom, const bool old) noexcept { + while (atom.load(std::memory_order_relaxed) == old ) busy_wait(); + } + + void atomic_wait_for(const atomic& atom, const bool old, const uint64_t wait_ms) noexcept { + const uint64_t start_time = time_ms(); + while (atom.load(std::memory_order_relaxed) == old and (time_ms() - start_time < wait_ms)) sleep_ms(1); + } + atomic_lock::atomic_lock(atomic& atom) : atom(atom) { active_locks++; - while (not this->atom.compare_exchange_strong(this->not_true, true)); + this->atom.store(true); } atomic_lock::~atomic_lock() { @@ -373,11 +384,10 @@ namespace Tools { namespace Logger { using namespace Tools; - namespace { - std::atomic busy (false); - bool first = true; - const string tdf = "%Y/%m/%d (%T) | "; - } + + mutex logger_mtx; + bool first = true; + const string tdf = "%Y/%m/%d (%T) | "; size_t loglevel; fs::path logfile; @@ -388,7 +398,7 @@ namespace Logger { void log_write(const size_t level, const string& msg) { if (loglevel < level or logfile.empty()) return; - atomic_lock lck(busy); + lock_guard lock(logger_mtx); std::error_code ec; try { if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) { diff --git a/src/btop_tools.hpp b/src/btop_tools.hpp index ea3b543..76d2245 100644 --- a/src/btop_tools.hpp +++ b/src/btop_tools.hpp @@ -276,7 +276,21 @@ namespace Tools { string hostname(); string username(); - inline void atomic_wait(const atomic& atom, const bool old=true) noexcept { while (atom.load() == old) sleep_ms(1); } + static inline void busy_wait (void) { + #if defined __i386__ || defined __x86_64__ + __builtin_ia32_pause(); + #elif defined __ia64__ + __asm volatile("hint @pause" : : : "memory"); + #elif defined __sparc__ && (defined __arch64__ || defined __sparc_v9__) + __asm volatile("membar #LoadLoad" : : : "memory"); + #else + __asm volatile("" : : : "memory"); + #endif + } + + void atomic_wait(const atomic& atom, const bool old=true) noexcept; + + void atomic_wait_for(const atomic& atom, const bool old=true, const uint64_t wait_ms=0) noexcept; //* Waits for atomic to be false and sets it to true on construct, sets to false on destruct class atomic_lock { diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 9ba9bcc..6d3abaa 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -1354,7 +1354,7 @@ namespace Proc { uid_user.clear(); pread.open(Shared::passwd_path); if (pread.good()) { - while (not pread.eof()) { + while (pread.good()) { getline(pread, r_user, ':'); pread.ignore(SSmax, ':'); getline(pread, r_uid, ':'); @@ -1422,7 +1422,7 @@ namespace Proc { if (not pread.good()) continue; string uid; string line; - while (not pread.eof()) { + while (pread.good()) { getline(pread, line, ':'); if (line == "Uid") { pread.ignore(); @@ -1465,9 +1465,9 @@ namespace Proc { uint64_t cpu_t = 0; try { for (;;) { - while (++x < next_x + offset) pread.ignore(SSmax, ' '); - getline(pread, short_str, ' '); + while (pread.good() and ++x < next_x + offset) pread.ignore(SSmax, ' '); if (not pread.good()) break; + else getline(pread, short_str, ' '); switch (x-offset) { case 3: //? Process state