diff --git a/bpytop.py b/bpytop.py index 5daa0de..4fa7dba 100755 --- a/bpytop.py +++ b/bpytop.py @@ -23,12 +23,13 @@ from datetime import timedelta from _thread import interrupt_main from collections import defaultdict from select import select +from itertools import cycle from distutils.util import strtobool from string import Template from math import ceil, floor from random import randint from shutil import which -from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable, Type +from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable, Type, NamedTuple errors: List[str] = [] try: import fcntl, termios, tty @@ -1610,15 +1611,15 @@ class MemBox(Box): Draw.buffer("mem_misc", out_misc, only_save=True) #* Mem - out += f'{Mv.to(y, x+1)}{THEME.title}{Fx.b}Total:{mem.string["total"]:>{cls.mem_width - 9}}{Fx.ub}{THEME.main_fg}' cx = 1; cy = 1 + + out += f'{Mv.to(y, x+1)}{THEME.title}{Fx.b}Total:{mem.string["total"]:>{cls.mem_width - 9}}{Fx.ub}{THEME.main_fg}' if cls.graph_height > 0: gli = f'{Mv.l(2)}{THEME.mem_box(Symbol.title_right)}{THEME.div_line}{Symbol.h_line * (cls.mem_width - 1)}{"" if CONFIG.show_disks else THEME.mem_box}{Symbol.title_left}{Mv.l(cls.mem_width - 1)}{THEME.title}' if cls.graph_height >= 2: gbg = f'{Mv.l(1)}' gmv = f'{Mv.l(cls.mem_width - 2)}{Mv.u(cls.graph_height - 1)}' - big_mem: bool = True if cls.mem_width > 21 else False for name in cls.mem_names: if cls.mem_size > 2: @@ -1633,6 +1634,7 @@ class MemBox(Box): if h - cy > 5: if cls.graph_height > 0: out += f'{Mv.to(y+cy, x+cx)}{gli}' cy += 1 + out += f'{Mv.to(y+cy, x+cx)}{THEME.title}{Fx.b}Swap:{mem.swap_string["total"]:>{cls.mem_width - 8}}{Fx.ub}{THEME.main_fg}' cy += 1 for name in cls.swap_names: @@ -1754,9 +1756,11 @@ class ProcBox(Box): width_p = 55 x = 1 y = 1 + current_y: int = 0 select_max: int = 0 selected: int = 0 - selected_pid: int + selected_pid: int = 0 + filtering: bool = False moved: bool = False start: int = 1 count: int = 0 @@ -1781,7 +1785,6 @@ class ProcBox(Box): cls.detailed_y = cls.y cls.detailed_height = 8 cls.detailed_width = cls.width - cls.select_max = cls.height - 3 cls.redraw = True cls.resized = True @@ -1790,7 +1793,7 @@ class ProcBox(Box): return create_box(box=cls, line_color=THEME.proc_box) @classmethod - def selector(cls, key: str) -> bool: + def selector(cls, key: str, mouse_pos: Tuple[int, int] = (0, 0)): old: Tuple[int, int] = (cls.start, cls.selected) if key == "up": if cls.selected == 1 and cls.start > 1: @@ -1818,18 +1821,35 @@ class ProcBox(Box): elif key == "end": if cls.start < ProcCollector.num_procs - cls.select_max + 1: cls.start = ProcCollector.num_procs - cls.select_max + 1 elif cls.selected < cls.select_max: cls.selected = cls.select_max + elif key == "mouse_click": + if mouse_pos[0] > cls.x + cls.width - 4 and mouse_pos[1] > cls.current_y and mouse_pos[1] < cls.current_y + cls.select_max: + if mouse_pos[1] == cls.current_y + 1: + cls.start = 1 + elif mouse_pos[1] == cls.current_y + cls.select_max - 1: + cls.start = ProcCollector.num_procs - cls.select_max + 1 + else: + cls.start = round((mouse_pos[1] - cls.current_y - 1) * ((ProcCollector.num_procs - cls.select_max - 2) / (cls.select_max - 2))) + else: + cls.selected = mouse_pos[1] - cls.current_y if mouse_pos[1] >= cls.current_y else cls.selected + elif key == "mouse_unselect": + cls.selected = 0 + if cls.start > ProcCollector.num_procs - cls.select_max + 1 and ProcCollector.num_procs > cls.select_max: cls.start = ProcCollector.num_procs - cls.select_max + 1 + elif cls.start > ProcCollector.num_procs: cls.start = ProcCollector.num_procs if cls.start < 1: cls.start = 1 - if cls.start > ProcCollector.num_procs - cls.select_max + 1: cls.start = ProcCollector.num_procs - cls.select_max + 1 + if cls.selected > ProcCollector.num_procs and ProcCollector.num_procs < cls.select_max: cls.selected = ProcCollector.num_procs + elif cls.selected > cls.select_max: cls.selected = cls.select_max + if cls.selected < 0: cls.selected = 0 + if old != (cls.start, cls.selected): cls.moved = True - return True - else: - return False + Collector.collect(ProcCollector, proc_interrupt=True, only_draw=True) + @classmethod def _draw_fg(cls): proc = ProcCollector + if proc.proc_interrupt: return if proc.redraw: cls.redraw = True out: str = "" out_misc: str = "" @@ -1838,47 +1858,65 @@ class ProcBox(Box): prog_len: int; arg_len: int; val: int; c_color: str; m_color: str; t_color: str; sort_pos: int; tree_len: int; is_selected: bool; calc: int l_count: int = 0 loc_string: str + scroll_pos: int = 0 indent: str = "" offset: int = 0 vals: List[str] - c_color = m_color = t_color = Fx.b g_color: str = "" + s_len: int = 0 + if proc.search_filter: s_len = len(proc.search_filter[:10]) end: str = "" - if w > 67: arg_len = w - 53; prog_len = 15 - else: arg_len = 0; prog_len = w - 38 + if w > 67: + arg_len = w - 53 - (1 if proc.num_procs > cls.select_max else 0) + prog_len = 15 + else: + arg_len = 0 + prog_len = w - 38 - (1 if proc.num_procs > cls.select_max else 0) if CONFIG.proc_tree: tree_len = arg_len + prog_len + 6 arg_len = 0 if cls.resized or cls.redraw: + cls.select_max = h - 1 + cls.current_y = y sort_pos = x + w - len(CONFIG.proc_sorting) - 7 Key.mouse["left"] = [[sort_pos + i, y-1] for i in range(3)] Key.mouse["right"] = [[sort_pos + len(CONFIG.proc_sorting) + 3 + i, y-1] for i in range(3)] Key.mouse["e"] = [[sort_pos - 5 + i, y-1] for i in range(4)] - - out_misc += (f'{Mv.to(y-1, x + 15)}{THEME.proc_box(Symbol.h_line * (w - 16))}' + out_misc += (f'{Mv.to(y-1, x + 8)}{THEME.proc_box(Symbol.h_line * (w - 9))}' f'{Mv.to(y-1, sort_pos)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg("<")} {THEME.title(CONFIG.proc_sorting)} ' - f'{THEME.hi_fg(">")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}' - f'{Mv.to(y-1, sort_pos - 6)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_tree else ""}' - f'{THEME.title("tre")}{THEME.hi_fg("e")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') - if w > 50: + f'{THEME.hi_fg(">")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') + + if w > 37 + s_len: + out_misc += (f'{Mv.to(y-1, sort_pos - 6)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_tree else ""}' + f'{THEME.title("tre")}{THEME.hi_fg("e")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') + if w > 45 + s_len: Key.mouse["r"] = [[sort_pos - 14 + i, y-1] for i in range(7)] out_misc += (f'{Mv.to(y-1, sort_pos - 15)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_reversed else ""}' f'{THEME.hi_fg("r")}{THEME.title("everse")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') - if w > 58: - Key.mouse["c"] = [[sort_pos - 22 + i, y-1] for i in range(6)] - out_misc += (f'{Mv.to(y-1, sort_pos - 23)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_colors else ""}' - f'{THEME.hi_fg("c")}{THEME.title("olors")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') - if w > 68: - Key.mouse["g"] = [[sort_pos - 32 + i, y-1] for i in range(8)] - out_misc += (f'{Mv.to(y-1, sort_pos - 33)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_gradient else ""}{THEME.hi_fg("g")}' - f'{THEME.title("radient")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') - if w > 78: - Key.mouse["o"] = [[sort_pos - 42 + i, y-1] for i in range(8)] - out_misc += (f'{Mv.to(y-1, sort_pos - 43)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_per_core else ""}' + if w > 55 + s_len: + Key.mouse["o"] = [[sort_pos - 24 + i, y-1] for i in range(8)] + out_misc += (f'{Mv.to(y-1, sort_pos - 25)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_per_core else ""}' f'{THEME.title("per-c")}{THEME.hi_fg("o")}{THEME.title("re")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') + if w > 65 + s_len: + Key.mouse["g"] = [[sort_pos - 34 + i, y-1] for i in range(8)] + out_misc += (f'{Mv.to(y-1, sort_pos - 35)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_gradient else ""}{THEME.hi_fg("g")}' + f'{THEME.title("radient")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') + if w > 73 + s_len: + Key.mouse["c"] = [[sort_pos - 42 + i, y-1] for i in range(6)] + out_misc += (f'{Mv.to(y-1, sort_pos - 43)}{THEME.proc_box(Symbol.title_left)}{Fx.b if CONFIG.proc_colors else ""}' + f'{THEME.hi_fg("c")}{THEME.title("olors")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') + + Key.mouse["f"] = [[x+9 + i, y-1] for i in range(6 if not proc.search_filter else 2 + len(proc.search_filter[-10:]))] + if proc.search_filter: + Key.mouse["delete"] = [[x+12 + len(proc.search_filter[-10:]) + i, y-1] for i in range(3)] + elif "delete" in Key.mouse: + del Key.mouse["delete"] + out_misc += (f'{Mv.to(y-1, x + 8)}{THEME.proc_box(Symbol.title_left)}{Fx.b if cls.filtering or proc.search_filter else ""}{THEME.hi_fg("f")}{THEME.title}' + + ("ilter" if not proc.search_filter and not cls.filtering else f' {proc.search_filter[-(10 if w < 83 else w - 74):]}{(Fx.bl + "█" + Fx.ubl) if cls.filtering else THEME.hi_fg(" del")}') + + f'{THEME.proc_box(Symbol.title_right)}') Draw.buffer("proc_misc", out_misc, only_save=True) @@ -1886,18 +1924,25 @@ class ProcBox(Box): if selected == "memory": selected = "mem" if selected == "threads" and not CONFIG.proc_tree and not arg_len: selected = "tr" if CONFIG.proc_tree: - out += f'{THEME.title}{Fx.b}{Mv.to(y, x)}{" Tree:":<{tree_len-2}}' "Threads: " f'{"User:":<9}Mem%{"Cpu%":>11}{Fx.ub}{THEME.main_fg}' + out += (f'{THEME.title}{Fx.b}{Mv.to(y, x)}{" Tree:":<{tree_len-2}}' "Threads: " f'{"User:":<9}Mem%{"Cpu%":>11}{Fx.ub}{THEME.main_fg} ' + + (" " if proc.num_procs > cls.select_max else "")) if selected in ["program", "arguments"]: selected = "tree" else: out += (f'{THEME.title}{Fx.b}{Mv.to(y, x)}{"Pid:":>7} {"Program:" if prog_len > 8 else "Prg:":<{prog_len}}' + (f'{"Arguments:":<{arg_len-4}}' if arg_len else "") + - f'{"Threads:" if arg_len else " Tr:"} {"User:":<9}Mem%{"Cpu%":>11}{Fx.ub}{THEME.main_fg}') + f'{"Threads:" if arg_len else " Tr:"} {"User:":<9}Mem%{"Cpu%":>11}{Fx.ub}{THEME.main_fg} ' + + (" " if proc.num_procs > cls.select_max else "")) if selected == "program" and prog_len <= 8: selected = "prg" selected = selected.split(" ")[0].capitalize() out = out.replace(selected, f'{Fx.u}{selected}{Fx.uu}') cy = 1 - if cls.start > proc.num_procs - cls.select_max + 1: cls.start = proc.num_procs - cls.select_max + 1 if proc.num_procs > cls.select_max else 0 - if cls.selected > cls.select_max: cls.selected = cls.select_max + if cls.start > proc.num_procs - cls.select_max + 1 and proc.num_procs > cls.select_max: cls.start = proc.num_procs - cls.select_max + 1 + elif cls.start > proc.num_procs: cls.start = proc.num_procs + if cls.start < 1: cls.start = 1 + if cls.selected > proc.num_procs and proc.num_procs < cls.select_max: cls.selected = proc.num_procs + elif cls.selected > cls.select_max: cls.selected = cls.select_max + if cls.selected < 0: cls.selected = 0 + for n, (pid, items) in enumerate(proc.processes.items(), start=1): if n < cls.start: continue @@ -1939,6 +1984,8 @@ class ProcBox(Box): else: vals += [f'{THEME.gradient["cpu"][v if v <= 100 else 100]}'] c_color, m_color, t_color = vals + else: + c_color = m_color = t_color = Fx.b if CONFIG.proc_gradient and not is_selected: g_color = f'{THEME.gradient["proc"][calc * 100 // cls.select_max]}' if is_selected: @@ -1951,21 +1998,34 @@ class ProcBox(Box): t_color + (f'{threads:>4} ' if threads < 1000 else "999> ") + end + g_color + (f'{username:<9.9}' if len(username) < 10 else f'{username[:8]:<8}+') + m_color + (f'{mem:>4.1f}' if mem < 100 else f'{mem:>4.0f} ') + end + - f' {THEME.inactive_fg}{"⡀"*5}{THEME.main_fg}{g_color}{c_color}' + (f' {cpu:>4.1f} ' if cpu < 100 else f'{cpu:>5.0f} ') + end) + f' {THEME.inactive_fg}{"⡀"*5}{THEME.main_fg}{g_color}{c_color}' + (f' {cpu:>4.1f} ' if cpu < 100 else f'{cpu:>5.0f} ') + end + + (" " if proc.num_procs > cls.select_max else "")) + if pid in Graphs.pid_cpu: - if is_selected: c_color = THEME.proc_misc + #if is_selected: c_color = THEME.proc_misc out += f'{Mv.to(y+cy, x + w - 11)}{c_color if CONFIG.proc_colors else THEME.proc_misc}{Graphs.pid_cpu[pid](None if cls.moved else round(cpu))}{THEME.main_fg}' - if selected: out += f'{Fx.ub}{Term.fg}{Term.bg}' + if is_selected: out += f'{Fx.ub}{Term.fg}{Term.bg}{Mv.to(y+cy, x + w - 1)}{" " if proc.num_procs > cls.select_max else ""}' cy += 1 if cy == h: break if cy < h: for i in range(h-cy): out += f'{Mv.to(y+cy+i, x)}{" " * w}' - loc_string = f'{cls.start + cls.selected - 1}/{len(proc.processes)}' + loc_string = f'{cls.start + cls.selected - 1}/{proc.num_procs}' out += (f'{Mv.to(y+h, x + w - 15 - len(loc_string))}{THEME.proc_box}{Symbol.h_line*10}{Symbol.title_left}{THEME.title}' f'{Fx.b}{loc_string}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') + if proc.num_procs > cls.select_max: + Key.mouse["mouse_scroll_up"] = [[x+w-1, y]] + Key.mouse["mouse_scroll_down"] = [[x+w-1, y+h-1]] + scroll_pos = round(cls.start * (cls.select_max - 2) / (proc.num_procs - (cls.select_max - 2))) + if scroll_pos < 0 or cls.start == 1: scroll_pos = 0 + elif scroll_pos > h - 3 or cls.start >= proc.num_procs - cls.select_max: scroll_pos = h - 3 + out += (f'{Mv.to(y, x+w-1)}{Fx.b}{THEME.main_fg}↑{Mv.to(y+h-1, x+w-1)}↓{Fx.ub}' + f'{Mv.to(y+1+scroll_pos, x+w-1)}█') + elif "scroll_up" in Key.mouse: + del Key.mouse["scroll_up"], Key.mouse["scroll_down"] + #▼▲ ↓ ↑ cls.count += 1 if cls.count == 100: cls.count == 0 @@ -1992,6 +2052,7 @@ class Collector: collect_done = threading.Event() collect_queue: List = [] collect_interrupt: bool = False + proc_interrupt: bool = False use_draw_list: bool = False @classmethod @@ -2045,15 +2106,18 @@ class Collector: cls.collect_done.set() except Exception as e: errlog.exception(f'Data collection thread failed with exception: {e}') + cls.collect_idle.set() cls.collect_done.set() clean_quit(1, thread=True) @classmethod - def collect(cls, *collectors, draw_now: bool = True, interrupt: bool = False, redraw: bool = False, only_draw: bool = False): + def collect(cls, *collectors, draw_now: bool = True, interrupt: bool = False, proc_interrupt: bool = False, redraw: bool = False, only_draw: bool = False): '''Setup collect queue for _runner''' cls.collect_interrupt = interrupt + cls.proc_interrupt = proc_interrupt cls.collect_idle.wait() cls.collect_interrupt = False + cls.proc_interrupt = False cls.use_draw_list = False cls.draw_now = draw_now cls.redraw = redraw @@ -2411,6 +2475,7 @@ class NetCollector(Collector): elif cls.nic_i < 0: cls.nic_i = len(cls.nics) - 1 cls.new_nic = cls.nics[cls.nic_i] cls.switched = True + Collector.collect(NetCollector, redraw=True) @classmethod def _collect(cls): @@ -2505,9 +2570,11 @@ class ProcCollector(Collector): #! add interrupt on _collect and _draw search_filter: str = "" processes: Dict = {} num_procs: int = 0 + detailed: bool = False + details: Dict[str, Union[str, int, float, NamedTuple]] = {} + details_cpu: List[int] = [] proc_dict: Dict = {} p_values: List[str] = ["pid", "name", "cmdline", "num_threads", "username", "memory_percent", "cpu_percent", "cpu_times", "create_time"] - sorting_index: int = Config.sorting_options.index(CONFIG.proc_sorting) sort_expr: Dict = {} sort_expr["pid"] = compile("p.info['pid']", "str", "eval") sort_expr["program"] = compile("p.info['name']", "str", "eval") @@ -2529,6 +2596,39 @@ class ProcCollector(Collector): #! add interrupt on _collect and _draw err: float = 0.0 n: int = 0 + if cls.detailed and not cls.details.get("killed", False): + try: + det = psutil.Process(ProcBox.selected_pid) + except (psutil.NoSuchProcess, psutil.ZombieProcess): + cls.details["killed"] = True + cls.details["status"] = psutil.STATUS_DEAD + else: + cls.details = det.as_dict(attrs=["pid", "name", "username", "status", "cmdline", "memory_info", "memory_percent", "create_time", "num_threads", "cpu_percent", "cpu_num"], ad_value="") + if det.parent() != None: cls.details["parent_name"] = det.parent().name() + else: cls.details["parent_name"] = "" + + cls.details["killed"] = False + + if isinstance(cls.details["cmdline"], list): cls.details["cmdline"] = " ".join(cls.details["cmdline"]) or f'[{cls.details["name"]}]' + + if hasattr(cls.details["memory_info"], "rss"): cls.details["memory_bytes"] = floating_humanizer(cls.details["memory_info"].rss) # type: ignore + else: cls.details["memory_bytes"] = "? Bytes" + + if isinstance(cls.details["create_time"], float): cls.details["uptime"] = f'{timedelta(seconds=round(time()-cls.details["create_time"],0))}' + else: cls.details["uptime"] = "??:??:??" + + for v in ["cpu_percent", "memory_percent"]: + if isinstance(cls.details[v], float): + if cls.details[v] >= 100: cls.details[v] = round(cls.details[v]) # type: ignore + elif cls.details[v] >= 10: cls.details[v] = round(cls.details[v], 1) # type: ignore + else: cls.details[v] = round(cls.details[v], 2) # type: ignore + else: + cls.details[v] = 0.0 + + cls.details_cpu.append(round(cls.details["cpu_percent"])) # type: ignore + if len(cls.details_cpu) > ProcBox.width: del cls.details_cpu[0] + + if CONFIG.proc_tree and sorting == "arguments": sorting = "program" @@ -2539,7 +2639,7 @@ class ProcCollector(Collector): #! add interrupt on _collect and _draw return for p in sorted(psutil.process_iter(cls.p_values, err), key=lambda p: eval(sort_cmd), reverse=reverse): - if cls.collect_interrupt: + if cls.collect_interrupt or cls.proc_interrupt: return if p.info["name"] == "idle" or p.info["name"] == err or p.info["pid"] == err: continue @@ -2593,7 +2693,6 @@ class ProcCollector(Collector): #! add interrupt on _collect and _draw else: infolist[p.pid] = p.info n += 1 - cls.num_procs = n if 0 in tree and 0 in tree[0]: tree[0].remove(0) @@ -2609,6 +2708,7 @@ class ProcCollector(Collector): #! add interrupt on _collect and _draw except psutil.Error: pass cont = False + name = "" if pid in infolist: getinfo = infolist[pid] else: @@ -2655,15 +2755,16 @@ class ProcCollector(Collector): #! add interrupt on _collect and _draw create_tree(min(tree), tree) if cls.collect_interrupt: return + cls.num_procs = len(out) cls.processes = out.copy() @classmethod def sorting(cls, key: str): - cls.sorting_index += 1 if key == "right" else -1 - if cls.sorting_index >= len(CONFIG.sorting_options): cls.sorting_index = 0 - elif cls.sorting_index < 0: cls.sorting_index = len(CONFIG.sorting_options) - 1 - CONFIG.proc_sorting = CONFIG.sorting_options[cls.sorting_index] - ProcBox.redraw = True + index: int = CONFIG.sorting_options.index(CONFIG.proc_sorting) + (1 if key == "right" else -1) + if index >= len(CONFIG.sorting_options): index = 0 + elif index < 0: index = len(CONFIG.sorting_options) - 1 + CONFIG.proc_sorting = CONFIG.sorting_options[index] + Collector.collect(ProcCollector, interrupt=True, redraw=True) @classmethod def _draw(cls): @@ -2955,6 +3056,7 @@ def floating_humanizer(value: Union[float, int], bit: bool = False, per_second: if isinstance(value, float): value = round(value * 100 * mult) elif value > 0: value *= 100 * mult + else: value = 0 while len(f'{value}') > 5 and value >= 102400: value >>= 10 @@ -2979,14 +3081,37 @@ def floating_humanizer(value: Union[float, int], bit: bool = False, per_second: return out def process_keys(): - mouse_pos: Tuple[int, int] + mouse_pos: Tuple[int, int] = (0, 0) + filtered: bool = False while Key.has_key(): key = Key.get() - if key in ["mouse_scroll_up", "mouse_scroll_down"]: + if key in ["mouse_scroll_up", "mouse_scroll_down", "mouse_click"]: mouse_pos = Key.get_mouse() - if mouse_pos[0] >= ProcBox.x and mouse_pos[1] >= ProcBox.y: + if mouse_pos[0] >= ProcBox.x and mouse_pos[1] >= ProcBox.current_y: pass - else: key = "_null" + elif key == "mouse_click": + key = "mouse_unselect" + else: + key = "_null" + + if ProcBox.filtering: + if key in ["enter", "mouse_click", "mouse_unselect"]: + ProcBox.filtering = False + Collector.collect(ProcCollector, redraw=True, only_draw=True) + continue + elif key in ["escape", "delete"]: + ProcCollector.search_filter = "" + ProcBox.filtering = False + elif len(key) == 1: + ProcCollector.search_filter += key + elif key == "backspace" and len(ProcCollector.search_filter) > 0: + ProcCollector.search_filter = ProcCollector.search_filter[:-1] + else: + continue + Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) + if filtered: Collector.collect_done.wait(0.1) + filtered = True + continue if key == "_null": @@ -3001,7 +3126,6 @@ def process_keys(): Box.draw_update_ms() elif key in ["b", "n"]: NetCollector.switch(key) - Collector.collect(NetCollector, redraw=True) elif key in ["m", "escape"]: Menu.main() elif key == "z": @@ -3009,7 +3133,6 @@ def process_keys(): Collector.collect(NetCollector) elif key in ["left", "right"]: ProcCollector.sorting(key) - Collector.collect(ProcCollector, interrupt=True, redraw=True) elif key == "e": CONFIG.proc_tree = not CONFIG.proc_tree Collector.collect(ProcCollector, interrupt=True, redraw=True) @@ -3027,27 +3150,30 @@ def process_keys(): Collector.collect(ProcCollector, interrupt=True, redraw=True) elif key == "h": CONFIG.mem_graphs = not CONFIG.mem_graphs - Collector.collect(MemCollector, redraw=True) + Collector.collect(MemCollector, interrupt=True, redraw=True) elif key == "p": CONFIG.swap_disk = not CONFIG.swap_disk - Collector.collect(MemCollector, redraw=True) - elif key in ["up", "down", "mouse_scroll_up", "mouse_scroll_down", "page_up", "page_down", "home", "end"]: - if ProcBox.selector(key): - Collector.collect(ProcCollector, only_draw=True) + Collector.collect(MemCollector, interrupt=True, redraw=True) + elif key == "f": + ProcBox.filtering = True + if not ProcCollector.search_filter: ProcBox.start = 0 + Collector.collect(ProcCollector, redraw=True, only_draw=True) + elif key == "delete" and ProcCollector.search_filter: + ProcCollector.search_filter = "" + Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) + elif key == "enter" and ProcBox.selected > 0: + if ProcCollector.details.get("pid", None) == ProcBox.selected_pid: + ProcCollector.detailed = False + elif psutil.pid_exists(ProcBox.selected_pid): + ProcCollector.detailed = True + else: + continue + ProcCollector.details = {} + ProcCollector.details_cpu = [] + Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) -#? Main function ---------------------------------------------------------------------------------> - -def main(): - Term.refresh() - Timer.stamp() - - while Timer.not_zero(): - if Key.input_wait(Timer.left()) and not Menu.active: - process_keys() - - Collector.collect() - - # Draw.buffer("!mouse_pos", f'{Key.mouse_pos}', z=0) + elif key in ["up", "down", "mouse_scroll_up", "mouse_scroll_down", "page_up", "page_down", "home", "end", "mouse_click", "mouse_unselect"]: + ProcBox.selector(key, mouse_pos) #? Pre main --------------------------------------------------------------------------------------> @@ -3085,12 +3211,14 @@ if __name__ == "__main__": Draw.buffer("initbg", z=10) for i in range(51): for _ in range(2): cls.initbg_colors.append(Color.fg(i, i, i)) - Draw.buffer("banner", f'{Banner.draw(Term.height // 2 - 10, center=True)}{Color.fg("#50")}\n', z=2) + Draw.buffer("banner", (f'{Banner.draw(Term.height // 2 - 10, center=True)}{Mv.d(1)}{Mv.l(11)}{Colors.black_bg}{Colors.default}' + f'{Fx.b}{Fx.i}Version: {VERSION}{Fx.ui}{Fx.ub}{Term.bg}{Term.fg}{Color.fg("#50")}'), z=2) for _i in range(7): perc = f'{str(round((_i + 1) * 14 + 2)) + "%":>5}' - Draw.buffer("+banner", f'{Mv.to(Term.height // 2 - 3 + _i, Term.width // 2 - 28)}{Fx.trans(perc)}{Symbol.v_line}') + Draw.buffer("+banner", f'{Mv.to(Term.height // 2 - 2 + _i, Term.width // 2 - 28)}{Fx.trans(perc)}{Symbol.v_line}') + Draw.out("banner") - Draw.buffer("+init!", f'{Color.fg("#cc")}{Fx.b}{Mv.to(Term.height // 2 - 3, Term.width // 2 - 21)}{Mv.save}') + Draw.buffer("+init!", f'{Color.fg("#cc")}{Fx.b}{Mv.to(Term.height // 2 - 2, Term.width // 2 - 21)}{Mv.save}') cls.initbg_data = [randint(0, 100) for _ in range(Term.width * 2)] cls.initbg_up = Graph(Term.width, Term.height // 2, cls.initbg_colors, cls.initbg_data, invert=True) @@ -3098,8 +3226,7 @@ if __name__ == "__main__": if start: return - - cls.draw_bg(5) + cls.draw_bg(10) Draw.buffer("+init!", f'{Mv.restore}{Symbol.ok}\n{Mv.r(Term.width // 2 - 22)}{Mv.save}') @classmethod @@ -3117,14 +3244,13 @@ if __name__ == "__main__": if cls.resized: Draw.now(Term.clear) else: - cls.draw_bg(15) - Draw.clear("initbg", "banner", "init") + cls.draw_bg(20) + Draw.clear("initbg", "banner", "init", saved=True) del cls.initbg_up, cls.initbg_down, cls.initbg_data, cls.initbg_colors - #? Switch to alternate screen, clear screen, hide cursor and disable input echo + #? Switch to alternate screen, clear screen, hide cursor, enable mouse reporting and disable input echo Draw.now(Term.alt_screen, Term.clear, Term.hide_cursor, Term.mouse_on) - Term.echo(False) Term.refresh() @@ -3212,13 +3338,25 @@ if __name__ == "__main__": Draw.out(clear=True) if DEBUG: TimeIt.stop("Init") + #? Main loop -------------------------------------------------------------------------------------> + + def main(): + while not False: + Term.refresh() + Timer.stamp() + + while Timer.not_zero(): + if Key.input_wait(Timer.left()) and not Menu.active: + process_keys() + + Collector.collect() + #? Start main loop - while not False: - try: - main() - except Exception as e: - errlog.exception(f'{e}') - clean_quit(1) + try: + main() + except Exception as e: + errlog.exception(f'{e}') + clean_quit(1) else: #? Quit cleanly even if false starts being true... clean_quit()