mirror of https://github.com/aristocratos/bpytop
pull/27/head
parent
443a4b1a9d
commit
e2de8e7401
927
pypytop
927
pypytop
|
@ -1,927 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# pylint: disable=not-callable, no-member
|
|
||||||
|
|
||||||
# Copyright 2020 Aristocratos (jakob@qvantnet.com)
|
|
||||||
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
import os, sys, time, threading, signal, re, subprocess, logging, logging.handlers
|
|
||||||
from select import select
|
|
||||||
from pathlib import Path
|
|
||||||
from distutils.util import strtobool
|
|
||||||
from typing import List, Set, Dict, Tuple, Optional, Union, Any, Callable, ContextManager, Iterable
|
|
||||||
|
|
||||||
errors: List[str] = []
|
|
||||||
try: import fcntl, termios, tty
|
|
||||||
except Exception as e: errors.append(f'{e}')
|
|
||||||
|
|
||||||
try: import psutil
|
|
||||||
except Exception as e: errors.append(f'{e}')
|
|
||||||
|
|
||||||
|
|
||||||
SYSTEM: str
|
|
||||||
if "linux" in sys.platform: SYSTEM = "Linux"
|
|
||||||
elif "bsd" in sys.platform: SYSTEM = "BSD"
|
|
||||||
elif "darwin" in sys.platform: SYSTEM = "MacOS"
|
|
||||||
else: SYSTEM = "Other"
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
print ("ERROR!")
|
|
||||||
for error in errors:
|
|
||||||
print(error)
|
|
||||||
if SYSTEM == "Other":
|
|
||||||
print("\nUnsupported platform!\n")
|
|
||||||
else:
|
|
||||||
print("\nInstall required modules!\n")
|
|
||||||
quit(1)
|
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
print: partial = partial(print, sep="", end="", flush=True) #* Setup print function to default to empty seperator and no new line
|
|
||||||
|
|
||||||
#? Variables ------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
BANNER_SRC: Dict[str, str] = {
|
|
||||||
"#E62525" : "██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗",
|
|
||||||
"#CD2121" : "██╔══██╗██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔═══██╗██╔══██╗",
|
|
||||||
"#B31D1D" : "██████╔╝██████╔╝ ╚████╔╝ ██║ ██║ ██║██████╔╝",
|
|
||||||
"#9A1919" : "██╔══██╗██╔═══╝ ╚██╔╝ ██║ ██║ ██║██╔═══╝ ",
|
|
||||||
"#801414" : "██████╔╝██║ ██║ ██║ ╚██████╔╝██║",
|
|
||||||
"#000000" : "╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝",
|
|
||||||
}
|
|
||||||
|
|
||||||
VERSION: str = "0.0.1"
|
|
||||||
|
|
||||||
DEFAULT_CONF: str = f'#? Config file for bpytop v. {VERSION}\n'
|
|
||||||
DEFAULT_CONF += '''
|
|
||||||
#* Color theme, looks for a .theme file in "~/.config/bpytop/themes" and "~/.config/bpytop/user_themes", "Default" for builtin default theme
|
|
||||||
color_theme = "Default"
|
|
||||||
|
|
||||||
#* 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 = 2500
|
|
||||||
|
|
||||||
#* 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 = "cpu lazy"
|
|
||||||
|
|
||||||
#* Reverse sorting order, True or False
|
|
||||||
proc_reversed = False
|
|
||||||
|
|
||||||
#* Show processes as a tree
|
|
||||||
proc_tree = False
|
|
||||||
|
|
||||||
#* Check cpu temperature, only works if "sensors", "vcgencmd" or "osx-cpu-temp" commands is available
|
|
||||||
check_temp = True
|
|
||||||
|
|
||||||
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable
|
|
||||||
draw_clock = "%X"
|
|
||||||
|
|
||||||
#* Update main ui when menus are showing, set this to false if the menus is flickering too much for comfort
|
|
||||||
background_update = True
|
|
||||||
|
|
||||||
#* Custom cpu model name, empty string to disable
|
|
||||||
custom_cpu_name = ""
|
|
||||||
|
|
||||||
#* Show color gradient in process list, True or False
|
|
||||||
proc_gradient = True
|
|
||||||
|
|
||||||
#* If process cpu usage should be of the core it's running on or usage of the total available cpu power
|
|
||||||
proc_per_core = False
|
|
||||||
|
|
||||||
#* Optional filter for shown disks, should be names of mountpoints, "root" replaces "/", separate multiple values with space
|
|
||||||
disks_filter = ""
|
|
||||||
|
|
||||||
#* Enable check for new version from github.com/aristocratos/bpytop at start
|
|
||||||
update_check = True
|
|
||||||
|
|
||||||
#* Enable graphs with double the horizontal resolution, increases cpu usage
|
|
||||||
hires_graphs = False
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
conf: Path = Path(f'{Path.home()}/.config/bpytop')
|
|
||||||
if not conf.is_dir():
|
|
||||||
try:
|
|
||||||
conf.mkdir(mode=0o777, parents=True)
|
|
||||||
except PermissionError:
|
|
||||||
print(f'ERROR!\nNo permission to write to "{conf}" directory!')
|
|
||||||
quit(1)
|
|
||||||
|
|
||||||
CONFIG_DIR: str = str(conf)
|
|
||||||
del conf
|
|
||||||
|
|
||||||
CORES: int = psutil.cpu_count(logical=False) or 1
|
|
||||||
THREADS: int = psutil.cpu_count(logical=True) or 1
|
|
||||||
|
|
||||||
DEFAULT_THEME: Dict[str, str] = {
|
|
||||||
"main_bg" : "",
|
|
||||||
"main_fg" : "#cc",
|
|
||||||
"title" : "#ee",
|
|
||||||
"hi_fg" : "#90",
|
|
||||||
"selected_bg" : "#7e2626",
|
|
||||||
"selected_fg" : "#ee",
|
|
||||||
"inactive_fg" : "#40",
|
|
||||||
"proc_misc" : "#0de756",
|
|
||||||
"cpu_box" : "#3d7b46",
|
|
||||||
"mem_box" : "#8a882e",
|
|
||||||
"net_box" : "#423ba5",
|
|
||||||
"proc_box" : "#923535",
|
|
||||||
"div_line" : "#30",
|
|
||||||
"temp_start" : "#4897d4",
|
|
||||||
"temp_mid" : "#5474e8",
|
|
||||||
"temp_end" : "#ff40b6",
|
|
||||||
"cpu_start" : "#50f095",
|
|
||||||
"cpu_mid" : "#f2e266",
|
|
||||||
"cpu_end" : "#fa1e1e",
|
|
||||||
"free_start" : "#223014",
|
|
||||||
"free_mid" : "#b5e685",
|
|
||||||
"free_end" : "#dcff85",
|
|
||||||
"cached_start" : "#0b1a29",
|
|
||||||
"cached_mid" : "#74e6fc",
|
|
||||||
"cached_end" : "#26c5ff",
|
|
||||||
"available_start" : "#292107",
|
|
||||||
"available_mid" : "#ffd77a",
|
|
||||||
"available_end" : "#ffb814",
|
|
||||||
"used_start" : "#3b1f1c",
|
|
||||||
"used_mid" : "#d9626d",
|
|
||||||
"used_end" : "#ff4769",
|
|
||||||
"download_start" : "#231a63",
|
|
||||||
"download_mid" : "#4f43a3",
|
|
||||||
"download_end" : "#b0a9de",
|
|
||||||
"upload_start" : "#510554",
|
|
||||||
"upload_mid" : "#7d4180",
|
|
||||||
"upload_end" : "#dcafde"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#? Setup error logger ---------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
try:
|
|
||||||
errlog = logging.getLogger("ErrorLogger")
|
|
||||||
errlog.setLevel(logging.DEBUG)
|
|
||||||
eh = logging.handlers.RotatingFileHandler(f'{CONFIG_DIR}/error.log', maxBytes=1048576, backupCount=4)
|
|
||||||
eh.setLevel(logging.DEBUG)
|
|
||||||
eh.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s: %(message)s", datefmt="%d/%m/%y (%X)"))
|
|
||||||
errlog.addHandler(eh)
|
|
||||||
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()}')
|
|
||||||
|
|
||||||
#? Classes --------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
class Term:
|
|
||||||
"""Terminal info and commands"""
|
|
||||||
width: int = os.get_terminal_size().columns #* Current terminal width in columns
|
|
||||||
height: int = os.get_terminal_size().lines #* Current terminal height in lines
|
|
||||||
resized: bool = False #* Flag indicating if terminal was recently resized
|
|
||||||
fg: str = "" #* Default foreground color
|
|
||||||
bg: str = "" #* Default background color
|
|
||||||
hide_cursor = "\033[?25l" #* Hide terminal cursor
|
|
||||||
show_cursor = "\033[?25h" #* Show terminal cursor
|
|
||||||
alt_screen = "\033[?1049h" #* Switch to alternate screen
|
|
||||||
normal_screen = "\033[?1049l" #* Switch to normal screen
|
|
||||||
clear = "\033[2J\033[0;0f" #* Clear screen and set cursor to position 0,0
|
|
||||||
@classmethod
|
|
||||||
def refresh(cls, *args):
|
|
||||||
"""Update width, height and set resized flag if terminal has been resized"""
|
|
||||||
w: int; h: int
|
|
||||||
w, h = os.get_terminal_size()
|
|
||||||
if (w, h) != (cls.width, cls.height):
|
|
||||||
cls.resized = True
|
|
||||||
cls.width, cls.height = w, h
|
|
||||||
@staticmethod
|
|
||||||
def echo(on: bool):
|
|
||||||
"""Toggle input echo"""
|
|
||||||
(iflag, oflag, cflag, lflag, ispeed, ospeed, cc) = termios.tcgetattr(sys.stdin.fileno())
|
|
||||||
if on:
|
|
||||||
lflag |= termios.ECHO # type: ignore
|
|
||||||
else:
|
|
||||||
lflag &= ~termios.ECHO # type: ignore
|
|
||||||
new_attr = [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]
|
|
||||||
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, new_attr)
|
|
||||||
|
|
||||||
class Fx:
|
|
||||||
"""Text effects"""
|
|
||||||
start = "\033[" #* Escape sequence start
|
|
||||||
sep = ";" #* Escape sequence separator
|
|
||||||
end = "m" #* Escape sequence end
|
|
||||||
reset = rs = "\033[0m" #* Reset foreground/background color and text effects
|
|
||||||
bold = b = "\033[1m" #* Bold on
|
|
||||||
unbold = ub = "\033[22m" #* Bold off
|
|
||||||
dark = d = "\033[2m" #* Dark on
|
|
||||||
undark = ud = "\033[22m" #* Dark off
|
|
||||||
italic = i = "\033[3m" #* Italic on
|
|
||||||
unitalic = ui = "\033[23m" #* Italic off
|
|
||||||
underline = u = "\033[4m" #* Underline on
|
|
||||||
ununderline = uu = "\033[24m" #* Underline off
|
|
||||||
blink = bl = "\033[5m" #* Blink on
|
|
||||||
unblink = ubl = "\033[25m" #* Blink off
|
|
||||||
strike = s = "\033[9m" #* Strike / crossed-out on
|
|
||||||
unstrike = us = "\033[29m" #* Strike / crossed-out off
|
|
||||||
|
|
||||||
class Raw(object):
|
|
||||||
"""Set raw input mode for device"""
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.fd = self.stream.fileno()
|
|
||||||
def __enter__(self):
|
|
||||||
self.original_stty = termios.tcgetattr(self.stream)
|
|
||||||
tty.setcbreak(self.stream)
|
|
||||||
def __exit__(self, type, value, traceback):
|
|
||||||
termios.tcsetattr(self.stream, termios.TCSANOW, self.original_stty)
|
|
||||||
|
|
||||||
class Nonblocking(object):
|
|
||||||
"""Set nonblocking mode for device"""
|
|
||||||
def __init__(self, stream):
|
|
||||||
self.stream = stream
|
|
||||||
self.fd = self.stream.fileno()
|
|
||||||
def __enter__(self):
|
|
||||||
self.orig_fl = fcntl.fcntl(self.fd, fcntl.F_GETFL)
|
|
||||||
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl | os.O_NONBLOCK)
|
|
||||||
def __exit__(self, *args):
|
|
||||||
fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl)
|
|
||||||
|
|
||||||
class Mv:
|
|
||||||
"""Class with collection of cursor movement functions: .t[o](line, column) | .r[ight](columns) | .l[eft](columns) | .u[p](lines) | .d[own](lines) | .save() | .restore()"""
|
|
||||||
@staticmethod
|
|
||||||
def to(line: int, col: int) -> str:
|
|
||||||
return f'\033[{line};{col}f' #* Move cursor to line, column
|
|
||||||
@staticmethod
|
|
||||||
def right(x: int) -> str: #* Move cursor right x columns
|
|
||||||
return f'\033[{x}C'
|
|
||||||
@staticmethod
|
|
||||||
def left(x: int) -> str: #* Move cursor left x columns
|
|
||||||
return f'\033[{x}D'
|
|
||||||
@staticmethod
|
|
||||||
def up(x: int) -> str: #* Move cursor up x lines
|
|
||||||
return f'\033[{x}A'
|
|
||||||
@staticmethod
|
|
||||||
def down(x: int) -> str: #* Move cursor down x lines
|
|
||||||
return f'\033[{x}B'
|
|
||||||
@staticmethod
|
|
||||||
def save() -> str: #* Save cursor position
|
|
||||||
return "\033[s"
|
|
||||||
@staticmethod
|
|
||||||
def restore() -> str: #* Restore saved cursor postion
|
|
||||||
return "\033[u"
|
|
||||||
t = to
|
|
||||||
r = right
|
|
||||||
l = left
|
|
||||||
u = up
|
|
||||||
d = down
|
|
||||||
|
|
||||||
class Key:
|
|
||||||
""".list = Input queue | .new = Input threading event | .reader = Input reader thread | .start() = Start input reader | .stop() = Stop input reader"""
|
|
||||||
list: List[str] = []
|
|
||||||
new = threading.Event()
|
|
||||||
stopping: bool = False
|
|
||||||
@classmethod
|
|
||||||
def start(cls):
|
|
||||||
cls.stopping = False
|
|
||||||
cls.reader = threading.Thread(target=get_key)
|
|
||||||
cls.reader.start()
|
|
||||||
@classmethod
|
|
||||||
def stop(cls):
|
|
||||||
if cls.reader.is_alive():
|
|
||||||
cls.stopping = True
|
|
||||||
try:
|
|
||||||
cls.reader.join()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Color:
|
|
||||||
'''self.__init__ accepts 6 digit hexadecimal: string "#RRGGBB", 2 digit hexadecimal: string "#FF" and decimal RGB "0-255 0-255 0-255" as a string.\n
|
|
||||||
self.__init__ also accepts depth="fg" or "bg" | default=bool\n
|
|
||||||
self.__call__(*args) converts arguments to a string and apply color\n
|
|
||||||
self.__str__ returns escape sequence to set color. __iter__ returns iteration over red, green and blue in integer values of 0-255.\n
|
|
||||||
Values: .hex: str | .dec: Tuple[int] | .red: int | .green: int | .blue: int | .depth: str | .escape: str\n
|
|
||||||
'''
|
|
||||||
hex: 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
|
|
||||||
self.default = default
|
|
||||||
try:
|
|
||||||
if not color:
|
|
||||||
if depth != "bg" and not default: raise ValueError("No RGB values given")
|
|
||||||
self.dec = (0, 0, 0)
|
|
||||||
self.hex = ""
|
|
||||||
self.red = self.green = self.blue = 0
|
|
||||||
self.escape = "\033[49m"
|
|
||||||
return
|
|
||||||
|
|
||||||
elif color.startswith("#"):
|
|
||||||
self.hex = color
|
|
||||||
if len(self.hex) == 3:
|
|
||||||
self.hex += self.hex[1:3] + self.hex[1:3]
|
|
||||||
c = int(self.hex[1:3], base=16)
|
|
||||||
self.dec = (c, c, c)
|
|
||||||
elif len(self.hex) == 7:
|
|
||||||
self.dec = (int(self.hex[1:3], base=16), int(self.hex[3:5], base=16), int(self.hex[5:7], base=16))
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Incorrectly formatted hexadeciaml rgb string: {self.hex}')
|
|
||||||
|
|
||||||
else:
|
|
||||||
c_t = tuple(map(int, color.split(" ")))
|
|
||||||
if len(c_t) == 3:
|
|
||||||
self.dec = c_t #type: ignore
|
|
||||||
else:
|
|
||||||
raise ValueError(f'RGB dec should be "0-255 0-255 0-255"')
|
|
||||||
|
|
||||||
ct = self.dec[0] + self.dec[1] + self.dec[2]
|
|
||||||
if ct > 255*3 or ct < 0:
|
|
||||||
raise ValueError(f'RGB values out of range: {color}')
|
|
||||||
except Exception as e:
|
|
||||||
errlog.exception(str(e))
|
|
||||||
self.escape = ""
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.dec and not self.hex: self.hex = f'{hex(self.dec[0]).lstrip("0x").zfill(2)}{hex(self.dec[1]).lstrip("0x").zfill(2)}{hex(self.dec[2]).lstrip("0x").zfill(2)}'
|
|
||||||
|
|
||||||
if self.dec and self.hex:
|
|
||||||
self.red, self.green, self.blue = self.dec
|
|
||||||
self.escape = f'\033[{38 if self.depth == "fg" else 48};2;{";".join(str(c) for c in self.dec)}m'
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.escape
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return repr(self.escape)
|
|
||||||
|
|
||||||
def __iter__(self) -> Iterable:
|
|
||||||
for c in self.dec: yield c
|
|
||||||
|
|
||||||
def __call__(self, *args) -> str:
|
|
||||||
if len(args) == 0: return ""
|
|
||||||
return f'{self.escape}{"".join(map(str, args))}{getattr(Term, self.depth)}'
|
|
||||||
|
|
||||||
class Theme:
|
|
||||||
'''__init__ accepts a dict containing { "color_element" : "color" } , errors defaults to default theme color'''
|
|
||||||
|
|
||||||
main_bg = main_fg = title = hi_fg = selected_bg = selected_fg = inactive_fg = proc_misc = cpu_box = mem_box = net_box = proc_box = div_line = temp_start = temp_mid = temp_end = cpu_start = cpu_mid = cpu_end = free_start = free_mid = free_end = cached_start = cached_mid = cached_end = available_start = available_mid = available_end = used_start = used_mid = used_end = download_start = download_mid = download_end = upload_start = upload_mid = upload_end = NotImplemented
|
|
||||||
|
|
||||||
def __init__(self, tdict: Dict[str, str]):
|
|
||||||
for item, value in tdict.items():
|
|
||||||
if hasattr(self, item):
|
|
||||||
default = False if item not in ["main_fg", "main_bg"] else True
|
|
||||||
depth = "fg" if item not in ["main_bg", "selected_bg"] else "bg"
|
|
||||||
setattr(self, item, Color(value, depth=depth, default=default))
|
|
||||||
if getattr(self, item).escape == "":
|
|
||||||
setattr(self, item, Color(DEFAULT_THEME[item], depth=depth, default=default))
|
|
||||||
|
|
||||||
Term.fg, Term.bg = self.main_fg, self.main_bg
|
|
||||||
print(self.main_fg, self.main_bg) #* Set terminal colors
|
|
||||||
|
|
||||||
class Draw:
|
|
||||||
'''Holds the draw buffer\n
|
|
||||||
Add to buffer: .buffer(name, *args, now=False, clear=False)\n
|
|
||||||
Print buffer: .out()\n
|
|
||||||
'''
|
|
||||||
strings: Dict[str, str] = {}
|
|
||||||
last_screen: str = ""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def buffer(cls, name: str, *args, now: bool = False, clear: bool = False):
|
|
||||||
string: str = ""
|
|
||||||
if args: string = "".join(map(str, args))
|
|
||||||
if name not in cls.strings or clear: cls.strings[name] = ""
|
|
||||||
cls.strings[name] += string
|
|
||||||
if now: print(string)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def out(cls):
|
|
||||||
cls.last_screen = "".join(cls.strings.values())
|
|
||||||
#cls.strings = {}
|
|
||||||
print(cls.last_screen)
|
|
||||||
|
|
||||||
class Symbol:
|
|
||||||
h_line: str = "─"
|
|
||||||
v_line: str = "│"
|
|
||||||
left_up: str = "┌"
|
|
||||||
right_up: str = "┐"
|
|
||||||
left_down: str = "└"
|
|
||||||
right_down: str = "┘"
|
|
||||||
title_left: str = "┤"
|
|
||||||
title_right: str = "├"
|
|
||||||
div_up: str = "┬"
|
|
||||||
div_down: str = "┴"
|
|
||||||
graph_up: Dict[float, str] = {
|
|
||||||
0.0 : "⠀", 0.1 : "⢀", 0.2 : "⢠", 0.3 : "⢰", 0.4 : "⢸",
|
|
||||||
1.0 : "⡀", 1.1 : "⣀", 1.2 : "⣠", 1.3 : "⣰", 1.4 : "⣸",
|
|
||||||
2.0 : "⡄", 2.1 : "⣄", 2.2 : "⣤", 2.3 : "⣴", 2.4 : "⣼",
|
|
||||||
3.0 : "⡆", 3.1 : "⣆", 3.2 : "⣦", 3.3 : "⣶", 3.4 : "⣾",
|
|
||||||
4.0 : "⡇", 4.1 : "⣇", 4.2 : "⣧", 4.3 : "⣷", 4.4 : "⣿"
|
|
||||||
}
|
|
||||||
graph_down: Dict[float, str] = {
|
|
||||||
0.0 : "⠀", 0.1 : "⠈", 0.2 : "⠘", 0.3 : "⠸", 0.4 : "⢸",
|
|
||||||
1.0 : "⠁", 1.1 : "⠉", 1.2 : "⠙", 1.3 : "⠹", 1.4 : "⢹",
|
|
||||||
2.0 : "⠃", 2.1 : "⠋", 2.2 : "⠛", 2.3 : "⠻", 2.4 : "⢻",
|
|
||||||
3.0 : "⠇", 3.1 : "⠏", 3.2 : "⠟", 3.3 : "⠿", 3.4 : "⢿",
|
|
||||||
4.0 : "⡇", 4.1 : "⡏", 4.2 : "⡟", 4.3 : "⡿", 4.4 : "⣿"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#? Functions ------------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
def get_cpu_name():
|
|
||||||
'''Fetch a suitable CPU identifier from the CPU model name string'''
|
|
||||||
name: str = ""
|
|
||||||
nlist: List = []
|
|
||||||
command: str = ""
|
|
||||||
cmd_out: str = ""
|
|
||||||
rem_line: str = ""
|
|
||||||
if SYSTEM == "Linux":
|
|
||||||
command = "cat /proc/cpuinfo"
|
|
||||||
rem_line = "model name"
|
|
||||||
elif SYSTEM == "MacOS":
|
|
||||||
command ="sysctl -n machdep.cpu.brand_string"
|
|
||||||
elif SYSTEM == "BSD":
|
|
||||||
command ="sysctl hw.model"
|
|
||||||
rem_line = "hw.model"
|
|
||||||
|
|
||||||
cmd_out = subprocess.check_output("LANG=C " + command, shell=True, universal_newlines=True)
|
|
||||||
if rem_line:
|
|
||||||
for line in cmd_out.split("\n"):
|
|
||||||
if rem_line in line:
|
|
||||||
name = re.sub( ".*" + rem_line + ".*:", "", line,1).lstrip()
|
|
||||||
else:
|
|
||||||
name = cmd_out
|
|
||||||
nlist = name.split(" ")
|
|
||||||
if "Xeon" in name:
|
|
||||||
name = nlist[nlist.index("CPU")+1]
|
|
||||||
elif "Ryzen" in name:
|
|
||||||
name = " ".join(nlist[nlist.index("Ryzen"):nlist.index("Ryzen")+3])
|
|
||||||
elif "CPU" in name:
|
|
||||||
name = nlist[nlist.index("CPU")-1]
|
|
||||||
|
|
||||||
return name
|
|
||||||
|
|
||||||
def load_theme(path: str) -> Dict[str, str]:
|
|
||||||
'''Load a bashtop formatted theme file'''
|
|
||||||
new_theme: Dict[str, str] = {}
|
|
||||||
try:
|
|
||||||
with open(path) as f:
|
|
||||||
for line in f:
|
|
||||||
if not line.startswith("theme["): continue
|
|
||||||
key = line[6:line.find("]")]
|
|
||||||
s = line.find('"')
|
|
||||||
value = line[s + 1:line.find('"', s + 1)]
|
|
||||||
new_theme[key] = value
|
|
||||||
except Exception as e:
|
|
||||||
errlog.exception(str(e))
|
|
||||||
|
|
||||||
return new_theme
|
|
||||||
|
|
||||||
def fg(h_r: Union[str, int], g: int = 0, b: int = 0) -> str:
|
|
||||||
"""Returns escape sequence to set foreground color, accepts either 6 digit hexadecimal: "#RRGGBB", 2 digit hexadecimal: "#FF" or decimal RGB: 0-255, 0-255, 0-255"""
|
|
||||||
color: str = ""
|
|
||||||
if isinstance(h_r, int): #* Check for decimal RGB
|
|
||||||
color = f'\033[38;2;{h_r};{g};{b}m'
|
|
||||||
|
|
||||||
else: #* Check for 2 or 6 digit hexadecimal RGB
|
|
||||||
if h_r[0] == "#": h_r = h_r[1:]
|
|
||||||
try:
|
|
||||||
if len(h_r) == 2:
|
|
||||||
c = int(h_r, base=16)
|
|
||||||
color = f'\033[38;2;{c};{c};{c}m'
|
|
||||||
elif len(h_r) == 6:
|
|
||||||
color = f'\033[38;2;{int(h_r[0:2], base=16)};{int(h_r[2:4], base=16)};{int(h_r[4:6], base=16)}m'
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return color
|
|
||||||
|
|
||||||
def bg(h_r: Union[str, int], g: int = 0, b: int = 0) -> str:
|
|
||||||
"""Returns escape sequence to set background color, accepts either 6 digit hexadecimal: "#RRGGBB", 2 digit hexadecimal: "#FF" or decimal RGB: 0-255, 0-255, 0-255"""
|
|
||||||
color: str = ""
|
|
||||||
if isinstance(h_r, int): #* Check for decimal RGB
|
|
||||||
color = f'\033[48;2;{h_r};{g};{b}m'
|
|
||||||
|
|
||||||
else: #* Check for 2 or 6 digit hexadecimal RGB
|
|
||||||
if h_r[0] == "#": h_r = h_r[1:]
|
|
||||||
try:
|
|
||||||
if len(h_r) == 2:
|
|
||||||
c = int(h_r, base=16)
|
|
||||||
color = f'\033[48;2;{c};{c};{c}m'
|
|
||||||
elif len(h_r) == 6:
|
|
||||||
color = f'\033[48;2;{int(h_r[0:2], base=16)};{int(h_r[2:4], base=16)};{int(h_r[4:6], base=16)}m'
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return color
|
|
||||||
|
|
||||||
def get_key():
|
|
||||||
"""Get a single key from stdin, convert to readable format and save to keys list"""
|
|
||||||
input_key: str = ""
|
|
||||||
clean_key: str = ""
|
|
||||||
with Raw(sys.stdin): #* Set raw mode
|
|
||||||
with Nonblocking(sys.stdin): #* Set nonblocking mode
|
|
||||||
while not Key.stopping:
|
|
||||||
if not select([sys.stdin], [], [], 0.1)[0]: #* Wait 100ms for input then restart loop to check for stop signal
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
input_key = sys.stdin.read(1) #* Read 1 character from stdin
|
|
||||||
if input_key == "\033": #* Read 3 additional characters if first is escape character
|
|
||||||
input_key += sys.stdin.read(3)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if input_key == "\033": clean_key = "escape"
|
|
||||||
elif input_key == "\n": clean_key = "enter"
|
|
||||||
elif input_key == "\x7f" or input_key == "\x08": clean_key = "backspace"
|
|
||||||
|
|
||||||
elif input_key.isprintable(): clean_key = input_key #* Return character if input key is printable
|
|
||||||
|
|
||||||
if clean_key:
|
|
||||||
Key.list.append(clean_key) #* Store keys in input queue for later processing
|
|
||||||
clean_key = ""
|
|
||||||
Key.new.set() #* Set threading event to interrupt main thread sleep
|
|
||||||
input_key = ""
|
|
||||||
sys.stdin.read(100) #* Clear stdin
|
|
||||||
|
|
||||||
def now_sleeping(signum, frame):
|
|
||||||
"""Reset terminal settings and stop background input read before putting to sleep"""
|
|
||||||
Key.stop()
|
|
||||||
print(Term.clear, Term.normal_screen, Term.show_cursor)
|
|
||||||
os.kill(os.getpid(), signal.SIGSTOP)
|
|
||||||
|
|
||||||
def now_awake(signum, frame):
|
|
||||||
"""Set terminal settings and restart background input read"""
|
|
||||||
print(Term.alt_screen, Term.clear, Term.hide_cursor)
|
|
||||||
Key.start()
|
|
||||||
|
|
||||||
def quit_sigint(signum, frame):
|
|
||||||
"""SIGINT redirection to clean_quit()"""
|
|
||||||
clean_quit()
|
|
||||||
|
|
||||||
def clean_quit(errcode: int = 0):
|
|
||||||
"""Reset terminal settings, save settings to config and stop background input read before quitting"""
|
|
||||||
Key.stop()
|
|
||||||
print(Term.clear, Term.normal_screen, Term.show_cursor)
|
|
||||||
raise SystemExit(errcode)
|
|
||||||
|
|
||||||
def calc_sizes():
|
|
||||||
'''Calculate sizes of boxes'''
|
|
||||||
|
|
||||||
#* Calculate lines and columns from percentage values
|
|
||||||
for box in boxes:
|
|
||||||
setattr(box, "height", round(Term.height * getattr(box, "height_p") / 100))
|
|
||||||
setattr(box, "width", round(Term.width * getattr(box, "width_p") / 100))
|
|
||||||
|
|
||||||
#* Set position values
|
|
||||||
proc.x = mem.width + 1
|
|
||||||
mem.y = proc.y = cpu.height + 1
|
|
||||||
net.y = cpu.height + mem.height + 2
|
|
||||||
|
|
||||||
#* Set values for detailed view in process box
|
|
||||||
proc.detailed_x = proc.x
|
|
||||||
proc.detailed_y = proc.y
|
|
||||||
proc.detailed_height = 8
|
|
||||||
proc.detailed_width = proc.width
|
|
||||||
|
|
||||||
#THREADS = 8
|
|
||||||
|
|
||||||
#* Set values for cpu info sub box
|
|
||||||
if THREADS > (cpu.height - 5) * 3 and cpu.width > 200: cpu.box_width = 24 * 4; cpu.box_height = round(THREADS / 4) + 4; cpu.box_columns = 4
|
|
||||||
elif THREADS > (cpu.height - 5) * 2 and cpu.width > 150: cpu.box_width = 24 * 3; cpu.box_height = round(THREADS / 3) + 5; cpu.box_columns = 3
|
|
||||||
elif THREADS > cpu.height - 5 and cpu.width > 100: cpu.box_width = 24 * 2; cpu.box_height = round(THREADS / 2) + 4; cpu.box_columns = 2
|
|
||||||
else: cpu.box_width = 24; cpu.box_height = THREADS + 4; cpu.box_columns = 1
|
|
||||||
|
|
||||||
if Config.check_temp: cpu.box_width += 13 * cpu.box_columns
|
|
||||||
if cpu.box_height > cpu.height - 3: cpu.box_height = cpu.height - 3
|
|
||||||
cpu.box_x = (cpu.width - 2) - cpu.box_width
|
|
||||||
cpu.box_y = cpu.y + ((cpu.height - 2) // 2) - round(cpu.box_height / 2) + 2
|
|
||||||
|
|
||||||
#* Set value for mem box divider
|
|
||||||
mem.mem_width = mem.disks_width = round((mem.width-2) / 2)
|
|
||||||
if mem.mem_width + mem.disks_width < mem.width - 2: mem.mem_width += 1
|
|
||||||
mem.divider = mem.x + mem.mem_width + 2
|
|
||||||
|
|
||||||
#* Set values for net sub box
|
|
||||||
net.box_width = 24
|
|
||||||
net.box_height = 9 if net.height > 12 else net.height - 4
|
|
||||||
net.box_x = net.width - net.box_width - 2
|
|
||||||
net.box_y = net.y + ((net.height - 2) // 2) - round(net.box_height / 2)
|
|
||||||
|
|
||||||
def create_box(x: int = 0, y: int = 0, width: int = 0, height: int = 0, title: str = "", line_color: Color = None, fill: bool = False, box_object: object = None):
|
|
||||||
'''Create a box from a box object or by given arguments'''
|
|
||||||
out: str = f'{Term.fg}{Term.bg}'
|
|
||||||
if not line_color: line_color = theme.div_line
|
|
||||||
|
|
||||||
#* Get values from box object if given
|
|
||||||
if box_object:
|
|
||||||
x = getattr(box_object, "x")
|
|
||||||
y = getattr(box_object, "y")
|
|
||||||
width = getattr(box_object, "width")
|
|
||||||
height = getattr(box_object, "height")
|
|
||||||
title = getattr(box_object, "name")
|
|
||||||
vlines: Tuple[int, int] = (x, x + width)
|
|
||||||
hlines: Tuple[int, int] = (y, y + height)
|
|
||||||
|
|
||||||
#* Fill box if enabled
|
|
||||||
if fill:
|
|
||||||
i: int
|
|
||||||
for i in range(y + 1, y + height):
|
|
||||||
out += f'{Mv.to(i, x)}{" " * (width - 1)}'
|
|
||||||
|
|
||||||
out += f'{line_color}'
|
|
||||||
|
|
||||||
#* Draw all horizontal lines
|
|
||||||
hpos: int
|
|
||||||
for hpos in hlines:
|
|
||||||
out += f'{Mv.to(hpos, x)}{Symbol.h_line * width}'
|
|
||||||
|
|
||||||
#* Draw all vertical lines
|
|
||||||
vpos: int
|
|
||||||
for vpos in vlines:
|
|
||||||
for hpos in range(y, y + height):
|
|
||||||
out += f'{Mv.to(hpos, vpos)}{Symbol.v_line}'
|
|
||||||
|
|
||||||
#* Draw corners
|
|
||||||
out += f'{Mv.to(y, x)}{Symbol.left_up}\
|
|
||||||
{Mv.to(y, x + width)}{Symbol.right_up}\
|
|
||||||
{Mv.to(y + height, x)}{Symbol.left_down}\
|
|
||||||
{Mv.to(y + height, x + width)}{Symbol.right_down}'
|
|
||||||
|
|
||||||
#* Draw title if enabled
|
|
||||||
if title:
|
|
||||||
out += f'{Mv.to(y, x + 2)}{Symbol.title_left}{theme.title}{Fx.b}{title}{Fx.ub}{line_color}{Symbol.title_right}'
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
#? Main function --------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
def main():
|
|
||||||
line: str = ""
|
|
||||||
this_key: str = ""
|
|
||||||
count: int = 0
|
|
||||||
while True:
|
|
||||||
count += 1
|
|
||||||
print(f'{Mv.to(1,1)}{Fx.b}{blue("Count:")} {count} {lime("Time:")} {time.strftime("%H:%M:%S", time.localtime())}')
|
|
||||||
print(f'{fg("#ff")} Width: {Term.width} Height: {Term.height} Resized: {Term.resized}')
|
|
||||||
while Key.list:
|
|
||||||
Key.new.clear()
|
|
||||||
this_key = Key.list.pop()
|
|
||||||
print(f'{Mv.to(2,1)}{fg("#ff9050")}{Fx.b}Last key= {Term.fg}{Fx.ub}{repr(this_key)}{" " * 40}')
|
|
||||||
if this_key == "backspace":
|
|
||||||
line = line[:-1]
|
|
||||||
elif this_key == "enter":
|
|
||||||
line += "\n"
|
|
||||||
else:
|
|
||||||
line += this_key
|
|
||||||
print(f'{Mv.to(3,1)}{fg("#90ff50")}{Fx.b}Full line= {Term.fg}{Fx.ub}{line}{Fx.bl}| {Fx.ubl}')
|
|
||||||
if this_key == "q":
|
|
||||||
clean_quit()
|
|
||||||
if this_key == "R":
|
|
||||||
raise Exception("Test ERROR")
|
|
||||||
if not Key.reader.is_alive():
|
|
||||||
clean_quit(1)
|
|
||||||
Key.new.wait(1.0)
|
|
||||||
|
|
||||||
|
|
||||||
#? Init Classes ---------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
class Banner:
|
|
||||||
out: List[str] = []
|
|
||||||
c_color: str = ""
|
|
||||||
length: int = 0
|
|
||||||
if not out:
|
|
||||||
for num, (color, line) in enumerate(BANNER_SRC.items()):
|
|
||||||
if len(line) > length: length = len(line)
|
|
||||||
out += [""]
|
|
||||||
line_color = fg(color)
|
|
||||||
line_dark = fg(f'#{80 - num * 6}')
|
|
||||||
for letter in line:
|
|
||||||
if letter == "█" and c_color != line_color:
|
|
||||||
c_color = line_color
|
|
||||||
out[num] += line_color
|
|
||||||
elif letter == " ":
|
|
||||||
letter = f'{Mv.r(1)}'
|
|
||||||
elif letter != "█" and c_color != line_dark:
|
|
||||||
c_color = line_dark
|
|
||||||
out[num] += line_dark
|
|
||||||
out[num] += letter
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def draw(cls, line: int, col: int = 0, center: bool = False, now: bool = False):
|
|
||||||
out: str = ""
|
|
||||||
if center: col = Term.width // 2 - cls.length // 2
|
|
||||||
for n, o in enumerate(cls.out):
|
|
||||||
out += f'{Mv.to(line + n, col)}{o}'
|
|
||||||
out += f'{Term.fg}'
|
|
||||||
if now: print(out)
|
|
||||||
else: return out
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
'''Holds all config variables'''
|
|
||||||
check_temp: bool = True
|
|
||||||
|
|
||||||
class Graphs:
|
|
||||||
'''Holds all graph objects and dicts for dynamically created graphs'''
|
|
||||||
cpu: object = None
|
|
||||||
cores: Dict[int, object] = {}
|
|
||||||
temps: Dict[int, object] = {}
|
|
||||||
net: object = None
|
|
||||||
detailed_cpu: object = None
|
|
||||||
detailed_mem: object = None
|
|
||||||
pid_cpu: Dict[int, object] = {}
|
|
||||||
|
|
||||||
class Meters:
|
|
||||||
'''Holds created meters to reuse instead of recreating meters of past values'''
|
|
||||||
cpu: Dict[int, str] = {}
|
|
||||||
mem_used: Dict[int, str] = {}
|
|
||||||
mem_available: Dict[int, str] = {}
|
|
||||||
mem_cached: Dict[int, str] = {}
|
|
||||||
mem_free: Dict[int, str] = {}
|
|
||||||
swap_used: Dict[int, str] = {}
|
|
||||||
swap_free: Dict[int, str] = {}
|
|
||||||
disks_used: Dict[int, str] = {}
|
|
||||||
disks_free: Dict[int, str] = {}
|
|
||||||
|
|
||||||
class Box:
|
|
||||||
'''Box object with all needed attributes for create_box() function'''
|
|
||||||
def __init__(self, name: str, height_p: int, width_p: int):
|
|
||||||
self.name: str = name
|
|
||||||
self.height_p: int = height_p
|
|
||||||
self.width_p: int = width_p
|
|
||||||
self.x: int = 0
|
|
||||||
self.y: int = 0
|
|
||||||
self.width: int = 0
|
|
||||||
self.height: int = 0
|
|
||||||
self.out: str = ""
|
|
||||||
if name == "proc":
|
|
||||||
self.detailed: bool = False
|
|
||||||
self.detailed_x: int = 0
|
|
||||||
self.detailed_y: int = 0
|
|
||||||
self.detailed_width: int = 0
|
|
||||||
self.detailed_height: int = 8
|
|
||||||
if name == "mem":
|
|
||||||
self.divider: int = 0
|
|
||||||
self.mem_width: int = 0
|
|
||||||
self.disks_width: int = 0
|
|
||||||
if name in ("cpu", "net"):
|
|
||||||
self.box_x: int = 0
|
|
||||||
self.box_y: int = 0
|
|
||||||
self.box_width: int = 0
|
|
||||||
self.box_height: int = 0
|
|
||||||
self.box_columns: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
#? Init variables -------------------------------------------------------------------------------->
|
|
||||||
|
|
||||||
CPU_NAME: str = get_cpu_name()
|
|
||||||
|
|
||||||
|
|
||||||
#theme = Theme(load_theme("/home/gnm/.config/bashtop/themes/monokai.theme"))
|
|
||||||
theme = Theme(DEFAULT_THEME)
|
|
||||||
|
|
||||||
cpu = Box("cpu", height_p=32, width_p=100)
|
|
||||||
mem = Box("mem", height_p=40, width_p=45)
|
|
||||||
net = Box("net", height_p=28, width_p=mem.width_p)
|
|
||||||
proc = Box("proc", height_p=100 - cpu.height_p, width_p=100 - mem.width_p)
|
|
||||||
|
|
||||||
boxes: List[object] = [cpu, mem, net, proc]
|
|
||||||
|
|
||||||
blue = theme.temp_start
|
|
||||||
lime = theme.cached_mid
|
|
||||||
orange = theme.available_end
|
|
||||||
green = theme.cpu_start
|
|
||||||
dfg = theme.main_fg
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def draw_bg(now: bool = True):
|
|
||||||
'''Draw all boxes to buffer and print to screen if now=True'''
|
|
||||||
Draw.buffer("bg", clear=True)
|
|
||||||
|
|
||||||
#* Draw cpu box and cpu sub box
|
|
||||||
cpu_box = f'{create_box(box_object=cpu, line_color=theme.cpu_box, fill=True)}\
|
|
||||||
{Mv.to(cpu.y, cpu.x + 10)}{theme.cpu_box(Symbol.title_left)}{Fx.b}{theme.hi_fg("m")}{theme.title("enu")}{Fx.ub}{theme.cpu_box(Symbol.title_right)}\
|
|
||||||
{create_box(x=cpu.box_x, y=cpu.box_y, width=cpu.box_width, height=cpu.box_height, line_color=theme.div_line, title=CPU_NAME[:18 if Config.check_temp else 9])}'
|
|
||||||
|
|
||||||
#* Draw mem/disk box and divider
|
|
||||||
mem_box = f'{create_box(box_object=mem, line_color=theme.mem_box, fill=True)}\
|
|
||||||
{Mv.to(mem.y, mem.divider + 2)}{theme.mem_box(Symbol.title_left)}{Fx.b}{theme.title("disks")}{Fx.ub}{theme.mem_box(Symbol.title_right)}\
|
|
||||||
{Mv.to(mem.y, mem.divider)}{theme.mem_box(Symbol.div_up)}\
|
|
||||||
{Mv.to(mem.y + mem.height, mem.divider)}{theme.mem_box(Symbol.div_down)}{theme.div_line}'
|
|
||||||
for i in range(1, mem.height):
|
|
||||||
mem_box += f'{Mv.to(mem.y + i, mem.divider)}{Symbol.v_line}'
|
|
||||||
|
|
||||||
#* Draw net box and net sub box
|
|
||||||
net_box = f'{create_box(box_object=net, line_color=theme.net_box, fill=True)}\
|
|
||||||
{create_box(x=net.box_x, y=net.box_y, width=net.box_width, height=net.box_height, line_color=theme.div_line, title="Download")}\
|
|
||||||
{Mv.to(net.box_y + net.box_height, net.box_x + 1)}{theme.div_line(Symbol.title_left)}{Fx.b}{theme.title("Upload")}{Fx.ub}{theme.div_line(Symbol.title_right)}'
|
|
||||||
|
|
||||||
#* Draw proc box
|
|
||||||
proc_box = create_box(box_object=proc, line_color=theme.proc_box, fill=True)
|
|
||||||
|
|
||||||
Draw.buffer("bg", cpu_box, mem_box, net_box, proc_box)
|
|
||||||
|
|
||||||
def testing_colors():
|
|
||||||
for item, _ in DEFAULT_THEME.items():
|
|
||||||
print(Fx.b, getattr(theme, item)(f'{item:<20}'), Fx.ub, f'{"hex=" + getattr(theme, item).hex:<20} dec={getattr(theme, item).dec}', end="\n")
|
|
||||||
|
|
||||||
print()
|
|
||||||
print(theme.temp_start, "Hej!\n")
|
|
||||||
print(Term.fg, "\nHEJ\n")
|
|
||||||
print(repr(Term.fg), repr(Term.bg))
|
|
||||||
|
|
||||||
quit()
|
|
||||||
|
|
||||||
def testing_banner():
|
|
||||||
print(Term.normal_screen, Term.alt_screen)
|
|
||||||
|
|
||||||
#try:
|
|
||||||
#sad
|
|
||||||
#except Exception as e:
|
|
||||||
# errlog.exception(f'{e}')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#eprint("Test")
|
|
||||||
calc_sizes()
|
|
||||||
|
|
||||||
draw_bg()
|
|
||||||
|
|
||||||
Draw.buffer("banner", Banner.draw(18, 45), clear=True)
|
|
||||||
Draw.out()
|
|
||||||
|
|
||||||
print(Mv.to(35, 1), repr(Term.fg), " ", repr(Term.bg), "\n")
|
|
||||||
|
|
||||||
# quit()
|
|
||||||
|
|
||||||
# global theme
|
|
||||||
|
|
||||||
# path = "/home/gnm/.config/bashtop/themes/"
|
|
||||||
# for file in os.listdir(path):
|
|
||||||
# if file.endswith(".theme"):
|
|
||||||
# theme = Theme(load_theme(path + file))
|
|
||||||
# draw_bg()
|
|
||||||
# Draw.out()
|
|
||||||
# time.sleep(1)
|
|
||||||
|
|
||||||
#draw_bg()
|
|
||||||
#Draw.buffer("banner", Banner.draw(5, center=True))
|
|
||||||
|
|
||||||
#Draw.out()
|
|
||||||
|
|
||||||
# print(f'\n{Fx.b}Terminal Height={Term.height} Width={Term.width}')
|
|
||||||
# total_h = total_w = 0
|
|
||||||
# for box in boxes:
|
|
||||||
# print(f'\n{getattr(box, "name")} Height={getattr(box, "height")} Width={getattr(box, "width")}')
|
|
||||||
# total_h += getattr(box, "height")
|
|
||||||
# total_w += getattr(box, "width")
|
|
||||||
# print(f'\nTotal Height={cpu.height + net.height + mem.height} Width={net.width + proc.width}')
|
|
||||||
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#testing_colors()
|
|
||||||
#error_log("/home/gnm/bashtop/misc/error.log")
|
|
||||||
|
|
||||||
|
|
||||||
#testing_banner()
|
|
||||||
|
|
||||||
|
|
||||||
#quit()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
#? Setup signal handlers for SIGSTP, SIGCONT, SIGINT and SIGWINCH
|
|
||||||
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
|
|
||||||
|
|
||||||
#? Switch to alternate screen, clear screen and hide cursor
|
|
||||||
print(Term.alt_screen, Term.clear, Term.hide_cursor)
|
|
||||||
|
|
||||||
#? Start a separate thread for reading keyboard input
|
|
||||||
try:
|
|
||||||
Key.start()
|
|
||||||
except Exception as e:
|
|
||||||
errlog.exception(f'{e}')
|
|
||||||
clean_quit(1)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except Exception as e:
|
|
||||||
errlog.exception(f'{e}')
|
|
||||||
clean_quit(1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
clean_quit()
|
|
Loading…
Reference in New Issue