From 3cde5a3f9d90f8ed7c113dd46face0ffd064dddc Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sat, 2 Jan 2021 20:51:27 +0100 Subject: [PATCH] Changed: All boxes are now toggeable with key 1-4 --- bpytop.py | 430 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 262 insertions(+), 168 deletions(-) diff --git a/bpytop.py b/bpytop.py index 30e5413..7c1b0eb 100755 --- a/bpytop.py +++ b/bpytop.py @@ -59,11 +59,9 @@ VERSION: str = "1.0.54" #? Argument parser -------------------------------------------------------------------------------> args = argparse.ArgumentParser() -args.add_argument("-f" , "--full" ,action="store_true" ,help ="Start in full mode showing all boxes [default]") -args.add_argument("-p" , "--proc" ,action="store_true" ,help ="Start in minimal mode without memory and net boxes") -args.add_argument("-s" , "--stat" ,action="store_true" ,help ="Start in minimal mode without process box") -args.add_argument("-v" , "--version" ,action="store_true" ,help ="Show version info and exit") -args.add_argument("--debug" ,action="store_true" ,help ="Start with loglevel set to DEBUG overriding value set in config") +args.add_argument("-b", "--boxes", action="store", dest="boxes", help ="Which boxes to show at start, example: -b \"cpu mem net proc\"") +args.add_argument("-v", "--version", action="store_true" ,help ="Show version info and exit") +args.add_argument("--debug", action="store_true" ,help ="Start with loglevel set to DEBUG overriding value set in config") stdargs = args.parse_args() if stdargs.version: @@ -71,14 +69,7 @@ if stdargs.version: f'psutil version: {".".join(str(x) for x in psutil.version_info)}') raise SystemExit(0) -ARG_MODE: str = "" - -if stdargs.full: - ARG_MODE = "full" -elif stdargs.proc: - ARG_MODE = "proc" -elif stdargs.stat: - ARG_MODE = "stat" +ARG_BOXES: str = stdargs.boxes DEBUG = stdargs.debug @@ -103,8 +94,8 @@ color_theme="$color_theme" #* If the theme set background should be shown, set to False if you want terminal background transparency theme_background=$theme_background -#* Set bpytop view mode, "full" for everything shown, "proc" for cpu stats and processes, "stat" for cpu, mem, disks and net stats shown. -view_mode=$view_mode +#* Manually set which boxes to show. Available values are "cpu mem net proc", seperate values with whitespace. +shown_boxes="$shown_boxes" #* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs. update_ms=$update_ms @@ -310,6 +301,8 @@ UNITS: Dict[str, Tuple[str, ...]] = { "byte" : ("Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB") } +SUBSCRIPT: Tuple[str, ...] = ("₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉") + #? Setup error logger ----------------------------------------------------------------> try: @@ -363,11 +356,12 @@ class Config: '''Holds all config variables and functions for loading from and saving to disk''' keys: List[str] = ["color_theme", "update_ms", "proc_sorting", "proc_reversed", "proc_tree", "check_temp", "draw_clock", "background_update", "custom_cpu_name", "proc_colors", "proc_gradient", "proc_per_core", "proc_mem_bytes", "disks_filter", "update_check", "log_level", "mem_graphs", "show_swap", - "swap_disk", "show_disks", "net_download", "net_upload", "net_auto", "net_color_fixed", "show_init", "view_mode", "theme_background", - "net_sync", "show_battery", "tree_depth", "cpu_sensor", "show_coretemp", "proc_update_mult"] + "swap_disk", "show_disks", "net_download", "net_upload", "net_auto", "net_color_fixed", "show_init", "theme_background", + "net_sync", "show_battery", "tree_depth", "cpu_sensor", "show_coretemp", "proc_update_mult", "shown_boxes"] conf_dict: Dict[str, Union[str, int, bool]] = {} color_theme: str = "Default" theme_background: bool = True + shown_boxes: str = "cpu mem net proc" update_ms: int = 2000 proc_update_mult: int = 2 proc_sorting: str = "cpu lazy" @@ -397,7 +391,6 @@ class Config: net_sync: bool = False show_battery: bool = True show_init: bool = True - view_mode: str = "full" log_level: str = "WARNING" warnings: List[str] = [] @@ -406,8 +399,6 @@ class Config: sorting_options: List[str] = ["pid", "program", "arguments", "threads", "user", "memory", "cpu lazy", "cpu responsive"] log_levels: List[str] = ["ERROR", "WARNING", "INFO", "DEBUG"] - view_modes: List[str] = ["full", "proc", "stat"] - cpu_sensors: List[str] = [ "Auto" ] if hasattr(psutil, "sensors_temperatures"): @@ -494,9 +485,6 @@ class Config: if "log_level" in new_config and not new_config["log_level"] in self.log_levels: new_config["log_level"] = "_error_" self.warnings.append(f'Config key "log_level" didn\'t get an acceptable value!') - if "view_mode" in new_config and not new_config["view_mode"] in self.view_modes: - new_config["view_mode"] = "_error_" - self.warnings.append(f'Config key "view_mode" didn\'t get an acceptable value!') if "update_ms" in new_config and int(new_config["update_ms"]) < 100: new_config["update_ms"] = 100 self.warnings.append(f'Config key "update_ms" can\'t be lower than 100!') @@ -506,6 +494,12 @@ class Config: if "cpu_sensor" in new_config and not new_config["cpu_sensor"] in self.cpu_sensors: new_config["cpu_sensor"] = "_error_" self.warnings.append(f'Config key "cpu_sensor" does not contain an available sensor!') + if "shown_boxes" in new_config and not new_config["shown_boxes"] == "": + for box in new_config["shown_boxes"].split(): #type: ignore + if not box in ["cpu", "mem", "net", "proc"]: + new_config["shown_boxes"] = "_error_" + self.warnings.append(f'Config key "shown_boxes" contains invalid box names!') + break return new_config def save_config(self): @@ -540,6 +534,14 @@ except Exception as e: errlog.exception(f'{e}') raise SystemExit(1) +if ARG_BOXES: + _new_boxes: List = [] + for _box in ARG_BOXES.split(): + if _box in ["cpu", "mem", "net", "proc"]: + _new_boxes.append(_box) + CONFIG.shown_boxes = " ".join(_new_boxes) + del _box, _new_boxes + if SYSTEM == "Linux" and not os.path.isdir("/sys/class/power_supply"): CONFIG.show_battery = False @@ -1504,14 +1506,22 @@ class Meters: class Box: '''Box class with all needed attributes for create_box() function''' name: str + num: int = 0 + boxes: List = [] + view_modes: Dict[str, List] = {"full" : ["cpu", "mem", "net", "proc"], "stat" : ["cpu", "mem", "net"], "proc" : ["cpu", "proc"]} + view_mode: str + for view_mode in view_modes: + if sorted(CONFIG.shown_boxes.split(), key=str.lower) == view_modes[view_mode]: + break + else: + view_mode = "user" + view_modes["user"] = CONFIG.shown_boxes.split() height_p: int width_p: int x: int y: int width: int height: int - proc_mode: bool = (CONFIG.view_mode == "proc" and not ARG_MODE) or ARG_MODE == "proc" - stat_mode: bool = (CONFIG.view_mode == "stat" and not ARG_MODE) or ARG_MODE == "stat" out: str bg: str _b_cpu_h: int @@ -1532,12 +1542,14 @@ class Box: @classmethod def calc_sizes(cls): '''Calculate sizes of boxes''' + cls.boxes = CONFIG.shown_boxes.split() for sub in cls.__subclasses__(): sub._calc_size() # type: ignore sub.resized = True # type: ignore @classmethod def draw_update_ms(cls, now: bool = True): + if not "cpu" in cls.boxes: return update_string: str = f'{CONFIG.update_ms}ms' xpos: int = CpuBox.x + CpuBox.width - len(update_string) - 15 if not "+" in Key.mouse: @@ -1553,9 +1565,10 @@ class Box: @classmethod def draw_clock(cls, force: bool = False): + if not "cpu" in cls.boxes or not cls.clock_on: return out: str = "" if force: pass - elif not cls.clock_on or Term.resized or strftime(CONFIG.draw_clock) == cls.clock: return + elif Term.resized or strftime(CONFIG.draw_clock) == cls.clock: return clock_string = cls.clock = strftime(CONFIG.draw_clock) for custom in cls.clock_custom_format: if custom in clock_string: @@ -1572,10 +1585,28 @@ class Box: if CONFIG.show_battery and hasattr(psutil, "sensors_battery") and psutil.sensors_battery(): Draw.out("battery") + @classmethod + def empty_bg(cls) -> str: + return (f'{Term.clear}{Banner.draw(Term.height // 2 - 10, center=True)}' + f'{Mv.d(1)}{Mv.l(46)}{Colors.black_bg}{Colors.default}{Fx.b}[esc] Menu' + f'{Mv.r(25)}{Fx.i}Version: {VERSION}{Fx.ui}' + f'{Mv.d(1)}{Mv.l(34)}{Fx.b}All boxes hidden!' + f'{Mv.d(1)}{Mv.l(17)}{Fx.b}[1] {Fx.ub}Toggle CPU box' + f'{Mv.d(1)}{Mv.l(18)}{Fx.b}[2] {Fx.ub}Toggle MEM box' + f'{Mv.d(1)}{Mv.l(18)}{Fx.b}[3] {Fx.ub}Toggle NET box' + f'{Mv.d(1)}{Mv.l(18)}{Fx.b}[4] {Fx.ub}Toggle PROC box' + f'{Mv.d(1)}{Mv.l(19)}{Fx.b}[m] {Fx.ub}Cycle presets' + f'{Mv.d(1)}{Mv.l(17)}{Fx.b}[q] Quit {Fx.ub}{Term.bg}{Term.fg}') + @classmethod def draw_bg(cls, now: bool = True): '''Draw all boxes outlines and titles''' - Draw.buffer("bg", "".join(sub._draw_bg() for sub in cls.__subclasses__()), now=now, z=1000, only_save=Menu.active, once=True) # type: ignore + out: str = "" + if not cls.boxes: + out = cls.empty_bg() + else: + out = "".join(sub._draw_bg() for sub in cls.__subclasses__()) # type: ignore + Draw.buffer("bg", out, now=now, z=1000, only_save=Menu.active, once=True) cls.draw_update_ms(now=now) if CONFIG.draw_clock: cls.draw_clock(force=True) @@ -1589,6 +1620,7 @@ class SubBox: class CpuBox(Box, SubBox): name = "cpu" + num = 1 x = 1 y = 1 height_p = 32 @@ -1612,10 +1644,16 @@ class CpuBox(Box, SubBox): @classmethod def _calc_size(cls): + if not "cpu" in cls.boxes: + Box._b_cpu_h = 0 + cls.width = Term.width + return cpu = CpuCollector height_p: int - if cls.proc_mode: height_p = 20 - else: height_p = cls.height_p + if cls.boxes == ["cpu"]: + height_p = 100 + else: + height_p = cls.height_p cls.width = round(Term.width * cls.width_p / 100) cls.height = round(Term.height * height_p / 100) if cls.height < 8: cls.height = 8 @@ -1643,6 +1681,7 @@ class CpuBox(Box, SubBox): @classmethod def _draw_bg(cls) -> str: + if not "cpu" in cls.boxes: return "" if not "M" in Key.mouse: Key.mouse["M"] = [[cls.x + 10 + i, cls.y] for i in range(6)] return (f'{create_box(box=cls, line_color=THEME.cpu_box)}' @@ -1692,6 +1731,7 @@ class CpuBox(Box, SubBox): @classmethod def _draw_fg(cls): + if not "cpu" in cls.boxes: return cpu = CpuCollector if cpu.redraw: cls.redraw = True out: str = "" @@ -1706,7 +1746,7 @@ class CpuBox(Box, SubBox): if cls.resized or cls.redraw: if not "m" in Key.mouse: Key.mouse["m"] = [[cls.x + 16 + i, cls.y] for i in range(12)] - out_misc += f'{Mv.to(cls.y, cls.x + 16)}{THEME.cpu_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg("m")}{THEME.title}ode:{ARG_MODE or CONFIG.view_mode}{Fx.ub}{THEME.cpu_box(Symbol.title_right)}' + out_misc += f'{Mv.to(cls.y, cls.x + 16)}{THEME.cpu_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg("m")}{THEME.title}ode:{Box.view_mode}{Fx.ub}{THEME.cpu_box(Symbol.title_right)}' Graphs.cpu["up"] = Graph(w - bw - 3, hh, THEME.gradient["cpu"], cpu.cpu_usage[0]) Graphs.cpu["down"] = Graph(w - bw - 3, h - hh, THEME.gradient["cpu"], cpu.cpu_usage[0], invert=True) Meters.cpu = Meter(cpu.cpu_usage[0][-1], bw - (21 if cpu.got_sensors else 9), "cpu") @@ -1812,6 +1852,7 @@ class CpuBox(Box, SubBox): class MemBox(Box): name = "mem" + num = 2 height_p = 38 width_p = 45 x = 1 @@ -1833,11 +1874,23 @@ class MemBox(Box): @classmethod def _calc_size(cls): + if not "mem" in cls.boxes: + Box._b_mem_h = 0 + cls.width = Term.width + return width_p: int; height_p: int - if cls.stat_mode: - width_p, height_p = 100, cls.height_p + if not "proc" in cls.boxes: + width_p = 100 + else: + width_p = cls.width_p + + if not "cpu" in cls.boxes: + height_p = 60 if "net" in cls.boxes else 98 + elif not "net" in cls.boxes: + height_p = 98 - CpuBox.height_p else: - width_p, height_p = cls.width_p, cls.height_p + height_p = cls.height_p + cls.width = round(Term.width * width_p / 100) cls.height = round(Term.height * height_p / 100) + 1 Box._b_mem_h = cls.height @@ -1874,19 +1927,23 @@ class MemBox(Box): @classmethod def _draw_bg(cls) -> str: - if cls.proc_mode: return "" + if not "mem" in cls.boxes: return "" out: str = "" out += f'{create_box(box=cls, line_color=THEME.mem_box)}' if CONFIG.show_disks: - out += (f'{Mv.to(cls.y, cls.divider + 2)}{THEME.mem_box(Symbol.title_left)}{Fx.b}{THEME.title("disks")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}' + out += (f'{Mv.to(cls.y, cls.divider + 2)}{THEME.mem_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg("d")}{THEME.title("isks")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}' f'{Mv.to(cls.y, cls.divider)}{THEME.mem_box(Symbol.div_up)}' f'{Mv.to(cls.y + cls.height - 1, cls.divider)}{THEME.mem_box(Symbol.div_down)}{THEME.div_line}' f'{"".join(f"{Mv.to(cls.y + i, cls.divider)}{Symbol.v_line}" for i in range(1, cls.height - 1))}') + Key.mouse["d"] = [[cls.divider + 3 + i, cls.y] for i in range(5)] + else: + out += f'{Mv.to(cls.y, cls.x + cls.width - 9)}{THEME.mem_box(Symbol.title_left)}{THEME.hi_fg("d")}{THEME.title("isks")}{THEME.mem_box(Symbol.title_right)}' + Key.mouse["d"] = [[cls.x + cls.width - 8 + i, cls.y] for i in range(5)] return out @classmethod def _draw_fg(cls): - if cls.proc_mode: return + if not "mem" in cls.boxes: return mem = MemCollector if mem.redraw: cls.redraw = True out: str = "" @@ -1923,8 +1980,8 @@ class MemBox(Box): if len(mem.disks) * 3 <= h + 1: Meters.disks_free[name] = Meter(mem.disks[name]["free_percent"], cls.disk_meter, "free") if not "g" in Key.mouse: - Key.mouse["g"] = [[x + cls.mem_width - 8 + i, y-1] for i in range(5)] - out_misc += (f'{Mv.to(y-1, x + cls.mem_width - 9)}{THEME.mem_box(Symbol.title_left)}{Fx.b if CONFIG.mem_graphs else ""}' + Key.mouse["g"] = [[x + 8 + i, y-1] for i in range(5)] + out_misc += (f'{Mv.to(y-1, x + 7)}{THEME.mem_box(Symbol.title_left)}{Fx.b if CONFIG.mem_graphs else ""}' f'{THEME.hi_fg("g")}{THEME.title("raph")}{Fx.ub}{THEME.mem_box(Symbol.title_right)}') if CONFIG.show_disks: if not "s" in Key.mouse: @@ -2004,6 +2061,7 @@ class MemBox(Box): class NetBox(Box, SubBox): name = "net" + num = 3 height_p = 30 width_p = 45 x = 1 @@ -2018,11 +2076,14 @@ class NetBox(Box, SubBox): @classmethod def _calc_size(cls): - width_p: int - if cls.stat_mode: + if not "net" in cls.boxes: + cls.width = Term.width + return + if not "proc" in cls.boxes: width_p = 100 else: width_p = cls.width_p + cls.width = round(Term.width * width_p / 100) cls.height = Term.height - Box._b_cpu_h - Box._b_mem_h cls.y = Term.height - cls.height + 1 @@ -2036,13 +2097,13 @@ class NetBox(Box, SubBox): @classmethod def _draw_bg(cls) -> str: - if cls.proc_mode: return "" + if not "net" in cls.boxes: return "" return f'{create_box(box=cls, line_color=THEME.net_box)}\ {create_box(x=cls.box_x, y=cls.box_y, width=cls.box_width, height=cls.box_height, line_color=THEME.div_line, fill=False, title="Download", title2="Upload")}' @classmethod def _draw_fg(cls): - if cls.proc_mode: return + if not "net" in cls.boxes: return net = NetCollector if net.redraw: cls.redraw = True if not net.nic: return @@ -2103,6 +2164,7 @@ class NetBox(Box, SubBox): class ProcBox(Box): name = "proc" + num = 4 height_p = 68 width_p = 55 x = 1 @@ -2131,11 +2193,20 @@ class ProcBox(Box): @classmethod def _calc_size(cls): + if not "proc" in cls.boxes: + cls.width = Term.width + return width_p: int; height_p: int - if cls.proc_mode: - width_p, height_p = 100, 80 + if not "net" in cls.boxes and not "mem" in cls.boxes: + width_p = 100 + else: + width_p = cls.width_p + + if not "cpu" in cls.boxes: + height_p = 100 else: - width_p, height_p = cls.width_p, cls.height_p + height_p = cls.height_p + cls.width = round(Term.width * width_p / 100) cls.height = round(Term.height * height_p / 100) if cls.height + Box._b_cpu_h > Term.height: cls.height = Term.height - Box._b_cpu_h @@ -2149,7 +2220,7 @@ class ProcBox(Box): @classmethod def _draw_bg(cls) -> str: - if cls.stat_mode: return "" + if not "proc" in cls.boxes: return "" return create_box(box=cls, line_color=THEME.proc_box) @classmethod @@ -2218,7 +2289,7 @@ class ProcBox(Box): @classmethod def _draw_fg(cls): - if cls.stat_mode: return + if not "proc" in cls.boxes: return proc = ProcCollector if proc.proc_interrupt: return if proc.redraw: cls.redraw = True @@ -2276,7 +2347,7 @@ class ProcBox(Box): for k in ["e", "r", "c", "t", "k", "i", "enter", "left", " ", "f", "delete"]: if k in Key.mouse: del Key.mouse[k] if proc.detailed: - killed = proc.details["killed"] + killed = proc.details.get("killed", False) main = THEME.main_fg if cls.selected == 0 and not killed else THEME.inactive_fg hi = THEME.hi_fg if cls.selected == 0 and not killed else THEME.inactive_fg title = THEME.title if cls.selected == 0 and not killed else THEME.inactive_fg @@ -2285,7 +2356,7 @@ class ProcBox(Box): cls.current_h = cls.height - 8 for i in range(7): out_misc += f'{Mv.to(dy+i, x)}{" " * w}' out_misc += (f'{Mv.to(dy+7, x-1)}{THEME.proc_box}{Symbol.title_right}{Symbol.h_line*w}{Symbol.title_left}' - f'{Mv.to(dy+7, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.title(cls.name)}{Fx.ub}{THEME.proc_box(Symbol.title_right)}{THEME.div_line}') + f'{Mv.to(dy+7, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(SUBSCRIPT[cls.num])}{THEME.title(cls.name)}{Fx.ub}{THEME.proc_box(Symbol.title_right)}{THEME.div_line}') for i in range(7): out_misc += f'{Mv.to(dy + i, dgx + dgw + 1)}{Symbol.v_line}' @@ -2322,7 +2393,7 @@ class ProcBox(Box): cls.current_h = cls.height y, h = cls.y + 1, cls.height - 2 out_misc += (f'{Mv.to(y-1, x-1)}{THEME.proc_box}{Symbol.left_up}{Symbol.h_line*w}{Symbol.right_up}' - f'{Mv.to(y-1, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.title(cls.name)}{Fx.ub}{THEME.proc_box(Symbol.title_right)}' + f'{Mv.to(y-1, x+1)}{THEME.proc_box(Symbol.title_left)}{Fx.b}{THEME.hi_fg(SUBSCRIPT[cls.num])}{THEME.title(cls.name)}{Fx.ub}{THEME.proc_box(Symbol.title_right)}' f'{Mv.to(y+7, x-1)}{THEME.proc_box(Symbol.v_line)}{Mv.r(w)}{THEME.proc_box(Symbol.v_line)}') cls.select_max = cls.height - 3 @@ -2352,12 +2423,12 @@ class ProcBox(Box): 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-")}{THEME.hi_fg("c")}{THEME.title("ore")}{Fx.ub}{THEME.proc_box(Symbol.title_right)}') - if not "f" in Key.mouse or cls.resized: Key.mouse["f"] = [[x+5 + i, y-1] for i in range(6 if not proc.search_filter else 2 + len(proc.search_filter[-10:]))] + if not "f" in Key.mouse or cls.resized: Key.mouse["f"] = [[x+6 + i, y-1] for i in range(6 if not proc.search_filter else 2 + len(proc.search_filter[-10:]))] if proc.search_filter: - if not "delete" in Key.mouse: Key.mouse["delete"] = [[x+11 + len(proc.search_filter[-10:]) + i, y-1] for i in range(3)] + if not "delete" in Key.mouse: 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 + 7)}{THEME.proc_box(Symbol.title_left)}{Fx.b if cls.filtering or proc.search_filter else ""}{THEME.hi_fg("f")}{THEME.title}' + + 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)}') @@ -2656,8 +2727,6 @@ class Collector: cls.proc_counter = 0 cls.proc_counter += 1 - - cls.collect_run.set() @@ -3278,7 +3347,7 @@ class ProcCollector(Collector): @classmethod def _collect(cls): '''List all processess with pid, name, arguments, threads, username, memory percent and cpu percent''' - if Box.stat_mode: return + if not "proc" in Box.boxes: return out: Dict = {} cls.det_cpu = 0.0 sorting: str = CONFIG.proc_sorting @@ -3694,7 +3763,12 @@ class Menu: "Selected (Mouse 1)" : "Show detailed information for selected process.", "(Mouse scroll)" : "Scrolls any scrollable list/text under cursor.", "(Esc, shift+m)" : "Toggles main menu.", - "(m)" : "Change current view mode, order full->proc->stat.", + "(m)" : "Cycle view presets, order: full->proc->stat->user.", + "(1)" : "Toggle CPU box.", + "(2)" : "Toggle MEM box.", + "(3)" : "Toggle NET box.", + "(4)" : "Toggle PROC box.", + "(d)" : "Toggle disks view in MEM box.", "(F2, o)" : "Shows options.", "(F1, h)" : "Shows this window.", "(ctrl+z)" : "Sleep program and put in background.", @@ -3819,7 +3893,6 @@ class Menu: d_quote: str inputting: bool = False input_val: str = "" - global ARG_MODE Theme.refresh() if not cls.background: cls.background = f'{THEME.inactive_fg}' + Fx.uncolor(f'{Draw.saved_buffer()}') + f'{Term.fg}' @@ -3841,12 +3914,13 @@ class Menu: '', 'Set to False if you want terminal background', 'transparency.'], - "view_mode" : [ - 'Set bpytop view mode.', + "shown_boxes" : [ + 'Manually set which boxes to show.', + '', + 'Available values are "cpu mem net proc".', + 'Seperate values with whitespace.', '', - '"full" for everything shown.', - '"proc" for cpu stats and processes.', - '"stat" for cpu, mem, disks and net stats shown.'], + 'Toggle between presets with mode key "m".'], "update_ms" : [ 'Update time in milliseconds.', '', @@ -4050,7 +4124,6 @@ class Menu: option_len: int = len(option_items) * 2 sorting_i: int = CONFIG.sorting_options.index(CONFIG.proc_sorting) loglevel_i: int = CONFIG.log_levels.index(CONFIG.log_level) - view_mode_i: int = CONFIG.view_modes.index(CONFIG.view_mode) cpu_sensor_i: int = CONFIG.cpu_sensors.index(CONFIG.cpu_sensor) color_i: int while not cls.close: @@ -4096,15 +4169,13 @@ class Menu: counter = f' {sorting_i + 1}/{len(CONFIG.sorting_options)}' elif opt == "log_level": counter = f' {loglevel_i + 1}/{len(CONFIG.log_levels)}' - elif opt == "view_mode": - counter = f' {view_mode_i + 1}/{len(CONFIG.view_modes)}' elif opt == "cpu_sensor": counter = f' {cpu_sensor_i + 1}/{len(CONFIG.cpu_sensors)}' else: counter = "" out += f'{Mv.to(y+1+cy, x+1)}{t_color}{Fx.b}{opt.replace("_", " ").capitalize() + counter:^24.24}{Fx.ub}{Mv.to(y+2+cy, x+1)}{v_color}' if opt == selected: - if isinstance(value, bool) or opt in ["color_theme", "proc_sorting", "log_level", "view_mode", "cpu_sensor"]: + if isinstance(value, bool) or opt in ["color_theme", "proc_sorting", "log_level", "cpu_sensor"]: out += f'{t_color} {Symbol.left}{v_color}{d_quote + str(value) + d_quote:^20.20}{t_color}{Symbol.right} ' elif inputting: out += f'{str(input_val)[-17:] + Fx.bl + "█" + Fx.ubl + "" + Symbol.enter:^33.33}' @@ -4183,6 +4254,15 @@ class Menu: else: CONFIG.tree_depth = int(input_val) ProcCollector.collapsed = {} + elif selected == "shown_boxes": + new_boxes: List = [] + for box in input_val.split(): + if box in ["cpu", "mem", "net", "proc"]: + new_boxes.append(box) + CONFIG.shown_boxes = " ".join(new_boxes) + Box.view_mode = "user" + Box.view_modes["user"] = CONFIG.shown_boxes.split() + Draw.clear(saved=True) elif isinstance(getattr(CONFIG, selected), str): setattr(CONFIG, selected, input_val) if selected.startswith("net_"): @@ -4205,7 +4285,7 @@ class Menu: elif key in ["escape", "o", "M", "f2"]: cls.close = True break - elif key == "enter" and selected in ["update_ms", "disks_filter", "custom_cpu_name", "net_download", "net_upload", "draw_clock", "tree_depth", "proc_update_mult"]: + elif key == "enter" and selected in ["update_ms", "disks_filter", "custom_cpu_name", "net_download", "net_upload", "draw_clock", "tree_depth", "proc_update_mult", "shown_boxes"]: inputting = True input_val = str(getattr(CONFIG, selected)) elif key == "left" and selected == "update_ms" and CONFIG.update_ms - 100 >= 100: @@ -4282,21 +4362,6 @@ class Menu: CpuCollector.get_sensors() Term.refresh(force=True) cls.resized = False - elif key in ["left", "right"] and selected == "view_mode": - if key == "left": - view_mode_i -= 1 - if view_mode_i < 0: view_mode_i = len(CONFIG.view_modes) - 1 - elif key == "right": - view_mode_i += 1 - if view_mode_i > len(CONFIG.view_modes) - 1: view_mode_i = 0 - CONFIG.view_mode = CONFIG.view_modes[view_mode_i] - Box.proc_mode = CONFIG.view_mode == "proc" - Box.stat_mode = CONFIG.view_mode == "stat" - if ARG_MODE: - ARG_MODE = "" - Draw.clear(saved=True) - Term.refresh(force=True) - cls.resized = False elif key == "up": selected_int -= 1 if selected_int < 0: selected_int = len(option_items) - 1 @@ -4527,6 +4592,7 @@ def get_cpu_core_mapping() -> List[int]: def create_box(x: int = 0, y: int = 0, width: int = 0, height: int = 0, title: str = "", title2: str = "", line_color: Color = None, title_color: Color = None, fill: bool = True, box = None) -> str: '''Create a box from a box object or by given arguments''' out: str = f'{Term.fg}{Term.bg}' + num: int = 0 if not line_color: line_color = THEME.div_line if not title_color: title_color = THEME.title @@ -4535,8 +4601,9 @@ def create_box(x: int = 0, y: int = 0, width: int = 0, height: int = 0, title: s x = box.x y = box.y width = box.width - height =box.height + height = box.height title = box.name + num = box.num hlines: Tuple[int, int] = (y, y + height - 1) out += f'{line_color}' @@ -4557,7 +4624,8 @@ def create_box(x: int = 0, y: int = 0, width: int = 0, height: int = 0, title: s #* Draw titles if enabled if title: - out += f'{Mv.to(y, x + 2)}{Symbol.title_left}{title_color}{Fx.b}{title}{Fx.ub}{line_color}{Symbol.title_right}' + numbered: str = "" if not num else f'{THEME.hi_fg(SUBSCRIPT[num])}' + out += f'{Mv.to(y, x + 2)}{Symbol.title_left}{Fx.b}{numbered}{title_color}{title}{Fx.ub}{line_color}{Symbol.title_right}' if title2: out += f'{Mv.to(hlines[1], x + 2)}{Symbol.title_left}{title_color}{Fx.b}{title2}{Fx.ub}{line_color}{Symbol.title_right}' @@ -4701,9 +4769,10 @@ def readfile(file: str, default: str = "") -> str: def process_keys(): mouse_pos: Tuple[int, int] = (0, 0) filtered: bool = False - global ARG_MODE + box_keys = {"1" : "cpu", "2" : "mem", "3" : "net", "4" : "proc"} while Key.has_key(): key = Key.get() + found: bool = True if key in ["mouse_scroll_up", "mouse_scroll_down", "mouse_click"]: mouse_pos = Key.get_mouse() if mouse_pos[0] >= ProcBox.x and ProcBox.current_y + 1 <= mouse_pos[1] < ProcBox.current_y + ProcBox.current_h - 1: @@ -4732,7 +4801,6 @@ def process_keys(): filtered = True continue - if key == "_null": continue elif key == "q": @@ -4743,99 +4811,125 @@ def process_keys(): elif key == "-" and CONFIG.update_ms - 100 >= 100: CONFIG.update_ms -= 100 Box.draw_update_ms() - elif key in ["b", "n"]: - NetCollector.switch(key) elif key in ["M", "escape"]: Menu.main() elif key in ["o", "f2"]: Menu.options() elif key in ["h", "f1"]: Menu.help() - elif key == "z": - NetCollector.reset = not NetCollector.reset - Collector.collect(NetCollector, redraw=True) - elif key == "y": - CONFIG.net_sync = not CONFIG.net_sync - Collector.collect(NetCollector, redraw=True) - elif key == "a": - NetCollector.auto_min = not NetCollector.auto_min - NetCollector.net_min = {"download" : -1, "upload" : -1} - Collector.collect(NetCollector, redraw=True) - elif key in ["left", "right"]: - ProcCollector.sorting(key) - elif key == " " and CONFIG.proc_tree and ProcBox.selected > 0: - if ProcBox.selected_pid in ProcCollector.collapsed: - ProcCollector.collapsed[ProcBox.selected_pid] = not ProcCollector.collapsed[ProcBox.selected_pid] - Collector.collect(ProcCollector, interrupt=True, redraw=True) - elif key == "e": - CONFIG.proc_tree = not CONFIG.proc_tree - Collector.collect(ProcCollector, interrupt=True, redraw=True) - elif key == "r": - CONFIG.proc_reversed = not CONFIG.proc_reversed - Collector.collect(ProcCollector, interrupt=True, redraw=True) - elif key == "c": - CONFIG.proc_per_core = not CONFIG.proc_per_core - Collector.collect(ProcCollector, interrupt=True, redraw=True) - elif key == "g": - CONFIG.mem_graphs = not CONFIG.mem_graphs - Collector.collect(MemCollector, interrupt=True, redraw=True) - elif key == "s": - Collector.collect_idle.wait() - CONFIG.swap_disk = not CONFIG.swap_disk - 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 == "m": - if ARG_MODE: - ARG_MODE = "" - elif CONFIG.view_modes.index(CONFIG.view_mode) + 1 > len(CONFIG.view_modes) - 1: - CONFIG.view_mode = CONFIG.view_modes[0] + if list(Box.view_modes).index(Box.view_mode) + 1 > len(list(Box.view_modes)) - 1: + Box.view_mode = list(Box.view_modes)[0] else: - CONFIG.view_mode = CONFIG.view_modes[(CONFIG.view_modes.index(CONFIG.view_mode) + 1)] - Box.proc_mode = CONFIG.view_mode == "proc" - Box.stat_mode = CONFIG.view_mode == "stat" + Box.view_mode = list(Box.view_modes)[(list(Box.view_modes).index(Box.view_mode) + 1)] + CONFIG.shown_boxes = " ".join(Box.view_modes[Box.view_mode]) Draw.clear(saved=True) Term.refresh(force=True) - elif key.lower() in ["t", "k", "i"] and (ProcBox.selected > 0 or ProcCollector.detailed): - pid: int = ProcBox.selected_pid if ProcBox.selected > 0 else ProcCollector.detailed_pid # type: ignore - if psutil.pid_exists(pid): - if key.lower() == "t": sig = signal.SIGTERM - elif key.lower() == "k": sig = signal.SIGKILL - elif key.lower() == "i": sig = signal.SIGINT - try: - os.kill(pid, sig) - except Exception as e: - errlog.error(f'Exception when sending signal {sig} to pid {pid}') - errlog.exception(f'{e}') - elif key == "delete" and ProcCollector.search_filter: - ProcCollector.search_filter = "" - Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) - elif key == "enter": - if ProcBox.selected > 0 and ProcCollector.detailed_pid != ProcBox.selected_pid and psutil.pid_exists(ProcBox.selected_pid): - ProcCollector.detailed = True - ProcBox.last_selection = ProcBox.selected - ProcBox.selected = 0 - ProcCollector.detailed_pid = ProcBox.selected_pid - ProcBox.resized = True - elif ProcCollector.detailed: - ProcBox.selected = ProcBox.last_selection - ProcBox.last_selection = 0 - ProcCollector.detailed = False - ProcCollector.detailed_pid = None - ProcBox.resized = True + elif key in box_keys: + boxes = CONFIG.shown_boxes.split() + if box_keys[key] in boxes: + boxes.remove(box_keys[key]) else: - continue - ProcCollector.details = {} - ProcCollector.details_cpu = [] - ProcCollector.details_mem = [] - Graphs.detailed_cpu = NotImplemented - Graphs.detailed_mem = NotImplemented - Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) + boxes.append(box_keys[key]) + CONFIG.shown_boxes = " ".join(boxes) + Box.view_mode = "user" + Box.view_modes["user"] = CONFIG.shown_boxes.split() + Draw.clear(saved=True) + Term.refresh(force=True) + else: + found = False + + if found: continue + + if "proc" in Box.boxes: + if key in ["left", "right"]: + ProcCollector.sorting(key) + elif key == " " and CONFIG.proc_tree and ProcBox.selected > 0: + if ProcBox.selected_pid in ProcCollector.collapsed: + ProcCollector.collapsed[ProcBox.selected_pid] = not ProcCollector.collapsed[ProcBox.selected_pid] + Collector.collect(ProcCollector, interrupt=True, redraw=True) + elif key == "e": + CONFIG.proc_tree = not CONFIG.proc_tree + Collector.collect(ProcCollector, interrupt=True, redraw=True) + elif key == "r": + CONFIG.proc_reversed = not CONFIG.proc_reversed + Collector.collect(ProcCollector, interrupt=True, redraw=True) + elif key == "c": + CONFIG.proc_per_core = not CONFIG.proc_per_core + Collector.collect(ProcCollector, 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.lower() in ["t", "k", "i"] and (ProcBox.selected > 0 or ProcCollector.detailed): + pid: int = ProcBox.selected_pid if ProcBox.selected > 0 else ProcCollector.detailed_pid # type: ignore + if psutil.pid_exists(pid): + if key.lower() == "t": sig = signal.SIGTERM + elif key.lower() == "k": sig = signal.SIGKILL + elif key.lower() == "i": sig = signal.SIGINT + try: + os.kill(pid, sig) + except Exception as e: + errlog.error(f'Exception when sending signal {sig} to pid {pid}') + errlog.exception(f'{e}') + elif key == "delete" and ProcCollector.search_filter: + ProcCollector.search_filter = "" + Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) + elif key == "enter": + if ProcBox.selected > 0 and ProcCollector.detailed_pid != ProcBox.selected_pid and psutil.pid_exists(ProcBox.selected_pid): + ProcCollector.detailed = True + ProcBox.last_selection = ProcBox.selected + ProcBox.selected = 0 + ProcCollector.detailed_pid = ProcBox.selected_pid + ProcBox.resized = True + Collector.proc_counter = 1 + elif ProcCollector.detailed: + ProcBox.selected = ProcBox.last_selection + ProcBox.last_selection = 0 + ProcCollector.detailed = False + ProcCollector.detailed_pid = None + ProcBox.resized = True + Collector.proc_counter = 1 + else: + continue + ProcCollector.details = {} + ProcCollector.details_cpu = [] + ProcCollector.details_mem = [] + Graphs.detailed_cpu = NotImplemented + Graphs.detailed_mem = NotImplemented + Collector.collect(ProcCollector, proc_interrupt=True, redraw=True) + 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) + + if "net" in Box.boxes: + if key in ["b", "n"]: + NetCollector.switch(key) + elif key == "z": + NetCollector.reset = not NetCollector.reset + Collector.collect(NetCollector, redraw=True) + elif key == "y": + CONFIG.net_sync = not CONFIG.net_sync + Collector.collect(NetCollector, redraw=True) + elif key == "a": + NetCollector.auto_min = not NetCollector.auto_min + NetCollector.net_min = {"download" : -1, "upload" : -1} + Collector.collect(NetCollector, redraw=True) + + if "mem" in Box.boxes: + if key == "g": + CONFIG.mem_graphs = not CONFIG.mem_graphs + Collector.collect(MemCollector, interrupt=True, redraw=True) + elif key == "s": + Collector.collect_idle.wait() + CONFIG.swap_disk = not CONFIG.swap_disk + Collector.collect(MemCollector, interrupt=True, redraw=True) + elif key == "d": + Collector.collect_idle.wait() + CONFIG.show_disks = not CONFIG.show_disks + Collector.collect(MemCollector, interrupt=True, redraw=True) + + - 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 -------------------------------------------------------------------------------------->