diff --git a/systats.py b/systats.py index c1d85b0..ddee9e3 100755 --- a/systats.py +++ b/systats.py @@ -437,6 +437,37 @@ C_POPUP_BG = 12 C_POPUP_HL = 13 +HELP_LINES = [ + ("NAVIGATION", ""), + ("j / Down", "Move cursor down"), + ("k / Up", "Move cursor up"), + ("PgDn", "Scroll down 20"), + ("PgUp", "Scroll up 20"), + ("Home", "Go to top"), + ("End", "Go to bottom"), + ("", ""), + ("SORTING", ""), + ("c", "Sort by CPU%"), + ("m", "Sort by MEM%"), + ("p", "Sort by PID"), + ("n", "Sort by name"), + ("r", "Reverse sort order"), + ("", ""), + ("PROCESS MGMT", ""), + ("T", "Send SIGTERM to process"), + ("K", "Send SIGKILL to process"), + ("F9", "Signal menu (choose signal)"), + ("+ / F8", "Renice process"), + ("- / F7", "Renice process (negative)"), + ("d / F5 / Enter", "Process details"), + ("/ / f", "Filter processes"), + ("", ""), + ("GENERAL", ""), + ("h / F1 / ?", "Toggle this help"), + ("q / Q / Esc", "Quit"), +] + + class ProcessManager: """Interactive process management: kill, nice, filter, details.""" @@ -586,6 +617,7 @@ class Renderer: self.data = data self.scroll_offset = 0 self.proc_mgr = ProcessManager() + self.show_help = False def draw(self): self.scr.erase() @@ -623,6 +655,10 @@ class Renderer: # Processes (rest of screen) self._draw_processes(y, 0, w, h - y) + # Help overlay + if self.show_help: + self._draw_help(h, w) + self.scr.refresh() def _draw_header(self, y, w): @@ -1029,6 +1065,41 @@ class Renderer: " Enter:apply Esc:cancel ".center(popup_w), curses.color_pair(C_POPUP_BG) | curses.A_DIM) + def _draw_help(self, h, w): + popup_w = min(w - 4, 52) + popup_h = min(h - 2, len(HELP_LINES) + 4) + mx = (w - popup_w) // 2 + my = (h - popup_h) // 2 + + # Background + for row in range(popup_h): + safe_addstr(self.scr, my + row, mx, " " * popup_w, curses.color_pair(C_POPUP_BG)) + + # Title + title = " SYSTATS - Keyboard Shortcuts " + safe_addstr(self.scr, my, mx, title.center(popup_w), + curses.color_pair(C_POPUP_HL) | curses.A_BOLD) + safe_addstr(self.scr, my + 1, mx, "\u2500" * popup_w, curses.color_pair(C_POPUP_BG)) + + for i, (key, desc) in enumerate(HELP_LINES): + row_y = my + 2 + i + if row_y >= my + popup_h - 1: + break + if not key and not desc: + continue + if not desc: + # Section header + safe_addstr(self.scr, row_y, mx + 2, key, + curses.color_pair(C_POPUP_HL) | curses.A_BOLD) + else: + line = f" {key:<20s} {desc}" + safe_addstr(self.scr, row_y, mx, line[:popup_w].ljust(popup_w), + curses.color_pair(C_POPUP_BG)) + + safe_addstr(self.scr, my + popup_h - 1, mx, + " Press h, F1 or ? to close ".center(popup_w), + curses.color_pair(C_POPUP_BG) | curses.A_DIM) + def _draw_filter_bar(self, y, x, w): pm = self.proc_mgr bar = f" Filter: {pm.filter_text}_ " @@ -1060,6 +1131,12 @@ def main(stdscr): processes = pm.filtered_processes(data.processes) max_idx = max(0, len(processes) - 1) + # ── Help overlay ── + if renderer.show_help: + if key in (ord("h"), ord("?"), curses.KEY_F1, 27): + renderer.show_help = False + continue + # ── Filter mode input ── if pm.filter_mode: if key == 27: # ESC @@ -1122,6 +1199,8 @@ def main(stdscr): break elif key == 27: # ESC break + elif key in (ord("h"), ord("?"), curses.KEY_F1): + renderer.show_help = True elif key == ord("c"): data.process_sort = "cpu" data.process_sort_reverse = True