Merge changes from main

This commit is contained in:
aristocratos 2021-10-16 19:34:10 +02:00
parent 70b4871062
commit 88a2528ca3
10 changed files with 247 additions and 202 deletions

View file

@ -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:

View file

@ -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

View file

@ -19,8 +19,6 @@ tab-size = 4
#include <csignal>
#include <pthread.h>
#include <thread>
#include <future>
#include <bitset>
#include <numeric>
#include <ranges>
#include <unistd.h>
@ -30,6 +28,7 @@ tab-size = 4
#include <tuple>
#include <regex>
#include <chrono>
#include <mutex>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
@ -40,7 +39,7 @@ tab-size = 4
#include <btop_menu.hpp>
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<bool> resized (false);
atomic<bool> resizing (false);
atomic<bool> quitting (false);
atomic<bool> _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<mutex> 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<string, uint_fast8_t> box_bits = {
{"proc", 0b0000'0001},
{"net", 0b0000'0100},
{"mem", 0b0001'0000},
{"cpu", 0b0100'0000},
};
enum bit_pos {
proc_present, proc_running,
net_present, net_running,
mem_present, mem_running,
cpu_present, cpu_running
};
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<string, array<uint64_t, 2>> debug_times;
struct runner_conf {
bitset<8> box_mask;
vector<string> 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::cpu_info&> cpu;
future<Mem::mem_info&> mem;
future<Net::net_info&> net;
future<vector<Proc::proc_info>&> 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

View file

@ -21,12 +21,13 @@ tab-size = 4
#include <atomic>
#include <fstream>
#include <string_view>
#include <mutex>
#include <btop_config.hpp>
#include <btop_shared.hpp>
#include <btop_tools.hpp>
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<bool> locked (false);
atomic<bool> writelock (false);
mutex writelock;
bool write_new;
const vector<array<string, 2>> 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<string, int> intsTmp;
bool _locked(const string& name) {
atomic_wait(writelock);
lock_guard<mutex> 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<mutex> 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<mutex> lock(writelock);
try {
if (Proc::shown) {
ints.at("selected_pid") = Proc::selected_pid;

View file

@ -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

View file

@ -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 {
}
}
}
}

View file

@ -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)))

View file

@ -24,6 +24,7 @@ tab-size = 4
#include <iomanip>
#include <utility>
#include <robin_hood.h>
#include <mutex>
#include <unistd.h>
#include <limits.h>
@ -33,7 +34,8 @@ tab-size = 4
#include <btop_shared.hpp>
#include <btop_tools.hpp>
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<bool>& atom, const bool old) noexcept {
while (atom.load(std::memory_order_relaxed) == old ) busy_wait();
}
void atomic_wait_for(const atomic<bool>& 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<bool>& 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<bool> 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<mutex> lock(logger_mtx);
std::error_code ec;
try {
if (fs::exists(logfile) and fs::file_size(logfile, ec) > 1024 << 10 and not ec) {

View file

@ -276,7 +276,21 @@ namespace Tools {
string hostname();
string username();
inline void atomic_wait(const atomic<bool>& 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<bool>& atom, const bool old=true) noexcept;
void atomic_wait_for(const atomic<bool>& atom, const bool old=true, const uint64_t wait_ms=0) noexcept;
//* Waits for atomic<bool> to be false and sets it to true on construct, sets to false on destruct
class atomic_lock {

View file

@ -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