From dca53f87e41636a8928de99f17eeecc3e8cbfff1 Mon Sep 17 00:00:00 2001 From: Barry Van Deerlin Date: Tue, 13 Jan 2026 17:53:21 -0800 Subject: [PATCH] Fixes and Improvements for Pause and Follow modes Reselection Issues with Pause and Follow - I found an edge case I had missed before for the very end of the process list (not view). For the process list pausing feature, if the process selected was the very last item in the whole list then the selection was moved up one. And vice versa when the list is paused and the very last item is selected, unpausing would also move it up one item in the list. For the process following feature the same thing would happen when unfollowing (if not using the detailed view follow) but for the last few items (upto halfway up the view) - This was fixed by adding 1 to either `selected` or `start` when required. - When process following with detailed view. unfollowing by clicking the process and then clicking again to close detail view threw the view off because following was disengaged. - Fixed with the modification to the code block that handles following. It now also restores the selection and view when exiting detailed view also. (I had a different fix for this but the fix for the next issue made it irrelevant and was removed) - If following a process with detailed view and it is one of the first or last few items in the whole list (not view) then if you change the reverse sort mode and then unfollow the process, instead of the selection returning to the process that was being followed it returns to where the process would have been if the sorting wasn't reversed. - I struggled to figure out a fix for this but landed on reusing the code block that locates the followed process to restore the selection when detailed view closes. - I actually quite like this fix since it also centers the selection if possiple when closing the detailed view. - The change that removed selection for detailed view following caused an issue where if the list was paused holding up would cause the selection to loop from the list position where the followed process was after reaching the top of the list. - This was fixed by adding some additional conditions before changing the selection back to the followed process when selected is 0 - This gets rid of this issue so that when paused scrolling to the top of the list doesn't loop back to the followed process location - When following a process and the view was at the end of the list, if the process died and following disengaged, a blank line would be left at the bottom of the list until the next collection cycle. - fixed this by making select_max not a const and adding 1 to it when the following mode disengaged. - When the followed process is the first process in the list, pressing up doesn't redraw the screen and get rid of the following banner until the next collection cycle. - I fixed this by moving `bool changed = false` to the top of the selection function and setting `changed` to true when reselecting the followed process before moving back up and `selected` is 0 again. - When following a process clicking on the banner with the mouse would exit following mode and select the last line. I am not sure if this is desirable or not. - I have fixed this with an extra condition in the mouse input logic - This can be reverted if you feel it is good for clicking the banner to exit following in that way. Improvements to follow mode list centering - The followed process was centered using select_max / 2. this works but would leave it off center by 2 if select_max was odd. - it has been updated to use select_max / 2 if select_max is even which has the followed process off center as close as it can be but slightly closer to the top of list. if select_max is odd then it uses select_max / 2 + 1 which places the process dead center. - If follow-detailed is disabled and a process is being followed while paused, opening or closing the detailed view doesn't recenter the followed process. - fixed this by relocating a line that sets update_following Issue with tree expand/collapse when following - When I made the change that set `selected = 0` when following the detailed process. I did not realize that this would prevent the user from being able to expand or collapse the tree of the process being followed without first unfollowing it. - I fixed this by slightly reworking the input code for expand/collapse with some extra conditions Misc improvement to follow mode - Added a line in the F input handling code that sets the selection to the followed process before exiting follow mode. This means that if if you press F after opening a detailed view and following that it will exit follow mode and move selected to the followed process location instead of leaving selected as 0. This is equivalent to pressing down after opening a detailed view when not following the process. - Since when following the detailed view process the selection can be moved up or down from the followed process, the up arrow for the select button should not be greyed out if following the detailed view process and `should_selection_return_to_followed` is false. - Added a comment in the selection function to explain what the following mode code block does there. Small optimization for Pause mode - I moved `Config::getB("keep_dead_proc_usage")` outside of the for loop that goes through all the current procs while paused. --- src/btop_config.cpp | 2 ++ src/btop_draw.cpp | 53 ++++++++++++++++++++++++++++-------- src/btop_input.cpp | 42 +++++++++++++++++----------- src/freebsd/btop_collect.cpp | 3 +- src/linux/btop_collect.cpp | 3 +- src/netbsd/btop_collect.cpp | 3 +- src/openbsd/btop_collect.cpp | 3 +- src/osx/btop_collect.cpp | 3 +- 8 files changed, 79 insertions(+), 33 deletions(-) diff --git a/src/btop_config.cpp b/src/btop_config.cpp index 159257d..8b9e24f 100644 --- a/src/btop_config.cpp +++ b/src/btop_config.cpp @@ -340,6 +340,7 @@ namespace Config { {"proc_follow_detailed", true}, {"follow_process", false}, {"update_following", false}, + {"should_selection_return_to_followed", false}, #ifdef GPU_SUPPORT {"nvml_measure_pcie_speeds", true}, {"rsmi_measure_pcie_speeds", true}, @@ -356,6 +357,7 @@ namespace Config { {"net_download", 100}, {"net_upload", 100}, {"detailed_pid", 0}, + {"restore_detailed_pid", 0}, {"selected_pid", 0}, {"followed_pid", 0}, {"selected_depth", 0}, diff --git a/src/btop_draw.cpp b/src/btop_draw.cpp index cadaefa..b85eea1 100644 --- a/src/btop_draw.cpp +++ b/src/btop_draw.cpp @@ -1561,6 +1561,7 @@ namespace Proc { 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; + bool previous_proc_banner_state = false; string box; @@ -1568,11 +1569,19 @@ namespace Proc { auto start = Config::getI("proc_start"); auto selected = Config::getI("proc_selected"); auto last_selected = Config::getI("proc_last_selected"); + bool changed = false; int select_max = (Config::getB("show_detailed") ? (Config::getB("proc_banner_shown") ? Proc::select_max - 9 : Proc::select_max - 8) : (Config::getB("proc_banner_shown") ? Proc::select_max - 1 : Proc::select_max)); + // Return the selection from the detailed view to the followed process before moving the selection + // Disengage following mode when moving the selection unless paused if (Config::getB("follow_process")) { - if (selected == 0) selected = Config::getI("proc_followed");; + if (Config::getB("show_detailed") and selected == 0 and Config::getB("should_selection_return_to_followed") + and Config::getI("detailed_pid") == Config::getI("followed_pid")) { + selected = Config::getI("proc_followed"); + Config::set("should_selection_return_to_followed", false); + changed = true; + } if (not Config::getB("pause_proc_list")) { Config::flip("follow_process"); Config::set("followed_pid", 0); @@ -1625,7 +1634,6 @@ namespace Proc { 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; @@ -1653,13 +1661,14 @@ namespace Proc { auto follow_process = Config::getB("follow_process"); int followed_pid = Config::getI("followed_pid"); int followed = Config::getI("proc_followed"); + bool should_selection_return_to_followed = Config::getB("should_selection_return_to_followed"); auto proc_banner_shown = pause_proc_list or follow_process; Config::set("proc_banner_shown", proc_banner_shown); 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_banner_shown ? Proc::select_max - 9 : Proc::select_max - 8) : + int select_max = show_detailed ? (proc_banner_shown ? Proc::select_max - 9 : Proc::select_max - 8) : (proc_banner_shown ? Proc::select_max - 1 : Proc::select_max); auto totalMem = Mem::get_totalMem(); int numpids = Proc::numpids; @@ -1668,13 +1677,15 @@ namespace Proc { out.reserve(width * height); //? Move current selection/view to the selected process when a process should be followed - if (follow_process and (not pause_proc_list or Config::getB("update_following"))) { + //? Restore view and selection to the detailed view process when detailed view is closed + const int restore_detailed_pid = Config::getI("restore_detailed_pid"); + if ((follow_process and (not pause_proc_list or Config::getB("update_following"))) or restore_detailed_pid > 0) { Config::set("update_following", false); int loc = 1; bool can_follow = false; for (auto& p : plist) { if (p.filtered or (proc_tree and p.tree_index == plist.size())) continue; - if (p.pid == (size_t)followed_pid) { + if (p.pid == (size_t)(restore_detailed_pid > 0 ? restore_detailed_pid : followed_pid)) { can_follow = true; break; } @@ -1682,19 +1693,35 @@ namespace Proc { } if (can_follow) { - start = max(0, loc - (select_max / 2)); - followed = loc < (select_max / 2) ? loc : start > numpids - select_max ? select_max - numpids + loc : select_max / 2; - Config::set("proc_followed", followed); - selected = followed_pid != Config::getI("detailed_pid") ? followed : 0; + const int list_middle = select_max % 2 == 0 ? select_max / 2 : select_max / 2 + 1; + start = max(0, loc - list_middle); + followed = loc < list_middle ? loc : start > numpids - select_max ? select_max - numpids + loc : list_middle; + if (restore_detailed_pid == 0) { + Config::set("proc_followed", followed); + Config::set("should_selection_return_to_followed", should_selection_return_to_followed = true); + } + selected = (followed_pid != Config::getI("detailed_pid") or restore_detailed_pid > 0) ? followed : 0; } - else { + else if (restore_detailed_pid == 0) { Config::set("followed_pid", followed_pid = 0); Config::set("follow_process", follow_process = false); Config::set("proc_banner_shown", proc_banner_shown = pause_proc_list); Config::set("proc_followed", 0); + if (not proc_banner_shown) select_max++; } + if (restore_detailed_pid > 0) Config::set("restore_detailed_pid", 0); } + //? Handle selection edge cases when list view is showing bottom of list + //? for Pause and Following modes + const bool proc_banner_changed = proc_banner_shown != previous_proc_banner_state; + previous_proc_banner_state = proc_banner_shown; + if (proc_banner_changed and not proc_banner_shown + and start + select_max - 1 == numpids) + selected++; + else if (pause_proc_list and selected > select_max) + start++; + //? redraw if selection reaches or leaves the end of the list if (selected != Config::getI("proc_last_selected")) { if (selected >= select_max and start >= numpids - select_max) { @@ -1860,11 +1887,13 @@ namespace Proc { //? select, info, signal, and follow buttons const string down_button = (is_last_process_in_list ? Theme::c("inactive_fg") : Theme::c("hi_fg")) + Symbols::down; + const bool is_up_button_highlighted = selected != 0 or (follow_process and followed_pid == Config::getI("detailed_pid") and should_selection_return_to_followed); + const string up_button = (is_up_button_highlighted ? Theme::c("hi_fg") : Theme::c("inactive_fg")) + Symbols::up; 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; + out += Mv::to(y + height - 1, x + 1) + title_left_down + Fx::b + hi_color + up_button + 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["info_enter"] = {y + height - 1, mouse_x, 1, 6}; mouse_x += 8; if (width > 60) { diff --git a/src/btop_input.cpp b/src/btop_input.cpp index f7aa15c..ce0bfc2 100644 --- a/src/btop_input.cpp +++ b/src/btop_input.cpp @@ -355,6 +355,10 @@ namespace Input { } else if (Config::getB("follow_process")) { Config::flip("follow_process"); + if (Config::getB("should_selection_return_to_followed")) + Config::set("proc_selected", Config::getI("proc_followed")); + else if (Config::getB("show_detailed") and Config::getI("followed_pid") == Config::getI("detailed_pid")) + Config::set("restore_detailed_pid", Config::getI("detailed_pid")); Config::set("followed_pid", 0); Config::set("proc_followed", 0); } @@ -395,6 +399,8 @@ namespace Input { process("enter"); return; } + else if (Config::getB("proc_banner_shown") and line == y + height - 2) + return; else if (current_selection == 0 or line - y - 1 == 0) redraw = true; @@ -449,31 +455,35 @@ namespace Input { if (Config::getB("proc_follow_detailed")) { Config::set("follow_process", true); Config::set("followed_pid", Config::getI("selected_pid")); - Config::set("update_following", true); } Config::set("show_detailed", true); } else if (Config::getB("show_detailed")) { - const int proc_start_offset = Config::getB("proc_follow_detailed") ? Config::getI("proc_followed") - Config::getI("proc_last_selected") : 0; - if (Config::getI("proc_last_selected") > 0) Config::set("proc_selected", Config::getI("proc_last_selected")); - Config::set("proc_start", std::max(0, Config::getI("proc_start") + proc_start_offset)); + if (Config::getB("proc_follow_detailed")) { + Config::set("restore_detailed_pid", Config::getI("detailed_pid")); + if (Config::getB("follow_process") and Config::getI("followed_pid") == Config::getI("detailed_pid")) { + Config::flip("follow_process"); + Config::set("followed_pid", 0); + Config::set("proc_followed", 0); + } + } + else if (Config::getI("proc_last_selected") > 0) Config::set("proc_selected", Config::getI("proc_last_selected")); Config::set("proc_last_selected", 0); Config::set("detailed_pid", 0); Config::set("show_detailed", false); - if (Config::getB("follow_process") and Config::getB("proc_follow_detailed")) { - Config::flip("follow_process"); - Config::set("followed_pid", 0); - Config::set("proc_followed", 0); - } } + Config::set("update_following", true); } - else if (is_in(key, "+", "-", "space", "C") and Config::getB("proc_tree") and Config::getI("proc_selected") > 0) { - atomic_wait(Runner::active); - auto& pid = Config::getI("selected_pid"); - if (key == "+" or key == "space") Proc::expand = pid; - if (key == "-" or key == "space") Proc::collapse = pid; - if (key == "C") Proc::toggle_children = pid; - no_update = false; + else if (is_in(key, "+", "-", "space", "C") and Config::getB("proc_tree")) { + const bool is_following_detailed = Config::getB("follow_process") and Config::getI("followed_pid") == Config::getI("detailed_pid"); + if (Config::getI("proc_selected") > 0 or is_following_detailed) { + atomic_wait(Runner::active); + auto& pid = is_following_detailed and Config::getI("proc_selected") == 0 ? Config::getI("followed_pid") : Config::getI("selected_pid"); + if (key == "+" or key == "space") Proc::expand = pid; + if (key == "-" or key == "space") Proc::collapse = pid; + if (key == "C") Proc::toggle_children = pid; + no_update = false; + } } else if (is_in(key, "t", kill_key) and (Config::getB("show_detailed") or Config::getI("selected_pid") > 0)) { atomic_wait(Runner::active); diff --git a/src/freebsd/btop_collect.cpp b/src/freebsd/btop_collect.cpp index 1a8bdf7..17f916f 100644 --- a/src/freebsd/btop_collect.cpp +++ b/src/freebsd/btop_collect.cpp @@ -1210,6 +1210,7 @@ namespace Proc { } //? Set correct state of dead processes if paused else { + const bool keep_dead_proc_usage = Config::getB("keep_dead_proc_usage"); for (auto& r : current_procs) { if (rng::find(found, r.pid) == found.end()) { if (r.state != 'X') { @@ -1220,7 +1221,7 @@ namespace Proc { r.state = 'X'; dead_procs.emplace(r.pid); //? Reset cpu usage for dead processes if paused and option is set - if (!Config::getB("keep_dead_proc_usage")) { + if (!keep_dead_proc_usage) { r.cpu_p = 0.0; r.mem = 0; } diff --git a/src/linux/btop_collect.cpp b/src/linux/btop_collect.cpp index 456abfa..ae0b035 100644 --- a/src/linux/btop_collect.cpp +++ b/src/linux/btop_collect.cpp @@ -3169,13 +3169,14 @@ namespace Proc { } //? Set correct state of dead processes if paused else { + const bool keep_dead_proc_usage = Config::getB("keep_dead_proc_usage"); for (auto& r : current_procs) { if (rng::find(found, r.pid) == found.end()) { if (r.state != 'X') r.death_time = round(uptime) - (r.cpu_s / Shared::clkTck); r.state = 'X'; dead_procs.emplace(r.pid); //? Reset cpu usage for dead processes if paused and option is set - if (!Config::getB("keep_dead_proc_usage")) { + if (!keep_dead_proc_usage) { r.cpu_p = 0.0; r.mem = 0; } diff --git a/src/netbsd/btop_collect.cpp b/src/netbsd/btop_collect.cpp index 050dba8..9f56899 100755 --- a/src/netbsd/btop_collect.cpp +++ b/src/netbsd/btop_collect.cpp @@ -1283,6 +1283,7 @@ namespace Proc { } //? Set correct state of dead processes if paused else { + const bool keep_dead_proc_usage = Config::getB("keep_dead_proc_usage"); for (auto& r : current_procs) { if (rng::find(found, r.pid) == found.end()) { if (r.state != 'X') { @@ -1293,7 +1294,7 @@ namespace Proc { r.state = 'X'; dead_procs.emplace(r.pid); //? Reset cpu usage for dead processes if paused and option is set - if (!Config::getB("keep_dead_proc_usage")) { + if (!keep_dead_proc_usage) { r.cpu_p = 0.0; r.mem = 0; } diff --git a/src/openbsd/btop_collect.cpp b/src/openbsd/btop_collect.cpp index 7e52700..faf3b2f 100644 --- a/src/openbsd/btop_collect.cpp +++ b/src/openbsd/btop_collect.cpp @@ -1148,6 +1148,7 @@ namespace Proc { } //? Set correct state of dead processes if paused else { + const bool keep_dead_proc_usage = Config::getB("keep_dead_proc_usage"); for (auto& r : current_procs) { if (rng::find(found, r.pid) == found.end()) { if (r.state != 'X') { @@ -1158,7 +1159,7 @@ namespace Proc { r.state = 'X'; dead_procs.emplace(r.pid); //? Reset cpu usage for dead processes if paused and option is set - if (!Config::getB("keep_dead_proc_usage")) { + if (!keep_dead_proc_usage) { r.cpu_p = 0.0; r.mem = 0; } diff --git a/src/osx/btop_collect.cpp b/src/osx/btop_collect.cpp index 068451e..197cec5 100644 --- a/src/osx/btop_collect.cpp +++ b/src/osx/btop_collect.cpp @@ -1290,6 +1290,7 @@ namespace Proc { } //? Set correct state of dead processes if paused else { + const bool keep_dead_proc_usage = Config::getB("keep_dead_proc_usage"); for (auto& r : current_procs) { if (rng::find(found, r.pid) == found.end()) { if (r.state != 'X') { @@ -1300,7 +1301,7 @@ namespace Proc { r.state = 'X'; dead_procs.emplace(r.pid); //? Reset cpu usage for dead processes if paused and option is set - if (!Config::getB("keep_dead_proc_usage")) { + if (!keep_dead_proc_usage) { r.cpu_p = 0.0; r.mem = 0; }