Changed init, added log_level config option

pull/27/head
aristocratos 2020-07-12 00:54:52 +02:00
parent d5e63f56c1
commit fa32357d6a
1 changed files with 306 additions and 128 deletions

434
bpytop.py
View File

@ -24,6 +24,7 @@ from select import select
from distutils.util import strtobool
from string import Template
from math import ceil
from random import randint
from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable
errors: List[str] = []
@ -53,16 +54,48 @@ if errors:
#? Variables ------------------------------------------------------------------------------------->
BANNER_SRC: Dict[str, str] = {
"#E62525" : "██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗",
"#CD2121" : "██╔══██╗██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔═══██╗██╔══██╗",
"#B31D1D" : "██████╔╝██████╔╝ ╚████╔╝ ██║ ██║ ██║██████╔╝",
"#9A1919" : "██╔══██╗██╔═══╝ ╚██╔╝ ██║ ██║ ██║██╔═══╝ ",
"#801414" : "██████╔╝██║ ██║ ██║ ╚██████╔╝██║",
"#000000" : "╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝",
}
BANNER_SRC: List[Tuple[str, str, str]] = [
("#ffa50a", "#0fd7ff", "██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗"),
("#f09800", "#00bfe6", "██╔══██╗██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔═══██╗██╔══██╗"),
("#db8b00", "#00a6c7", "██████╔╝██████╔╝ ╚████╔╝ ██║ ██║ ██║██████╔╝"),
("#c27b00", "#008ca8", "██╔══██╗██╔═══╝ ╚██╔╝ ██║ ██║ ██║██╔═══╝ "),
("#a86b00", "#006e85", "██████╔╝██║ ██║ ██║ ╚██████╔╝██║"),
("#000000", "#000000", "╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝"),
]
VERSION: str = "0.0.1"
#RED
# "#E62525"
# "#CD2121"
# "#B31D1D"
# "#9A1919"
# "#801414"
# "#000000"
#GREEN
# "#75e12d"
# "#5bbb1b"
# "#499914"
# "#3b7811"
# "#30620e"
# "#000000"
#YELLOW
# "#fadd00"
# "#dbc200"
# "#b8a200"
# "#998700"
# "#807100"
# "#000000"
#BLUE
# "#0084ff"
# "#016cd0"
# "#0157a7"
# "#014484"
# "#003261"
# "#000000"
VERSION: str = "0.0.3"
#*?This is the template used to create the config file
DEFAULT_CONF: Template = Template(f'#? Config file for bpytop v. {VERSION}' + '''
@ -70,45 +103,46 @@ DEFAULT_CONF: Template = Template(f'#? Config file for bpytop v. {VERSION}' + ''
#* Color theme, looks for a .theme file in "~/.config/bpytop/themes" and "~/.config/bpytop/user_themes", "Default" for builtin default theme
color_theme="$color_theme"
#* 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 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
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive"
#* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive",
#* "cpu lazy" updates sorting over time, "cpu responsive" updates sorting directly.
proc_sorting="$proc_sorting"
#* Reverse sorting order, True or False
#* Reverse sorting order, True or False.
proc_reversed=$proc_reversed
#* Show processes as a tree
proc_tree=$proc_tree
#* Check cpu temperature, needs "vcgencmd" on Raspberry Pi and "osx-cpu-temp" on MacOS X
#* Check cpu temperature, needs "vcgencmd" on Raspberry Pi and "osx-cpu-temp" on MacOS X.
check_temp=$check_temp
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
draw_clock="$draw_clock"
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
background_update=$background_update
#* Custom cpu model name, empty string to disable
#* Custom cpu model name, empty string to disable.
custom_cpu_name="$custom_cpu_name"
#* Show color gradient in process list, True or False
#* Show color gradient in process list, True or False.
proc_gradient=$proc_gradient
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power.
proc_per_core=$proc_per_core
#* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space
#* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space.
disks_filter="$disks_filter"
#* Enable check for new version from github.com/aristocratos/bpytop at start
#* Enable check for new version from github.com/aristocratos/bpytop at start.
update_check=$update_check
#* Enable graphs with double the horizontal resolution, increases cpu usage
hires_graphs=$hires_graphs
#* Set loglevel for "~/.config/bpytop/error.log" levels are: "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG".
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
log_level="$log_level"
''')
CONFIG_DIR: str = f'{os.path.expanduser("~")}/.config/bpytop'
@ -186,8 +220,6 @@ except PermissionError:
print(f'ERROR!\nNo permission to write to "{CONFIG_DIR}" directory!')
quit(1)
errlog.info(f'New instance of bpytop version {VERSION} started with pid {os.getpid()}')
#! Timers, remove ----------------------------------------------------------------------->
class Timer:
@ -229,7 +261,7 @@ def timerd(func):
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_gradient", "proc_per_core", "disks_filter", "update_check", "hires_graphs"]
keys: List[str] = ["color_theme", "update_ms", "proc_sorting", "proc_reversed", "proc_tree", "check_temp", "draw_clock", "background_update", "custom_cpu_name", "proc_gradient", "proc_per_core", "disks_filter", "update_check", "log_level"]
conf_dict: Dict[str, Union[str, int, bool]] = {}
color_theme: str = "Default"
update_ms: int = 2500
@ -244,7 +276,12 @@ class Config:
proc_per_core: bool = False
disks_filter: str = ""
update_check: bool = True
hires_graphs: bool = False
log_level: str = "WARNING"
warnings: List[str] = []
sorting_options: List[str] = ["pid", "program", "arguments", "threads", "user", "memory", "cpu lazy", "cpu responsive"]
log_levels: List[str] = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
changed: bool = False
recreate: bool = False
@ -257,10 +294,10 @@ class Config:
conf: Dict[str, Union[str, int, bool]] = self.load_config()
if not "version" in conf.keys():
self.recreate = True
errlog.warning(f'Config file malformatted or missing, will be recreated on exit!')
self.warnings.append(f'Config file malformatted or missing, will be recreated on exit!')
elif conf["version"] != VERSION:
self.recreate = True
errlog.warning(f'Config file version and bpytop version missmatch, will be recreated on exit!')
self.warnings.append(f'Config file version and bpytop version missmatch, will be recreated on exit!')
for key in self.keys:
if key in conf.keys() and conf[key] != "_error_":
setattr(self, key, conf[key])
@ -295,19 +332,22 @@ class Config:
try:
new_config[key] = int(line)
except ValueError:
errlog.warning(f'Config key "{key}" should be an integer!')
self.warnings.append(f'Config key "{key}" should be an integer!')
if type(getattr(self, key)) == bool:
try:
new_config[key] = bool(strtobool(line))
except ValueError:
errlog.warning(f'Config key "{key}" can only be True or False!')
self.warnings.append(f'Config key "{key}" can only be True or False!')
if type(getattr(self, key)) == str:
new_config[key] = str(line)
except Exception as e:
errlog.exception(str(e))
if "proc_sorting" in new_config and not new_config["proc_sorting"] in ["pid", "program", "arguments", "threads", "user", "memory", "cpu lazy", "cpu responsive"]:
if "proc_sorting" in new_config and not new_config["proc_sorting"] in self.sorting_options:
new_config["proc_sorting"] = "_error_"
errlog.warning(f'Config key "proc_sorted" didn\'t get an acceptable value!')
self.warnings.append(f'Config key "proc_sorted" didn\'t get an acceptable value!')
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!')
return new_config
def save_config(self):
@ -321,6 +361,13 @@ class Config:
try:
CONFIG: Config = Config(CONFIG_FILE)
errlog.setLevel(getattr(logging, CONFIG.log_level))
errlog.info(f'New instance of bpytop version {VERSION} started with pid {os.getpid()}')
errlog.debug(f'Loglevel set to {CONFIG.log_level}')
if CONFIG.warnings:
for warning in CONFIG.warnings:
errlog.warning(warning)
CONFIG.warnings = []
except Exception as e:
errlog.exception(f'{e}')
quit(1)
@ -365,7 +412,7 @@ class Term:
cls._w, cls._h = os.get_terminal_size()
cls._resizing = False
Box.calc_sizes()
Box.draw_bg()
Box.draw_bg(now=True if not Init.running else False)
@staticmethod
@ -478,6 +525,25 @@ class Key:
except:
pass
@classmethod
def last(cls) -> str:
if cls.list: return cls.list.pop()
else: return ""
@classmethod
def get(cls) -> str:
if cls.list: return cls.list.pop(0)
else: return ""
@classmethod
def has_key(cls) -> bool:
if cls.list: return True
else: return False
@classmethod
def clear(cls):
cls.list = []
@classmethod
def input_wait(cls, sec: float = 0.0) -> bool:
'''Returns True if key is detected else waits out timer and returns False'''
@ -533,7 +599,7 @@ class Key:
with Nonblocking(sys.stdin): #* Set non blocking to prevent read stall if less than 5 characters
input_key += sys.stdin.read(5)
if input_key == "\033": clean_key = "escape" #* Key is escape if only containing \033
if input_key == "\\": clean_key = "\\" #* Clean up "\" to not return escaped
elif input_key == "\\": clean_key = "\\" #* Clean up "\" to not return escaped
else:
for code in escape.keys(): #* Go trough dict of escape codes to get the cleaned key name
if input_key.lstrip("\033").startswith(code):
@ -542,9 +608,10 @@ class Key:
else: #* If not found in escape dict and length of key is 1, assume regular character
if len(input_key) == 1:
clean_key = input_key
#if testing: errlog.debug(f'Input key: {repr(input_key)} Clean key: {clean_key}') #! Remove
if testing: errlog.debug(f'Input key: {repr(input_key)} Clean key: {clean_key}') #! Remove
if clean_key:
cls.list.append(clean_key) #* Store keys in input queue for later processing
cls.list.append(clean_key) #* Store up to 10 keys in input queue for later processing
if len(cls.list) > 10: del cls.list[0]
clean_key = ""
cls.new.set() #* Set threading event to interrupt main thread sleep
input_key = ""
@ -557,7 +624,7 @@ class Key:
class Draw:
'''Holds the draw buffer and manages IO blocking queue
* .buffer([+]name[!], *args, append=False, now=False) : Add *args to buffer
* .buffer([+]name[!], *args, append=False, now=False, z=100) : Add *args to buffer
* - Adding "+" prefix to name sets append to True and appends to name's current string
* - Adding "!" suffix to name sets now to True and print name's current string
* .out(clear=False) : Print all strings in buffer, clear=True clear all buffers after
@ -565,6 +632,7 @@ class Draw:
* .clear(*names) : Clear named buffers, all if no argument
'''
strings: Dict[str, str] = {}
z_order: Dict[str, int] = {}
last_screen: str = ""
idle = threading.Event()
idle.set()
@ -584,7 +652,7 @@ class Draw:
cls.idle.set()
@classmethod
def buffer(cls, name: str, *args: str, append: bool = False, now: bool = False):
def buffer(cls, name: str, *args: str, append: bool = False, now: bool = False, z: int = 100):
string: str = ""
if name.startswith("+"):
name = name.lstrip("+")
@ -592,29 +660,47 @@ class Draw:
if name.endswith("!"):
name = name.rstrip("!")
now = True
if name == "": name = "_null"
if not name in cls.z_order or z != 100: cls.z_order[name] = z
if args: string = "".join(args)
if name not in cls.strings or not append: cls.strings[name] = ""
cls.strings[name] += string
if now: cls.now(string)
@classmethod
def out(cls, clear = False):
cls.last_screen = "".join(cls.strings.values())
if clear: cls.strings = {}
cls.now(cls.last_screen)
def out(cls, *names: str, clear = False, no_order: bool = False):
out: str = ""
if not cls.strings: return
if no_order:
cls.last_screen = "".join(cls.strings.values())
if clear: cls.strings = {}
cls.now(cls.last_screen)
elif names:
for name in sorted(cls.z_order, key=cls.z_order.get, reverse=True):
if name in names:
out += cls.strings[name]
if clear:
del cls.strings[name]
del cls.z_order[name]
cls.now(out)
else:
for name in sorted(cls.z_order, key=cls.z_order.get, reverse=True):
if name in cls.strings: out += cls.strings[name]
if clear: cls.strings = {}
cls.last_screen = out
cls.now(cls.last_screen)
@classmethod
def clear(cls, *names):
if names:
for name in names:
if name in cls.strings:
del cls.strings["name"]
del cls.strings[name]
del cls.z_order[name]
else:
cls.strings = {}
class Color:
'''Holds representations for a 24-bit color
'''Holds representations for a 24-bit color value
__init__(color, depth="fg", default=False)
-- color accepts 6 digit hexadecimal: string "#RRGGBB", 2 digit hexadecimal: string "#FF" or decimal RGB "255 255 255" as a string.
-- depth accepts "fg" or "bg"
@ -623,7 +709,7 @@ class Color:
__iter__ returns iteration over red, green and blue in integer values of 0-255.
* Values: .hexa: str | .dec: Tuple[int, int, int] | .red: int | .green: int | .blue: int | .depth: str | .escape: str
'''
hex: str; dec: Tuple[int, int, int]; red: int; green: int; blue: int; depth: str; escape: str; default: bool
hexa: str; dec: Tuple[int, int, int]; red: int; green: int; blue: int; depth: str; escape: str; default: bool
def __init__(self, color: str, depth: str = "fg", default: bool = False):
self.depth = depth
@ -748,7 +834,9 @@ class Theme:
tdict = self._load_file(self.themes[theme])
self.cached[theme] = tdict
else:
errlog.warning(f'No theme named "{theme}" found!')
theme = "Default"
CONFIG.color_theme = theme
tdict = DEFAULT_THEME
self.current = theme
#if CONFIG.color_theme != theme: CONFIG.color_theme = theme
@ -817,21 +905,25 @@ class Banner:
c_color: str = ""
length: int = 0
if not out:
for num, (color, line) in enumerate(BANNER_SRC.items()):
for num, (color, color2, line) in enumerate(BANNER_SRC):
if len(line) > length: length = len(line)
out += [""]
out_var = ""
line_color = Color.fg(color)
line_color2 = Color.fg(color2)
line_dark = Color.fg(f'#{80 - num * 6}')
for letter in line:
for n, letter in enumerate(line):
if letter == "" and c_color != line_color:
c_color = line_color
out[num] += line_color
if n > 5 and n < 25: c_color = line_color2
else: c_color = line_color
out_var += c_color
elif letter == " ":
letter = f'{Mv.r(1)}'
c_color = ""
elif letter != "" and c_color != line_dark:
c_color = line_dark
out[num] += line_dark
out[num] += letter
out_var += line_dark
out_var += letter
out.append(out_var)
@classmethod
def draw(cls, line: int, col: int = 0, center: bool = False, now: bool = False):
@ -873,6 +965,10 @@ class Symbol:
fail: str = f'{Color.fg("#ff3050")}!{Color.fg("#cc")}'
class Graph:
'''Class for creating and adding to graphs
* __str__ : returns graph as a string
* add(value: int) : adds a value to graph and returns it as a string
'''
out: str
width: int
height: int
@ -910,9 +1006,10 @@ class Graph:
value_width = ceil(len(data) / 2)
elif value_width < width: #* If the size of given data set is smaller then width of graph, fill graph with whitespace
filler = " " * (width - value_width)
if len(data) % 2 == 1: data.insert(0, 0)
for _ in range(height):
for b in [True, False]:
self.graphs[b].append(filler if filler else "")
self.graphs[b].append(filler)
self._create(data, new=True)
def _create(self, data: List[int], new: bool = False):
@ -970,7 +1067,7 @@ class Graphs:
net: Graph
detailed_cpu: Graph
detailed_mem: Graph
pid_cpu: List[Graph] = []
pid_cpu: Dict[int, Graph] = {}
class Meter:
'''Creates a percentage meter
@ -1065,8 +1162,8 @@ class SubBox:
class CpuBox(Box, SubBox):
name = "cpu"
x = 0
y = 0
x = 1
y = 1
height_p = 32
width_p = 100
@ -1102,8 +1199,8 @@ class MemBox(Box):
name = "mem"
height_p = 40
width_p = 45
x = 0
y = 0
x = 1
y = 1
divider: int = 0
mem_width: int = 0
disks_width: int = 0
@ -1131,8 +1228,8 @@ class NetBox(Box, SubBox):
name = "net"
height_p = 28
width_p = 45
x = 0
y = 0
x = 1
y = 1
@classmethod
def _calc_size(cls):
@ -1154,8 +1251,8 @@ class ProcBox(Box):
name = "proc"
height_p = 68
width_p = 55
x = 0
y = 0
x = 1
y = 1
detailed: bool = False
detailed_x: int = 0
detailed_y: int = 0
@ -1294,6 +1391,16 @@ def testing_collectors():
#Draw.now(f'Cpu usage: {CpuCollector.cpu_usage}\nCpu freq: {CpuCollector.cpu_freq}\nLoad avg: {CpuCollector.load_avg}\n')
class Menu:
'''Holds the main menu and all submenus
* uncolor(string: str) : removes all color and returns string with THEME.inactive_fg color
'''
color_re = re.compile(r"\033\[\d+;\d?;?\d*;?\d*;?\d*m{1}")
@classmethod
def uncolor(cls, string: str) -> str:
return f'{THEME.inactive_fg}{cls.color_re.sub("", string)}{Term.fg}'
#? Functions ------------------------------------------------------------------------------------->
@ -1400,8 +1507,8 @@ def clean_quit(errcode: int = 0, errmsg: str = ""):
Key.stop()
Collector.stop()
if not errcode: CONFIG.save_config()
#Draw.now(Term.clear, Term.normal_screen, Term.show_cursor) #! Enable
Draw.now(Term.show_cursor) #! Remove
if not testing: Draw.now(Term.clear, Term.normal_screen, Term.show_cursor) #! Enable
else: Draw.now(Term.show_cursor) #! Remove
Term.echo(True)
if errcode == 0:
errlog.info(f'Exiting. Runtime {timedelta(seconds=round(time() - SELF_START, 0))} \n')
@ -1457,7 +1564,7 @@ def main():
CPU_NAME: str = get_cpu_name()
testing = True #! Remove
testing = False #! Remove
#! For testing ------------------------------------------------------------------------------->
@ -1566,136 +1673,207 @@ def testing_keyinput():
Draw.clear()
def testing_graphs():
from random import randint
my_data = [x for x in range(0, 101)]
my_data += [x for x in range(100, -1, -1)]
my_graph = Graph(Term.width, Term.height // 3, THEME.gradient['cpu'], my_data, invert=False)
my_graph2 = Graph(Term.width, Term.height // 3, THEME.gradient['cpu'], my_data, invert=True)
my_graph3 = Graph(Term.width, 1, THEME.proc_misc, my_data)
my_graph4 = Graph(Term.width, 2, None, my_data)
#my_data100 = [100 for _ in range(200)]
Draw.now(f'{Mv.to(0, 0)}{my_graph}')
Draw.now(f'{Mv.to(Term.height // 3 + 1, 0)}{my_graph2}')
Draw.now(f'{Mv.to(Term.height - (Term.height // 3) + 2, 0)}{my_graph3}')
Draw.now(f'{Mv.to(Term.height - (Term.height // 3) + 4, 0)}{my_graph4}')
my_data100 = [randint(0, 100) for _ in range(Term.width * 2)]
my_data2 = my_data[-90:]
my_data3 = my_data[:86]
my_colors = []
for i in range(51):
for _ in range(2): my_colors.append(Color.fg(i, i, i))
#my_colors.reverse()
for _ in range(100):
sleep(0.1)
my_graph = Graph(Term.width, Term.height // 2, my_colors, my_data100, invert=True)
my_graph2 = Graph(Term.width, Term.height // 2, my_colors, my_data100, invert=False)
# my_graph3 = Graph(100 // 3 + 10, 1, THEME.proc_misc, my_data2)
# my_graph4 = Graph(100 // 3 + 10, 1, THEME.proc_misc, my_data3)
# my_graph5 = Graph(100, Term.height // 3, THEME.inactive_fg, my_data)
#pause = re.compile(r"\033\[\d+;\d?;?\d*;?\d*;?\d*m{1}")
#repl = "\033[0;37m"
banner = Banner.draw(Term.height // 3 - 2, center=True)
Draw.now(f'{Fx.ub}{Mv.to(0, 0)}{my_graph}\
{Mv.to(Term.height // 2, 0)}{my_graph2}\
{banner}')
# {Mv.to(Term.height - (Term.height // 3), Term.width // 2 - 50)}{my_graph5}\
# {Mv.to(Term.height - (Term.height // 3) - 1, Term.width // 2 - 50)}{my_graph3}\
# {Mv.to(Term.height - (Term.height // 3) - 1, Term.width // 2 + 7)}{my_graph4}\
#t = 1
x = 0
for _ in range(200):
sleep(0.05)
x = randint(0, 100)
Draw.now(f'{Mv.to(0, 0)}{my_graph.add(x)}')
Draw.now(f'{Mv.to(Term.height // 3 + 1, 0)}{my_graph2.add(x)}')
Draw.now(f'{Mv.to(Term.height - (Term.height // 3) + 2, 0)}{my_graph3.add(x)}')
Draw.now(f'{Mv.to(Term.height - (Term.height // 3) + 4, 0)}{my_graph4.add(x)}')
# x += 1 if t == 1 else -1
# if x == 100: t = 0
# if x == 0: t = 1
Draw.now(f'{Fx.ub}{Mv.to(0, 0)}{my_graph.add(x)}\
{Mv.to(Term.height // 2, 0)}{my_graph2.add(x)}\
{banner}')
# Draw.now(f'{Mv.to(Term.height - (Term.height // 3), Term.width // 2 - 50)}{my_graph5.add(x)}')
# Draw.now(f'{Mv.to(Term.height - (Term.height // 3) - 1, Term.width // 2 - 50)}{my_graph3.add(x)}')
# Draw.now(f'{Mv.to(Term.height - (Term.height // 3) - 1, Term.width // 2 + 7)}{my_graph4.add(x)}')
Draw.now(Mv.to(Term.height -4, 0))
# if not Key.reader.is_alive():
# clean_quit(1)
# Key.new.wait(1.0)
#! Remove ------------------------------------------------------------------------------------<
if __name__ == "__main__":
#? Init -------------------------------------------------------------------------------------->
Timer.start("Init")
#? Temporary functions for init
def _fail(err):
Draw.now(f'{Symbol.fail}')
errlog.exception(f'{err}')
sleep(2)
clean_quit(1, errmsg=f'Error during init! See {CONFIG_DIR}/error.log for more information.')
def _success():
if not testing: sleep(0.1) #! Remove if
Draw.now(f'{Symbol.ok}\n{Mv.r(Term.width // 2 - 19)}')
class Init:
running: bool = True
initbg_colors: List[str] = []
initbg_data: List[int]
initbg_up: Graph
initbg_down: Graph
@staticmethod
def fail(err):
Draw.buffer("+init!", f'{Mv.restore()}{Symbol.fail}')
errlog.exception(f'{err}')
sleep(2)
clean_quit(1, errmsg=f'Error during init! See {CONFIG_DIR}/error.log for more information.')
@classmethod
def success(cls, start: bool = False):
if start:
Draw.buffer("initbg", z=10)
Draw.buffer("init", z=1)
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)
for _i in range(10):
perc = f'{str((_i + 1) * 10) + "%":>5}'
Draw.buffer("+banner", f'{Mv.to(Term.height // 2 - 3 + _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()}')
if start or Term.resized:
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)
cls.initbg_down = Graph(Term.width, Term.height // 2, cls.initbg_colors, cls.initbg_data, invert=False)
if start: return
if not testing:
cls.draw_bg(10)
Draw.buffer("+init!", f'{Mv.restore()}{Symbol.ok}\n{Mv.r(Term.width // 2 - 22)}{Mv.save()}')
@classmethod
def draw_bg(cls, times: int = 10):
for _ in range(times):
sleep(0.05)
x = randint(0, 100)
Draw.buffer("initbg", f'{Fx.ub}{Mv.to(0, 0)}{cls.initbg_up.add(x)}{Mv.to(Term.height // 2, 0)}{cls.initbg_down.add(x)}')
Draw.out("initbg", "banner", "init")
@classmethod
def done(cls):
cls.draw_bg(20)
Draw.clear("initbg", "banner", "init")
cls.running = False
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
Draw.now(Term.alt_screen, Term.clear, Term.hide_cursor) #! Enable
#Draw.now(Term.clear, Term.hide_cursor) #! Disable
if not testing: Draw.now(Term.alt_screen, Term.clear, Term.hide_cursor) #! Enable
else: Draw.now(Term.clear, Term.hide_cursor) #! Disable
Term.echo(False)
#? Draw banner and init status
Draw.now(Banner.draw(Term.height // 2 - 10, center=True), "\n")
Draw.now(Color.fg("#50"))
for _i in range(10):
Draw.now(f'{Mv.to(Term.height // 2 - 3 + _i, Term.width // 2 - 25)}{str((_i + 1) * 10) + "%":>5}{Symbol.v_line}')
Draw.now(f'{Color.fg("#cc")}{Fx.b}{Mv.to(Term.height // 2 - 3, Term.width // 2 - 18)}')
if not testing: Init.success(start=True)
#? Load theme
Draw.now("Loading theme and creating colors... ")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Loading theme and creating colors... ")}{Mv.save()}')
try:
THEME: Theme = Theme(CONFIG.color_theme)
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
#? Setup boxes
Draw.now("Doing some maths and drawing... ")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Doing some maths and drawing... ")}{Mv.save()}')
try:
Box.calc_sizes()
Box.draw_bg(now=False)
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
#? Setup signal handlers for SIGSTP, SIGCONT, SIGINT and SIGWINCH
Draw.now("Setting up signal handlers... ")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Setting up signal handlers... ")}{Mv.save()}')
try:
signal.signal(signal.SIGTSTP, now_sleeping) #* Ctrl-Z
signal.signal(signal.SIGCONT, now_awake) #* Resume
signal.signal(signal.SIGINT, quit_sigint) #* Ctrl-C
signal.signal(signal.SIGWINCH, Term.refresh) #* Terminal resized
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
#? Start a separate thread for reading keyboard input
Draw.now("Starting input reader thread... ")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Starting input reader thread... ")}{Mv.save()}')
try:
Key.start()
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
#? Start a separate thread for data collection and drawing
Draw.now("Starting data collection and drawer thread... ")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Starting data collection and drawer thread... ")}{Mv.save()}')
try:
Collector.start()
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
#? Collect data and draw to buffer
Draw.now("Collecting data and drawing...")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Collecting data and drawing... ")}{Mv.save()}')
try:
#Collector.collect(draw_now=False)
pass
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Collecting nuclear launch codes... ")}{Mv.save()}')
Init.success()
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Launching missiles... ")}{Mv.save()}')
Init.success()
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Alien invasion... ")}{Mv.save()}')
Init.success()
#? Draw to screen
Draw.now("Finishing up...")
Draw.buffer("+init!", f'{Mv.restore()}{Fx.trans("Finishing up... ")}{Mv.save()}')
try:
#Collector.collect_done.wait()
pass
except Exception as e:
_fail(e)
Init.fail(e)
else:
_success()
Init.success()
if not testing: sleep(1) #! Remove if
del _fail, _success
if not testing: Init.done() #! Remove if
if not testing: Draw.out(clear=True) #! Remove if
else: Draw.clear(); Draw.now(Term.clear) #! Remove
Timer.stop("Init")
@ -1704,12 +1882,12 @@ if __name__ == "__main__":
#! For testing ------------------------------------------------------------------------------->
if testing:
try:
testing_graphs()
#testing_graphs()
#testing_collectors()
#testing_humanizer()
# waitone(1)
#testing_keyinput()
#testing_banner()
testing_banner()
# waitone(1)
#testing_colors()
# waitone(1)