diff --git a/bpytop.py b/bpytop.py index d935601..51ecbd4 100755 --- a/bpytop.py +++ b/bpytop.py @@ -161,6 +161,9 @@ cpu_sensor=$cpu_sensor #* Show temperatures for cpu cores also if check_temp is True and sensors has been found show_coretemp=$show_coretemp +#* Which temperature scale to use, available values: "celsius", "fahrenheit", "kelvin" and "rankine" +temp_scale="$temp_scale" + #* Draw a clock at top of screen, formatting according to strftime, empty string to disable. draw_clock="$draw_clock" @@ -405,7 +408,7 @@ class Config: "swap_disk", "show_disks", "use_fstab", "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", "net_iface", "only_physical", "truecolor", "io_mode", "io_graph_combined", "io_graph_speeds", "show_io_stat", "cpu_graph_upper", "cpu_graph_lower", "cpu_invert_lower", - "cpu_single_graph", "show_uptime"] + "cpu_single_graph", "show_uptime", "temp_scale"] conf_dict: Dict[str, Union[str, int, bool]] = {} color_theme: str = "Default" theme_background: bool = True @@ -429,6 +432,7 @@ class Config: check_temp: bool = True cpu_sensor: str = "Auto" show_coretemp: bool = True + temp_scale: str = "celsius" draw_clock: str = "%X" background_update: bool = True custom_cpu_name: str = "" @@ -461,6 +465,7 @@ class Config: log_levels: List[str] = ["ERROR", "WARNING", "INFO", "DEBUG"] cpu_percent_fields: List = ["total"] cpu_percent_fields.extend(getattr(psutil.cpu_times_percent(), "_fields", [])) + temp_scales: List[str] = ["celsius", "fahrenheit", "kelvin", "rankine"] cpu_sensors: List[str] = [ "Auto" ] @@ -567,6 +572,9 @@ class Config: if cpu_graph in new_config and not new_config[cpu_graph] in self.cpu_percent_fields: new_config[cpu_graph] = "_error_" self.warnings.append(f'Config key "{cpu_graph}" does not contain an available cpu stat attribute!') + if "temp_scale" in new_config and not new_config["temp_scale"] in self.temp_scales: + new_config["temp_scale"] = "_error_" + self.warnings.append(f'Config key "temp_scale" does not contain a recognized temperature scale!') return new_config def save_config(self): @@ -1449,7 +1457,7 @@ class Graph: if max_value: self.lowest = 1 if self.round_up_low else 0 self.max_value = max_value - data = [ min_max((v + offset) * 100 // (max_value + offset), self.lowest, 100) for v in data ] #* Convert values to percentage values of max_value with max_value as ceiling + data = [ min_max((v + offset) * 100 // (max_value + offset), min_max(v + offset, 0, self.lowest), 100) for v in data ] #* Convert values to percentage values of max_value with max_value as ceiling else: self.max_value = 0 if color_max_value: @@ -1533,7 +1541,7 @@ class Graph: else: for n in range(self.height): self.graphs[self.current][n] = self.graphs[self.current][n][1:] - if self.max_value: value = min_max((value + self.offset) * 100 // (self.max_value + self.offset), self.lowest, 100) + if self.max_value: value = min_max((value + self.offset) * 100 // (self.max_value + self.offset), min_max(value + self.offset, 0, self.lowest), 100) self._create([value]) return self.out @@ -1865,6 +1873,8 @@ class CpuBox(Box, SubBox): hh: int = ceil(h / 2) hh2: int = h - hh mid_line: bool = False + temp: int = 0 + unit: str = "" if not CONFIG.cpu_single_graph and CONFIG.cpu_graph_upper != CONFIG.cpu_graph_lower: mid_line = True if h % 2: hh = floor(h / 2) @@ -1937,8 +1947,9 @@ class CpuBox(Box, SubBox): f'{THEME.gradient["cpu"][cpu.cpu_usage[0][-1]]}{cpu.cpu_usage[0][-1]:>4}{THEME.main_fg}%') if cpu.got_sensors: try: + temp, unit = temperature(cpu.cpu_temp[0][-1], CONFIG.temp_scale) out += (f'{THEME.inactive_fg} ⡀⡀⡀⡀⡀{Mv.l(5)}{THEME.gradient["temp"][min_max(cpu.cpu_temp[0][-1], 0, cpu.cpu_temp_crit) * 100 // cpu.cpu_temp_crit]}{Graphs.temps[0](None if cls.resized else cpu.cpu_temp[0][-1])}' - f'{cpu.cpu_temp[0][-1]:>4}{THEME.main_fg}°C') + f'{temp:>4}{THEME.main_fg}{unit}') except: cpu.got_sensors = False @@ -1952,11 +1963,12 @@ class CpuBox(Box, SubBox): out += f'{cpu.cpu_usage[n][-1]:>{3 if cls.column_size < 2 else 4}}{THEME.main_fg}%' if cpu.got_sensors and cpu.cpu_temp[n] and not hide_cores: try: + temp, unit = temperature(cpu.cpu_temp[n][-1], CONFIG.temp_scale) if cls.column_size > 1: - out += f'{THEME.inactive_fg} ⡀⡀⡀⡀⡀{Mv.l(5)}{THEME.gradient["temp"][100 if cpu.cpu_temp[n][-1] >= cpu.cpu_temp_crit else (cpu.cpu_temp[n][-1] * 100 // cpu.cpu_temp_crit)]}{Graphs.temps[n](None if cls.resized else cpu.cpu_temp[n][-1])}' + out += f'{THEME.inactive_fg} ⡀⡀⡀⡀⡀{Mv.l(5)}{THEME.gradient["temp"][min_max(cpu.cpu_temp[n][-1], 0, cpu.cpu_temp_crit) * 100 // cpu.cpu_temp_crit]}{Graphs.temps[n](None if cls.resized else cpu.cpu_temp[n][-1])}' else: - out += f'{THEME.gradient["temp"][100 if cpu.cpu_temp[n][-1] >= cpu.cpu_temp_crit else (cpu.cpu_temp[n][-1] * 100 // cpu.cpu_temp_crit)]}' - out += f'{cpu.cpu_temp[n][-1]:>4}{THEME.main_fg}°C' + out += f'{THEME.gradient["temp"][min_max(temp, 0, cpu.cpu_temp_crit) * 100 // cpu.cpu_temp_crit]}' + out += f'{temp:>4}{THEME.main_fg}{unit}' except: cpu.got_sensors = False elif cpu.got_sensors and not hide_cores: @@ -3062,7 +3074,7 @@ class CpuCollector(Collector): s_name, s_label = CONFIG.cpu_sensor.split(":", 1) for name, entries in psutil.sensors_temperatures().items(): for num, entry in enumerate(entries, 1): - if name == s_name and (entry.label == s_label or str(num) == s_label) and round(entry.current) > 0: + if name == s_name and (entry.label == s_label or str(num) == s_label): if entry.label.startswith("Package"): cpu_type = "intel" elif entry.label.startswith("Tdie"): @@ -3074,7 +3086,7 @@ class CpuCollector(Collector): if getattr(entry, "critical", None) != None and entry.critical > 1: cls.cpu_temp_crit = round(entry.critical) else: cls.cpu_temp_crit = 95 temp = round(entry.current) - elif entry.label.startswith(("Package", "Tdie")) and cpu_type in ["", "other"] and s_name == "_-_" and hasattr(entry, "current") and round(entry.current) > 0: + elif entry.label.startswith(("Package", "Tdie")) and cpu_type in ["", "other"] and s_name == "_-_" and hasattr(entry, "current"): if not cls.cpu_temp_high or cls.sensor_swap or cpu_type == "other": cls.sensor_swap = False if getattr(entry, "high", None) != None and entry.high > 1: cls.cpu_temp_high = round(entry.high) @@ -3083,7 +3095,7 @@ class CpuCollector(Collector): else: cls.cpu_temp_crit = 95 cpu_type = "intel" if entry.label.startswith("Package") else "ryzen" temp = round(entry.current) - elif (entry.label.startswith(("Core", "Tccd", "CPU")) or (name.lower().startswith("cpu") and not entry.label)) and hasattr(entry, "current") and round(entry.current) > 0: + elif (entry.label.startswith(("Core", "Tccd", "CPU")) or (name.lower().startswith("cpu") and not entry.label)) and hasattr(entry, "current"): if entry.label.startswith(("Core", "Tccd")): entry_int = int(entry.label.replace("Core", "").replace("Tccd", "")) if entry_int in core_dict and cpu_type != "ryzen": @@ -4368,7 +4380,18 @@ class Menu: '', 'Only works if check_temp is True and', 'the system is reporting core temps.'], - + "temp_scale" : [ + 'Which temperature scale to use.', + '', + 'Celsius, default scale.', + '', + 'Fahrenheit, the american one.', + '', + 'Kelvin, 0 = absolute zero, 1 degree change', + 'equals 1 degree change in Celsius.', + '', + 'Rankine, 0 = abosulte zero, 1 degree change', + 'equals 1 degree change in Fahrenheit.'], "custom_cpu_name" : [ 'Custom cpu model name in cpu percentage box.', '', @@ -4572,6 +4595,7 @@ class Menu: cpu_sensor_i: int = CONFIG.cpu_sensors.index(CONFIG.cpu_sensor) cpu_graph_i: Dict[str, int] = { "cpu_graph_upper" : CONFIG.cpu_percent_fields.index(CONFIG.cpu_graph_upper), "cpu_graph_lower" : CONFIG.cpu_percent_fields.index(CONFIG.cpu_graph_lower)} + temp_scale_i: int = CONFIG.temp_scales.index(CONFIG.temp_scale) color_i: int max_opt_len: int = max([len(categories[x]) for x in categories]) * 2 cat_list = list(categories) @@ -4635,11 +4659,13 @@ class Menu: counter = f' {cpu_sensor_i + 1}/{len(CONFIG.cpu_sensors)}' elif opt in ["cpu_graph_upper", "cpu_graph_lower"]: counter = f' {cpu_graph_i[opt] + 1}/{len(CONFIG.cpu_percent_fields)}' + elif opt == "temp_scale": + counter = f' {temp_scale_i + 1}/{len(CONFIG.temp_scales)}' 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", "cpu_sensor", "cpu_graph_upper", "cpu_graph_lower"]: + if isinstance(value, bool) or opt in ["color_theme", "proc_sorting", "log_level", "cpu_sensor", "cpu_graph_upper", "cpu_graph_lower", "temp_scale"]: 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}' @@ -4849,6 +4875,16 @@ class Menu: setattr(CpuCollector, selected.replace("_graph", ""), []) Term.refresh(force=True) cls.resized = False + elif key in ["left", "right"] and selected == "temp_scale": + if key == "left": + temp_scale_i -= 1 + if temp_scale_i < 0: temp_scale_i = len(CONFIG.temp_scales) - 1 + if key == "right": + temp_scale_i += 1 + if temp_scale_i > len(CONFIG.temp_scales) - 1: temp_scale_i = 0 + CONFIG.temp_scale = CONFIG.temp_scales[temp_scale_i] + Term.refresh(force=True) + cls.resized = False elif key in ["left", "right"] and selected == "cpu_sensor" and len(CONFIG.cpu_sensors) > 1: if key == "left": cpu_sensor_i -= 1 @@ -5277,6 +5313,19 @@ def readfile(file: str, default: str = "") -> str: pass return default if out is None else out +def temperature(value: int, scale: str = "celsius") -> Tuple[int, str]: + """Returns a tuple with integer value and string unit converted from an integer in celsius to: celsius, fahrenheit, kelvin or rankine.""" + if scale == "celsius": + return (value, "°C") + elif scale == "fahrenheit": + return (round(value * 1.8 + 32), "°F") + elif scale == "kelvin": + return (round(value + 273.15), "°K") + elif scale == "rankine": + return (round(value * 1.8 + 491.67), "°R") + else: + return (0, "") + def process_keys(): mouse_pos: Tuple[int, int] = (0, 0) filtered: bool = False