2020-07-09 00:56:03 +00:00
#!/usr/bin/env python3
# pylint: disable=not-callable, no-member
# indent = tab
# tab-size = 4
# 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.
2020-08-22 18:20:58 +00:00
import os , sys , threading , signal , re , subprocess , logging , logging . handlers , site
2020-08-09 15:43:36 +00:00
import urllib . request
2020-07-09 00:56:03 +00:00
from time import time , sleep , strftime , localtime
from datetime import timedelta
2020-07-16 00:51:55 +00:00
from _thread import interrupt_main
2020-07-24 01:44:11 +00:00
from collections import defaultdict
2020-07-09 00:56:03 +00:00
from select import select
from distutils . util import strtobool
from string import Template
2020-07-18 01:16:01 +00:00
from math import ceil , floor
2020-07-11 22:54:52 +00:00
from random import randint
2020-07-14 00:20:40 +00:00
from shutil import which
2020-07-27 01:13:13 +00:00
from typing import List , Set , Dict , Tuple , Optional , Union , Any , Callable , ContextManager , Iterable , Type , NamedTuple
2020-07-09 00:56:03 +00:00
errors : List [ str ] = [ ]
try : import fcntl , termios , tty
except Exception as e : errors . append ( f ' { e } ' )
try : import psutil # type: ignore
except Exception as e : errors . append ( f ' { e } ' )
SELF_START = time ( )
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 ( " \n Unsupported platform! \n " )
else :
print ( " \n Install required modules! \n " )
2020-08-10 14:27:35 +00:00
raise SystemExit ( 1 )
2020-07-09 00:56:03 +00:00
2020-08-24 15:18:01 +00:00
VERSION : str = " 1.0.21 "
2020-08-02 18:10:42 +00:00
#? Argument parser ------------------------------------------------------------------------------->
if len ( sys . argv ) > 1 :
for arg in sys . argv [ 1 : ] :
if not arg in [ " -m " , " --mini " , " -v " , " --version " , " -h " , " --help " , " --debug " ] :
print ( f ' Unrecognized argument: { arg } \n '
f ' Use argument -h or --help for help ' )
raise SystemExit ( 1 )
if " -h " in sys . argv or " --help " in sys . argv :
print ( f ' USAGE: { sys . argv [ 0 ] } [argument] \n \n '
f ' Arguments: \n '
f ' -m, --mini Start in minimal mode without memory and net boxes \n '
f ' -v, --version Show version info and exit \n '
f ' -h, --help Show this help message and exit \n '
f ' --debug Start with loglevel set to DEBUG overriding value set in config \n '
)
raise SystemExit ( 0 )
elif " -v " in sys . argv or " --version " in sys . argv :
print ( f ' bpytop version: { VERSION } \n '
f ' psutil version: { " . " . join ( str ( x ) for x in psutil . version_info ) } ' )
raise SystemExit ( 0 )
2020-07-09 00:56:03 +00:00
#? Variables ------------------------------------------------------------------------------------->
2020-07-11 22:54:52 +00:00
BANNER_SRC : List [ Tuple [ str , str , str ] ] = [
( " #ffa50a " , " #0fd7ff " , " ██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗ ██████╗ " ) ,
( " #f09800 " , " #00bfe6 " , " ██╔══██╗██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔═══██╗██╔══██╗ " ) ,
( " #db8b00 " , " #00a6c7 " , " ██████╔╝██████╔╝ ╚████╔╝ ██║ ██║ ██║██████╔╝ " ) ,
( " #c27b00 " , " #008ca8 " , " ██╔══██╗██╔═══╝ ╚██╔╝ ██║ ██║ ██║██╔═══╝ " ) ,
( " #a86b00 " , " #006e85 " , " ██████╔╝██║ ██║ ██║ ╚██████╔╝██║ " ) ,
( " #000000 " , " #000000 " , " ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ " ) ,
]
2020-07-09 00:56:03 +00:00
#*?This is the template used to create the config file
DEFAULT_CONF : Template = Template ( f ' #? Config file for bpytop v. { VERSION } ' + '''
2020-08-02 18:10:42 +00:00
#* Color theme, looks for a .theme file in "/usr/[local/]share/bpytop/themes" and "~/.config/bpytop/themes", "Default" for builtin default theme.
#* Prefix name by a plus sign (+) for a theme located in user themes folder, i.e. color_theme="+monokai"
2020-07-09 00:56:03 +00:00
color_theme = " $color_theme "
2020-08-23 10:37:34 +00:00
#* If the theme set background should be shown, set to False if you want terminal background transparency
theme_background = $ theme_background
2020-07-24 01:44:11 +00:00
#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs.
2020-07-09 00:56:03 +00:00
update_ms = $ update_ms
2020-07-11 22:54:52 +00:00
#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive",
2020-07-18 01:16:01 +00:00
#* "cpu lazy" updates top process over time, "cpu responsive" updates top process directly.
2020-07-09 00:56:03 +00:00
proc_sorting = " $proc_sorting "
2020-07-11 22:54:52 +00:00
#* Reverse sorting order, True or False.
2020-07-09 00:56:03 +00:00
proc_reversed = $ proc_reversed
#* Show processes as a tree
proc_tree = $ proc_tree
2020-07-24 01:44:11 +00:00
#* Use the cpu graph colors in the process list.
proc_colors = $ proc_colors
#* Use a darkening gradient in the process list.
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.
proc_per_core = $ proc_per_core
2020-08-09 12:45:03 +00:00
#* Show process memory as bytes instead of percent
proc_mem_bytes = $ proc_mem_bytes
2020-08-23 19:13:01 +00:00
#* Check cpu temperature, needs "osx-cpu-temp" on MacOS X.
2020-07-09 00:56:03 +00:00
check_temp = $ check_temp
2020-07-11 22:54:52 +00:00
#* Draw a clock at top of screen, formatting according to strftime, empty string to disable.
2020-07-09 00:56:03 +00:00
draw_clock = " $draw_clock "
2020-07-11 22:54:52 +00:00
#* Update main ui in background when menus are showing, set this to false if the menus is flickering too much for comfort.
2020-07-09 00:56:03 +00:00
background_update = $ background_update
2020-07-11 22:54:52 +00:00
#* Custom cpu model name, empty string to disable.
2020-07-09 00:56:03 +00:00
custom_cpu_name = " $custom_cpu_name "
2020-07-18 01:16:01 +00:00
#* Optional filter for shown disks, should be last folder in path of a mountpoint, "root" replaces "/", separate multiple values with comma.
#* Begin line with "exclude=" to change to exclude filter, oterwise defaults to "most include" filter. Example: disks_filter="exclude=boot, home"
2020-07-09 00:56:03 +00:00
disks_filter = " $disks_filter "
2020-07-18 01:16:01 +00:00
#* Show graphs instead of meters for memory values.
mem_graphs = $ mem_graphs
#* If swap memory should be shown in memory box.
show_swap = $ show_swap
#* Show swap as a disk, ignores show_swap value above, inserts itself after first disk.
swap_disk = $ swap_disk
#* If mem box should be split to also show disks info.
show_disks = $ show_disks
2020-08-15 15:39:45 +00:00
#* Set fixed values for network graphs, default "10M" = 10 Mibibytes, possible units "K", "M", "G", append with "bit" for bits instead of bytes, i.e "100mbit"
net_download = " $net_download "
net_upload = " $net_upload "
2020-08-09 14:20:00 +00:00
2020-08-15 15:39:45 +00:00
#* Start in network graphs auto rescaling mode, ignores any values set above and rescales down to 10 Kibibytes at the lowest.
net_auto = $ net_auto
#* If the network graphs color gradient should scale to bandwith usage or auto scale, bandwith usage is based on "net_download" and "net_upload" values
net_color_fixed = $ net_color_fixed
2020-08-09 14:20:00 +00:00
2020-07-20 22:52:34 +00:00
#* Show init screen at startup, the init screen is purely cosmetical
show_init = $ show_init
2020-07-11 22:54:52 +00:00
#* Enable check for new version from github.com/aristocratos/bpytop at start.
2020-07-09 00:56:03 +00:00
update_check = $ update_check
2020-08-03 15:02:34 +00:00
#* Enable start in mini mode, can be toggled with shift+m at any time.
2020-08-02 18:10:42 +00:00
mini_mode = $ mini_mode
2020-07-20 22:52:34 +00:00
#* Set loglevel for "~/.config/bpytop/error.log" levels are: "ERROR" "WARNING" "INFO" "DEBUG".
2020-07-11 22:54:52 +00:00
#* The level set includes all lower levels, i.e. "DEBUG" will show all logging info.
2020-07-20 22:52:34 +00:00
log_level = $ log_level
2020-07-09 00:56:03 +00:00
''' )
CONFIG_DIR : str = f ' { os . path . expanduser ( " ~ " ) } /.config/bpytop '
if not os . path . isdir ( CONFIG_DIR ) :
try :
os . makedirs ( CONFIG_DIR )
os . mkdir ( f ' { CONFIG_DIR } /themes ' )
except PermissionError :
print ( f ' ERROR! \n No permission to write to " { CONFIG_DIR } " directory! ' )
2020-08-10 14:27:35 +00:00
raise SystemExit ( 1 )
2020-07-09 00:56:03 +00:00
CONFIG_FILE : str = f ' { CONFIG_DIR } /bpytop.conf '
2020-08-02 18:10:42 +00:00
THEME_DIR : str = " "
2020-08-22 18:20:58 +00:00
for td in site . getsitepackages ( ) + [ site . getusersitepackages ( ) ] :
if os . path . isdir ( f ' { td } /bpytop-themes ' ) :
THEME_DIR = f ' { td } /bpytop-themes '
2020-08-02 18:10:42 +00:00
break
2020-08-22 18:20:58 +00:00
else :
for td in [ " local/ " , " " ] :
if os . path . isdir ( f ' /usr/ { td } share/bpytop/themes ' ) :
THEME_DIR = f ' /usr/ { td } share/bpytop/themes '
break
2020-08-02 18:10:42 +00:00
USER_THEME_DIR : str = f ' { CONFIG_DIR } /themes '
2020-07-09 00:56:03 +00:00
CORES : int = psutil . cpu_count ( logical = False ) or 1
THREADS : int = psutil . cpu_count ( logical = True ) or 1
2020-07-16 00:51:55 +00:00
THREAD_ERROR : int = 0
2020-07-20 22:52:34 +00:00
if " --debug " in sys . argv :
DEBUG = True
else :
DEBUG = False
2020-07-09 00:56:03 +00:00
DEFAULT_THEME : Dict [ str , str ] = {
" main_bg " : " " ,
" main_fg " : " #cc " ,
" title " : " #ee " ,
2020-08-02 18:10:42 +00:00
" hi_fg " : " #969696 " ,
2020-07-09 00:56:03 +00:00
" selected_bg " : " #7e2626 " ,
" selected_fg " : " #ee " ,
" inactive_fg " : " #40 " ,
2020-08-14 17:37:40 +00:00
" graph_text " : " #60 " ,
" meter_bg " : " #40 " ,
2020-07-09 00:56:03 +00:00
" 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 " ,
2020-08-14 17:37:40 +00:00
" upload_end " : " #dcafde " ,
" process_start " : " #80d0a3 " ,
" process_mid " : " #dcd179 " ,
" process_end " : " #d45454 " ,
2020-07-09 00:56:03 +00:00
}
2020-07-24 01:44:11 +00:00
MENUS : Dict [ str , Dict [ str , Tuple [ str , . . . ] ] ] = {
" options " : {
" normal " : (
" ┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐ " ,
" │ │├─┘ │ ││ ││││└─┐ " ,
" └─┘┴ ┴ ┴└─┘┘└┘└─┘ " ) ,
" selected " : (
" ╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ " ,
" ║ ║╠═╝ ║ ║║ ║║║║╚═╗ " ,
" ╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝ " ) } ,
" help " : {
" normal " : (
" ┬ ┬┌─┐┬ ┌─┐ " ,
" ├─┤├┤ │ ├─┘ " ,
" ┴ ┴└─┘┴─┘┴ " ) ,
" selected " : (
" ╦ ╦╔═╗╦ ╔═╗ " ,
" ╠═╣║╣ ║ ╠═╝ " ,
" ╩ ╩╚═╝╩═╝╩ " ) } ,
" quit " : {
" normal " : (
" ┌─┐ ┬ ┬ ┬┌┬┐ " ,
" │─┼┐│ │ │ │ " ,
" └─┘└└─┘ ┴ ┴ " ) ,
" selected " : (
" ╔═╗ ╦ ╦ ╦╔╦╗ " ,
" ║═╬╗║ ║ ║ ║ " ,
" ╚═╝╚╚═╝ ╩ ╩ " ) }
}
MENU_COLORS : Dict [ str , Tuple [ str , . . . ] ] = {
" normal " : ( " #0fd7ff " , " #00bfe6 " , " #00a6c7 " , " #008ca8 " ) ,
" selected " : ( " #ffa50a " , " #f09800 " , " #db8b00 " , " #c27b00 " )
}
2020-07-09 00:56:03 +00:00
#? Units for floating_humanizer function
UNITS : Dict [ str , Tuple [ str , . . . ] ] = {
" bit " : ( " bit " , " Kib " , " Mib " , " Gib " , " Tib " , " Pib " , " Eib " , " Zib " , " Yib " , " Bib " , " GEb " ) ,
" byte " : ( " Byte " , " KiB " , " MiB " , " GiB " , " TiB " , " PiB " , " EiB " , " ZiB " , " YiB " , " BiB " , " GEB " )
}
#? 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! \n No permission to write to " { CONFIG_DIR } " directory! ' )
2020-08-10 14:27:35 +00:00
raise SystemExit ( 1 )
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
#? Timers for testing and debugging -------------------------------------------------------------->
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
class TimeIt :
2020-07-09 00:56:03 +00:00
timers : Dict [ str , float ] = { }
paused : Dict [ str , float ] = { }
@classmethod
def start ( cls , name ) :
cls . timers [ name ] = time ( )
@classmethod
def pause ( cls , name ) :
if name in cls . timers :
cls . paused [ name ] = time ( ) - cls . timers [ name ]
del cls . timers [ name ]
@classmethod
def stop ( cls , name ) :
if name in cls . timers :
total : float = time ( ) - cls . timers [ name ]
del cls . timers [ name ]
if name in cls . paused :
total + = cls . paused [ name ]
del cls . paused [ name ]
errlog . debug ( f ' { name } completed in { total : .6f } seconds ' )
2020-07-20 22:52:34 +00:00
def timeit_decorator ( func ) :
2020-07-09 00:56:03 +00:00
def timed ( * args , * * kw ) :
ts = time ( )
out = func ( * args , * * kw )
2020-07-20 22:52:34 +00:00
errlog . debug ( f ' { func . __name__ } completed in { time ( ) - ts : .6f } seconds ' )
2020-07-09 00:56:03 +00:00
return out
return timed
2020-07-20 22:52:34 +00:00
#? Set up config class and load config ----------------------------------------------------------->
2020-07-09 00:56:03 +00:00
class Config :
''' Holds all config variables and functions for loading from and saving to disk '''
2020-08-09 12:45:03 +00:00
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 " ,
2020-08-23 10:37:34 +00:00
" 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 " , " mini_mode " , " theme_background " ]
2020-07-09 00:56:03 +00:00
conf_dict : Dict [ str , Union [ str , int , bool ] ] = { }
color_theme : str = " Default "
2020-08-23 10:37:34 +00:00
theme_background : bool = True
2020-07-24 01:44:11 +00:00
update_ms : int = 2000
2020-07-09 00:56:03 +00:00
proc_sorting : str = " cpu lazy "
proc_reversed : bool = False
proc_tree : bool = False
2020-07-24 01:44:11 +00:00
proc_colors : bool = True
proc_gradient : bool = True
proc_per_core : bool = False
2020-08-09 12:45:03 +00:00
proc_mem_bytes : bool = True
2020-07-09 00:56:03 +00:00
check_temp : bool = True
draw_clock : str = " %X "
background_update : bool = True
custom_cpu_name : str = " "
disks_filter : str = " "
update_check : bool = True
2020-07-18 01:16:01 +00:00
mem_graphs : bool = True
2020-07-20 22:52:34 +00:00
show_swap : bool = True
2020-07-18 01:16:01 +00:00
swap_disk : bool = True
show_disks : bool = True
2020-08-15 15:39:45 +00:00
net_download : str = " 10M "
net_upload : str = " 10M "
net_color_fixed : bool = False
2020-08-23 13:11:33 +00:00
net_auto : bool = True
2020-07-20 22:52:34 +00:00
show_init : bool = True
2020-07-30 02:29:40 +00:00
mini_mode : bool = False
2020-07-11 22:54:52 +00:00
log_level : str = " WARNING "
warnings : List [ str ] = [ ]
2020-07-20 22:52:34 +00:00
info : List [ str ] = [ ]
2020-07-11 22:54:52 +00:00
sorting_options : List [ str ] = [ " pid " , " program " , " arguments " , " threads " , " user " , " memory " , " cpu lazy " , " cpu responsive " ]
2020-07-20 22:52:34 +00:00
log_levels : List [ str ] = [ " ERROR " , " WARNING " , " INFO " , " DEBUG " ]
2020-07-09 00:56:03 +00:00
changed : bool = False
recreate : bool = False
config_file : str = " "
_initialized : bool = False
def __init__ ( self , path : str ) :
self . config_file = path
conf : Dict [ str , Union [ str , int , bool ] ] = self . load_config ( )
if not " version " in conf . keys ( ) :
self . recreate = True
2020-07-20 22:52:34 +00:00
self . info . append ( f ' Config file malformatted or missing, will be recreated on exit! ' )
2020-07-09 00:56:03 +00:00
elif conf [ " version " ] != VERSION :
self . recreate = True
2020-07-20 22:52:34 +00:00
self . info . append ( f ' Config file version and bpytop version missmatch, will be recreated on exit! ' )
2020-07-09 00:56:03 +00:00
for key in self . keys :
if key in conf . keys ( ) and conf [ key ] != " _error_ " :
setattr ( self , key , conf [ key ] )
else :
self . recreate = True
self . conf_dict [ key ] = getattr ( self , key )
self . _initialized = True
def __setattr__ ( self , name , value ) :
if self . _initialized :
object . __setattr__ ( self , " changed " , True )
object . __setattr__ ( self , name , value )
if name not in [ " _initialized " , " recreate " , " changed " ] :
self . conf_dict [ name ] = value
def load_config ( self ) - > Dict [ str , Union [ str , int , bool ] ] :
''' Load config from file, set correct types for values and return a dict '''
new_config : Dict [ str , Union [ str , int , bool ] ] = { }
2020-08-03 20:00:55 +00:00
conf_file : str = " "
if os . path . isfile ( self . config_file ) :
conf_file = self . config_file
elif os . path . isfile ( " /etc/bpytop.conf " ) :
conf_file = " /etc/bpytop.conf "
else :
return new_config
2020-07-09 00:56:03 +00:00
try :
2020-08-03 20:00:55 +00:00
with open ( conf_file , " r " ) as f :
2020-07-09 00:56:03 +00:00
for line in f :
line = line . strip ( )
if line . startswith ( " #? Config " ) :
new_config [ " version " ] = line [ line . find ( " v. " ) + 3 : ]
for key in self . keys :
if line . startswith ( key ) :
2020-07-24 01:44:11 +00:00
line = line . replace ( key + " = " , " " )
2020-07-09 00:56:03 +00:00
if line . startswith ( ' " ' ) :
line = line . strip ( ' " ' )
if type ( getattr ( self , key ) ) == int :
try :
new_config [ key ] = int ( line )
except ValueError :
2020-07-11 22:54:52 +00:00
self . warnings . append ( f ' Config key " { key } " should be an integer! ' )
2020-07-09 00:56:03 +00:00
if type ( getattr ( self , key ) ) == bool :
try :
new_config [ key ] = bool ( strtobool ( line ) )
except ValueError :
2020-07-11 22:54:52 +00:00
self . warnings . append ( f ' Config key " { key } " can only be True or False! ' )
2020-07-09 00:56:03 +00:00
if type ( getattr ( self , key ) ) == str :
new_config [ key ] = str ( line )
except Exception as e :
errlog . exception ( str ( e ) )
2020-07-11 22:54:52 +00:00
if " proc_sorting " in new_config and not new_config [ " proc_sorting " ] in self . sorting_options :
2020-07-09 00:56:03 +00:00
new_config [ " proc_sorting " ] = " _error_ "
2020-07-11 22:54:52 +00:00
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! ' )
2020-07-20 22:52:34 +00:00
if isinstance ( new_config [ " update_ms " ] , int ) and new_config [ " update_ms " ] < 100 :
new_config [ " update_ms " ] = 100
self . warnings . append ( f ' Config key " update_ms " can \' t be lower than 100! ' )
2020-08-20 16:31:12 +00:00
for net_name in [ " net_download " , " net_upload " ] :
if net_name in new_config and not new_config [ net_name ] [ 0 ] . isdigit ( ) : # type: ignore
new_config [ net_name ] = " _error_ "
2020-07-09 00:56:03 +00:00
return new_config
def save_config ( self ) :
''' Save current config to config file if difference in values or version, creates a new file if not found '''
if not self . changed and not self . recreate : return
try :
with open ( self . config_file , " w " if os . path . isfile ( self . config_file ) else " x " ) as f :
f . write ( DEFAULT_CONF . substitute ( self . conf_dict ) )
except Exception as e :
errlog . exception ( str ( e ) )
try :
CONFIG : Config = Config ( CONFIG_FILE )
2020-08-23 12:59:38 +00:00
if DEBUG :
2020-08-23 10:14:00 +00:00
errlog . setLevel ( logging . DEBUG )
2020-07-20 22:52:34 +00:00
else :
errlog . setLevel ( getattr ( logging , CONFIG . log_level ) )
if CONFIG . log_level == " DEBUG " : DEBUG = True
2020-07-11 22:54:52 +00:00
errlog . info ( f ' New instance of bpytop version { VERSION } started with pid { os . getpid ( ) } ' )
2020-08-14 17:55:15 +00:00
errlog . info ( f ' Loglevel set to { " DEBUG " if DEBUG else CONFIG . log_level } ' )
2020-08-02 18:10:42 +00:00
errlog . debug ( f ' Using psutil version { " . " . join ( str ( x ) for x in psutil . version_info ) } ' )
errlog . debug ( f ' CMD: { " " . join ( sys . argv ) } ' )
2020-07-20 22:52:34 +00:00
if CONFIG . info :
for info in CONFIG . info :
errlog . info ( info )
CONFIG . info = [ ]
2020-07-11 22:54:52 +00:00
if CONFIG . warnings :
for warning in CONFIG . warnings :
errlog . warning ( warning )
CONFIG . warnings = [ ]
2020-07-09 00:56:03 +00:00
except Exception as e :
errlog . exception ( f ' { e } ' )
2020-08-10 14:27:35 +00:00
raise SystemExit ( 1 )
2020-07-09 00:56:03 +00:00
2020-08-18 19:51:34 +00:00
if psutil . version_info [ 0 ] < 5 or ( psutil . version_info [ 0 ] == 5 and psutil . version_info [ 1 ] < 7 ) :
warn = f ' psutil version { " . " . join ( str ( x ) for x in psutil . version_info ) } detected, version 5.7.0 or later required for full functionality! '
print ( " WARNING! " , warn )
errlog . warning ( warn )
2020-07-09 00:56:03 +00:00
#? Classes --------------------------------------------------------------------------------------->
class Term :
""" Terminal info and commands """
2020-08-23 10:14:00 +00:00
width : int = 0
height : int = 0
2020-07-18 01:16:01 +00:00
resized : bool = False
2020-07-09 00:56:03 +00:00
_w : int = 0
_h : int = 0
2020-07-24 01:44:11 +00:00
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
mouse_on = " \033 [?1002h \033 [?1015h \033 [?1006h " #* Enable reporting of mouse position on click and release
mouse_off = " \033 [?1002l " #* Disable mouse reporting
mouse_direct_on = " \033 [?1003h " #* Enable reporting of mouse position at any movement
mouse_direct_off = " \033 [?1003l " #* Disable direct mouse reporting
2020-07-20 22:52:34 +00:00
winch = threading . Event ( )
2020-07-09 00:56:03 +00:00
@classmethod
2020-07-30 02:29:40 +00:00
def refresh ( cls , * args , force : bool = False ) :
2020-07-09 00:56:03 +00:00
""" Update width, height and set resized flag if terminal has been resized """
2020-07-20 22:52:34 +00:00
if cls . resized : cls . winch . set ( ) ; return
2020-07-09 00:56:03 +00:00
cls . _w , cls . _h = os . get_terminal_size ( )
2020-07-30 02:29:40 +00:00
if ( cls . _w , cls . _h ) == ( cls . width , cls . height ) and not force : return
2020-08-02 18:10:42 +00:00
if force : Collector . collect_interrupt = True
2020-07-20 22:52:34 +00:00
while ( cls . _w , cls . _h ) != ( cls . width , cls . height ) or ( cls . _w < 80 or cls . _h < 24 ) :
if Init . running : Init . resized = True
2020-08-02 18:10:42 +00:00
CpuBox . clock_block = True
2020-07-09 00:56:03 +00:00
cls . resized = True
2020-07-24 01:44:11 +00:00
Collector . collect_interrupt = True
2020-07-09 00:56:03 +00:00
cls . width , cls . height = cls . _w , cls . _h
Draw . now ( Term . clear )
2020-07-24 01:44:11 +00:00
Draw . now ( f ' { create_box ( cls . _w / / 2 - 25 , cls . _h / / 2 - 2 , 50 , 3 , " resizing " , line_color = Colors . green , title_color = Colors . white ) } ' ,
2020-07-20 22:52:34 +00:00
f ' { Mv . r ( 12 ) } { Colors . default } { Colors . black_bg } { Fx . b } Width : { cls . _w } Height: { cls . _h } { Fx . ub } { Term . bg } { Term . fg } ' )
2020-07-24 01:44:11 +00:00
if cls . _w < 80 or cls . _h < 24 :
while cls . _w < 80 or cls . _h < 24 :
Draw . now ( Term . clear )
Draw . now ( f ' { create_box ( cls . _w / / 2 - 25 , cls . _h / / 2 - 2 , 50 , 4 , " warning " , line_color = Colors . red , title_color = Colors . white ) } ' ,
f ' { Mv . r ( 12 ) } { Colors . default } { Colors . black_bg } { Fx . b } Width: { Colors . red if cls . _w < 80 else Colors . green } { cls . _w } ' ,
f ' { Colors . default } Height: { Colors . red if cls . _h < 24 else Colors . green } { cls . _h } { Term . bg } { Term . fg } ' ,
f ' { Mv . to ( cls . _h / / 2 , cls . _w / / 2 - 23 ) } { Colors . default } { Colors . black_bg } Width and Height needs to be at least 80 x 24 ! { Fx . ub } { Term . bg } { Term . fg } ' )
cls . winch . wait ( 0.3 )
cls . winch . clear ( )
cls . _w , cls . _h = os . get_terminal_size ( )
else :
2020-07-20 22:52:34 +00:00
cls . winch . wait ( 0.3 )
cls . winch . clear ( )
2020-07-09 00:56:03 +00:00
cls . _w , cls . _h = os . get_terminal_size ( )
2020-08-04 19:35:49 +00:00
Key . mouse = { }
2020-07-20 22:52:34 +00:00
Box . calc_sizes ( )
if Init . running : cls . resized = False ; return
2020-07-24 01:44:11 +00:00
if Menu . active : Menu . resized = True
Box . draw_bg ( now = False )
2020-07-20 22:52:34 +00:00
cls . resized = False
Timer . finish ( )
2020-07-09 00:56:03 +00:00
@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 )
2020-08-09 15:56:06 +00:00
@staticmethod
def title ( text : str = " " ) - > str :
if text : text = f ' { text } '
return f ' \033 ]0; { os . environ . get ( " TERMINAL_TITLE " , " " ) } { text } \a '
2020-07-09 00:56:03 +00:00
class Fx :
2020-07-16 00:51:55 +00:00
""" Text effects
2020-07-24 01:44:11 +00:00
* trans ( string : str ) : Replace whitespace with escape move right to not overwrite background behind whitespace .
* uncolor ( string : str ) : Removes all 24 - bit color and returns string . """
2020-07-09 00:56:03 +00:00
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
2020-07-24 01:44:11 +00:00
#* Precompiled regex for finding a 24-bit color escape sequence in a string
2020-07-16 00:51:55 +00:00
color_re = re . compile ( r " \ 033 \ [ \ d+; \ d?;? \ d*;? \ d*;? \ d*m " )
2020-07-09 00:56:03 +00:00
@staticmethod
def trans ( string : str ) :
return string . replace ( " " , " \033 [1C " )
2020-07-16 00:51:55 +00:00
@classmethod
def uncolor ( cls , string : str ) - > str :
2020-07-24 01:44:11 +00:00
return f ' { cls . color_re . sub ( " " , string ) } '
2020-07-16 00:51:55 +00:00
2020-07-09 00:56:03 +00:00
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 '
2020-07-18 01:16:01 +00:00
save : str = " \033 [s " #* Save cursor position
restore : str = " \033 [u " #* Restore saved cursor postion
2020-07-09 00:56:03 +00:00
t = to
r = right
l = left
u = up
d = down
class Key :
2020-07-24 01:44:11 +00:00
""" Handles the threaded input reader for keypresses and mouse events """
2020-07-09 00:56:03 +00:00
list : List [ str ] = [ ]
2020-07-24 01:44:11 +00:00
mouse : Dict [ str , List [ List [ int ] ] ] = { }
mouse_pos : Tuple [ int , int ] = ( 0 , 0 )
2020-07-20 22:52:34 +00:00
escape : Dict [ Union [ str , Tuple [ str , str ] ] , str ] = {
" \n " : " enter " ,
( " \x7f " , " \x08 " ) : " backspace " ,
( " [A " , " OA " ) : " up " ,
( " [B " , " OB " ) : " down " ,
( " [D " , " OD " ) : " left " ,
( " [C " , " OC " ) : " right " ,
" [2~ " : " insert " ,
" [3~ " : " delete " ,
" [H " : " home " ,
" [F " : " end " ,
" [5~ " : " page_up " ,
" [6~ " : " page_down " ,
2020-07-24 01:44:11 +00:00
" \t " : " tab " ,
2020-07-20 22:52:34 +00:00
" [Z " : " shift_tab " ,
" OP " : " f1 " ,
" OQ " : " f2 " ,
" OR " : " f3 " ,
" OS " : " f4 " ,
" [15 " : " f5 " ,
" [17 " : " f6 " ,
" [18 " : " f7 " ,
" [19 " : " f8 " ,
" [20 " : " f9 " ,
" [21 " : " f10 " ,
" [23 " : " f11 " ,
" [24 " : " f12 "
}
2020-07-09 00:56:03 +00:00
new = threading . Event ( )
idle = threading . Event ( )
2020-07-24 01:44:11 +00:00
mouse_move = threading . Event ( )
mouse_report : bool = False
2020-07-09 00:56:03 +00:00
idle . set ( )
stopping : bool = False
started : bool = False
reader : threading . Thread
@classmethod
def start ( cls ) :
cls . stopping = False
cls . reader = threading . Thread ( target = cls . _get_key )
cls . reader . start ( )
cls . started = True
@classmethod
def stop ( cls ) :
if cls . started and cls . reader . is_alive ( ) :
cls . stopping = True
try :
cls . reader . join ( )
except :
pass
2020-07-11 22:54:52 +00:00
@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 " "
2020-07-24 01:44:11 +00:00
@classmethod
def get_mouse ( cls ) - > Tuple [ int , int ] :
if cls . new . is_set ( ) :
cls . new . clear ( )
return cls . mouse_pos
@classmethod
def mouse_moved ( cls ) - > bool :
if cls . mouse_move . is_set ( ) :
cls . mouse_move . clear ( )
return True
else :
return False
2020-07-11 22:54:52 +00:00
@classmethod
def has_key ( cls ) - > bool :
if cls . list : return True
else : return False
@classmethod
def clear ( cls ) :
cls . list = [ ]
2020-07-09 00:56:03 +00:00
@classmethod
2020-07-24 01:44:11 +00:00
def input_wait ( cls , sec : float = 0.0 , mouse : bool = False ) - > bool :
2020-07-09 00:56:03 +00:00
''' Returns True if key is detected else waits out timer and returns False '''
2020-08-02 18:10:42 +00:00
if cls . list : return True
2020-07-24 01:44:11 +00:00
if mouse : Draw . now ( Term . mouse_direct_on )
2020-07-09 00:56:03 +00:00
cls . new . wait ( sec if sec > 0 else 0.0 )
2020-07-24 01:44:11 +00:00
if mouse : Draw . now ( Term . mouse_direct_off , Term . mouse_on )
2020-07-09 00:56:03 +00:00
if cls . new . is_set ( ) :
cls . new . clear ( )
return True
else :
return False
2020-07-24 01:44:11 +00:00
@classmethod
def break_wait ( cls ) :
cls . list . append ( " _null " )
cls . new . set ( )
sleep ( 0.01 )
cls . new . clear ( )
2020-07-09 00:56:03 +00:00
@classmethod
def _get_key ( cls ) :
2020-07-24 01:44:11 +00:00
""" Get a key or escape sequence from stdin, convert to readable format and save to keys list. Meant to be run in it ' s own thread. """
2020-07-09 00:56:03 +00:00
input_key : str = " "
clean_key : str = " "
try :
while not cls . stopping :
with Raw ( sys . stdin ) :
2020-07-24 01:44:11 +00:00
if not select ( [ sys . stdin ] , [ ] , [ ] , 0.1 ) [ 0 ] : #* Wait 100ms for input on stdin then restart loop to check for stop flag
2020-07-09 00:56:03 +00:00
continue
2020-07-24 01:44:11 +00:00
input_key + = sys . stdin . read ( 1 ) #* Read 1 key safely with blocking on
if input_key == " \033 " : #* If first character is a escape sequence keep reading
cls . idle . clear ( ) #* Report IO block in progress to prevent Draw functions from getting a IO Block error
Draw . idle . wait ( ) #* Wait for Draw function to finish if busy
with Nonblocking ( sys . stdin ) : #* Set non blocking to prevent read stall
2020-07-20 22:52:34 +00:00
input_key + = sys . stdin . read ( 20 )
2020-08-03 19:44:54 +00:00
if input_key . startswith ( " \033 [< " ) :
2020-07-24 01:44:11 +00:00
_ = sys . stdin . read ( 1000 )
cls . idle . set ( ) #* Report IO blocking done
#errlog.debug(f'{repr(input_key)}')
if input_key == " \033 " : clean_key = " escape " #* Key is "escape" key if only containing \033
elif input_key . startswith ( ( " \033 [<0; " , " \033 [<35; " , " \033 [<64; " , " \033 [<65; " ) ) : #* Detected mouse event
2020-07-20 22:52:34 +00:00
try :
2020-07-24 01:44:11 +00:00
cls . mouse_pos = ( int ( input_key . split ( " ; " ) [ 1 ] ) , int ( input_key . split ( " ; " ) [ 2 ] . rstrip ( " mM " ) ) )
2020-07-20 22:52:34 +00:00
except :
pass
else :
2020-07-24 01:44:11 +00:00
if input_key . startswith ( " \033 [<35; " ) : #* Detected mouse move in mouse direct mode
cls . mouse_move . set ( )
cls . new . set ( )
elif input_key . startswith ( " \033 [<64; " ) : #* Detected mouse scroll up
clean_key = " mouse_scroll_up "
elif input_key . startswith ( " \033 [<65; " ) : #* Detected mouse scroll down
clean_key = " mouse_scroll_down "
elif input_key . startswith ( " \033 [<0; " ) and input_key . endswith ( " m " ) : #* Detected mouse click release
if Menu . active :
clean_key = " mouse_click "
else :
for key_name , positions in cls . mouse . items ( ) : #* Check if mouse position is clickable
if list ( cls . mouse_pos ) in positions :
clean_key = key_name
break
else :
clean_key = " mouse_click "
elif input_key == " \\ " : clean_key = " \\ " #* Clean up "\" to not return escaped
2020-07-09 00:56:03 +00:00
else :
2020-07-24 01:44:11 +00:00
for code in cls . escape . keys ( ) : #* Go trough dict of escape codes to get the cleaned key name
2020-07-09 00:56:03 +00:00
if input_key . lstrip ( " \033 " ) . startswith ( code ) :
2020-07-20 22:52:34 +00:00
clean_key = cls . escape [ code ]
2020-07-09 00:56:03 +00:00
break
2020-07-24 01:44:11 +00:00
else : #* If not found in escape dict and length of key is 1, assume regular character
2020-07-09 00:56:03 +00:00
if len ( input_key ) == 1 :
clean_key = input_key
if clean_key :
2020-07-24 01:44:11 +00:00
cls . list . append ( clean_key ) #* Store up to 10 keys in input queue for later processing
2020-07-11 22:54:52 +00:00
if len ( cls . list ) > 10 : del cls . list [ 0 ]
2020-07-09 00:56:03 +00:00
clean_key = " "
2020-07-24 01:44:11 +00:00
cls . new . set ( ) #* Set threading event to interrupt main thread sleep
2020-07-09 00:56:03 +00:00
input_key = " "
2020-07-24 01:44:11 +00:00
2020-07-09 00:56:03 +00:00
except Exception as e :
2020-07-16 00:51:55 +00:00
errlog . exception ( f ' Input thread failed with exception: { e } ' )
2020-07-09 00:56:03 +00:00
cls . idle . set ( )
cls . list . clear ( )
2020-07-16 00:51:55 +00:00
clean_quit ( 1 , thread = True )
2020-07-09 00:56:03 +00:00
class Draw :
''' Holds the draw buffer and manages IO blocking queue
2020-07-11 22:54:52 +00:00
* . buffer ( [ + ] name [ ! ] , * args , append = False , now = False , z = 100 ) : Add * args to buffer
2020-07-09 00:56:03 +00:00
* - 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
* . now ( * args ) : Prints all arguments as a string
* . clear ( * names ) : Clear named buffers , all if no argument
2020-07-14 00:20:40 +00:00
* . last_screen ( ) : Prints all saved buffers
2020-07-09 00:56:03 +00:00
'''
strings : Dict [ str , str ] = { }
2020-07-11 22:54:52 +00:00
z_order : Dict [ str , int ] = { }
2020-07-24 01:44:11 +00:00
saved : Dict [ str , str ] = { }
save : Dict [ str , bool ] = { }
once : Dict [ str , bool ] = { }
2020-07-09 00:56:03 +00:00
idle = threading . Event ( )
idle . set ( )
@classmethod
def now ( cls , * args ) :
2020-07-24 01:44:11 +00:00
''' Wait for input reader and self to be idle then print to screen '''
2020-07-09 00:56:03 +00:00
Key . idle . wait ( )
cls . idle . wait ( )
cls . idle . clear ( )
try :
print ( * args , sep = " " , end = " " , flush = True )
except BlockingIOError :
pass
Key . idle . wait ( )
print ( * args , sep = " " , end = " " , flush = True )
cls . idle . set ( )
@classmethod
2020-07-24 01:44:11 +00:00
def buffer ( cls , name : str , * args : str , append : bool = False , now : bool = False , z : int = 100 , only_save : bool = False , no_save : bool = False , once : bool = False ) :
2020-07-09 00:56:03 +00:00
string : str = " "
if name . startswith ( " + " ) :
name = name . lstrip ( " + " )
append = True
if name . endswith ( " ! " ) :
name = name . rstrip ( " ! " )
now = True
2020-07-24 01:44:11 +00:00
cls . save [ name ] = not no_save
cls . once [ name ] = once
2020-07-11 22:54:52 +00:00
if not name in cls . z_order or z != 100 : cls . z_order [ name ] = z
2020-07-09 00:56:03 +00:00
if args : string = " " . join ( args )
2020-07-24 01:44:11 +00:00
if only_save :
if name not in cls . saved or not append : cls . saved [ name ] = " "
cls . saved [ name ] + = string
2020-07-16 00:51:55 +00:00
else :
2020-07-24 01:44:11 +00:00
if name not in cls . strings or not append : cls . strings [ name ] = " "
2020-07-16 00:51:55 +00:00
cls . strings [ name ] + = string
2020-07-24 01:44:11 +00:00
if now :
cls . out ( name )
2020-07-09 00:56:03 +00:00
@classmethod
2020-07-14 00:20:40 +00:00
def out ( cls , * names : str , clear = False ) :
2020-07-11 22:54:52 +00:00
out : str = " "
if not cls . strings : return
2020-07-14 00:20:40 +00:00
if names :
2020-07-11 22:54:52 +00:00
for name in sorted ( cls . z_order , key = cls . z_order . get , reverse = True ) :
2020-07-24 01:44:11 +00:00
if name in names and name in cls . strings :
2020-07-11 22:54:52 +00:00
out + = cls . strings [ name ]
2020-07-24 01:44:11 +00:00
if cls . save [ name ] :
cls . saved [ name ] = cls . strings [ name ]
if clear or cls . once [ name ] :
cls . clear ( name )
2020-07-11 22:54:52 +00:00
cls . now ( out )
else :
for name in sorted ( cls . z_order , key = cls . z_order . get , reverse = True ) :
2020-07-14 00:20:40 +00:00
if name in cls . strings :
out + = cls . strings [ name ]
2020-07-24 01:44:11 +00:00
if cls . save [ name ] :
cls . saved [ name ] = out
if cls . once [ name ] and not clear :
cls . clear ( name )
if clear :
cls . clear ( )
2020-07-14 00:20:40 +00:00
cls . now ( out )
@classmethod
2020-07-24 01:44:11 +00:00
def saved_buffer ( cls ) - > str :
2020-07-14 00:20:40 +00:00
out : str = " "
for name in sorted ( cls . z_order , key = cls . z_order . get , reverse = True ) :
2020-07-24 01:44:11 +00:00
if name in cls . saved :
out + = cls . saved [ name ]
2020-07-14 00:20:40 +00:00
return out
2020-07-09 00:56:03 +00:00
@classmethod
2020-07-24 01:44:11 +00:00
def clear ( cls , * names , saved : bool = False ) :
2020-07-09 00:56:03 +00:00
if names :
for name in names :
if name in cls . strings :
2020-07-11 22:54:52 +00:00
del cls . strings [ name ]
2020-07-24 01:44:11 +00:00
if name in cls . save :
del cls . save [ name ]
if name in cls . once :
del cls . once [ name ]
if saved :
if name in cls . saved :
del cls . saved [ name ]
if name in cls . z_order :
2020-07-14 00:20:40 +00:00
del cls . z_order [ name ]
2020-07-09 00:56:03 +00:00
else :
cls . strings = { }
2020-07-24 01:44:11 +00:00
cls . save = { }
cls . once = { }
if saved :
cls . saved = { }
cls . z_order = { }
2020-07-09 00:56:03 +00:00
class Color :
2020-07-11 22:54:52 +00:00
''' Holds representations for a 24-bit color value
2020-07-09 00:56:03 +00:00
__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 "
2020-07-24 01:44:11 +00:00
__call__ ( * args ) joins str arguments to a string and apply color
2020-07-09 00:56:03 +00:00
__str__ returns escape sequence to set 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
'''
2020-07-11 22:54:52 +00:00
hexa : str ; dec : Tuple [ int , int , int ] ; red : int ; green : int ; blue : int ; depth : str ; escape : str ; default : bool
2020-07-09 00:56:03 +00:00
def __init__ ( self , color : str , depth : str = " fg " , default : bool = False ) :
self . depth = depth
self . default = default
try :
if not color :
self . dec = ( - 1 , - 1 , - 1 )
self . hexa = " "
self . red = self . green = self . blue = - 1
self . escape = " \033 [49m " if depth == " bg " and default else " "
return
elif color . startswith ( " # " ) :
self . hexa = color
if len ( self . hexa ) == 3 :
self . hexa + = self . hexa [ 1 : 3 ] + self . hexa [ 1 : 3 ]
c = int ( self . hexa [ 1 : 3 ] , base = 16 )
self . dec = ( c , c , c )
elif len ( self . hexa ) == 7 :
self . dec = ( int ( self . hexa [ 1 : 3 ] , base = 16 ) , int ( self . hexa [ 3 : 5 ] , base = 16 ) , int ( self . hexa [ 5 : 7 ] , base = 16 ) )
else :
raise ValueError ( f ' Incorrectly formatted hexadeciaml rgb string: { self . hexa } ' )
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 . hexa : self . hexa = 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 . hexa :
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 ) - > str :
2020-07-24 01:44:11 +00:00
if len ( args ) < 1 : return " "
2020-07-09 00:56:03 +00:00
return f ' { self . escape } { " " . join ( args ) } { getattr ( Term , self . depth ) } '
@staticmethod
def escape_color ( hexa : str = " " , r : int = 0 , g : int = 0 , b : int = 0 , depth : str = " fg " ) - > str :
""" Returns escape sequence to set color
* accepts either 6 digit hexadecimal hexa = " #RRGGBB " , 2 digit hexadecimal : hexa = " #FF "
* or decimal RGB : r = 0 - 255 , g = 0 - 255 , b = 0 - 255
* depth = " fg " or " bg "
"""
dint : int = 38 if depth == " fg " else 48
color : str = " "
if hexa :
try :
if len ( hexa ) == 3 :
c = int ( hexa [ 1 : ] , base = 16 )
color = f ' \033 [ { dint } ;2; { c } ; { c } ; { c } m '
elif len ( hexa ) == 7 :
color = f ' \033 [ { dint } ;2; { int ( hexa [ 1 : 3 ] , base = 16 ) } ; { int ( hexa [ 3 : 5 ] , base = 16 ) } ; { int ( hexa [ 5 : 7 ] , base = 16 ) } m '
except ValueError as e :
errlog . exception ( f ' { e } ' )
else :
color = f ' \033 [ { dint } ;2; { r } ; { g } ; { b } m '
return color
@classmethod
def fg ( cls , * args ) - > str :
2020-07-24 01:44:11 +00:00
if len ( args ) > 2 : return cls . escape_color ( r = args [ 0 ] , g = args [ 1 ] , b = args [ 2 ] , depth = " fg " )
2020-07-09 00:56:03 +00:00
else : return cls . escape_color ( hexa = args [ 0 ] , depth = " fg " )
@classmethod
def bg ( cls , * args ) - > str :
2020-07-24 01:44:11 +00:00
if len ( args ) > 2 : return cls . escape_color ( r = args [ 0 ] , g = args [ 1 ] , b = args [ 2 ] , depth = " bg " )
2020-07-09 00:56:03 +00:00
else : return cls . escape_color ( hexa = args [ 0 ] , depth = " bg " )
2020-07-20 22:52:34 +00:00
class Colors :
2020-07-24 01:44:11 +00:00
''' Standard colors for menus and dialogs '''
2020-07-20 22:52:34 +00:00
default = Color ( " #cc " )
white = Color ( " #ff " )
red = Color ( " #bf3636 " )
green = Color ( " #68bf36 " )
2020-07-24 01:44:11 +00:00
blue = Color ( " #0fd7ff " )
yellow = Color ( " #db8b00 " )
2020-07-20 22:52:34 +00:00
black_bg = Color ( " #00 " , depth = " bg " )
2020-07-24 01:44:11 +00:00
null = Color ( " " )
2020-07-20 22:52:34 +00:00
2020-07-09 00:56:03 +00:00
class Theme :
''' __init__ accepts a dict containing { " color_element " : " color " } '''
themes : Dict [ str , str ] = { }
cached : Dict [ str , Dict [ str , str ] ] = { " Default " : DEFAULT_THEME }
current : str = " "
2020-08-14 17:37:40 +00:00
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 = graph_text = meter_bg = process_start = process_mid = process_end = NotImplemented
2020-07-09 00:56:03 +00:00
gradient : Dict [ str , List [ str ] ] = {
" temp " : [ ] ,
" cpu " : [ ] ,
" free " : [ ] ,
" cached " : [ ] ,
" available " : [ ] ,
" used " : [ ] ,
" download " : [ ] ,
2020-07-24 01:44:11 +00:00
" upload " : [ ] ,
" proc " : [ ] ,
2020-08-14 17:37:40 +00:00
" proc_color " : [ ] ,
" process " : [ ] ,
2020-07-09 00:56:03 +00:00
}
def __init__ ( self , theme : str ) :
self . refresh ( )
self . _load_theme ( theme )
def __call__ ( self , theme : str ) :
for k in self . gradient . keys ( ) : self . gradient [ k ] = [ ]
self . _load_theme ( theme )
def _load_theme ( self , theme : str ) :
tdict : Dict [ str , str ]
if theme in self . cached :
tdict = self . cached [ theme ]
elif theme in self . themes :
tdict = self . _load_file ( self . themes [ theme ] )
self . cached [ theme ] = tdict
else :
2020-07-11 22:54:52 +00:00
errlog . warning ( f ' No theme named " { theme } " found! ' )
2020-07-09 00:56:03 +00:00
theme = " Default "
2020-07-11 22:54:52 +00:00
CONFIG . color_theme = theme
2020-07-09 00:56:03 +00:00
tdict = DEFAULT_THEME
self . current = theme
#if CONFIG.color_theme != theme: CONFIG.color_theme = theme
2020-08-14 17:37:40 +00:00
if not " graph_text " in tdict and " inactive_fg " in tdict :
tdict [ " graph_text " ] = tdict [ " inactive_fg " ]
if not " meter_bg " in tdict and " inactive_fg " in tdict :
tdict [ " meter_bg " ] = tdict [ " inactive_fg " ]
if not " process_start " in tdict and " cpu_start " in tdict :
tdict [ " process_start " ] = tdict [ " cpu_start " ]
tdict [ " process_mid " ] = tdict . get ( " cpu_mid " , " " )
tdict [ " process_end " ] = tdict . get ( " cpu_end " , " " )
2020-07-09 00:56:03 +00:00
#* Get key names from DEFAULT_THEME dict to not leave any color unset if missing from theme dict
for item , value in DEFAULT_THEME . items ( ) :
default = False if item not in [ " main_fg " , " main_bg " ] else True
depth = " fg " if item not in [ " main_bg " , " selected_bg " ] else " bg "
2020-08-14 17:37:40 +00:00
if item in tdict :
2020-07-09 00:56:03 +00:00
setattr ( self , item , Color ( tdict [ item ] , depth = depth , default = default ) )
else :
setattr ( self , item , Color ( value , depth = depth , default = default ) )
2020-08-14 17:37:40 +00:00
2020-07-10 00:50:24 +00:00
#* Create color gradients from one, two or three colors, 101 values indexed 0-100
2020-08-14 17:37:40 +00:00
self . proc_start , self . proc_mid , self . proc_end = self . main_fg , Colors . null , self . inactive_fg
self . proc_color_start , self . proc_color_mid , self . proc_color_end = self . inactive_fg , Colors . null , self . process_start
2020-07-24 01:44:11 +00:00
2020-07-09 00:56:03 +00:00
rgb : Dict [ str , Tuple [ int , int , int ] ]
2020-07-10 00:50:24 +00:00
colors : List [ List [ int ] ] = [ ]
2020-08-14 17:37:40 +00:00
for name in self . gradient :
2020-07-09 00:56:03 +00:00
rgb = { " start " : getattr ( self , f ' { name } _start ' ) . dec , " mid " : getattr ( self , f ' { name } _mid ' ) . dec , " end " : getattr ( self , f ' { name } _end ' ) . dec }
2020-07-10 00:50:24 +00:00
colors = [ list ( getattr ( self , f ' { name } _start ' ) ) ]
2020-07-09 00:56:03 +00:00
if rgb [ " end " ] [ 0 ] > = 0 :
r = 50 if rgb [ " mid " ] [ 0 ] > = 0 else 100
for first , second in [ " start " , " mid " if r == 50 else " end " ] , [ " mid " , " end " ] :
for i in range ( r ) :
2020-07-10 00:50:24 +00:00
colors + = [ [ rgb [ first ] [ n ] + i * ( rgb [ second ] [ n ] - rgb [ first ] [ n ] ) / / r for n in range ( 3 ) ] ]
2020-07-09 00:56:03 +00:00
if r == 100 :
break
2020-07-10 00:50:24 +00:00
self . gradient [ name ] + = [ Color . fg ( * color ) for color in colors ]
2020-07-09 00:56:03 +00:00
else :
c = Color . fg ( * rgb [ " start " ] )
2020-08-15 13:17:21 +00:00
for _ in range ( 101 ) :
2020-07-09 00:56:03 +00:00
self . gradient [ name ] + = [ c ]
#* Set terminal colors
2020-08-23 10:37:34 +00:00
Term . fg = self . main_fg
Term . bg = self . main_bg if CONFIG . theme_background else " \033 [49m "
2020-07-09 00:56:03 +00:00
Draw . now ( self . main_fg , self . main_bg )
2020-08-02 18:10:42 +00:00
@classmethod
def refresh ( cls ) :
2020-07-09 00:56:03 +00:00
''' Sets themes dict with names and paths to all found themes '''
2020-08-02 18:10:42 +00:00
cls . themes = { " Default " : " Default " }
2020-07-09 00:56:03 +00:00
try :
for d in ( THEME_DIR , USER_THEME_DIR ) :
2020-08-02 18:10:42 +00:00
if not d : continue
2020-07-09 00:56:03 +00:00
for f in os . listdir ( d ) :
if f . endswith ( " .theme " ) :
2020-08-02 18:10:42 +00:00
cls . themes [ f ' { " " if d == THEME_DIR else " + " } { f [ : - 6 ] } ' ] = f ' { d } / { f } '
2020-07-09 00:56:03 +00:00
except Exception as e :
errlog . exception ( str ( e ) )
@staticmethod
def _load_file ( path : str ) - > Dict [ str , str ] :
''' Load a bashtop formatted theme file and return a dict '''
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
class Banner :
''' Holds the bpytop banner, .draw(line, [col=0], [center=False], [now=False]) '''
out : List [ str ] = [ ]
c_color : str = " "
length : int = 0
if not out :
2020-07-11 22:54:52 +00:00
for num , ( color , color2 , line ) in enumerate ( BANNER_SRC ) :
2020-07-09 00:56:03 +00:00
if len ( line ) > length : length = len ( line )
2020-07-11 22:54:52 +00:00
out_var = " "
2020-07-09 00:56:03 +00:00
line_color = Color . fg ( color )
2020-07-11 22:54:52 +00:00
line_color2 = Color . fg ( color2 )
2020-07-09 00:56:03 +00:00
line_dark = Color . fg ( f ' # { 80 - num * 6 } ' )
2020-07-11 22:54:52 +00:00
for n , letter in enumerate ( line ) :
2020-07-09 00:56:03 +00:00
if letter == " █ " and c_color != line_color :
2020-07-11 22:54:52 +00:00
if n > 5 and n < 25 : c_color = line_color2
else : c_color = line_color
out_var + = c_color
2020-07-09 00:56:03 +00:00
elif letter == " " :
letter = f ' { Mv . r ( 1 ) } '
2020-07-11 22:54:52 +00:00
c_color = " "
2020-07-09 00:56:03 +00:00
elif letter != " █ " and c_color != line_dark :
c_color = line_dark
2020-07-11 22:54:52 +00:00
out_var + = line_dark
out_var + = letter
out . append ( out_var )
2020-07-09 00:56:03 +00:00
@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 : Draw . out ( out )
else : return out
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 ] = {
2020-07-18 01:16:01 +00:00
0.0 : " " , 0.1 : " ⢀ " , 0.2 : " ⢠ " , 0.3 : " ⢰ " , 0.4 : " ⢸ " ,
2020-07-09 00:56:03 +00:00
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 : " ⣿ "
}
2020-07-16 00:51:55 +00:00
graph_up_small = graph_up . copy ( )
graph_up_small [ 0.0 ] = " \033 [1C "
2020-07-09 00:56:03 +00:00
graph_down : Dict [ float , str ] = {
2020-07-18 01:16:01 +00:00
0.0 : " " , 0.1 : " ⠈ " , 0.2 : " ⠘ " , 0.3 : " ⠸ " , 0.4 : " ⢸ " ,
2020-07-09 00:56:03 +00:00
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 : " ⣿ "
}
2020-07-16 00:51:55 +00:00
graph_down_small = graph_down . copy ( )
graph_down_small [ 0.0 ] = " \033 [1C "
2020-07-09 00:56:03 +00:00
meter : str = " ■ "
2020-07-30 02:29:40 +00:00
up : str = " ↑ "
down : str = " ↓ "
left : str = " ← "
right : str = " → "
enter : str = " ↲ "
2020-07-09 00:56:03 +00:00
ok : str = f ' { Color . fg ( " #30ff50 " ) } √ { Color . fg ( " #cc " ) } '
fail : str = f ' { Color . fg ( " #ff3050 " ) } ! { Color . fg ( " #cc " ) } '
class Graph :
2020-07-11 22:54:52 +00:00
''' 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
2020-07-18 01:16:01 +00:00
* __call__ : same as add
2020-07-11 22:54:52 +00:00
'''
2020-07-10 00:50:24 +00:00
out : str
width : int
height : int
graphs : Dict [ bool , List [ str ] ]
2020-07-09 00:56:03 +00:00
colors : List [ str ]
2020-07-10 00:50:24 +00:00
invert : bool
max_value : int
2020-08-10 00:08:51 +00:00
color_max_value : int
2020-07-16 00:51:55 +00:00
offset : int
2020-07-10 00:50:24 +00:00
current : bool
last : int
2020-07-09 00:56:03 +00:00
symbol : Dict [ float , str ]
2020-08-15 15:39:45 +00:00
def __init__ ( self , width : int , height : int , color : Union [ List [ str ] , Color , None ] , data : List [ int ] , invert : bool = False , max_value : int = 0 , offset : int = 0 , color_max_value : Union [ int , None ] = None ) :
2020-07-10 00:50:24 +00:00
self . graphs : Dict [ bool , List [ str ] ] = { False : [ ] , True : [ ] }
self . current : bool = True
self . width = width
self . height = height
self . invert = invert
2020-07-16 00:51:55 +00:00
self . offset = offset
2020-07-10 00:50:24 +00:00
if not data : data = [ 0 ]
2020-07-09 00:56:03 +00:00
if max_value :
self . max_value = max_value
2020-08-15 15:39:45 +00:00
data = [ min ( 100 , ( v + offset ) * 100 / / ( max_value + offset ) ) for v in data ] #* Convert values to percentage values of max_value with max_value as ceiling
2020-07-10 00:50:24 +00:00
else :
self . max_value = 0
2020-08-10 00:08:51 +00:00
if color_max_value :
self . color_max_value = color_max_value
else :
self . color_max_value = self . max_value
if self . color_max_value and self . max_value :
color_scale = int ( 100.0 * self . max_value / self . color_max_value )
else :
color_scale = 100
self . colors : List [ str ] = [ ]
if isinstance ( color , list ) and height > 1 :
for i in range ( 1 , height + 1 ) : self . colors . insert ( 0 , color [ min ( 100 , i * color_scale / / height ) ] ) #* Calculate colors of graph
if invert : self . colors . reverse ( )
elif isinstance ( color , Color ) and height > 1 :
self . colors = [ f ' { color } ' for _ in range ( height ) ]
else :
if isinstance ( color , list ) : self . colors = color
elif isinstance ( color , Color ) : self . colors = [ f ' { color } ' for _ in range ( 101 ) ]
2020-07-16 00:51:55 +00:00
if self . height == 1 :
self . symbol = Symbol . graph_down_small if invert else Symbol . graph_up_small
else :
self . symbol = Symbol . graph_down if invert else Symbol . graph_up
2020-07-10 00:50:24 +00:00
value_width : int = ceil ( len ( data ) / 2 )
filler : str = " "
if value_width > width : #* If the size of given data set is bigger then width of graph, shrink data set
data = data [ - ( width * 2 ) : ]
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
2020-07-16 00:51:55 +00:00
filler = self . symbol [ 0.0 ] * ( width - value_width )
2020-07-24 01:44:11 +00:00
if len ( data ) % 2 : data . insert ( 0 , 0 )
2020-07-10 00:50:24 +00:00
for _ in range ( height ) :
for b in [ True , False ] :
2020-07-11 22:54:52 +00:00
self . graphs [ b ] . append ( filler )
2020-07-10 00:50:24 +00:00
self . _create ( data , new = True )
def _create ( self , data : List [ int ] , new : bool = False ) :
h_high : int
h_low : int
value : Dict [ str , int ] = { " left " : 0 , " right " : 0 }
val : int
side : str
#* Create the graph
for h in range ( self . height ) :
h_high = round ( 100 * ( self . height - h ) / self . height ) if self . height > 1 else 100
h_low = round ( 100 * ( self . height - ( h + 1 ) ) / self . height ) if self . height > 1 else 0
for v in range ( len ( data ) ) :
if new : self . current = bool ( v % 2 ) #* Switch between True and False graphs
if new and v == 0 : self . last = 0
for val , side in [ self . last , " left " ] , [ data [ v ] , " right " ] : # type: ignore
if val > = h_high :
value [ side ] = 4
elif val < = h_low :
value [ side ] = 0
else :
if self . height == 1 : value [ side ] = round ( val * 4 / 100 + 0.5 )
else : value [ side ] = round ( ( val - h_low ) * 4 / ( h_high - h_low ) + 0.1 )
if new : self . last = data [ v ]
self . graphs [ self . current ] [ h ] + = self . symbol [ float ( value [ " left " ] + value [ " right " ] / 10 ) ]
2020-07-18 01:16:01 +00:00
if data : self . last = data [ - 1 ]
2020-07-10 00:50:24 +00:00
self . out = " "
2020-07-18 01:16:01 +00:00
if self . height == 1 :
self . out + = f ' { " " if not self . colors else self . colors [ self . last ] } { self . graphs [ self . current ] [ 0 ] } '
elif self . height > 1 :
for h in range ( self . height ) :
if h > 0 : self . out + = f ' { Mv . d ( 1 ) } { Mv . l ( self . width ) } '
self . out + = f ' { " " if not self . colors else self . colors [ h ] } { self . graphs [ self . current ] [ h if not self . invert else ( self . height - 1 ) - h ] } '
2020-07-16 00:51:55 +00:00
if self . colors : self . out + = f ' { Term . fg } '
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
def __call__ ( self , value : Union [ int , None ] = None ) - > str :
if not isinstance ( value , int ) : return self . out
2020-07-09 00:56:03 +00:00
self . current = not self . current
2020-07-16 00:51:55 +00:00
if self . height == 1 :
if self . graphs [ self . current ] [ 0 ] . startswith ( self . symbol [ 0.0 ] ) :
self . graphs [ self . current ] [ 0 ] = self . graphs [ self . current ] [ 0 ] . replace ( self . symbol [ 0.0 ] , " " , 1 )
else :
self . graphs [ self . current ] [ 0 ] = self . graphs [ self . current ] [ 0 ] [ 1 : ]
else :
for n in range ( self . height ) :
self . graphs [ self . current ] [ n ] = self . graphs [ self . current ] [ n ] [ 1 : ]
if self . max_value : value = ( value + self . offset ) * 100 / / ( self . max_value + self . offset ) if value < self . max_value else 100
2020-07-10 00:50:24 +00:00
self . _create ( [ value ] )
return self . out
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
def add ( self , value : Union [ int , None ] = None ) - > str :
return self . __call__ ( value )
2020-07-18 01:16:01 +00:00
2020-07-09 00:56:03 +00:00
def __str__ ( self ) :
return self . out
2020-07-10 00:50:24 +00:00
def __repr__ ( self ) :
return repr ( self . out )
2020-07-09 00:56:03 +00:00
class Graphs :
''' Holds all graphs and lists of graphs for dynamically created graphs '''
2020-07-16 00:51:55 +00:00
cpu : Dict [ str , Graph ] = { }
cores : List [ Graph ] = [ NotImplemented ] * THREADS
temps : List [ Graph ] = [ NotImplemented ] * ( THREADS + 1 )
2020-07-20 22:52:34 +00:00
net : Dict [ str , Graph ] = { }
2020-07-30 02:29:40 +00:00
detailed_cpu : Graph = NotImplemented
detailed_mem : Graph = NotImplemented
2020-07-11 22:54:52 +00:00
pid_cpu : Dict [ int , Graph ] = { }
2020-07-09 00:56:03 +00:00
class Meter :
''' Creates a percentage meter
__init__ ( value , width , theme , gradient_name ) to create new meter
__call__ ( value ) to set value and return meter as a string
__str__ returns last set meter as a string
'''
2020-07-10 00:50:24 +00:00
out : str
2020-07-09 00:56:03 +00:00
color_gradient : List [ str ]
color_inactive : Color
2020-07-20 22:52:34 +00:00
gradient_name : str
2020-07-10 00:50:24 +00:00
width : int
saved : Dict [ int , str ]
2020-07-09 00:56:03 +00:00
def __init__ ( self , value : int , width : int , gradient_name : str ) :
2020-07-20 22:52:34 +00:00
self . gradient_name = gradient_name
2020-07-09 00:56:03 +00:00
self . color_gradient = THEME . gradient [ gradient_name ]
2020-08-14 17:37:40 +00:00
self . color_inactive = THEME . meter_bg
2020-07-09 00:56:03 +00:00
self . width = width
2020-07-20 22:52:34 +00:00
self . saved = { }
2020-07-09 00:56:03 +00:00
self . out = self . _create ( value )
2020-07-20 22:52:34 +00:00
def __call__ ( self , value : Union [ int , None ] ) - > str :
if not isinstance ( value , int ) : return self . out
if value > 100 : value = 100
elif value < 0 : value = 100
if value in self . saved :
2020-07-09 00:56:03 +00:00
self . out = self . saved [ value ]
else :
self . out = self . _create ( value )
return self . out
2020-07-20 22:52:34 +00:00
def __str__ ( self ) - > str :
2020-07-09 00:56:03 +00:00
return self . out
def __repr__ ( self ) :
return repr ( self . out )
2020-07-20 22:52:34 +00:00
def _create ( self , value : int ) - > str :
2020-07-09 00:56:03 +00:00
if value > 100 : value = 100
elif value < 0 : value = 100
out : str = " "
for i in range ( 1 , self . width + 1 ) :
if value > = round ( i * 100 / self . width ) :
out + = f ' { self . color_gradient [ round ( i * 100 / self . width ) ] } { Symbol . meter } '
else :
out + = self . color_inactive ( Symbol . meter * ( self . width + 1 - i ) )
break
else :
out + = f ' { Term . fg } '
2020-07-20 22:52:34 +00:00
if not value in self . saved :
self . saved [ value ] = out
2020-07-09 00:56:03 +00:00
return out
class Meters :
cpu : Meter
2020-07-18 01:16:01 +00:00
mem : Dict [ str , Union [ Meter , Graph ] ] = { }
swap : Dict [ str , Union [ Meter , Graph ] ] = { }
disks_used : Dict [ str , Meter ] = { }
disks_free : Dict [ str , Meter ] = { }
2020-07-09 00:56:03 +00:00
class Box :
''' Box class with all needed attributes for create_box() function '''
2020-07-10 00:50:24 +00:00
name : str
height_p : int
width_p : int
x : int
y : int
width : int
height : int
2020-08-02 18:10:42 +00:00
mini_mode : bool = True if CONFIG . mini_mode or " -m " in sys . argv or " --mini " in sys . argv else False
2020-07-10 00:50:24 +00:00
out : str
bg : str
_b_cpu_h : int
_b_mem_h : int
redraw_all : bool
2020-07-16 00:51:55 +00:00
buffers : List [ str ] = [ ]
2020-08-02 18:10:42 +00:00
clock_on : bool = False
clock : str = " "
2020-07-18 01:16:01 +00:00
resized : bool = False
2020-07-09 00:56:03 +00:00
@classmethod
def calc_sizes ( cls ) :
''' Calculate sizes of boxes '''
for sub in cls . __subclasses__ ( ) :
sub . _calc_size ( ) # type: ignore
2020-07-16 00:51:55 +00:00
sub . resized = True # type: ignore
2020-07-09 00:56:03 +00:00
2020-07-18 01:16:01 +00:00
@classmethod
def draw_update_ms ( cls , now : bool = True ) :
2020-07-20 22:52:34 +00:00
update_string : str = f ' { CONFIG . update_ms } ms '
xpos : int = CpuBox . x + CpuBox . width - len ( update_string ) - 14
2020-08-04 19:35:49 +00:00
if not " + " in Key . mouse :
Key . mouse [ " + " ] = [ [ xpos + 7 + i , CpuBox . y ] for i in range ( 3 ) ]
Key . mouse [ " - " ] = [ [ CpuBox . x + CpuBox . width - 4 + i , CpuBox . y ] for i in range ( 3 ) ]
2020-07-20 22:52:34 +00:00
Draw . buffer ( " update_ms! " if now and not Menu . active else " update_ms " ,
f ' { Mv . to ( CpuBox . y , xpos ) } { THEME . cpu_box ( Symbol . h_line * 7 , Symbol . title_left ) } { Fx . b } { THEME . hi_fg ( " + " ) } ' ,
2020-07-24 01:44:11 +00:00
f ' { THEME . title ( update_string ) } { THEME . hi_fg ( " - " ) } { Fx . ub } { THEME . cpu_box ( Symbol . title_right ) } ' , only_save = Menu . active , once = True )
2020-07-18 01:16:01 +00:00
if now and not Menu . active : Draw . clear ( " update_ms " )
2020-08-02 18:10:42 +00:00
@classmethod
def draw_clock ( cls , force : bool = False ) :
if force : pass
elif not cls . clock_on or Term . resized or strftime ( CONFIG . draw_clock ) == cls . clock : return
cls . clock = strftime ( CONFIG . draw_clock )
clock_len = len ( cls . clock [ : ( CpuBox . width - 58 ) ] )
now : bool = False if Menu . active else not force
Draw . buffer ( " clock " , ( f ' { Mv . to ( CpuBox . y , ( ( CpuBox . width - 2 ) / / 2 ) - ( clock_len / / 2 ) - 5 ) } { Fx . ub } { THEME . cpu_box } { Symbol . h_line * 4 } '
f ' { Symbol . title_left } { Fx . b } { THEME . title ( cls . clock [ : clock_len ] ) } { Fx . ub } { THEME . cpu_box } { Symbol . title_right } { Symbol . h_line * 4 } { Term . fg } ' ) ,
z = 1 , now = now , once = not force , only_save = Menu . active )
2020-07-09 00:56:03 +00:00
@classmethod
def draw_bg ( cls , now : bool = True ) :
''' Draw all boxes outlines and titles '''
2020-07-24 01:44:11 +00:00
Draw . buffer ( " bg " , " " . join ( sub . _draw_bg ( ) for sub in cls . __subclasses__ ( ) ) , now = now , z = 1000 , only_save = Menu . active , once = True ) # type: ignore
2020-07-18 01:16:01 +00:00
cls . draw_update_ms ( now = now )
2020-08-02 18:10:42 +00:00
if CONFIG . draw_clock : cls . draw_clock ( force = True )
2020-07-09 00:56:03 +00:00
class SubBox :
box_x : int = 0
box_y : int = 0
box_width : int = 0
box_height : int = 0
box_columns : int = 0
2020-07-16 00:51:55 +00:00
column_size : int = 0
2020-07-09 00:56:03 +00:00
class CpuBox ( Box , SubBox ) :
name = " cpu "
2020-07-11 22:54:52 +00:00
x = 1
y = 1
2020-07-09 00:56:03 +00:00
height_p = 32
width_p = 100
2020-07-16 00:51:55 +00:00
resized : bool = True
2020-07-24 01:44:11 +00:00
redraw : bool = False
2020-07-16 00:51:55 +00:00
buffer : str = " cpu "
2020-08-02 18:10:42 +00:00
clock_block : bool = True
2020-07-16 00:51:55 +00:00
Box . buffers . append ( buffer )
2020-07-09 00:56:03 +00:00
@classmethod
def _calc_size ( cls ) :
2020-08-02 18:10:42 +00:00
cpu = CpuCollector
2020-07-30 02:29:40 +00:00
height_p : int
if cls . mini_mode : height_p = 20
else : height_p = cls . height_p
2020-07-09 00:56:03 +00:00
cls . width = round ( Term . width * cls . width_p / 100 )
2020-07-30 02:29:40 +00:00
cls . height = round ( Term . height * height_p / 100 )
if cls . height < 8 : cls . height = 8
2020-07-09 00:56:03 +00:00
Box . _b_cpu_h = cls . height
2020-08-15 22:53:51 +00:00
#THREADS = 64
2020-07-16 00:51:55 +00:00
cls . box_columns = ceil ( ( THREADS + 1 ) / ( cls . height - 5 ) )
2020-08-15 22:53:51 +00:00
if cls . box_columns * ( 20 + 13 if cpu . got_sensors else 21 ) < cls . width - ( cls . width / / 3 ) :
2020-08-05 19:01:44 +00:00
cls . column_size = 2
2020-08-07 17:39:58 +00:00
cls . box_width = ( 20 + 13 if cpu . got_sensors else 21 ) * cls . box_columns - ( ( cls . box_columns - 1 ) * 1 )
2020-08-15 22:53:51 +00:00
elif cls . box_columns * ( 15 + 6 if cpu . got_sensors else 15 ) < cls . width - ( cls . width / / 3 ) :
2020-08-05 19:01:44 +00:00
cls . column_size = 1
2020-08-07 17:39:58 +00:00
cls . box_width = ( 15 + 6 if cpu . got_sensors else 15 ) * cls . box_columns - ( ( cls . box_columns - 1 ) * 1 )
2020-08-15 22:53:51 +00:00
elif cls . box_columns * ( 8 + 6 if cpu . got_sensors else 8 ) < cls . width - ( cls . width / / 3 ) :
2020-08-05 19:01:44 +00:00
cls . column_size = 0
else :
2020-08-15 22:53:51 +00:00
cls . box_columns = ( cls . width - cls . width / / 3 ) / / ( 8 + 6 if cpu . got_sensors else 8 ) ; cls . column_size = 0
2020-08-05 19:01:44 +00:00
2020-08-07 17:39:58 +00:00
if cls . column_size == 0 : cls . box_width = ( 8 + 6 if cpu . got_sensors else 8 ) * cls . box_columns + 1
2020-08-05 19:01:44 +00:00
2020-07-16 00:51:55 +00:00
cls . box_height = ceil ( THREADS / cls . box_columns ) + 4
2020-07-09 00:56:03 +00:00
2020-07-16 00:51:55 +00:00
if cls . box_height > cls . height - 2 : cls . box_height = cls . height - 2
cls . box_x = ( cls . width - 1 ) - cls . box_width
cls . box_y = cls . y + ceil ( ( cls . height - 2 ) / 2 ) - ceil ( cls . box_height / 2 ) + 1
2020-07-09 00:56:03 +00:00
@classmethod
def _draw_bg ( cls ) - > str :
2020-08-04 19:35:49 +00:00
if not " M " in Key . mouse :
Key . mouse [ " M " ] = [ [ cls . x + 10 + i , cls . y ] for i in range ( 6 ) ]
2020-07-16 00:51:55 +00:00
return ( f ' { create_box ( box = cls , line_color = THEME . cpu_box ) } '
2020-08-04 19:35:49 +00:00
f ' { Mv . to ( cls . y , cls . 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 ) } '
2020-08-09 11:53:42 +00:00
f ' { 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 = CPU_NAME [ : cls . box_width - 14 ] if not CONFIG . custom_cpu_name else CONFIG . custom_cpu_name [ : cls . box_width - 14 ] ) } ' )
2020-07-09 00:56:03 +00:00
@classmethod
2020-07-18 01:16:01 +00:00
def _draw_fg ( cls ) :
cpu = CpuCollector
2020-07-24 01:44:11 +00:00
if cpu . redraw : cls . redraw = True
2020-07-16 00:51:55 +00:00
out : str = " "
2020-07-30 02:29:40 +00:00
out_misc : str = " "
2020-07-16 00:51:55 +00:00
lavg : str = " "
x , y , w , h = cls . x + 1 , cls . y + 1 , cls . width - 2 , cls . height - 2
bx , by , bw , bh = cls . box_x + 1 , cls . box_y + 1 , cls . box_width - 2 , cls . box_height - 2
hh : int = ceil ( h / 2 )
2020-07-24 01:44:11 +00:00
if cls . resized or cls . redraw :
2020-08-04 19:35:49 +00:00
if not " m " in Key . mouse :
Key . mouse [ " m " ] = [ [ cls . x + 16 + i , cls . y ] for i in range ( 6 ) ]
out_misc + = f ' { Mv . to ( cls . y , cls . x + 16 ) } { THEME . cpu_box ( Symbol . title_left ) } { Fx . b if Box . mini_mode else " " } { THEME . hi_fg ( " m " ) } { THEME . title ( " ini " ) } { Fx . ub } { THEME . cpu_box ( Symbol . title_right ) } '
2020-07-16 00:51:55 +00:00
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 )
2020-08-07 17:39:58 +00:00
Meters . cpu = Meter ( cpu . cpu_usage [ 0 ] [ - 1 ] , bw - ( 21 if cpu . got_sensors else 9 ) , " cpu " )
2020-07-16 00:51:55 +00:00
if cls . column_size > 0 :
for n in range ( THREADS ) :
Graphs . cores [ n ] = Graph ( 5 * cls . column_size , 1 , None , cpu . cpu_usage [ n + 1 ] )
2020-08-02 18:10:42 +00:00
if cpu . got_sensors :
2020-07-16 00:51:55 +00:00
Graphs . temps [ 0 ] = Graph ( 5 , 1 , None , cpu . cpu_temp [ 0 ] , max_value = cpu . cpu_temp_crit , offset = - 23 )
if cls . column_size > 1 :
for n in range ( 1 , THREADS + 1 ) :
Graphs . temps [ n ] = Graph ( 5 , 1 , None , cpu . cpu_temp [ n ] , max_value = cpu . cpu_temp_crit , offset = - 23 )
2020-07-30 02:29:40 +00:00
Draw . buffer ( " cpu_misc " , out_misc , only_save = True )
2020-07-16 00:51:55 +00:00
cx = cy = cc = 0
2020-07-18 01:16:01 +00:00
ccw = ( bw + 1 ) / / cls . box_columns
2020-07-20 22:52:34 +00:00
if cpu . cpu_freq :
freq : str = f ' { cpu . cpu_freq } Mhz ' if cpu . cpu_freq < 1000 else f ' { float ( cpu . cpu_freq / 1000 ) : .1f } GHz '
out + = f ' { Mv . to ( by - 1 , bx + bw - 9 ) } { THEME . div_line ( Symbol . title_left ) } { Fx . b } { THEME . title ( freq ) } { Fx . ub } { THEME . div_line ( Symbol . title_right ) } '
out + = ( f ' { Mv . to ( y , x ) } { Graphs . cpu [ " up " ] ( None if cls . resized else cpu . cpu_usage [ 0 ] [ - 1 ] ) } { Mv . to ( y + hh , x ) } { Graphs . cpu [ " down " ] ( None if cls . resized else cpu . cpu_usage [ 0 ] [ - 1 ] ) } '
2020-08-05 19:01:44 +00:00
f ' { THEME . main_fg } { Mv . to ( by + cy , bx + cx ) } { Fx . b } { " CPU " } { Fx . ub } { Meters . cpu ( cpu . cpu_usage [ 0 ] [ - 1 ] ) } '
2020-07-16 00:51:55 +00:00
f ' { THEME . gradient [ " cpu " ] [ cpu . cpu_usage [ 0 ] [ - 1 ] ] } { cpu . cpu_usage [ 0 ] [ - 1 ] : >4 } { THEME . main_fg } % ' )
2020-08-02 18:10:42 +00:00
if cpu . got_sensors :
2020-08-11 18:23:19 +00:00
out + = ( f ' { THEME . inactive_fg } ⡀⡀⡀⡀⡀ { Mv . l ( 5 ) } { THEME . gradient [ " temp " ] [ 100 if cpu . cpu_temp [ 0 ] [ - 1 ] > = cpu . cpu_temp_crit else ( cpu . cpu_temp [ 0 ] [ - 1 ] * 100 / / cpu . cpu_temp_crit ) ] } { Graphs . temps [ 0 ] ( None if cls . resized else cpu . cpu_temp [ 0 ] [ - 1 ] ) } '
2020-07-16 00:51:55 +00:00
f ' { cpu . cpu_temp [ 0 ] [ - 1 ] : >4 } { THEME . main_fg } °C ' )
cy + = 1
for n in range ( 1 , THREADS + 1 ) :
2020-08-07 17:39:58 +00:00
out + = f ' { THEME . main_fg } { Mv . to ( by + cy , bx + cx ) } { Fx . b + " C " + Fx . ub if THREADS < 100 else " " } { str ( n ) : < { 2 if cls . column_size == 0 else 3 } } '
2020-07-16 00:51:55 +00:00
if cls . column_size > 0 :
2020-07-20 22:52:34 +00:00
out + = f ' { THEME . inactive_fg } { " ⡀ " * ( 5 * cls . column_size ) } { Mv . l ( 5 * cls . column_size ) } { THEME . gradient [ " cpu " ] [ cpu . cpu_usage [ n ] [ - 1 ] ] } { Graphs . cores [ n - 1 ] ( None if cls . resized else cpu . cpu_usage [ n ] [ - 1 ] ) } '
2020-07-16 00:51:55 +00:00
else :
out + = f ' { THEME . gradient [ " cpu " ] [ cpu . cpu_usage [ n ] [ - 1 ] ] } '
2020-08-07 17:39:58 +00:00
out + = f ' { cpu . cpu_usage [ n ] [ - 1 ] : > { 3 if cls . column_size < 2 else 4 } } { THEME . main_fg } % '
2020-08-02 18:10:42 +00:00
if cpu . got_sensors :
2020-07-16 00:51:55 +00:00
if cls . column_size > 1 :
2020-08-11 18:23:19 +00:00
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 ] ) } '
2020-07-16 00:51:55 +00:00
else :
out + = f ' { THEME . gradient [ " temp " ] [ cpu . cpu_temp [ n ] [ - 1 ] ] } '
2020-08-07 17:39:58 +00:00
out + = f ' { cpu . cpu_temp [ n ] [ - 1 ] : >4 } { THEME . main_fg } °C '
out + = f ' { THEME . div_line ( Symbol . v_line ) } '
2020-07-16 00:51:55 +00:00
cy + = 1
if cy == bh :
cc + = 1 ; cy = 1 ; cx = ccw * cc
if cc == cls . box_columns : break
if cy < bh - 1 : cy = bh - 1
2020-08-11 15:09:05 +00:00
if cy < bh and cc < cls . box_columns :
if cls . column_size == 2 and cpu . got_sensors :
lavg = f ' Load AVG: { " " . join ( str ( l ) for l in cpu . load_avg ) : ^19.19 } '
elif cls . column_size == 2 or ( cls . column_size == 1 and cpu . got_sensors ) :
lavg = f ' LAV: { " " . join ( str ( l ) for l in cpu . load_avg ) : ^14.14 } '
elif cls . column_size == 1 or ( cls . column_size == 0 and cpu . got_sensors ) :
lavg = f ' L { " " . join ( str ( round ( l , 1 ) ) for l in cpu . load_avg ) : ^11.11 } '
else :
lavg = f ' { " " . join ( str ( round ( l , 1 ) ) for l in cpu . load_avg [ : 2 ] ) : ^7.7 } '
out + = f ' { Mv . to ( by + cy , bx + cx ) } { THEME . main_fg } { lavg } { THEME . div_line ( Symbol . v_line ) } '
2020-07-16 00:51:55 +00:00
2020-08-14 17:37:40 +00:00
out + = f ' { Mv . to ( y + h - 1 , x + 1 ) } { THEME . graph_text } up { cpu . uptime } '
2020-07-16 00:51:55 +00:00
2020-07-30 02:29:40 +00:00
Draw . buffer ( cls . buffer , f ' { out_misc } { out } { Term . fg } ' , only_save = Menu . active )
2020-08-02 18:10:42 +00:00
cls . resized = cls . redraw = cls . clock_block = False
2020-07-16 00:51:55 +00:00
2020-07-09 00:56:03 +00:00
class MemBox ( Box ) :
name = " mem "
height_p = 40
width_p = 45
2020-07-11 22:54:52 +00:00
x = 1
y = 1
2020-07-18 01:16:01 +00:00
mem_meter : int = 0
mem_size : int = 0
disk_meter : int = 0
2020-07-09 00:56:03 +00:00
divider : int = 0
mem_width : int = 0
disks_width : int = 0
2020-07-18 01:16:01 +00:00
graph_height : int
resized : bool = True
redraw : bool = False
2020-07-16 00:51:55 +00:00
buffer : str = " mem "
2020-07-18 01:16:01 +00:00
swap_on : bool = CONFIG . show_swap
2020-07-16 00:51:55 +00:00
Box . buffers . append ( buffer )
2020-07-18 01:16:01 +00:00
mem_names : List [ str ] = [ " used " , " available " , " cached " , " free " ]
swap_names : List [ str ] = [ " used " , " free " ]
2020-07-09 00:56:03 +00:00
@classmethod
def _calc_size ( cls ) :
cls . width = round ( Term . width * cls . width_p / 100 )
cls . height = round ( Term . height * cls . height_p / 100 ) + 1
Box . _b_mem_h = cls . height
cls . y = Box . _b_cpu_h + 1
2020-07-18 01:16:01 +00:00
if CONFIG . show_disks :
cls . mem_width = ceil ( ( cls . width - 3 ) / 2 )
cls . disks_width = cls . width - cls . mem_width - 3
if cls . mem_width + cls . disks_width < cls . width - 2 : cls . mem_width + = 1
cls . divider = cls . x + cls . mem_width
else :
cls . mem_width = cls . width - 1
2020-07-20 22:52:34 +00:00
item_height : int = 6 if cls . swap_on and not CONFIG . swap_disk else 4
if cls . height - ( 3 if cls . swap_on and not CONFIG . swap_disk else 2 ) > 2 * item_height : cls . mem_size = 3
2020-07-18 01:16:01 +00:00
elif cls . mem_width > 25 : cls . mem_size = 2
else : cls . mem_size = 1
cls . mem_meter = cls . width - ( cls . disks_width if CONFIG . show_disks else 0 ) - ( 9 if cls . mem_size > 2 else 20 )
if cls . mem_size == 1 : cls . mem_meter + = 6
if cls . mem_meter < 1 : cls . mem_meter = 0
if CONFIG . mem_graphs :
2020-07-20 22:52:34 +00:00
cls . graph_height = round ( ( ( cls . height - ( 2 if cls . swap_on and not CONFIG . swap_disk else 1 ) ) - ( 2 if cls . mem_size == 3 else 1 ) * item_height ) / item_height )
2020-07-18 01:16:01 +00:00
if cls . graph_height == 0 : cls . graph_height = 1
if cls . graph_height > 1 : cls . mem_meter + = 6
else :
cls . graph_height = 0
if CONFIG . show_disks :
cls . disk_meter = cls . width - cls . mem_width - 23
if cls . disks_width < 25 :
cls . disk_meter + = 10
if cls . disk_meter < 1 : cls . disk_meter = 0
2020-07-09 00:56:03 +00:00
@classmethod
def _draw_bg ( cls ) - > str :
2020-07-30 02:29:40 +00:00
if cls . mini_mode : return " "
2020-07-18 01:16:01 +00:00
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 ) } '
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 ) ) } ' )
return out
@classmethod
def _draw_fg ( cls ) :
2020-07-30 02:29:40 +00:00
if cls . mini_mode : return
2020-07-18 01:16:01 +00:00
mem = MemCollector
2020-07-24 01:44:11 +00:00
if mem . redraw : cls . redraw = True
2020-07-18 01:16:01 +00:00
out : str = " "
2020-07-24 01:44:11 +00:00
out_misc : str = " "
2020-07-18 01:16:01 +00:00
gbg : str = " "
gmv : str = " "
gli : str = " "
2020-07-24 01:44:11 +00:00
x , y , w , h = cls . x + 1 , cls . y + 1 , cls . width - 2 , cls . height - 2
2020-07-20 22:52:34 +00:00
if cls . resized or cls . redraw :
2020-07-24 01:44:11 +00:00
cls . _calc_size ( )
out_misc + = cls . _draw_bg ( )
2020-07-18 01:16:01 +00:00
Meters . mem = { }
Meters . swap = { }
Meters . disks_used = { }
Meters . disks_free = { }
if cls . mem_meter > 0 :
for name in cls . mem_names :
if CONFIG . mem_graphs :
Meters . mem [ name ] = Graph ( cls . mem_meter , cls . graph_height , THEME . gradient [ name ] , mem . vlist [ name ] )
else :
Meters . mem [ name ] = Meter ( mem . percent [ name ] , cls . mem_meter , name )
if cls . swap_on :
for name in cls . swap_names :
if CONFIG . mem_graphs and not CONFIG . swap_disk :
Meters . swap [ name ] = Graph ( cls . mem_meter , cls . graph_height , THEME . gradient [ name ] , mem . swap_vlist [ name ] )
2020-08-06 17:07:42 +00:00
elif CONFIG . swap_disk and CONFIG . show_disks :
2020-07-18 01:16:01 +00:00
Meters . disks_used [ " __swap " ] = Meter ( mem . swap_percent [ " used " ] , cls . disk_meter , " used " )
if len ( mem . disks ) * 3 < = h + 1 :
Meters . disks_free [ " __swap " ] = Meter ( mem . swap_percent [ " free " ] , cls . disk_meter , " free " )
break
else :
Meters . swap [ name ] = Meter ( mem . swap_percent [ name ] , cls . mem_meter , name )
if cls . disk_meter > 0 :
2020-07-20 22:52:34 +00:00
for n , name in enumerate ( mem . disks . keys ( ) ) :
if n * 2 > h : break
2020-07-18 01:16:01 +00:00
Meters . disks_used [ name ] = Meter ( mem . disks [ name ] [ " used_percent " ] , cls . disk_meter , " used " )
if len ( mem . disks ) * 3 < = h + 1 :
Meters . disks_free [ name ] = Meter ( mem . disks [ name ] [ " free_percent " ] , cls . disk_meter , " free " )
2020-08-04 19:35:49 +00:00
if not " g " in Key . mouse :
Key . mouse [ " g " ] = [ [ x + cls . mem_width - 8 + i , y - 1 ] for i in range ( 5 ) ]
2020-07-24 01:44:11 +00:00
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 " " } '
2020-08-02 18:10:42 +00:00
f ' { THEME . hi_fg ( " g " ) } { THEME . title ( " raph " ) } { Fx . ub } { THEME . mem_box ( Symbol . title_right ) } ' )
2020-07-24 01:44:11 +00:00
if CONFIG . show_disks :
2020-08-04 19:35:49 +00:00
if not " s " in Key . mouse :
Key . mouse [ " s " ] = [ [ x + w - 6 + i , y - 1 ] for i in range ( 4 ) ]
2020-07-24 01:44:11 +00:00
out_misc + = ( f ' { Mv . to ( y - 1 , x + w - 7 ) } { THEME . mem_box ( Symbol . title_left ) } { Fx . b if CONFIG . swap_disk else " " } '
2020-08-02 18:10:42 +00:00
f ' { THEME . hi_fg ( " s " ) } { THEME . title ( " wap " ) } { Fx . ub } { THEME . mem_box ( Symbol . title_right ) } ' )
2020-07-24 01:44:11 +00:00
Draw . buffer ( " mem_misc " , out_misc , only_save = True )
2020-07-18 01:16:01 +00:00
#* Mem
cx = 1 ; cy = 1
2020-07-27 01:13:13 +00:00
out + = f ' { Mv . to ( y , x + 1 ) } { THEME . title } { Fx . b } Total: { mem . string [ " total " ] : > { cls . mem_width - 9 } } { Fx . ub } { THEME . main_fg } '
2020-07-18 01:16:01 +00:00
if cls . graph_height > 0 :
gli = f ' { Mv . l ( 2 ) } { THEME . mem_box ( Symbol . title_right ) } { THEME . div_line } { Symbol . h_line * ( cls . mem_width - 1 ) } { " " if CONFIG . show_disks else THEME . mem_box } { Symbol . title_left } { Mv . l ( cls . mem_width - 1 ) } { THEME . title } '
if cls . graph_height > = 2 :
gbg = f ' { Mv . l ( 1 ) } '
gmv = f ' { Mv . l ( cls . mem_width - 2 ) } { Mv . u ( cls . graph_height - 1 ) } '
2020-07-20 22:52:34 +00:00
big_mem : bool = True if cls . mem_width > 21 else False
2020-07-18 01:16:01 +00:00
for name in cls . mem_names :
if cls . mem_size > 2 :
2020-07-20 22:52:34 +00:00
out + = ( f ' { Mv . to ( y + cy , x + cx ) } { gli } { name . capitalize ( ) [ : None if big_mem else 5 ] + " : " : < { 1 if big_mem else 6.6 } } { Mv . to ( y + cy , x + cx + cls . mem_width - 3 - ( len ( mem . string [ name ] ) ) ) } { Fx . trans ( mem . string [ name ] ) } '
f ' { Mv . to ( y + cy + 1 , x + cx ) } { gbg } { Meters . mem [ name ] ( None if cls . resized else mem . percent [ name ] ) } { gmv } { str ( mem . percent [ name ] ) + " % " : >4 } ' )
cy + = 2 if not cls . graph_height else cls . graph_height + 1
2020-07-18 01:16:01 +00:00
else :
2020-07-20 22:52:34 +00:00
out + = f ' { Mv . to ( y + cy , x + cx ) } { name . capitalize ( ) : { 5.5 if cls . mem_size > 1 else 1.1 } } { gbg } { Meters . mem [ name ] ( None if cls . resized else mem . percent [ name ] ) } { mem . string [ name ] [ : None if cls . mem_size > 1 else - 2 ] : > { 9 if cls . mem_size > 1 else 7 } } '
cy + = 1 if not cls . graph_height else cls . graph_height
2020-07-18 01:16:01 +00:00
#* Swap
if cls . swap_on and CONFIG . show_swap and not CONFIG . swap_disk :
if h - cy > 5 :
if cls . graph_height > 0 : out + = f ' { Mv . to ( y + cy , x + cx ) } { gli } '
cy + = 1
2020-07-27 01:13:13 +00:00
2020-07-20 22:52:34 +00:00
out + = f ' { Mv . to ( y + cy , x + cx ) } { THEME . title } { Fx . b } Swap: { mem . swap_string [ " total " ] : > { cls . mem_width - 8 } } { Fx . ub } { THEME . main_fg } '
cy + = 1
2020-07-18 01:16:01 +00:00
for name in cls . swap_names :
if cls . mem_size > 2 :
2020-07-20 22:52:34 +00:00
out + = ( f ' { Mv . to ( y + cy , x + cx ) } { gli } { name . capitalize ( ) [ : None if big_mem else 5 ] + " : " : < { 1 if big_mem else 6.6 } } { Mv . to ( y + cy , x + cx + cls . mem_width - 3 - ( len ( mem . swap_string [ name ] ) ) ) } { Fx . trans ( mem . swap_string [ name ] ) } '
f ' { Mv . to ( y + cy + 1 , x + cx ) } { gbg } { Meters . swap [ name ] ( None if cls . resized else mem . swap_percent [ name ] ) } { gmv } { str ( mem . swap_percent [ name ] ) + " % " : >4 } ' )
cy + = 2 if not cls . graph_height else cls . graph_height + 1
2020-07-18 01:16:01 +00:00
else :
2020-07-20 22:52:34 +00:00
out + = f ' { Mv . to ( y + cy , x + cx ) } { name . capitalize ( ) : { 5.5 if cls . mem_size > 1 else 1.1 } } { gbg } { Meters . swap [ name ] ( None if cls . resized else mem . swap_percent [ name ] ) } { mem . swap_string [ name ] [ : None if cls . mem_size > 1 else - 2 ] : > { 9 if cls . mem_size > 1 else 7 } } ' ; cy + = 1 if not cls . graph_height else cls . graph_height
2020-07-18 01:16:01 +00:00
if cls . graph_height > 0 and not cy == h : out + = f ' { Mv . to ( y + cy , x + cx ) } { gli } '
#* Disks
if CONFIG . show_disks :
cx = x + cls . mem_width - 1 ; cy = 0
2020-07-20 22:52:34 +00:00
big_disk : bool = True if cls . disks_width > = 25 else False
gli = f ' { Mv . l ( 2 ) } { THEME . div_line } { Symbol . title_right } { Symbol . h_line * cls . disks_width } { THEME . mem_box } { Symbol . title_left } { Mv . l ( cls . disks_width - 1 ) } '
2020-07-18 01:16:01 +00:00
for name , item in mem . disks . items ( ) :
if cy > h - 2 : break
2020-07-20 22:52:34 +00:00
out + = Fx . trans ( f ' { Mv . to ( y + cy , x + cx ) } { gli } { THEME . title } { Fx . b } { item [ " name " ] : { cls . disks_width - 2 } .12 } { Mv . to ( y + cy , x + cx + cls . disks_width - 11 ) } { item [ " total " ] [ : None if big_disk else - 2 ] : >9 } ' )
out + = f ' { Mv . to ( y + cy , x + cx + ( cls . disks_width / / 2 ) - ( len ( item [ " io " ] ) / / 2 ) - 2 ) } { Fx . ub } { THEME . main_fg } { item [ " io " ] } { Fx . ub } { THEME . main_fg } { Mv . to ( y + cy + 1 , x + cx ) } '
out + = f ' Used: { str ( item [ " used_percent " ] ) + " % " : >4 } ' if big_disk else " U "
out + = f ' { Meters . disks_used [ name ] } { item [ " used " ] [ : None if big_disk else - 2 ] : > { 9 if big_disk else 7 } } '
cy + = 2
2020-07-18 01:16:01 +00:00
if len ( mem . disks ) * 3 < = h + 1 :
if cy > h - 1 : break
out + = Mv . to ( y + cy , x + cx )
2020-07-20 22:52:34 +00:00
out + = f ' Free: { str ( item [ " free_percent " ] ) + " % " : >4 } ' if big_disk else f ' { " F " } '
out + = f ' { Meters . disks_free [ name ] } { item [ " free " ] [ : None if big_disk else - 2 ] : > { 9 if big_disk else 7 } } '
cy + = 1
2020-07-18 01:16:01 +00:00
if len ( mem . disks ) * 4 < = h + 1 : cy + = 1
2020-07-24 01:44:11 +00:00
Draw . buffer ( cls . buffer , f ' { out_misc } { out } { Term . fg } ' , only_save = Menu . active )
2020-07-20 22:52:34 +00:00
cls . resized = cls . redraw = False
2020-07-09 00:56:03 +00:00
class NetBox ( Box , SubBox ) :
name = " net "
height_p = 28
width_p = 45
2020-07-11 22:54:52 +00:00
x = 1
y = 1
2020-07-20 22:52:34 +00:00
resized : bool = True
2020-07-14 00:20:40 +00:00
redraw : bool = True
2020-07-20 22:52:34 +00:00
graph_height : Dict [ str , int ] = { }
symbols : Dict [ str , str ] = { " download " : " ▼ " , " upload " : " ▲ " }
2020-07-16 00:51:55 +00:00
buffer : str = " net "
2020-07-20 22:52:34 +00:00
2020-07-16 00:51:55 +00:00
Box . buffers . append ( buffer )
2020-07-09 00:56:03 +00:00
@classmethod
def _calc_size ( cls ) :
cls . width = round ( Term . width * cls . width_p / 100 )
cls . height = Term . height - Box . _b_cpu_h - Box . _b_mem_h
cls . y = Term . height - cls . height + 1
2020-07-20 22:52:34 +00:00
cls . box_width = 27
cls . box_height = 9 if cls . height > 10 else cls . height - 2
cls . box_x = cls . width - cls . box_width - 1
2020-07-09 00:56:03 +00:00
cls . box_y = cls . y + ( ( cls . height - 2 ) / / 2 ) - cls . box_height / / 2 + 1
2020-07-20 22:52:34 +00:00
cls . graph_height [ " download " ] = round ( ( cls . height - 2 ) / 2 )
cls . graph_height [ " upload " ] = cls . height - 2 - cls . graph_height [ " download " ]
cls . redraw = True
2020-07-09 00:56:03 +00:00
@classmethod
def _draw_bg ( cls ) - > str :
2020-07-30 02:29:40 +00:00
if cls . mini_mode : return " "
2020-07-09 00:56:03 +00:00
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 " ) } '
2020-07-20 22:52:34 +00:00
@classmethod
def _draw_fg ( cls ) :
2020-07-30 02:29:40 +00:00
if cls . mini_mode : return
2020-07-20 22:52:34 +00:00
net = NetCollector
2020-07-24 01:44:11 +00:00
if net . redraw : cls . redraw = True
2020-07-20 22:52:34 +00:00
if not net . nic : return
out : str = " "
out_misc : str = " "
x , y , w , h = cls . x + 1 , cls . y + 1 , cls . width - 2 , cls . height - 2
bx , by , bw , bh = cls . box_x + 1 , cls . box_y + 1 , cls . box_width - 2 , cls . box_height - 2
2020-07-24 01:44:11 +00:00
reset : bool = bool ( net . stats [ net . nic ] [ " download " ] [ " offset " ] )
2020-07-20 22:52:34 +00:00
if cls . resized or cls . redraw :
2020-07-24 01:44:11 +00:00
out_misc + = cls . _draw_bg ( )
2020-08-04 19:35:49 +00:00
if not " b " in Key . mouse :
2020-08-09 14:20:00 +00:00
Key . mouse [ " b " ] = [ [ x + w - len ( net . nic [ : 10 ] ) - 9 + i , y - 1 ] for i in range ( 4 ) ]
2020-08-04 19:35:49 +00:00
Key . mouse [ " n " ] = [ [ x + w - 5 + i , y - 1 ] for i in range ( 4 ) ]
2020-08-09 14:20:00 +00:00
Key . mouse [ " z " ] = [ [ x + w - len ( net . nic [ : 10 ] ) - 14 + i , y - 1 ] for i in range ( 4 ) ]
2020-07-20 22:52:34 +00:00
2020-08-09 14:20:00 +00:00
out_misc + = ( f ' { Mv . to ( y - 1 , x + w - 25 ) } { THEME . net_box } { Symbol . h_line * ( 10 - len ( net . nic [ : 10 ] ) ) } { Symbol . title_left } { Fx . b if reset else " " } { THEME . hi_fg ( " z " ) } { THEME . title ( " ero " ) } '
2020-07-24 01:44:11 +00:00
f ' { Fx . ub } { THEME . net_box ( Symbol . title_right ) } { Term . fg } '
f ' { THEME . net_box } { Symbol . title_left } { Fx . b } { THEME . hi_fg ( " <b " ) } { THEME . title ( net . nic [ : 10 ] ) } { THEME . hi_fg ( " n> " ) } { Fx . ub } { THEME . net_box ( Symbol . title_right ) } { Term . fg } ' )
2020-08-09 14:20:00 +00:00
if w - len ( net . nic [ : 10 ] ) - 20 > 6 :
if not " a " in Key . mouse : Key . mouse [ " a " ] = [ [ x + w - 20 - len ( net . nic [ : 10 ] ) + i , y - 1 ] for i in range ( 4 ) ]
out_misc + = ( f ' { Mv . to ( y - 1 , x + w - 21 - len ( net . nic [ : 10 ] ) ) } { THEME . net_box ( Symbol . title_left ) } { Fx . b if net . auto_min else " " } { THEME . hi_fg ( " a " ) } { THEME . title ( " uto " ) } '
f ' { Fx . ub } { THEME . net_box ( Symbol . title_right ) } { Term . fg } ' )
2020-07-24 01:44:11 +00:00
Draw . buffer ( " net_misc " , out_misc , only_save = True )
2020-07-20 22:52:34 +00:00
cy = 0
for direction in [ " download " , " upload " ] :
strings = net . strings [ net . nic ] [ direction ]
stats = net . stats [ net . nic ] [ direction ]
2020-07-24 01:44:11 +00:00
if stats [ " redraw " ] or cls . resized :
2020-07-20 22:52:34 +00:00
if cls . redraw : stats [ " redraw " ] = True
2020-08-15 15:39:45 +00:00
Graphs . net [ direction ] = Graph ( w - bw - 3 , cls . graph_height [ direction ] , THEME . gradient [ direction ] , stats [ " speed " ] , max_value = stats [ " graph_top " ] ,
invert = False if direction == " download " else True , color_max_value = net . net_min . get ( direction ) if CONFIG . net_color_fixed else None )
2020-07-20 22:52:34 +00:00
out + = f ' { Mv . to ( y if direction == " download " else y + cls . graph_height [ " download " ] , x ) } { Graphs . net [ direction ] ( None if stats [ " redraw " ] else stats [ " speed " ] [ - 1 ] ) } '
2020-07-24 01:44:11 +00:00
out + = f ' { Mv . to ( by + cy , bx ) } { THEME . main_fg } { cls . symbols [ direction ] } { strings [ " byte_ps " ] : <10.10 } { Mv . to ( by + cy , bx + bw - 12 ) } { " ( " + strings [ " bit_ps " ] + " ) " : >12.12 } '
2020-07-20 22:52:34 +00:00
cy + = 1 if bh != 3 else 2
if bh > = 6 :
2020-07-24 01:44:11 +00:00
out + = f ' { Mv . to ( by + cy , bx ) } { cls . symbols [ direction ] } { " Top: " } { Mv . to ( by + cy , bx + bw - 12 ) } { " ( " + strings [ " top " ] + " ) " : >12.12 } '
2020-07-20 22:52:34 +00:00
cy + = 1
if bh > = 4 :
out + = f ' { Mv . to ( by + cy , bx ) } { cls . symbols [ direction ] } { " Total: " } { Mv . to ( by + cy , bx + bw - 10 ) } { strings [ " total " ] : >10.10 } '
if bh > 2 and bh % 2 : cy + = 2
else : cy + = 1
stats [ " redraw " ] = False
2020-08-14 17:37:40 +00:00
out + = f ' { Mv . to ( y , x ) } { THEME . graph_text ( net . strings [ net . nic ] [ " download " ] [ " graph_top " ] ) } { Mv . to ( y + h - 1 , x ) } { THEME . graph_text ( net . strings [ net . nic ] [ " upload " ] [ " graph_top " ] ) } '
2020-07-20 22:52:34 +00:00
2020-07-24 01:44:11 +00:00
Draw . buffer ( cls . buffer , f ' { out_misc } { out } { Term . fg } ' , only_save = Menu . active )
2020-07-20 22:52:34 +00:00
cls . redraw = cls . resized = False
2020-07-09 00:56:03 +00:00
class ProcBox ( Box ) :
name = " proc "
height_p = 68
width_p = 55
2020-07-11 22:54:52 +00:00
x = 1
y = 1
2020-07-27 01:13:13 +00:00
current_y : int = 0
2020-07-30 02:29:40 +00:00
current_h : int = 0
2020-07-24 01:44:11 +00:00
select_max : int = 0
selected : int = 0
2020-07-27 01:13:13 +00:00
selected_pid : int = 0
2020-08-05 19:56:13 +00:00
last_selection : int = 0
2020-07-27 01:13:13 +00:00
filtering : bool = False
2020-07-24 01:44:11 +00:00
moved : bool = False
start : int = 1
count : int = 0
2020-07-30 02:29:40 +00:00
s_len : int = 0
2020-07-09 00:56:03 +00:00
detailed : bool = False
detailed_x : int = 0
detailed_y : int = 0
detailed_width : int = 0
detailed_height : int = 8
2020-07-24 01:44:11 +00:00
resized : bool = True
2020-07-14 00:20:40 +00:00
redraw : bool = True
2020-07-16 00:51:55 +00:00
buffer : str = " proc "
2020-07-24 01:44:11 +00:00
pid_counter : Dict [ int , int ] = { }
2020-07-16 00:51:55 +00:00
Box . buffers . append ( buffer )
2020-07-09 00:56:03 +00:00
@classmethod
def _calc_size ( cls ) :
2020-07-30 02:29:40 +00:00
width_p : int ; height_p : int
if cls . mini_mode :
width_p , height_p = 100 , 80
else :
width_p , height_p = cls . width_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
2020-07-09 00:56:03 +00:00
cls . x = Term . width - cls . width + 1
cls . y = Box . _b_cpu_h + 1
2020-07-30 02:29:40 +00:00
cls . current_y = cls . y
cls . current_h = cls . height
cls . select_max = cls . height - 3
2020-07-24 01:44:11 +00:00
cls . redraw = True
cls . resized = True
2020-07-09 00:56:03 +00:00
@classmethod
def _draw_bg ( cls ) - > str :
return create_box ( box = cls , line_color = THEME . proc_box )
2020-07-24 01:44:11 +00:00
@classmethod
2020-07-27 01:13:13 +00:00
def selector ( cls , key : str , mouse_pos : Tuple [ int , int ] = ( 0 , 0 ) ) :
2020-07-24 01:44:11 +00:00
old : Tuple [ int , int ] = ( cls . start , cls . selected )
2020-07-30 02:29:40 +00:00
new_sel : int
2020-07-24 01:44:11 +00:00
if key == " up " :
if cls . selected == 1 and cls . start > 1 :
cls . start - = 1
elif cls . selected == 1 :
cls . selected = 0
elif cls . selected > 1 :
cls . selected - = 1
elif key == " down " :
2020-08-05 19:56:13 +00:00
if cls . selected == 0 and ProcCollector . detailed and cls . last_selection :
cls . selected = cls . last_selection
cls . last_selection = 0
2020-07-24 01:44:11 +00:00
if cls . selected == cls . select_max and cls . start < ProcCollector . num_procs - cls . select_max + 1 :
cls . start + = 1
elif cls . selected < cls . select_max :
cls . selected + = 1
elif key == " mouse_scroll_up " and cls . start > 1 :
cls . start - = 5
elif key == " mouse_scroll_down " and cls . start < ProcCollector . num_procs - cls . select_max + 1 :
cls . start + = 5
elif key == " page_up " and cls . start > 1 :
cls . start - = cls . select_max
elif key == " page_down " and cls . start < ProcCollector . num_procs - cls . select_max + 1 :
cls . start + = cls . select_max
elif key == " home " :
if cls . start > 1 : cls . start = 1
elif cls . selected > 0 : cls . selected = 0
elif key == " end " :
if cls . start < ProcCollector . num_procs - cls . select_max + 1 : cls . start = ProcCollector . num_procs - cls . select_max + 1
elif cls . selected < cls . select_max : cls . selected = cls . select_max
2020-07-27 01:13:13 +00:00
elif key == " mouse_click " :
2020-07-30 02:29:40 +00:00
if mouse_pos [ 0 ] > cls . x + cls . width - 4 and mouse_pos [ 1 ] > cls . current_y + 1 and mouse_pos [ 1 ] < cls . current_y + 1 + cls . select_max + 1 :
if mouse_pos [ 1 ] == cls . current_y + 2 :
2020-07-27 01:13:13 +00:00
cls . start = 1
2020-07-30 02:29:40 +00:00
elif mouse_pos [ 1 ] == cls . current_y + 1 + cls . select_max :
2020-07-27 01:13:13 +00:00
cls . start = ProcCollector . num_procs - cls . select_max + 1
else :
2020-07-30 02:29:40 +00:00
cls . start = round ( ( mouse_pos [ 1 ] - cls . current_y ) * ( ( ProcCollector . num_procs - cls . select_max - 2 ) / ( cls . select_max - 2 ) ) )
2020-07-27 01:13:13 +00:00
else :
2020-07-30 02:29:40 +00:00
new_sel = mouse_pos [ 1 ] - cls . current_y - 1 if mouse_pos [ 1 ] > = cls . current_y - 1 else 0
if new_sel > 0 and new_sel == cls . selected :
Key . list . insert ( 0 , " enter " )
return
elif new_sel > 0 and new_sel != cls . selected :
2020-08-05 19:56:13 +00:00
if cls . last_selection : cls . last_selection = 0
2020-07-30 02:29:40 +00:00
cls . selected = new_sel
2020-07-27 01:13:13 +00:00
elif key == " mouse_unselect " :
cls . selected = 0
2020-07-24 01:44:11 +00:00
2020-07-27 01:13:13 +00:00
if cls . start > ProcCollector . num_procs - cls . select_max + 1 and ProcCollector . num_procs > cls . select_max : cls . start = ProcCollector . num_procs - cls . select_max + 1
elif cls . start > ProcCollector . num_procs : cls . start = ProcCollector . num_procs
2020-07-24 01:44:11 +00:00
if cls . start < 1 : cls . start = 1
2020-07-27 01:13:13 +00:00
if cls . selected > ProcCollector . num_procs and ProcCollector . num_procs < cls . select_max : cls . selected = ProcCollector . num_procs
elif cls . selected > cls . select_max : cls . selected = cls . select_max
if cls . selected < 0 : cls . selected = 0
2020-07-24 01:44:11 +00:00
if old != ( cls . start , cls . selected ) :
cls . moved = True
2020-07-30 02:29:40 +00:00
Collector . collect ( ProcCollector , proc_interrupt = True , redraw = True , only_draw = True )
2020-07-27 01:13:13 +00:00
2020-07-24 01:44:11 +00:00
@classmethod
def _draw_fg ( cls ) :
proc = ProcCollector
2020-07-27 01:13:13 +00:00
if proc . proc_interrupt : return
2020-07-24 01:44:11 +00:00
if proc . redraw : cls . redraw = True
out : str = " "
out_misc : str = " "
n : int = 0
2020-07-30 02:29:40 +00:00
x , y , w , h = cls . x + 1 , cls . current_y + 1 , cls . width - 2 , cls . current_h - 2
2020-07-24 01:44:11 +00:00
prog_len : int ; arg_len : int ; val : int ; c_color : str ; m_color : str ; t_color : str ; sort_pos : int ; tree_len : int ; is_selected : bool ; calc : int
2020-07-30 02:29:40 +00:00
dgx : int ; dgw : int ; dx : int ; dw : int ; dy : int
2020-07-24 01:44:11 +00:00
l_count : int = 0
2020-07-27 01:13:13 +00:00
scroll_pos : int = 0
2020-07-30 02:29:40 +00:00
killed : bool = True
2020-07-24 01:44:11 +00:00
indent : str = " "
offset : int = 0
vals : List [ str ]
g_color : str = " "
2020-07-27 01:13:13 +00:00
s_len : int = 0
if proc . search_filter : s_len = len ( proc . search_filter [ : 10 ] )
2020-07-30 02:29:40 +00:00
loc_string : str = f ' { cls . start + cls . selected - 1 } / { proc . num_procs } '
2020-07-24 01:44:11 +00:00
end : str = " "
2020-07-30 02:29:40 +00:00
if proc . detailed :
dgx , dgw = x , w / / 3
2020-08-02 18:10:42 +00:00
dw = w - dgw - 1
if dw > 120 :
dw = 120
dgw = w - 121
dx = x + dgw + 2
2020-07-30 02:29:40 +00:00
dy = cls . y + 1
2020-07-27 01:13:13 +00:00
if w > 67 :
arg_len = w - 53 - ( 1 if proc . num_procs > cls . select_max else 0 )
prog_len = 15
else :
arg_len = 0
prog_len = w - 38 - ( 1 if proc . num_procs > cls . select_max else 0 )
2020-07-24 01:44:11 +00:00
if CONFIG . proc_tree :
tree_len = arg_len + prog_len + 6
arg_len = 0
2020-07-30 02:29:40 +00:00
#* Buttons and titles only redrawn if needed
2020-07-24 01:44:11 +00:00
if cls . resized or cls . redraw :
2020-07-30 02:29:40 +00:00
s_len + = len ( CONFIG . proc_sorting )
if cls . resized or s_len != cls . s_len or proc . detailed :
cls . s_len = s_len
2020-08-15 22:53:51 +00:00
for k in [ " e " , " r " , " c " , " t " , " k " , " i " , " enter " , " left " , " " ] :
2020-07-30 02:29:40 +00:00
if k in Key . mouse : del Key . mouse [ k ]
if proc . detailed :
killed = proc . details [ " killed " ]
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
if cls . current_y != cls . y + 8 or cls . resized or Graphs . detailed_cpu is NotImplemented :
cls . current_y = cls . y + 8
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 } ' )
for i in range ( 7 ) :
out_misc + = f ' { Mv . to ( dy + i , dgx + dgw + 1 ) } { Symbol . v_line } '
out_misc + = ( f ' { Mv . to ( dy - 1 , x - 1 ) } { THEME . proc_box } { Symbol . left_up } { Symbol . h_line * w } { Symbol . right_up } '
f ' { Mv . to ( dy - 1 , dgx + dgw + 1 ) } { Symbol . div_up } '
f ' { Mv . to ( dy - 1 , x + 1 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { THEME . title ( str ( proc . details [ " pid " ] ) ) } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { THEME . title ( proc . details [ " name " ] [ : ( dgw - 11 ) ] ) } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
if cls . selected == 0 :
Key . mouse [ " enter " ] = [ [ dx + dw - 10 + i , dy - 1 ] for i in range ( 7 ) ]
if cls . selected == 0 and not killed :
Key . mouse [ " t " ] = [ [ dx + 2 + i , dy - 1 ] for i in range ( 9 ) ]
out_misc + = ( f ' { Mv . to ( dy - 1 , dx + dw - 11 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { title if cls . selected > 0 else THEME . title } close { Fx . ub } { main if cls . selected > 0 else THEME . main_fg } { Symbol . enter } { THEME . proc_box ( Symbol . title_right ) } '
f ' { Mv . to ( dy - 1 , dx + 1 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } t { title } erminate { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
if dw > 28 :
if cls . selected == 0 and not killed and not " k " in Key . mouse : Key . mouse [ " k " ] = [ [ dx + 13 + i , dy - 1 ] for i in range ( 4 ) ]
out_misc + = f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } k { title } ill { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
if dw > 39 :
2020-08-02 18:10:42 +00:00
if cls . selected == 0 and not killed and not " i " in Key . mouse : Key . mouse [ " i " ] = [ [ dx + 19 + i , dy - 1 ] for i in range ( 9 ) ]
out_misc + = f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } i { title } nterrupt { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
2020-07-30 02:29:40 +00:00
if Graphs . detailed_cpu is NotImplemented or cls . resized :
Graphs . detailed_cpu = Graph ( dgw + 1 , 7 , THEME . gradient [ " cpu " ] , proc . details_cpu )
Graphs . detailed_mem = Graph ( dw / / 3 , 1 , None , proc . details_mem )
cls . select_max = cls . height - 11
y = cls . y + 9
h = cls . height - 10
else :
if cls . current_y != cls . y or cls . resized :
cls . current_y = cls . y
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 + 7 , x - 1 ) } { THEME . proc_box ( Symbol . v_line ) } { Mv . r ( w ) } { THEME . proc_box ( Symbol . v_line ) } ' )
cls . select_max = cls . height - 3
2020-07-24 01:44:11 +00:00
sort_pos = x + w - len ( CONFIG . proc_sorting ) - 7
2020-08-04 19:35:49 +00:00
if not " left " in Key . mouse :
Key . mouse [ " left " ] = [ [ sort_pos + i , y - 1 ] for i in range ( 3 ) ]
Key . mouse [ " right " ] = [ [ sort_pos + len ( CONFIG . proc_sorting ) + 3 + i , y - 1 ] for i in range ( 3 ) ]
2020-07-24 01:44:11 +00:00
2020-07-30 02:29:40 +00:00
out_misc + = ( f ' { Mv . to ( y - 1 , x + 8 ) } { THEME . proc_box ( Symbol . h_line * ( w - 9 ) ) } ' +
( " " if not proc . detailed else f " { Mv . to ( dy + 7 , dgx + dgw + 1 ) } { THEME . proc_box ( Symbol . div_down ) } " ) +
2020-07-24 01:44:11 +00:00
f ' { Mv . to ( y - 1 , sort_pos ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { THEME . hi_fg ( " < " ) } { THEME . title ( CONFIG . proc_sorting ) } '
2020-07-27 01:13:13 +00:00
f ' { THEME . hi_fg ( " > " ) } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
2020-07-30 02:29:40 +00:00
if w > 29 + s_len :
2020-08-04 19:35:49 +00:00
if not " e " in Key . mouse : Key . mouse [ " e " ] = [ [ sort_pos - 5 + i , y - 1 ] for i in range ( 4 ) ]
2020-07-27 01:13:13 +00:00
out_misc + = ( f ' { Mv . to ( y - 1 , sort_pos - 6 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b if CONFIG . proc_tree else " " } '
2020-08-04 19:35:49 +00:00
f ' { THEME . title ( " tre " ) } { THEME . hi_fg ( " e " ) } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
2020-07-30 02:29:40 +00:00
if w > 37 + s_len :
2020-08-04 19:35:49 +00:00
if not " r " in Key . mouse : Key . mouse [ " r " ] = [ [ sort_pos - 14 + i , y - 1 ] for i in range ( 7 ) ]
2020-07-24 01:44:11 +00:00
out_misc + = ( f ' { Mv . to ( y - 1 , sort_pos - 15 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b if CONFIG . proc_reversed else " " } '
2020-08-04 19:35:49 +00:00
f ' { THEME . hi_fg ( " r " ) } { THEME . title ( " everse " ) } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
2020-07-30 02:29:40 +00:00
if w > 47 + s_len :
2020-08-04 19:35:49 +00:00
if not " c " in Key . mouse : Key . mouse [ " c " ] = [ [ sort_pos - 24 + i , y - 1 ] for i in range ( 8 ) ]
2020-07-27 01:13:13 +00:00
out_misc + = ( f ' { Mv . to ( y - 1 , sort_pos - 25 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b if CONFIG . proc_per_core else " " } '
2020-08-04 19:35:49 +00:00
f ' { THEME . title ( " per- " ) } { THEME . hi_fg ( " c " ) } { THEME . title ( " ore " ) } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
2020-07-27 01:13:13 +00:00
2020-07-30 02:29:40 +00:00
if not " f " in Key . mouse or cls . resized : Key . mouse [ " f " ] = [ [ x + 9 + i , y - 1 ] for i in range ( 6 if not proc . search_filter else 2 + len ( proc . search_filter [ - 10 : ] ) ) ]
2020-07-27 01:13:13 +00:00
if proc . search_filter :
2020-07-30 02:29:40 +00:00
if not " delete " in Key . mouse : Key . mouse [ " delete " ] = [ [ x + 12 + len ( proc . search_filter [ - 10 : ] ) + i , y - 1 ] for i in range ( 3 ) ]
2020-07-27 01:13:13 +00:00
elif " delete " in Key . mouse :
del Key . mouse [ " delete " ]
out_misc + = ( f ' { Mv . to ( y - 1 , x + 8 ) } { THEME . proc_box ( Symbol . title_left ) } { Fx . b if cls . filtering or proc . search_filter else " " } { THEME . hi_fg ( " f " ) } { THEME . title } ' +
( " ilter " if not proc . search_filter and not cls . filtering else f ' { proc . search_filter [ - ( 10 if w < 83 else w - 74 ) : ] } { ( Fx . bl + " █ " + Fx . ubl ) if cls . filtering else THEME . hi_fg ( " del " ) } ' ) +
f ' { THEME . proc_box ( Symbol . title_right ) } ' )
2020-07-24 01:44:11 +00:00
2020-07-30 02:29:40 +00:00
main = THEME . inactive_fg if cls . selected == 0 else THEME . main_fg
hi = THEME . inactive_fg if cls . selected == 0 else THEME . hi_fg
title = THEME . inactive_fg if cls . selected == 0 else THEME . title
out_misc + = ( f ' { Mv . to ( y + h , x + 1 ) } { THEME . proc_box } { Symbol . h_line * ( w - 4 ) } '
f ' { Mv . to ( y + h , x + 1 ) } { THEME . proc_box ( Symbol . title_left ) } { main } { Symbol . up } { Fx . b } { THEME . main_fg ( " select " ) } { Fx . ub } '
f ' { THEME . inactive_fg if cls . selected == cls . select_max else THEME . main_fg } { Symbol . down } { THEME . proc_box ( Symbol . title_right ) } '
f ' { THEME . proc_box ( Symbol . title_left ) } { title } { Fx . b } info { Fx . ub } { main } { Symbol . enter } { THEME . proc_box ( Symbol . title_right ) } ' )
if not " enter " in Key . mouse : Key . mouse [ " enter " ] = [ [ x + 14 + i , y + h ] for i in range ( 6 ) ]
if w - len ( loc_string ) > 34 :
if not " t " in Key . mouse : Key . mouse [ " t " ] = [ [ x + 22 + i , y + h ] for i in range ( 9 ) ]
out_misc + = f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } t { title } erminate { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
if w - len ( loc_string ) > 40 :
if not " k " in Key . mouse : Key . mouse [ " k " ] = [ [ x + 33 + i , y + h ] for i in range ( 4 ) ]
out_misc + = f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } k { title } ill { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
if w - len ( loc_string ) > 51 :
2020-08-02 18:10:42 +00:00
if not " i " in Key . mouse : Key . mouse [ " i " ] = [ [ x + 39 + i , y + h ] for i in range ( 9 ) ]
out_misc + = f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } i { title } nterrupt { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
2020-08-15 22:53:51 +00:00
if CONFIG . proc_tree and w - len ( loc_string ) > 65 :
if not " " in Key . mouse : Key . mouse [ " " ] = [ [ x + 50 + i , y + h ] for i in range ( 12 ) ]
out_misc + = f ' { THEME . proc_box ( Symbol . title_left ) } { Fx . b } { hi } spc { title } collapse { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } '
2020-08-02 18:10:42 +00:00
#* Processes labels
selected : str = CONFIG . proc_sorting
label : str
if selected == " memory " : selected = " mem "
if selected == " threads " and not CONFIG . proc_tree and not arg_len : selected = " tr "
if CONFIG . proc_tree :
label = ( f ' { THEME . title } { Fx . b } { Mv . to ( y , x ) } { " Tree: " : < { tree_len - 2 } } ' " Threads: " f ' { " User: " : <9 } Mem% { " Cpu % " : >11 } { Fx . ub } { THEME . main_fg } ' +
( " " if proc . num_procs > cls . select_max else " " ) )
2020-08-15 22:53:51 +00:00
if selected in [ " pid " , " program " , " arguments " ] : selected = " tree "
2020-08-02 18:10:42 +00:00
else :
label = ( f ' { THEME . title } { Fx . b } { Mv . to ( y , x ) } { " Pid: " : >7 } { " Program: " if prog_len > 8 else " Prg: " : < { prog_len } } ' + ( f ' { " Arguments: " : < { arg_len - 4 } } ' if arg_len else " " ) +
f ' { " Threads: " if arg_len else " Tr: " } { " User: " : <9 } Mem% { " Cpu % " : >11 } { Fx . ub } { THEME . main_fg } ' +
( " " if proc . num_procs > cls . select_max else " " ) )
if selected == " program " and prog_len < = 8 : selected = " prg "
selected = selected . split ( " " ) [ 0 ] . capitalize ( )
2020-08-09 12:45:03 +00:00
if CONFIG . proc_mem_bytes : label = label . replace ( " Mem % " , " MemB " )
label = label . replace ( selected , f ' { Fx . u } { selected } { Fx . uu } ' )
out_misc + = label
2020-07-30 02:29:40 +00:00
2020-07-24 01:44:11 +00:00
Draw . buffer ( " proc_misc " , out_misc , only_save = True )
2020-07-30 02:29:40 +00:00
#* Detailed box draw
if proc . detailed :
if proc . details [ " status " ] == psutil . STATUS_RUNNING : stat_color = Fx . b
elif proc . details [ " status " ] in [ psutil . STATUS_DEAD , psutil . STATUS_STOPPED , psutil . STATUS_ZOMBIE ] : stat_color = THEME . inactive_fg
else : stat_color = " "
2020-08-02 18:10:42 +00:00
expand = proc . expand
iw = ( dw - 3 ) / / ( 4 + expand )
2020-07-30 02:29:40 +00:00
iw2 = iw - 1
out + = ( f ' { Mv . to ( dy , dgx ) } { Graphs . detailed_cpu ( None if cls . moved or proc . details [ " killed " ] else proc . details_cpu [ - 1 ] ) } '
2020-08-04 19:35:49 +00:00
f ' { Mv . to ( dy , dgx ) } { THEME . title } { Fx . b } { 0 if proc . details [ " killed " ] else proc . details [ " cpu_percent " ] } % { Mv . r ( 1 ) } { " " if SYSTEM == " MacOS " else ( ( " C " if dgw < 20 else " Core " ) + str ( proc . details [ " cpu_num " ] ) ) } ' )
2020-07-30 02:29:40 +00:00
for i , l in enumerate ( [ " C " , " P " , " U " ] ) :
out + = f ' { Mv . to ( dy + 2 + i , dgx ) } { l } '
for i , l in enumerate ( [ " C " , " M " , " D " ] ) :
out + = f ' { Mv . to ( dy + 4 + i , dx + 1 ) } { l } '
2020-08-02 18:10:42 +00:00
out + = ( f ' { Mv . to ( dy , dx + 1 ) } { " Status: " : ^ { iw } . { iw2 } } { " Elapsed: " : ^ { iw } . { iw2 } } ' +
( f ' { " Parent: " : ^ { iw } . { iw2 } } ' if dw > 28 else " " ) + ( f ' { " User: " : ^ { iw } . { iw2 } } ' if dw > 38 else " " ) +
( f ' { " Threads: " : ^ { iw } . { iw2 } } ' if expand > 0 else " " ) + ( f ' { " Nice: " : ^ { iw } . { iw2 } } ' if expand > 1 else " " ) +
( f ' { " IO Read: " : ^ { iw } . { iw2 } } ' if expand > 2 else " " ) + ( f ' { " IO Write: " : ^ { iw } . { iw2 } } ' if expand > 3 else " " ) +
( f ' { " TTY: " : ^ { iw } . { iw2 } } ' if expand > 4 else " " ) +
f ' { Mv . to ( dy + 1 , dx + 1 ) } { Fx . ub } { THEME . main_fg } { stat_color } { proc . details [ " status " ] : ^ { iw } . { iw2 } } { Fx . ub } { THEME . main_fg } { proc . details [ " uptime " ] : ^ { iw } . { iw2 } } ' +
( f ' { proc . details [ " parent_name " ] : ^ { iw } . { iw2 } } ' if dw > 28 else " " ) + ( f ' { proc . details [ " username " ] : ^ { iw } . { iw2 } } ' if dw > 38 else " " ) +
( f ' { proc . details [ " threads " ] : ^ { iw } . { iw2 } } ' if expand > 0 else " " ) + ( f ' { proc . details [ " nice " ] : ^ { iw } . { iw2 } } ' if expand > 1 else " " ) +
( f ' { proc . details [ " io_read " ] : ^ { iw } . { iw2 } } ' if expand > 2 else " " ) + ( f ' { proc . details [ " io_write " ] : ^ { iw } . { iw2 } } ' if expand > 3 else " " ) +
( f ' { proc . details [ " terminal " ] [ - ( iw2 ) : ] : ^ { iw } . { iw2 } } ' if expand > 4 else " " ) +
f ' { Mv . to ( dy + 3 , dx ) } { THEME . title } { Fx . b } { ( " Memory: " if dw > 42 else " M: " ) + str ( round ( proc . details [ " memory_percent " ] , 1 ) ) + " % " : > { dw / / 3 - 1 } } { Fx . ub } { THEME . inactive_fg } { " ⡀ " * ( dw / / 3 ) } '
2020-07-30 02:29:40 +00:00
f ' { Mv . l ( dw / / 3 ) } { THEME . proc_misc } { Graphs . detailed_mem ( None if cls . moved else proc . details_mem [ - 1 ] ) } '
2020-08-02 18:10:42 +00:00
f ' { THEME . title } { Fx . b } { proc . details [ " memory_bytes " ] : . { dw / / 3 - 2 } } { THEME . main_fg } { Fx . ub } ' )
2020-07-30 02:29:40 +00:00
cy = dy + ( 4 if len ( proc . details [ " cmdline " ] ) > dw - 5 else 5 )
for i in range ( ceil ( len ( proc . details [ " cmdline " ] ) / ( dw - 5 ) ) ) :
out + = f ' { Mv . to ( cy + i , dx + 3 ) } { proc . details [ " cmdline " ] [ ( ( dw - 5 ) * i ) : ] [ : ( dw - 5 ) ] : { " ^ " if i == 0 else " < " } { dw - 5 } } '
if i == 2 : break
#* Checking for selection out of bounds
2020-07-27 01:13:13 +00:00
if cls . start > proc . num_procs - cls . select_max + 1 and proc . num_procs > cls . select_max : cls . start = proc . num_procs - cls . select_max + 1
elif cls . start > proc . num_procs : cls . start = proc . num_procs
if cls . start < 1 : cls . start = 1
if cls . selected > proc . num_procs and proc . num_procs < cls . select_max : cls . selected = proc . num_procs
elif cls . selected > cls . select_max : cls . selected = cls . select_max
if cls . selected < 0 : cls . selected = 0
2020-07-30 02:29:40 +00:00
#* Start iteration over all processes and info
2020-08-02 18:10:42 +00:00
cy = 1
2020-07-24 01:44:11 +00:00
for n , ( pid , items ) in enumerate ( proc . processes . items ( ) , start = 1 ) :
if n < cls . start : continue
l_count + = 1
if l_count == cls . selected :
is_selected = True
cls . selected_pid = pid
else : is_selected = False
2020-08-04 19:35:49 +00:00
2020-08-09 12:45:03 +00:00
indent , name , cmd , threads , username , mem , mem_b , cpu = [ items . get ( v , d ) for v , d in [ ( " indent " , " " ) , ( " name " , " " ) , ( " cmd " , " " ) , ( " threads " , 0 ) , ( " username " , " ? " ) , ( " mem " , 0.0 ) , ( " mem_b " , 0 ) , ( " cpu " , 0.0 ) ] ]
2020-08-04 19:35:49 +00:00
if CONFIG . proc_tree :
2020-08-15 22:53:51 +00:00
arg_len = 0
2020-08-04 19:35:49 +00:00
offset = tree_len - len ( f ' { indent } { pid } ' )
if offset < 1 : offset = 0
indent = f ' { indent : . { tree_len - len ( str ( pid ) ) } } '
2020-08-15 22:53:51 +00:00
if offset - len ( name ) > 12 :
cmd = cmd . split ( " " ) [ 0 ] . split ( " / " ) [ - 1 ]
if not cmd . startswith ( name ) :
offset = len ( name )
arg_len = tree_len - len ( f ' { indent } { pid } { name } ' ) + 2
cmd = f ' ( { cmd [ : ( arg_len - 4 ) ] } ) '
2020-08-04 19:35:49 +00:00
else :
offset = prog_len - 1
2020-07-24 01:44:11 +00:00
if cpu > 1.0 or pid in Graphs . pid_cpu :
if pid not in Graphs . pid_cpu :
Graphs . pid_cpu [ pid ] = Graph ( 5 , 1 , None , [ 0 ] )
cls . pid_counter [ pid ] = 0
elif cpu < 1.0 :
cls . pid_counter [ pid ] + = 1
if cls . pid_counter [ pid ] > 10 :
del cls . pid_counter [ pid ] , Graphs . pid_cpu [ pid ]
else :
cls . pid_counter [ pid ] = 0
end = f ' { THEME . main_fg } { Fx . ub } ' if CONFIG . proc_colors else Fx . ub
if cls . selected > cy : calc = cls . selected - cy
elif cls . selected > 0 and cls . selected < = cy : calc = cy - cls . selected
else : calc = cy
if CONFIG . proc_colors and not is_selected :
vals = [ ]
for v in [ int ( cpu ) , int ( mem ) , int ( threads / / 3 ) ] :
if CONFIG . proc_gradient :
val = ( ( v if v < = 100 else 100 ) + 100 ) - calc * 100 / / cls . select_max
2020-08-14 17:37:40 +00:00
vals + = [ f ' { THEME . gradient [ " proc_color " if val < 100 else " process " ] [ val if val < 100 else val - 100 ] } ' ]
2020-07-24 01:44:11 +00:00
else :
2020-08-14 17:37:40 +00:00
vals + = [ f ' { THEME . gradient [ " process " ] [ v if v < = 100 else 100 ] } ' ]
2020-07-24 01:44:11 +00:00
c_color , m_color , t_color = vals
2020-07-27 01:13:13 +00:00
else :
c_color = m_color = t_color = Fx . b
2020-07-24 01:44:11 +00:00
if CONFIG . proc_gradient and not is_selected :
g_color = f ' { THEME . gradient [ " proc " ] [ calc * 100 / / cls . select_max ] } '
if is_selected :
c_color = m_color = t_color = g_color = end = " "
out + = f ' { THEME . selected_bg } { THEME . selected_fg } { Fx . b } '
2020-07-30 02:29:40 +00:00
#* Creates one line for a process with all gathered information
2020-07-24 01:44:11 +00:00
out + = ( f ' { Mv . to ( y + cy , x ) } { g_color } { indent } { pid : > { ( 1 if CONFIG . proc_tree else 7 ) } } ' +
f ' { c_color } { name : < { offset } . { offset } } { end } ' +
( f ' { g_color } { cmd : < { arg_len } . { arg_len - 1 } } ' if arg_len else " " ) +
t_color + ( f ' { threads : >4 } ' if threads < 1000 else " 999> " ) + end +
g_color + ( f ' { username : <9.9 } ' if len ( username ) < 10 else f ' { username [ : 8 ] : <8 } + ' ) +
2020-08-09 12:45:03 +00:00
m_color + ( ( f ' { mem : >4.1f } ' if mem < 100 else f ' { mem : >4.0f } ' ) if not CONFIG . proc_mem_bytes else f ' { floating_humanizer ( mem_b , short = True ) : >4.4 } ' ) + end +
2020-07-27 01:13:13 +00:00
f ' { THEME . inactive_fg } { " ⡀ " * 5 } { THEME . main_fg } { g_color } { c_color } ' + ( f ' { cpu : >4.1f } ' if cpu < 100 else f ' { cpu : >5.0f } ' ) + end +
( " " if proc . num_procs > cls . select_max else " " ) )
2020-07-30 02:29:40 +00:00
#* Draw small cpu graph for process if cpu usage was above 1% in the last 10 updates
2020-07-24 01:44:11 +00:00
if pid in Graphs . pid_cpu :
2020-08-02 18:10:42 +00:00
out + = f ' { Mv . to ( y + cy , x + w - ( 12 if proc . num_procs > cls . select_max else 11 ) ) } { c_color if CONFIG . proc_colors else THEME . proc_misc } { Graphs . pid_cpu [ pid ] ( None if cls . moved else round ( cpu ) ) } { THEME . main_fg } '
2020-07-27 01:13:13 +00:00
if is_selected : out + = f ' { Fx . ub } { Term . fg } { Term . bg } { Mv . to ( y + cy , x + w - 1 ) } { " " if proc . num_procs > cls . select_max else " " } '
2020-07-24 01:44:11 +00:00
cy + = 1
if cy == h : break
if cy < h :
for i in range ( h - cy ) :
out + = f ' { Mv . to ( y + cy + i , x ) } { " " * w } '
2020-07-30 02:29:40 +00:00
#* Draw scrollbar if needed
2020-07-27 01:13:13 +00:00
if proc . num_procs > cls . select_max :
2020-07-30 02:29:40 +00:00
if cls . resized :
Key . mouse [ " mouse_scroll_up " ] = [ [ x + w - 2 + i , y ] for i in range ( 3 ) ]
Key . mouse [ " mouse_scroll_down " ] = [ [ x + w - 2 + i , y + h - 1 ] for i in range ( 3 ) ]
2020-07-27 01:13:13 +00:00
scroll_pos = round ( cls . start * ( cls . select_max - 2 ) / ( proc . num_procs - ( cls . select_max - 2 ) ) )
if scroll_pos < 0 or cls . start == 1 : scroll_pos = 0
elif scroll_pos > h - 3 or cls . start > = proc . num_procs - cls . select_max : scroll_pos = h - 3
out + = ( f ' { Mv . to ( y , x + w - 1 ) } { Fx . b } { THEME . main_fg } ↑ { Mv . to ( y + h - 1 , x + w - 1 ) } ↓ { Fx . ub } '
f ' { Mv . to ( y + 1 + scroll_pos , x + w - 1 ) } █ ' )
elif " scroll_up " in Key . mouse :
del Key . mouse [ " scroll_up " ] , Key . mouse [ " scroll_down " ]
2020-07-30 02:29:40 +00:00
#* Draw current selection and number of processes
out + = ( f ' { Mv . to ( y + h , x + w - 3 - len ( loc_string ) ) } { THEME . proc_box } { Symbol . h_line * 1 } { Symbol . title_left } { THEME . title } '
f ' { Fx . b } { loc_string } { Fx . ub } { THEME . proc_box ( Symbol . title_right ) } ' )
#* Clean up dead processes graphs and counters
2020-07-24 01:44:11 +00:00
cls . count + = 1
if cls . count == 100 :
cls . count == 0
for p in list ( cls . pid_counter ) :
if not psutil . pid_exists ( p ) :
del cls . pid_counter [ p ] , Graphs . pid_cpu [ p ]
2020-07-30 02:29:40 +00:00
Draw . buffer ( cls . buffer , f ' { out_misc } { out } { Term . fg } ' , only_save = Menu . active )
2020-07-24 01:44:11 +00:00
cls . redraw = cls . resized = cls . moved = False
2020-07-09 00:56:03 +00:00
class Collector :
''' Data collector master class
* . start ( ) : Starts collector thread
* . stop ( ) : Stops collector thread
* . collect ( * collectors : Collector , draw_now : bool = True , interrupt : bool = False ) : queues up collectors to run '''
stopping : bool = False
started : bool = False
draw_now : bool = False
2020-07-24 01:44:11 +00:00
redraw : bool = False
only_draw : bool = False
2020-07-09 00:56:03 +00:00
thread : threading . Thread
collect_run = threading . Event ( )
collect_idle = threading . Event ( )
collect_idle . set ( )
collect_done = threading . Event ( )
collect_queue : List = [ ]
collect_interrupt : bool = False
2020-07-27 01:13:13 +00:00
proc_interrupt : bool = False
2020-07-24 01:44:11 +00:00
use_draw_list : bool = False
2020-07-09 00:56:03 +00:00
@classmethod
def start ( cls ) :
cls . stopping = False
cls . thread = threading . Thread ( target = cls . _runner , args = ( ) )
cls . thread . start ( )
cls . started = True
@classmethod
def stop ( cls ) :
if cls . started and cls . thread . is_alive ( ) :
cls . stopping = True
2020-07-18 01:16:01 +00:00
cls . started = False
cls . collect_queue = [ ]
cls . collect_idle . set ( )
cls . collect_done . set ( )
2020-07-09 00:56:03 +00:00
try :
cls . thread . join ( )
except :
pass
@classmethod
def _runner ( cls ) :
''' This is meant to run in it ' s own thread, collecting and drawing when collect_run is set '''
2020-07-16 00:51:55 +00:00
draw_buffers : List [ str ] = [ ]
2020-07-20 22:52:34 +00:00
debugged : bool = False
2020-07-16 00:51:55 +00:00
try :
while not cls . stopping :
2020-08-02 18:10:42 +00:00
if CONFIG . draw_clock : Box . draw_clock ( )
2020-07-16 00:51:55 +00:00
cls . collect_run . wait ( 0.1 )
if not cls . collect_run . is_set ( ) :
continue
2020-07-18 01:16:01 +00:00
draw_buffers = [ ]
cls . collect_interrupt = False
2020-07-16 00:51:55 +00:00
cls . collect_run . clear ( )
cls . collect_idle . clear ( )
2020-07-24 01:44:11 +00:00
cls . collect_done . clear ( )
2020-07-20 22:52:34 +00:00
if DEBUG and not debugged : TimeIt . start ( " Collect and draw " )
2020-07-16 00:51:55 +00:00
while cls . collect_queue :
collector = cls . collect_queue . pop ( )
2020-07-24 01:44:11 +00:00
if not cls . only_draw :
collector . _collect ( )
2020-07-16 00:51:55 +00:00
collector . _draw ( )
2020-07-24 01:44:11 +00:00
if cls . use_draw_list : draw_buffers . append ( collector . buffer )
2020-07-18 01:16:01 +00:00
if cls . collect_interrupt : break
2020-07-20 22:52:34 +00:00
if DEBUG and not debugged : TimeIt . stop ( " Collect and draw " ) ; debugged = True
2020-07-18 01:16:01 +00:00
if cls . draw_now and not Menu . active and not cls . collect_interrupt :
2020-07-24 01:44:11 +00:00
if cls . use_draw_list : Draw . out ( * draw_buffers )
else : Draw . out ( )
2020-07-16 00:51:55 +00:00
cls . collect_idle . set ( )
cls . collect_done . set ( )
except Exception as e :
errlog . exception ( f ' Data collection thread failed with exception: { e } ' )
2020-07-27 01:13:13 +00:00
cls . collect_idle . set ( )
2020-07-09 00:56:03 +00:00
cls . collect_done . set ( )
2020-07-16 00:51:55 +00:00
clean_quit ( 1 , thread = True )
2020-07-09 00:56:03 +00:00
@classmethod
2020-07-27 01:13:13 +00:00
def collect ( cls , * collectors , draw_now : bool = True , interrupt : bool = False , proc_interrupt : bool = False , redraw : bool = False , only_draw : bool = False ) :
2020-07-09 00:56:03 +00:00
''' Setup collect queue for _runner '''
cls . collect_interrupt = interrupt
2020-07-27 01:13:13 +00:00
cls . proc_interrupt = proc_interrupt
2020-07-09 00:56:03 +00:00
cls . collect_idle . wait ( )
cls . collect_interrupt = False
2020-07-27 01:13:13 +00:00
cls . proc_interrupt = False
2020-07-24 01:44:11 +00:00
cls . use_draw_list = False
2020-07-09 00:56:03 +00:00
cls . draw_now = draw_now
2020-07-24 01:44:11 +00:00
cls . redraw = redraw
cls . only_draw = only_draw
2020-07-09 00:56:03 +00:00
if collectors :
cls . collect_queue = [ * collectors ]
2020-07-24 01:44:11 +00:00
cls . use_draw_list = True
2020-07-09 00:56:03 +00:00
else :
cls . collect_queue = list ( cls . __subclasses__ ( ) )
cls . collect_run . set ( )
class CpuCollector ( Collector ) :
2020-07-18 01:16:01 +00:00
''' Collects cpu usage for cpu and cores, cpu frequency, load_avg, uptime and cpu temps '''
2020-07-16 00:51:55 +00:00
cpu_usage : List [ List [ int ] ] = [ ]
cpu_temp : List [ List [ int ] ] = [ ]
2020-07-14 00:20:40 +00:00
cpu_temp_high : int = 0
cpu_temp_crit : int = 0
2020-07-16 00:51:55 +00:00
for _ in range ( THREADS + 1 ) :
2020-07-09 00:56:03 +00:00
cpu_usage . append ( [ ] )
2020-07-14 00:20:40 +00:00
cpu_temp . append ( [ ] )
2020-08-03 20:10:23 +00:00
freq_error : bool = False
2020-07-09 00:56:03 +00:00
cpu_freq : int = 0
load_avg : List [ float ] = [ ]
2020-07-16 00:51:55 +00:00
uptime : str = " "
buffer : str = CpuBox . buffer
2020-08-02 18:10:42 +00:00
sensor_method : str = " "
got_sensors : bool = False
2020-07-09 00:56:03 +00:00
2020-08-02 18:10:42 +00:00
@classmethod
def get_sensors ( cls ) :
2020-07-14 00:20:40 +00:00
''' Check if we can get cpu temps and return method of getting temps '''
2020-08-02 18:10:42 +00:00
cls . sensor_method = " "
2020-07-14 00:20:40 +00:00
if SYSTEM == " MacOS " :
try :
if which ( " osx-cpu-temp " ) and subprocess . check_output ( " osx-cpu-temp " , text = True ) . rstrip ( ) . endswith ( " °C " ) :
2020-08-02 18:10:42 +00:00
cls . sensor_method = " osx-cpu-temp "
2020-07-14 00:20:40 +00:00
except : pass
elif hasattr ( psutil , " sensors_temperatures " ) :
try :
temps = psutil . sensors_temperatures ( )
if temps :
2020-08-02 18:10:42 +00:00
for name , entries in temps . items ( ) :
2020-08-15 13:27:54 +00:00
if name . lower ( ) . startswith ( " cpu " ) :
2020-08-02 18:10:42 +00:00
cls . sensor_method = " psutil "
break
2020-07-14 00:20:40 +00:00
for entry in entries :
2020-08-02 18:10:42 +00:00
if entry . label . startswith ( ( " Package " , " Core 0 " , " Tdie " , " CPU " ) ) :
cls . sensor_method = " psutil "
break
2020-07-14 00:20:40 +00:00
except : pass
2020-08-02 18:10:42 +00:00
if not cls . sensor_method and SYSTEM == " Linux " :
try :
2020-08-11 14:50:25 +00:00
if which ( " vcgencmd " ) and subprocess . check_output ( [ " vcgencmd " , " measure_temp " ] , text = True ) . strip ( ) . endswith ( " ' C " ) :
2020-08-02 18:10:42 +00:00
cls . sensor_method = " vcgencmd "
except : pass
cls . got_sensors = True if cls . sensor_method else False
2020-07-14 00:20:40 +00:00
2020-07-09 00:56:03 +00:00
@classmethod
def _collect ( cls ) :
cls . cpu_usage [ 0 ] . append ( round ( psutil . cpu_percent ( percpu = False ) ) )
for n , thread in enumerate ( psutil . cpu_percent ( percpu = True ) , start = 1 ) :
cls . cpu_usage [ n ] . append ( round ( thread ) )
2020-07-14 00:20:40 +00:00
if len ( cls . cpu_usage [ n ] ) > Term . width * 2 :
del cls . cpu_usage [ n ] [ 0 ]
2020-08-03 20:10:23 +00:00
try :
if hasattr ( psutil . cpu_freq ( ) , " current " ) :
cls . cpu_freq = round ( psutil . cpu_freq ( ) . current )
except Exception as e :
if not cls . freq_error :
cls . freq_error = True
errlog . error ( " Exception while getting cpu frequency! " )
errlog . exception ( f ' { e } ' )
else :
pass
2020-07-14 00:20:40 +00:00
cls . load_avg = [ round ( lavg , 2 ) for lavg in os . getloadavg ( ) ]
2020-07-16 00:51:55 +00:00
cls . uptime = str ( timedelta ( seconds = round ( time ( ) - psutil . boot_time ( ) , 0 ) ) ) [ : - 3 ]
2020-07-09 00:56:03 +00:00
2020-07-14 00:20:40 +00:00
if CONFIG . check_temp and cls . got_sensors :
cls . _collect_temps ( )
@classmethod
def _collect_temps ( cls ) :
temp : int
cores : List [ int ] = [ ]
cpu_type : str = " "
if cls . sensor_method == " psutil " :
2020-08-02 18:10:42 +00:00
try :
for name , entries in psutil . sensors_temperatures ( ) . items ( ) :
for entry in entries :
2020-08-23 19:13:01 +00:00
if entry . label . startswith ( ( " Package " , " Tdie " ) ) and hasattr ( entry , " current " ) and round ( entry . current ) > 0 :
2020-08-02 18:10:42 +00:00
cpu_type = " intel " if entry . label . startswith ( " Package " ) else " ryzen "
2020-07-14 00:20:40 +00:00
if not cls . cpu_temp_high :
2020-08-05 14:49:19 +00:00
if hasattr ( entry , " high " ) and entry . high : cls . cpu_temp_high = round ( entry . high )
else : cls . cpu_temp_high = 80
if hasattr ( entry , " critical " ) and entry . critical : cls . cpu_temp_crit = round ( entry . critical )
else : cls . cpu_temp_crit = 95
2020-07-14 00:20:40 +00:00
temp = round ( entry . current )
2020-08-23 19:13:01 +00:00
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 :
2020-08-02 18:10:42 +00:00
if not cpu_type :
cpu_type = " other "
if not cls . cpu_temp_high :
2020-08-05 14:49:19 +00:00
if hasattr ( entry , " high " ) and entry . high : cls . cpu_temp_high = round ( entry . high )
2020-08-15 13:27:54 +00:00
else : cls . cpu_temp_high = 60 if name == " cpu_thermal " else 80
2020-08-05 14:49:19 +00:00
if hasattr ( entry , " critical " ) and entry . critical : cls . cpu_temp_crit = round ( entry . critical )
2020-08-15 13:27:54 +00:00
else : cls . cpu_temp_crit = 80 if name == " cpu_thermal " else 95
2020-08-02 18:10:42 +00:00
temp = round ( entry . current )
cores . append ( round ( entry . current ) )
if len ( cores ) < THREADS :
if cpu_type == " intel " or ( cpu_type == " other " and len ( cores ) == THREADS / / 2 ) :
cls . cpu_temp [ 0 ] . append ( temp )
for n , t in enumerate ( cores , start = 1 ) :
2020-08-03 17:23:10 +00:00
try :
cls . cpu_temp [ n ] . append ( t )
cls . cpu_temp [ THREADS / / 2 + n ] . append ( t )
except IndexError :
break
2020-08-02 18:10:42 +00:00
elif cpu_type == " ryzen " or cpu_type == " other " :
cls . cpu_temp [ 0 ] . append ( temp )
if len ( cores ) < 1 : cores . append ( temp )
z = 1
for t in cores :
2020-08-03 17:23:10 +00:00
try :
for i in range ( THREADS / / len ( cores ) ) :
cls . cpu_temp [ z + i ] . append ( t )
z + = i
except IndexError :
break
2020-08-06 15:12:31 +00:00
if cls . cpu_temp [ 0 ] :
for n in range ( 1 , len ( cls . cpu_temp ) ) :
if len ( cls . cpu_temp [ n ] ) != len ( cls . cpu_temp [ n - 1 ] ) :
cls . cpu_temp [ n ] = cls . cpu_temp [ n / / 2 ] . copy ( )
2020-08-02 18:10:42 +00:00
else :
cores . insert ( 0 , temp )
for n , t in enumerate ( cores ) :
2020-08-03 17:23:10 +00:00
try :
cls . cpu_temp [ n ] . append ( t )
except IndexError :
break
2020-08-02 18:10:42 +00:00
except Exception as e :
errlog . exception ( f ' { e } ' )
cls . got_sensors = False
#CONFIG.check_temp = False
CpuBox . _calc_size ( )
2020-07-14 00:20:40 +00:00
else :
2020-07-09 00:56:03 +00:00
try :
2020-07-14 00:20:40 +00:00
if cls . sensor_method == " osx-cpu-temp " :
2020-08-11 19:37:57 +00:00
temp = round ( float ( subprocess . check_output ( " osx-cpu-temp " , text = True ) . strip ( ) [ : - 2 ] ) )
if not cls . cpu_temp_high :
cls . cpu_temp_high = 85
cls . cpu_temp_crit = 100
2020-07-14 00:20:40 +00:00
elif cls . sensor_method == " vcgencmd " :
2020-08-10 19:50:15 +00:00
temp = round ( float ( subprocess . check_output ( [ " vcgencmd " , " measure_temp " ] , text = True ) . strip ( ) [ 5 : - 2 ] ) )
2020-08-11 19:37:57 +00:00
if not cls . cpu_temp_high :
cls . cpu_temp_high = 60
cls . cpu_temp_crit = 80
2020-07-14 00:20:40 +00:00
except Exception as e :
errlog . exception ( f ' { e } ' )
cls . got_sensors = False
2020-08-02 18:10:42 +00:00
#CONFIG.check_temp = False
2020-07-14 00:20:40 +00:00
CpuBox . _calc_size ( )
else :
2020-07-09 00:56:03 +00:00
for n in range ( THREADS + 1 ) :
2020-07-14 00:20:40 +00:00
cls . cpu_temp [ n ] . append ( temp )
if len ( cls . cpu_temp [ 0 ] ) > 5 :
for n in range ( len ( cls . cpu_temp ) ) :
del cls . cpu_temp [ n ] [ 0 ]
2020-07-09 00:56:03 +00:00
@classmethod
def _draw ( cls ) :
2020-07-18 01:16:01 +00:00
CpuBox . _draw_fg ( )
class MemCollector ( Collector ) :
''' Collects memory and disks information '''
values : Dict [ str , int ] = { }
vlist : Dict [ str , List [ int ] ] = { }
percent : Dict [ str , int ] = { }
string : Dict [ str , str ] = { }
swap_values : Dict [ str , int ] = { }
swap_vlist : Dict [ str , List [ int ] ] = { }
swap_percent : Dict [ str , int ] = { }
swap_string : Dict [ str , str ] = { }
disks : Dict [ str , Dict ]
disk_hist : Dict [ str , Tuple ] = { }
2020-07-20 22:52:34 +00:00
timestamp : float = time ( )
2020-08-03 15:47:44 +00:00
io_error : bool = False
2020-07-20 22:52:34 +00:00
old_disks : List [ str ] = [ ]
2020-07-18 01:16:01 +00:00
excludes : List [ str ] = [ " squashfs " ]
if SYSTEM == " BSD " : excludes + = [ " devfs " , " tmpfs " , " procfs " , " linprocfs " , " gvfs " , " fusefs " ]
buffer : str = MemBox . buffer
@classmethod
def _collect ( cls ) :
#* Collect memory
mem = psutil . virtual_memory ( )
if hasattr ( mem , " cached " ) :
cls . values [ " cached " ] = mem . cached
else :
cls . values [ " cached " ] = mem . active
cls . values [ " total " ] , cls . values [ " free " ] , cls . values [ " available " ] = mem . total , mem . free , mem . available
cls . values [ " used " ] = cls . values [ " total " ] - cls . values [ " available " ]
for key , value in cls . values . items ( ) :
cls . string [ key ] = floating_humanizer ( value )
if key == " total " : continue
cls . percent [ key ] = round ( value * 100 / cls . values [ " total " ] )
if CONFIG . mem_graphs :
if not key in cls . vlist : cls . vlist [ key ] = [ ]
cls . vlist [ key ] . append ( cls . percent [ key ] )
if len ( cls . vlist [ key ] ) > MemBox . width : del cls . vlist [ key ] [ 0 ]
#* Collect swap
if CONFIG . show_swap or CONFIG . swap_disk :
swap = psutil . swap_memory ( )
cls . swap_values [ " total " ] , cls . swap_values [ " free " ] = swap . total , swap . free
cls . swap_values [ " used " ] = cls . swap_values [ " total " ] - cls . swap_values [ " free " ]
if swap . total :
2020-07-20 22:52:34 +00:00
if not MemBox . swap_on :
MemBox . redraw = True
2020-07-18 01:16:01 +00:00
MemBox . swap_on = True
for key , value in cls . swap_values . items ( ) :
cls . swap_string [ key ] = floating_humanizer ( value )
if key == " total " : continue
cls . swap_percent [ key ] = round ( value * 100 / cls . swap_values [ " total " ] )
if CONFIG . mem_graphs :
if not key in cls . swap_vlist : cls . swap_vlist [ key ] = [ ]
cls . swap_vlist [ key ] . append ( cls . swap_percent [ key ] )
if len ( cls . swap_vlist [ key ] ) > MemBox . width : del cls . swap_vlist [ key ] [ 0 ]
else :
2020-07-20 22:52:34 +00:00
if MemBox . swap_on :
MemBox . redraw = True
2020-07-18 01:16:01 +00:00
MemBox . swap_on = False
else :
2020-07-20 22:52:34 +00:00
if MemBox . swap_on :
MemBox . redraw = True
2020-07-18 01:16:01 +00:00
MemBox . swap_on = False
if not CONFIG . show_disks : return
#* Collect disks usage
disk_read : int = 0
disk_write : int = 0
dev_name : str
disk_name : str
filtering : Tuple = ( )
filter_exclude : bool = False
io_string : str
u_percent : int
disk_list : List [ str ] = [ ]
cls . disks = { }
if CONFIG . disks_filter :
if CONFIG . disks_filter . startswith ( " exclude= " ) :
filter_exclude = True
filtering = tuple ( v . strip ( ) for v in CONFIG . disks_filter . replace ( " exclude= " , " " ) . strip ( ) . split ( " , " ) )
else :
filtering = tuple ( v . strip ( ) for v in CONFIG . disks_filter . strip ( ) . split ( " , " ) )
2020-08-03 15:47:44 +00:00
try :
io_counters = psutil . disk_io_counters ( perdisk = True if SYSTEM == " Linux " else False , nowrap = True )
except ValueError as e :
if not cls . io_error :
cls . io_error = True
errlog . error ( f ' Non fatal error during disk io collection! ' )
if psutil . version_info [ 0 ] < 5 or ( psutil . version_info [ 0 ] == 5 and psutil . version_info [ 1 ] < 7 ) :
errlog . error ( f ' Caused by outdated psutil version. ' )
errlog . exception ( f ' { e } ' )
io_counters = None
2020-07-18 01:16:01 +00:00
for disk in psutil . disk_partitions ( ) :
disk_io = None
io_string = " "
disk_name = disk . mountpoint . rsplit ( ' / ' , 1 ) [ - 1 ] if not disk . mountpoint == " / " else " root "
while disk_name in disk_list : disk_name + = " _ "
disk_list + = [ disk_name ]
if cls . excludes and disk . fstype in cls . excludes :
continue
if filtering and ( ( not filter_exclude and not disk_name . endswith ( filtering ) ) or ( filter_exclude and disk_name . endswith ( filtering ) ) ) :
continue
#elif filtering and disk_name.endswith(filtering)
if SYSTEM == " MacOS " and disk . mountpoint == " /private/var/vm " :
continue
try :
disk_u = psutil . disk_usage ( disk . mountpoint )
except :
pass
u_percent = round ( disk_u . percent )
cls . disks [ disk . device ] = { }
cls . disks [ disk . device ] [ " name " ] = disk_name
cls . disks [ disk . device ] [ " used_percent " ] = u_percent
cls . disks [ disk . device ] [ " free_percent " ] = 100 - u_percent
for name in [ " total " , " used " , " free " ] :
cls . disks [ disk . device ] [ name ] = floating_humanizer ( getattr ( disk_u , name , 0 ) )
#* Collect disk io
2020-08-03 15:47:44 +00:00
if io_counters :
try :
if SYSTEM == " Linux " :
dev_name = os . path . realpath ( disk . device ) . rsplit ( ' / ' , 1 ) [ - 1 ]
if dev_name . startswith ( " md " ) :
try :
dev_name = dev_name [ : dev_name . index ( " p " ) ]
except :
pass
disk_io = io_counters [ dev_name ]
elif disk . mountpoint == " / " :
disk_io = io_counters
else :
raise Exception
disk_read = round ( ( disk_io . read_bytes - cls . disk_hist [ disk . device ] [ 0 ] ) / ( time ( ) - cls . timestamp ) )
disk_write = round ( ( disk_io . write_bytes - cls . disk_hist [ disk . device ] [ 1 ] ) / ( time ( ) - cls . timestamp ) )
except :
disk_read = disk_write = 0
else :
2020-07-18 01:16:01 +00:00
disk_read = disk_write = 0
if disk_io :
cls . disk_hist [ disk . device ] = ( disk_io . read_bytes , disk_io . write_bytes )
if MemBox . disks_width > 30 :
if disk_read > 0 :
io_string + = f ' ▲ { floating_humanizer ( disk_read , short = True ) } '
if disk_write > 0 :
io_string + = f ' ▼ { floating_humanizer ( disk_write , short = True ) } '
elif disk_read + disk_write > 0 :
io_string + = f ' ▼▲ { floating_humanizer ( disk_read + disk_write , short = True ) } '
cls . disks [ disk . device ] [ " io " ] = io_string
2020-07-20 22:52:34 +00:00
if CONFIG . swap_disk and MemBox . swap_on :
2020-07-18 01:16:01 +00:00
cls . disks [ " __swap " ] = { }
cls . disks [ " __swap " ] [ " name " ] = " swap "
cls . disks [ " __swap " ] [ " used_percent " ] = cls . swap_percent [ " used " ]
cls . disks [ " __swap " ] [ " free_percent " ] = cls . swap_percent [ " free " ]
for name in [ " total " , " used " , " free " ] :
cls . disks [ " __swap " ] [ name ] = cls . swap_string [ name ]
cls . disks [ " __swap " ] [ " io " ] = " "
if len ( cls . disks ) > 2 :
try :
new = { list ( cls . disks ) [ 0 ] : cls . disks . pop ( list ( cls . disks ) [ 0 ] ) }
new [ " __swap " ] = cls . disks . pop ( " __swap " )
new . update ( cls . disks )
cls . disks = new
except :
pass
2020-07-20 22:52:34 +00:00
if disk_list != cls . old_disks :
MemBox . redraw = True
cls . old_disks = disk_list . copy ( )
cls . timestamp = time ( )
2020-07-18 01:16:01 +00:00
@classmethod
def _draw ( cls ) :
MemBox . _draw_fg ( )
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
class NetCollector ( Collector ) :
2020-07-24 01:44:11 +00:00
''' Collects network stats '''
2020-07-20 22:52:34 +00:00
buffer : str = NetBox . buffer
nics : List [ str ] = [ ]
nic_i : int = 0
nic : str = " "
new_nic : str = " "
2020-08-03 16:22:14 +00:00
nic_error : bool = False
2020-07-24 01:44:11 +00:00
reset : bool = False
2020-07-20 22:52:34 +00:00
graph_raise : Dict [ str , int ] = { " download " : 5 , " upload " : 5 }
graph_lower : Dict [ str , int ] = { " download " : 5 , " upload " : 5 }
2020-08-09 14:20:00 +00:00
#min_top: int = 10<<10
2020-07-20 22:52:34 +00:00
#* Stats structure = stats[netword device][download, upload][total, last, top, graph_top, offset, speed, redraw, graph_raise, graph_low] = int, List[int], bool
stats : Dict [ str , Dict [ str , Dict [ str , Any ] ] ] = { }
#* Strings structure strings[network device][download, upload][total, byte_ps, bit_ps, top, graph_top] = str
strings : Dict [ str , Dict [ str , Dict [ str , str ] ] ] = { }
switched : bool = False
timestamp : float = time ( )
2020-08-09 14:20:00 +00:00
net_min : Dict [ str , int ] = { " download " : - 1 , " upload " : - 1 }
2020-08-15 15:39:45 +00:00
auto_min : bool = CONFIG . net_auto
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
@classmethod
def _get_nics ( cls ) :
''' Get a list of all network devices sorted by highest throughput '''
cls . nic_i = 0
cls . nic = " "
2020-08-03 16:22:14 +00:00
try :
io_all = psutil . net_io_counters ( pernic = True )
except Exception as e :
if not cls . nic_error :
cls . nic_error = True
errlog . exception ( f ' { e } ' )
2020-07-20 22:52:34 +00:00
if not io_all : return
up_stat = psutil . net_if_stats ( )
2020-08-03 17:42:09 +00:00
for nic in sorted ( io_all . keys ( ) , key = lambda nic : ( getattr ( io_all [ nic ] , " bytes_recv " , 0 ) + getattr ( io_all [ nic ] , " bytes_sent " , 0 ) ) , reverse = True ) :
2020-07-20 22:52:34 +00:00
if nic not in up_stat or not up_stat [ nic ] . isup :
continue
cls . nics . append ( nic )
if not cls . nics : cls . nics = [ " " ]
cls . nic = cls . nics [ cls . nic_i ]
2020-07-14 00:20:40 +00:00
2020-07-20 22:52:34 +00:00
@classmethod
def switch ( cls , key : str ) :
if len ( cls . nics ) < 2 : return
cls . nic_i + = + 1 if key == " n " else - 1
if cls . nic_i > = len ( cls . nics ) : cls . nic_i = 0
elif cls . nic_i < 0 : cls . nic_i = len ( cls . nics ) - 1
cls . new_nic = cls . nics [ cls . nic_i ]
cls . switched = True
2020-07-27 01:13:13 +00:00
Collector . collect ( NetCollector , redraw = True )
2020-07-14 00:20:40 +00:00
2020-07-20 22:52:34 +00:00
@classmethod
def _collect ( cls ) :
speed : int
stat : Dict
up_stat = psutil . net_if_stats ( )
if cls . switched :
cls . nic = cls . new_nic
cls . switched = False
if not cls . nic or cls . nic not in up_stat or not up_stat [ cls . nic ] . isup :
cls . _get_nics ( )
if not cls . nic : return
try :
io_all = psutil . net_io_counters ( pernic = True ) [ cls . nic ]
except KeyError :
pass
return
if not cls . nic in cls . stats :
cls . stats [ cls . nic ] = { }
cls . strings [ cls . nic ] = { " download " : { } , " upload " : { } }
for direction , value in [ " download " , io_all . bytes_recv ] , [ " upload " , io_all . bytes_sent ] :
2020-08-15 15:39:45 +00:00
cls . stats [ cls . nic ] [ direction ] = { " total " : value , " last " : value , " top " : 0 , " graph_top " : 0 , " offset " : 0 , " speed " : [ ] , " redraw " : True , " graph_raise " : 0 , " graph_lower " : 7 }
2020-07-20 22:52:34 +00:00
for v in [ " total " , " byte_ps " , " bit_ps " , " top " , " graph_top " ] :
cls . strings [ cls . nic ] [ direction ] [ v ] = " "
cls . stats [ cls . nic ] [ " download " ] [ " total " ] = io_all . bytes_recv
cls . stats [ cls . nic ] [ " upload " ] [ " total " ] = io_all . bytes_sent
for direction in [ " download " , " upload " ] :
stat = cls . stats [ cls . nic ] [ direction ]
strings = cls . strings [ cls . nic ] [ direction ]
#* Calculate current speed
stat [ " speed " ] . append ( round ( ( stat [ " total " ] - stat [ " last " ] ) / ( time ( ) - cls . timestamp ) ) )
stat [ " last " ] = stat [ " total " ]
speed = stat [ " speed " ] [ - 1 ]
2020-08-15 15:39:45 +00:00
if cls . net_min [ direction ] == - 1 :
2020-08-15 18:57:14 +00:00
cls . net_min [ direction ] = units_to_bytes ( getattr ( CONFIG , " net_ " + direction ) )
2020-08-15 15:39:45 +00:00
stat [ " graph_top " ] = cls . net_min [ direction ]
stat [ " graph_lower " ] = 7
if not cls . auto_min :
stat [ " redraw " ] = True
strings [ " graph_top " ] = floating_humanizer ( stat [ " graph_top " ] , short = True )
2020-07-24 01:44:11 +00:00
if stat [ " offset " ] and stat [ " offset " ] > stat [ " total " ] :
cls . reset = True
if cls . reset :
if not stat [ " offset " ] :
stat [ " offset " ] = stat [ " total " ]
else :
stat [ " offset " ] = 0
if direction == " upload " :
cls . reset = False
NetBox . redraw = True
2020-07-20 22:52:34 +00:00
if len ( stat [ " speed " ] ) > NetBox . width * 2 :
del stat [ " speed " ] [ 0 ]
2020-07-24 01:44:11 +00:00
strings [ " total " ] = floating_humanizer ( stat [ " total " ] - stat [ " offset " ] )
2020-07-20 22:52:34 +00:00
strings [ " byte_ps " ] = floating_humanizer ( stat [ " speed " ] [ - 1 ] , per_second = True )
strings [ " bit_ps " ] = floating_humanizer ( stat [ " speed " ] [ - 1 ] , bit = True , per_second = True )
if speed > stat [ " top " ] or not stat [ " top " ] :
stat [ " top " ] = speed
strings [ " top " ] = floating_humanizer ( stat [ " top " ] , bit = True , per_second = True )
2020-08-15 15:39:45 +00:00
if cls . auto_min :
if speed > stat [ " graph_top " ] :
stat [ " graph_raise " ] + = 1
if stat [ " graph_lower " ] > 0 : stat [ " graph_lower " ] - = 1
2020-08-15 16:07:44 +00:00
elif speed < stat [ " graph_top " ] / / 10 :
2020-08-15 15:39:45 +00:00
stat [ " graph_lower " ] + = 1
if stat [ " graph_raise " ] > 0 : stat [ " graph_raise " ] - = 1
if stat [ " graph_raise " ] > = 5 or stat [ " graph_lower " ] > = 5 :
if stat [ " graph_raise " ] > = 5 :
2020-08-15 16:07:44 +00:00
stat [ " graph_top " ] = round ( max ( stat [ " speed " ] [ - 5 : ] ) / 0.8 )
2020-08-15 15:39:45 +00:00
elif stat [ " graph_lower " ] > = 5 :
2020-08-15 16:07:44 +00:00
stat [ " graph_top " ] = max ( 10 << 10 , max ( stat [ " speed " ] [ - 5 : ] ) * 3 )
2020-08-15 15:39:45 +00:00
stat [ " graph_raise " ] = 0
stat [ " graph_lower " ] = 0
stat [ " redraw " ] = True
strings [ " graph_top " ] = floating_humanizer ( stat [ " graph_top " ] , short = True )
2020-07-20 22:52:34 +00:00
cls . timestamp = time ( )
2020-07-18 01:16:01 +00:00
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
@classmethod
def _draw ( cls ) :
NetBox . _draw_fg ( )
2020-08-11 18:32:20 +00:00
class ProcCollector ( Collector ) :
2020-07-24 01:44:11 +00:00
''' Collects process stats '''
buffer : str = ProcBox . buffer
search_filter : str = " "
processes : Dict = { }
num_procs : int = 0
2020-08-06 15:28:19 +00:00
det_cpu : float = 0.0
2020-07-27 01:13:13 +00:00
detailed : bool = False
2020-07-30 02:29:40 +00:00
detailed_pid : Union [ int , None ] = None
details : Dict [ str , Any ] = { }
2020-07-27 01:13:13 +00:00
details_cpu : List [ int ] = [ ]
2020-07-30 02:29:40 +00:00
details_mem : List [ int ] = [ ]
2020-08-02 18:10:42 +00:00
expand : int = 0
2020-08-15 22:53:51 +00:00
collapsed : Dict = { }
tree_counter : int = 0
2020-07-24 01:44:11 +00:00
p_values : List [ str ] = [ " pid " , " name " , " cmdline " , " num_threads " , " username " , " memory_percent " , " cpu_percent " , " cpu_times " , " create_time " ]
sort_expr : Dict = { }
sort_expr [ " pid " ] = compile ( " p.info[ ' pid ' ] " , " str " , " eval " )
2020-08-11 18:32:20 +00:00
sort_expr [ " program " ] = compile ( " ' ' if p.info[ ' name ' ] == 0.0 else p.info[ ' name ' ] " , " str " , " eval " )
sort_expr [ " arguments " ] = compile ( " ' ' .join(str(p.info[ ' cmdline ' ])) or ( ' ' if p.info[ ' name ' ] == 0.0 else p.info[ ' name ' ]) " , " str " , " eval " )
sort_expr [ " threads " ] = compile ( " 0 if p.info[ ' num_threads ' ] == 0.0 else p.info[ ' num_threads ' ] " , " str " , " eval " )
sort_expr [ " user " ] = compile ( " ' ' if p.info[ ' username ' ] == 0.0 else p.info[ ' username ' ] " , " str " , " eval " )
2020-08-04 19:35:49 +00:00
sort_expr [ " memory " ] = compile ( " p.info[ ' memory_percent ' ] " , " str " , " eval " )
2020-07-24 01:44:11 +00:00
sort_expr [ " cpu lazy " ] = compile ( " (sum(p.info[ ' cpu_times ' ][:2] if not p.info[ ' cpu_times ' ] == 0.0 else [0.0, 0.0]) * 1000 / (time() - p.info[ ' create_time ' ])) " , " str " , " eval " )
sort_expr [ " cpu responsive " ] = compile ( " (p.info[ ' cpu_percent ' ] if CONFIG.proc_per_core else (p.info[ ' cpu_percent ' ] / THREADS)) " , " str " , " eval " )
@classmethod
def _collect ( cls ) :
''' List all processess with pid, name, arguments, threads, username, memory percent and cpu percent '''
out : Dict = { }
2020-08-06 15:28:19 +00:00
cls . det_cpu = 0.0
2020-07-24 01:44:11 +00:00
sorting : str = CONFIG . proc_sorting
reverse : bool = not CONFIG . proc_reversed
proc_per_cpu : bool = CONFIG . proc_per_core
search : str = cls . search_filter
err : float = 0.0
n : int = 0
2020-07-30 02:29:40 +00:00
if CONFIG . proc_tree and sorting == " arguments " :
sorting = " program "
sort_cmd = cls . sort_expr [ sorting ]
if CONFIG . proc_tree :
cls . _tree ( sort_cmd = sort_cmd , reverse = reverse , proc_per_cpu = proc_per_cpu , search = search )
else :
2020-08-11 18:08:05 +00:00
for p in sorted ( psutil . process_iter ( cls . p_values + ( [ " memory_info " ] if CONFIG . proc_mem_bytes else [ ] ) , err ) , key = lambda p : eval ( sort_cmd ) , reverse = reverse ) :
2020-07-30 02:29:40 +00:00
if cls . collect_interrupt or cls . proc_interrupt :
return
if p . info [ " name " ] == " idle " or p . info [ " name " ] == err or p . info [ " pid " ] == err :
continue
if p . info [ " cmdline " ] == err :
p . info [ " cmdline " ] = " "
if p . info [ " username " ] == err :
p . info [ " username " ] = " "
if p . info [ " num_threads " ] == err :
p . info [ " num_threads " ] = 0
if search :
2020-08-06 15:28:19 +00:00
if cls . detailed and p . info [ " pid " ] == cls . detailed_pid :
cls . det_cpu = p . info [ " cpu_percent " ]
2020-07-30 02:29:40 +00:00
for value in [ p . info [ " name " ] , " " . join ( p . info [ " cmdline " ] ) , str ( p . info [ " pid " ] ) , p . info [ " username " ] ] :
for s in search . split ( " , " ) :
if s . strip ( ) in value :
break
else : continue
break
else : continue
2020-08-15 22:53:51 +00:00
cpu = p . info [ " cpu_percent " ] if proc_per_cpu else round ( p . info [ " cpu_percent " ] / THREADS , 2 )
2020-07-30 02:29:40 +00:00
mem = p . info [ " memory_percent " ]
2020-08-09 12:45:03 +00:00
if CONFIG . proc_mem_bytes and hasattr ( p . info [ " memory_info " ] , " rss " ) :
mem_b = p . info [ " memory_info " ] . rss
else :
mem_b = 0
2020-07-30 02:29:40 +00:00
cmd = " " . join ( p . info [ " cmdline " ] ) or " [ " + p . info [ " name " ] + " ] "
out [ p . info [ " pid " ] ] = {
" name " : p . info [ " name " ] ,
" cmd " : cmd ,
" threads " : p . info [ " num_threads " ] ,
" username " : p . info [ " username " ] ,
" mem " : mem ,
2020-08-09 12:45:03 +00:00
" mem_b " : mem_b ,
2020-07-30 02:29:40 +00:00
" cpu " : cpu }
n + = 1
cls . num_procs = n
cls . processes = out . copy ( )
2020-08-02 18:10:42 +00:00
if cls . detailed :
cls . expand = ( ( ProcBox . width - 2 ) - ( ( ProcBox . width - 2 ) / / 3 ) - 40 ) / / 10
if cls . expand > 5 : cls . expand = 5
2020-07-27 01:13:13 +00:00
if cls . detailed and not cls . details . get ( " killed " , False ) :
try :
2020-07-30 02:29:40 +00:00
c_pid = cls . detailed_pid
det = psutil . Process ( c_pid )
2020-07-27 01:13:13 +00:00
except ( psutil . NoSuchProcess , psutil . ZombieProcess ) :
cls . details [ " killed " ] = True
cls . details [ " status " ] = psutil . STATUS_DEAD
2020-07-30 02:29:40 +00:00
ProcBox . redraw = True
2020-07-27 01:13:13 +00:00
else :
2020-08-04 19:35:49 +00:00
attrs : List [ str ] = [ " status " , " memory_info " , " create_time " ]
if not SYSTEM == " MacOS " : attrs . extend ( [ " cpu_num " ] )
if cls . expand :
attrs . extend ( [ " nice " , " terminal " ] )
if not SYSTEM == " MacOS " : attrs . extend ( [ " io_counters " ] )
2020-08-06 15:28:19 +00:00
if not c_pid in cls . processes : attrs . extend ( [ " pid " , " name " , " cmdline " , " num_threads " , " username " , " memory_percent " ] )
2020-08-05 14:49:19 +00:00
2020-08-04 19:35:49 +00:00
cls . details = det . as_dict ( attrs = attrs , ad_value = " " )
2020-07-27 01:13:13 +00:00
if det . parent ( ) != None : cls . details [ " parent_name " ] = det . parent ( ) . name ( )
else : cls . details [ " parent_name " ] = " "
2020-07-30 02:29:40 +00:00
cls . details [ " pid " ] = c_pid
2020-08-05 14:49:19 +00:00
if c_pid in cls . processes :
cls . details [ " name " ] = cls . processes [ c_pid ] [ " name " ]
cls . details [ " cmdline " ] = cls . processes [ c_pid ] [ " cmd " ]
cls . details [ " threads " ] = f ' { cls . processes [ c_pid ] [ " threads " ] } '
cls . details [ " username " ] = cls . processes [ c_pid ] [ " username " ]
cls . details [ " memory_percent " ] = cls . processes [ c_pid ] [ " mem " ]
cls . details [ " cpu_percent " ] = round ( cls . processes [ c_pid ] [ " cpu " ] * ( 1 if CONFIG . proc_per_core else THREADS ) )
else :
cls . details [ " cmdline " ] = " " . join ( cls . details [ " cmdline " ] ) or " [ " + cls . details [ " name " ] + " ] "
2020-08-05 20:30:11 +00:00
cls . details [ " threads " ] = f ' { cls . details [ " num_threads " ] } '
2020-08-06 15:28:19 +00:00
cls . details [ " cpu_percent " ] = round ( cls . det_cpu )
2020-08-05 20:30:11 +00:00
2020-07-27 01:13:13 +00:00
cls . details [ " killed " ] = False
2020-08-04 19:35:49 +00:00
if SYSTEM == " MacOS " :
cls . details [ " cpu_num " ] = - 1
cls . details [ " io_counters " ] = " "
2020-07-27 01:13:13 +00:00
if hasattr ( cls . details [ " memory_info " ] , " rss " ) : cls . details [ " memory_bytes " ] = floating_humanizer ( cls . details [ " memory_info " ] . rss ) # type: ignore
else : cls . details [ " memory_bytes " ] = " ? Bytes "
2020-07-30 02:29:40 +00:00
if isinstance ( cls . details [ " create_time " ] , float ) :
uptime = timedelta ( seconds = round ( time ( ) - cls . details [ " create_time " ] , 0 ) )
if uptime . days > 0 : cls . details [ " uptime " ] = f ' { uptime . days } d { str ( uptime ) . split ( " , " ) [ 1 ] [ : - 3 ] . strip ( ) } '
else : cls . details [ " uptime " ] = f ' { uptime } '
2020-07-27 01:13:13 +00:00
else : cls . details [ " uptime " ] = " ??:??:?? "
2020-08-02 18:10:42 +00:00
if cls . expand :
if cls . expand > 1 : cls . details [ " nice " ] = f ' { cls . details [ " nice " ] } '
2020-07-30 02:29:40 +00:00
if SYSTEM == " BSD " :
2020-08-02 18:10:42 +00:00
if cls . expand > 2 :
if hasattr ( cls . details [ " io_counters " ] , " read_count " ) : cls . details [ " io_read " ] = f ' { cls . details [ " io_counters " ] . read_count } '
else : cls . details [ " io_read " ] = " ? "
if cls . expand > 3 :
if hasattr ( cls . details [ " io_counters " ] , " write_count " ) : cls . details [ " io_write " ] = f ' { cls . details [ " io_counters " ] . write_count } '
else : cls . details [ " io_write " ] = " ? "
2020-07-27 01:13:13 +00:00
else :
2020-08-02 18:10:42 +00:00
if cls . expand > 2 :
if hasattr ( cls . details [ " io_counters " ] , " read_bytes " ) : cls . details [ " io_read " ] = floating_humanizer ( cls . details [ " io_counters " ] . read_bytes )
else : cls . details [ " io_read " ] = " ? "
if cls . expand > 3 :
if hasattr ( cls . details [ " io_counters " ] , " write_bytes " ) : cls . details [ " io_write " ] = floating_humanizer ( cls . details [ " io_counters " ] . write_bytes )
else : cls . details [ " io_write " ] = " ? "
if cls . expand > 4 : cls . details [ " terminal " ] = f ' { cls . details [ " terminal " ] } ' . replace ( " /dev/ " , " " )
2020-07-30 02:29:40 +00:00
cls . details_cpu . append ( cls . details [ " cpu_percent " ] )
mem = cls . details [ " memory_percent " ]
if mem > 80 : mem = round ( mem )
elif mem > 60 : mem = round ( mem * 1.2 )
elif mem > 30 : mem = round ( mem * 1.5 )
elif mem > 10 : mem = round ( mem * 2 )
elif mem > 5 : mem = round ( mem * 10 )
else : mem = round ( mem * 20 )
cls . details_mem . append ( mem )
2020-07-27 01:13:13 +00:00
if len ( cls . details_cpu ) > ProcBox . width : del cls . details_cpu [ 0 ]
2020-07-30 02:29:40 +00:00
if len ( cls . details_mem ) > ProcBox . width : del cls . details_mem [ 0 ]
2020-07-24 01:44:11 +00:00
@classmethod
def _tree ( cls , sort_cmd , reverse : bool , proc_per_cpu : bool , search : str ) :
''' List all processess in a tree view with pid, name, threads, username, memory percent and cpu percent '''
out : Dict = { }
err : float = 0.0
2020-08-06 15:28:19 +00:00
det_cpu : float = 0.0
2020-07-24 01:44:11 +00:00
infolist : Dict = { }
2020-08-15 22:53:51 +00:00
cls . tree_counter + = 1
2020-07-24 01:44:11 +00:00
tree = defaultdict ( list )
n : int = 0
2020-08-12 14:30:27 +00:00
for p in sorted ( psutil . process_iter ( cls . p_values + ( [ " memory_info " ] if CONFIG . proc_mem_bytes else [ ] ) , err ) , key = lambda p : eval ( sort_cmd ) , reverse = reverse ) :
2020-07-24 01:44:11 +00:00
if cls . collect_interrupt : return
try :
tree [ p . ppid ( ) ] . append ( p . pid )
except ( psutil . NoSuchProcess , psutil . ZombieProcess ) :
pass
else :
infolist [ p . pid ] = p . info
n + = 1
if 0 in tree and 0 in tree [ 0 ] :
tree [ 0 ] . remove ( 0 )
2020-08-15 22:53:51 +00:00
def create_tree ( pid : int , tree : defaultdict , indent : str = " " , inindent : str = " " , found : bool = False , depth : int = 0 , collapse_to : Union [ None , int ] = None ) :
2020-08-06 15:28:19 +00:00
nonlocal infolist , proc_per_cpu , search , out , det_cpu
2020-08-15 22:53:51 +00:00
name : str ; threads : int ; username : str ; mem : float ; cpu : float ; collapse : bool = False
2020-07-24 01:44:11 +00:00
cont : bool = True
2020-08-05 19:56:13 +00:00
getinfo : Dict = { }
2020-07-24 01:44:11 +00:00
if cls . collect_interrupt : return
try :
name = psutil . Process ( pid ) . name ( )
if name == " idle " : return
except psutil . Error :
pass
cont = False
2020-07-27 01:13:13 +00:00
name = " "
2020-07-24 01:44:11 +00:00
if pid in infolist :
getinfo = infolist [ pid ]
if search and not found :
2020-08-06 15:28:19 +00:00
if cls . detailed and pid == cls . detailed_pid :
det_cpu = getinfo [ " cpu_percent " ]
2020-08-05 19:56:13 +00:00
if " username " in getinfo and isinstance ( getinfo [ " username " ] , float ) : getinfo [ " username " ] = " "
if " cmdline " in getinfo and isinstance ( getinfo [ " cmdline " ] , float ) : getinfo [ " cmdline " ] = " "
for value in [ name , str ( pid ) , getinfo . get ( " username " , " " ) , " " . join ( getinfo . get ( " cmdline " , " " ) ) ] :
2020-07-24 01:44:11 +00:00
for s in search . split ( " , " ) :
if s . strip ( ) in value :
found = True
break
else : continue
break
else : cont = False
if cont :
if getinfo :
if getinfo [ " num_threads " ] == err : threads = 0
else : threads = getinfo [ " num_threads " ]
if getinfo [ " username " ] == err : username = " "
else : username = getinfo [ " username " ]
2020-08-15 22:53:51 +00:00
cpu = getinfo [ " cpu_percent " ] if proc_per_cpu else round ( getinfo [ " cpu_percent " ] / THREADS , 2 )
2020-08-04 19:35:49 +00:00
mem = getinfo [ " memory_percent " ]
if getinfo [ " cmdline " ] == err : cmd = " "
else : cmd = " " . join ( getinfo [ " cmdline " ] ) or " [ " + getinfo [ " name " ] + " ] "
2020-08-09 12:45:03 +00:00
if CONFIG . proc_mem_bytes and hasattr ( getinfo [ " memory_info " ] , " rss " ) :
mem_b = getinfo [ " memory_info " ] . rss
else :
mem_b = 0
2020-07-24 01:44:11 +00:00
else :
2020-08-09 12:45:03 +00:00
threads = mem_b = 0
2020-07-24 01:44:11 +00:00
username = " "
mem = cpu = 0.0
2020-08-15 22:53:51 +00:00
if pid in cls . collapsed :
collapse = cls . collapsed [ pid ]
else :
collapse = True if depth > 3 else False
cls . collapsed [ pid ] = collapse
if collapse_to and not search :
out [ collapse_to ] [ " threads " ] + = threads
out [ collapse_to ] [ " mem " ] + = mem
out [ collapse_to ] [ " mem_b " ] + = mem_b
out [ collapse_to ] [ " cpu " ] + = cpu
else :
if pid in tree and len ( tree [ pid ] ) > 0 :
if collapse :
inindent = inindent . replace ( " ├─ " , " [+]─ " ) . replace ( " └─ " , " [+]─ " )
else :
inindent = inindent . replace ( " ├─ " , " [-]─ " ) . replace ( " └─ " , " [-]─ " )
out [ pid ] = {
" indent " : inindent ,
" name " : name ,
" cmd " : cmd ,
" threads " : threads ,
" username " : username ,
" mem " : mem ,
" mem_b " : mem_b ,
" cpu " : cpu ,
" depth " : depth ,
}
if search : collapse = False
elif collapse and not collapse_to :
collapse_to = pid
2020-07-24 01:44:11 +00:00
if pid not in tree :
return
children = tree [ pid ] [ : - 1 ]
2020-08-15 22:53:51 +00:00
2020-07-24 01:44:11 +00:00
for child in children :
2020-08-15 22:53:51 +00:00
create_tree ( child , tree , indent + " │ " , indent + " ├─ " , found = found , depth = depth + 1 , collapse_to = collapse_to )
create_tree ( tree [ pid ] [ - 1 ] , tree , indent + " " , indent + " └─ " , depth = depth + 1 , collapse_to = collapse_to )
2020-07-24 01:44:11 +00:00
create_tree ( min ( tree ) , tree )
2020-08-06 15:28:19 +00:00
cls . det_cpu = det_cpu
2020-07-24 01:44:11 +00:00
if cls . collect_interrupt : return
2020-08-15 22:53:51 +00:00
if cls . tree_counter > = 100 :
cls . tree_counter = 0
for pid in list ( cls . collapsed ) :
if not psutil . pid_exists ( pid ) :
del cls . collapsed [ pid ]
2020-07-27 01:13:13 +00:00
cls . num_procs = len ( out )
2020-07-24 01:44:11 +00:00
cls . processes = out . copy ( )
@classmethod
def sorting ( cls , key : str ) :
2020-07-27 01:13:13 +00:00
index : int = CONFIG . sorting_options . index ( CONFIG . proc_sorting ) + ( 1 if key == " right " else - 1 )
if index > = len ( CONFIG . sorting_options ) : index = 0
elif index < 0 : index = len ( CONFIG . sorting_options ) - 1
CONFIG . proc_sorting = CONFIG . sorting_options [ index ]
2020-08-04 19:52:21 +00:00
if " left " in Key . mouse : del Key . mouse [ " left " ]
2020-07-27 01:13:13 +00:00
Collector . collect ( ProcCollector , interrupt = True , redraw = True )
2020-07-24 01:44:11 +00:00
@classmethod
def _draw ( cls ) :
ProcBox . _draw_fg ( )
2020-07-20 22:52:34 +00:00
2020-07-11 22:54:52 +00:00
class Menu :
2020-07-24 01:44:11 +00:00
''' Holds all menus '''
2020-07-16 00:51:55 +00:00
active : bool = False
2020-07-24 01:44:11 +00:00
close : bool = False
resized : bool = True
menus : Dict [ str , Dict [ str , str ] ] = { }
menu_length : Dict [ str , int ] = { }
2020-08-02 18:10:42 +00:00
background : str = " "
2020-07-24 01:44:11 +00:00
for name , menu in MENUS . items ( ) :
menu_length [ name ] = len ( menu [ " normal " ] [ 0 ] )
menus [ name ] = { }
for sel in [ " normal " , " selected " ] :
menus [ name ] [ sel ] = " "
for i in range ( len ( menu [ sel ] ) ) :
menus [ name ] [ sel ] + = Fx . trans ( f ' { Color . fg ( MENU_COLORS [ sel ] [ i ] ) } { menu [ sel ] [ i ] } ' )
if i < len ( menu [ sel ] ) - 1 : menus [ name ] [ sel ] + = f ' { Mv . d ( 1 ) } { Mv . l ( len ( menu [ sel ] [ i ] ) ) } '
@classmethod
def main ( cls ) :
out : str = " "
banner : str = " "
redraw : bool = True
key : str = " "
mx : int = 0
my : int = 0
skip : bool = False
mouse_over : bool = False
mouse_items : Dict [ str , Dict [ str , int ] ] = { }
cls . active = True
cls . resized = True
menu_names : List [ str ] = list ( cls . menus . keys ( ) )
menu_index : int = 0
menu_current : str = menu_names [ 0 ]
2020-08-02 18:10:42 +00:00
cls . background = f ' { THEME . inactive_fg } ' + Fx . uncolor ( f ' { Draw . saved_buffer ( ) } ' ) + f ' { Term . fg } '
2020-07-24 01:44:11 +00:00
while not cls . close :
key = " "
if cls . resized :
banner = ( f ' { Banner . draw ( Term . height / / 2 - 10 , center = True ) } { Mv . d ( 1 ) } { Mv . l ( 46 ) } { Colors . black_bg } { Colors . default } { Fx . b } ← esc '
f ' { Mv . r ( 30 ) } { Fx . i } Version: { VERSION } { Fx . ui } { Fx . ub } { Term . bg } { Term . fg } ' )
2020-08-09 15:43:36 +00:00
if UpdateChecker . version != VERSION :
banner + = f ' { Mv . to ( Term . height , 1 ) } { Fx . b } { THEME . title } New release { UpdateChecker . version } available at https://github.com/aristocratos/bpytop { Fx . ub } { Term . fg } '
2020-07-24 01:44:11 +00:00
cy = 0
for name , menu in cls . menus . items ( ) :
ypos = Term . height / / 2 - 2 + cy
xpos = Term . width / / 2 - ( cls . menu_length [ name ] / / 2 )
mouse_items [ name ] = { " x1 " : xpos , " x2 " : xpos + cls . menu_length [ name ] - 1 , " y1 " : ypos , " y2 " : ypos + 2 }
cy + = 3
redraw = True
cls . resized = False
if redraw :
out = " "
for name , menu in cls . menus . items ( ) :
out + = f ' { Mv . to ( mouse_items [ name ] [ " y1 " ] , mouse_items [ name ] [ " x1 " ] ) } { menu [ " selected " if name == menu_current else " normal " ] } '
if skip and redraw :
Draw . now ( out )
elif not skip :
2020-08-02 18:10:42 +00:00
Draw . now ( f ' { cls . background } { banner } { out } ' )
2020-07-24 01:44:11 +00:00
skip = redraw = False
2020-08-02 18:10:42 +00:00
if Key . input_wait ( Timer . left ( ) , mouse = True ) :
2020-07-24 01:44:11 +00:00
if Key . mouse_moved ( ) :
mx , my = Key . get_mouse ( )
for name , pos in mouse_items . items ( ) :
if mx > = pos [ " x1 " ] and mx < = pos [ " x2 " ] and my > = pos [ " y1 " ] and my < = pos [ " y2 " ] :
mouse_over = True
if name != menu_current :
menu_current = name
menu_index = menu_names . index ( name )
redraw = True
break
else :
mouse_over = False
else :
key = Key . get ( )
if key == " mouse_click " and not mouse_over :
2020-08-11 15:09:05 +00:00
key = " M "
2020-07-24 01:44:11 +00:00
if key == " q " :
clean_quit ( )
2020-08-11 15:09:05 +00:00
elif key in [ " escape " , " M " ] :
2020-07-24 01:44:11 +00:00
cls . close = True
break
elif key in [ " up " , " mouse_scroll_up " , " shift_tab " ] :
menu_index - = 1
if menu_index < 0 : menu_index = len ( menu_names ) - 1
menu_current = menu_names [ menu_index ]
redraw = True
elif key in [ " down " , " mouse_scroll_down " , " tab " ] :
menu_index + = 1
if menu_index > len ( menu_names ) - 1 : menu_index = 0
menu_current = menu_names [ menu_index ]
redraw = True
elif key == " enter " or ( key == " mouse_click " and mouse_over ) :
if menu_current == " quit " :
clean_quit ( )
2020-08-02 18:10:42 +00:00
elif menu_current == " options " :
cls . options ( )
cls . resized = True
elif menu_current == " help " :
cls . help ( )
cls . resized = True
2020-07-24 01:44:11 +00:00
if Timer . not_zero ( ) and not cls . resized :
skip = True
else :
Collector . collect ( )
Collector . collect_done . wait ( 1 )
2020-08-02 18:10:42 +00:00
if CONFIG . background_update : cls . background = f ' { THEME . inactive_fg } ' + Fx . uncolor ( f ' { Draw . saved_buffer ( ) } ' ) + f ' { Term . fg } '
2020-07-24 01:44:11 +00:00
Timer . stamp ( )
Draw . now ( f ' { Draw . saved_buffer ( ) } ' )
2020-08-02 18:10:42 +00:00
cls . background = " "
cls . active = False
cls . close = False
@classmethod
def help ( cls ) :
out : str = " "
out_misc : str = " "
redraw : bool = True
key : str = " "
skip : bool = False
main_active : bool = True if cls . active else False
cls . active = True
cls . resized = True
if not cls . background :
cls . background = f ' { THEME . inactive_fg } ' + Fx . uncolor ( f ' { Draw . saved_buffer ( ) } ' ) + f ' { Term . fg } '
help_items : Dict [ str , str ] = {
" (Mouse 1) " : " Clicks buttons and selects in process list. " ,
" Selected (Mouse 1) " : " Show detailed information for selected process. " ,
" (Mouse scroll) " : " Scrolls any scrollable list/text under cursor. " ,
2020-08-04 19:52:21 +00:00
" (Esc, shift+m) " : " Toggles main menu. " ,
" (m) " : " Toggle mini mode. " ,
2020-08-02 18:10:42 +00:00
" (F2, o) " : " Shows options. " ,
" (F1, h) " : " Shows this window. " ,
" (ctrl+z) " : " Sleep program and put in background. " ,
" (ctrl+c, q) " : " Quits program. " ,
" (+) / (-) " : " Add/Subtract 100ms to/from update timer. " ,
" (Up) (Down) " : " Select in process list. " ,
" (Enter) " : " Show detailed information for selected process. " ,
2020-08-15 22:53:51 +00:00
" (Spacebar) " : " Expand/collapse the selected process in tree view. " ,
2020-08-02 18:10:42 +00:00
" (Pg Up) (Pg Down) " : " Jump 1 page in process list. " ,
" (Home) (End) " : " Jump to first or last page in process list. " ,
" (Left) (Right) " : " Select previous/next sorting column. " ,
" (b) (n) " : " Select previous/next network device. " ,
" (z) " : " Toggle totals reset for current network device " ,
2020-08-15 22:53:51 +00:00
" (a) " : " Toggle auto scaling for the network graphs. " ,
2020-08-02 18:10:42 +00:00
" (f) " : " Input a string to filter processes with. " ,
2020-08-04 19:52:21 +00:00
" (c) " : " Toggle per-core cpu usage of processes. " ,
" (r) " : " Reverse sorting order in processes box. " ,
" (e) " : " Toggle processes tree view " ,
2020-08-02 18:10:42 +00:00
" (delete) " : " Clear any entered filter. " ,
" Selected (T, t) " : " Terminate selected process with SIGTERM - 15. " ,
" Selected (K, k) " : " Kill selected process with SIGKILL - 9. " ,
" Selected (I, i) " : " Interrupt selected process with SIGINT - 2. " ,
" _1 " : " " ,
" _2 " : " For bug reporting and project updates, visit: " ,
" _3 " : " https://github.com/aristocratos/bpytop " ,
}
while not cls . close :
key = " "
if cls . resized :
y = 8 if Term . height < len ( help_items ) + 10 else Term . height / / 2 - len ( help_items ) / / 2 + 4
out_misc = ( f ' { Banner . draw ( y - 7 , center = True ) } { Mv . d ( 1 ) } { Mv . l ( 46 ) } { Colors . black_bg } { Colors . default } { Fx . b } ← esc '
f ' { Mv . r ( 30 ) } { Fx . i } Version: { VERSION } { Fx . ui } { Fx . ub } { Term . bg } { Term . fg } ' )
x = Term . width / / 2 - 36
h , w = Term . height - 2 - y , 72
if len ( help_items ) > h :
pages = ceil ( len ( help_items ) / h )
else :
h = len ( help_items )
pages = 0
page = 1
out_misc + = create_box ( x , y , w , h + 3 , " help " , line_color = THEME . div_line )
redraw = True
cls . resized = False
if redraw :
out = " "
cy = 0
if pages :
out + = ( f ' { Mv . to ( y , x + 56 ) } { THEME . div_line ( Symbol . title_left ) } { Fx . b } { THEME . title ( " pg " ) } { Fx . ub } { THEME . main_fg ( Symbol . up ) } { Fx . b } { THEME . title } { page } / { pages } '
f ' pg { Fx . ub } { THEME . main_fg ( Symbol . down ) } { THEME . div_line ( Symbol . title_right ) } ' )
out + = f ' { Mv . to ( y + 1 , x + 1 ) } { THEME . title } { Fx . b } { " Keys: " : ^20 } Description: { THEME . main_fg } '
for n , ( keys , desc ) in enumerate ( help_items . items ( ) ) :
if pages and n < ( page - 1 ) * h : continue
out + = f ' { Mv . to ( y + 2 + cy , x + 1 ) } { Fx . b } { ( " " if keys . startswith ( " _ " ) else keys ) : ^20.20 } { Fx . ub } { desc : 50.50 } '
cy + = 1
if cy == h : break
if cy < h :
for i in range ( h - cy ) :
out + = f ' { Mv . to ( y + 2 + cy + i , x + 1 ) } { " " * ( w - 2 ) } '
if skip and redraw :
Draw . now ( out )
elif not skip :
Draw . now ( f ' { cls . background } { out_misc } { out } ' )
skip = redraw = False
if Key . input_wait ( Timer . left ( ) ) :
key = Key . get ( )
if key == " mouse_click " :
mx , my = Key . get_mouse ( )
if mx > = x and mx < x + w and my > = y and my < y + h + 3 :
if pages and my == y and mx > x + 56 and mx < x + 61 :
key = " up "
elif pages and my == y and mx > x + 63 and mx < x + 68 :
key = " down "
else :
key = " escape "
if key == " q " :
clean_quit ( )
2020-08-04 19:52:21 +00:00
elif key in [ " escape " , " M " , " enter " , " backspace " , " h " , " f1 " ] :
2020-08-02 18:10:42 +00:00
cls . close = True
break
elif key in [ " up " , " mouse_scroll_up " , " page_up " ] and pages :
page - = 1
if page < 1 : page = pages
redraw = True
elif key in [ " down " , " mouse_scroll_down " , " page_down " ] and pages :
page + = 1
if page > pages : page = 1
redraw = True
if Timer . not_zero ( ) and not cls . resized :
skip = True
else :
Collector . collect ( )
Collector . collect_done . wait ( 1 )
if CONFIG . background_update : cls . background = f ' { THEME . inactive_fg } ' + Fx . uncolor ( f ' { Draw . saved_buffer ( ) } ' ) + f ' { Term . fg } '
Timer . stamp ( )
if main_active :
cls . close = False
return
Draw . now ( f ' { Draw . saved_buffer ( ) } ' )
cls . background = " "
2020-07-24 01:44:11 +00:00
cls . active = False
cls . close = False
2020-08-02 18:10:42 +00:00
@classmethod
def options ( cls ) :
out : str = " "
out_misc : str = " "
redraw : bool = True
key : str = " "
skip : bool = False
main_active : bool = True if cls . active else False
cls . active = True
cls . resized = True
d_quote : str
inputting : bool = False
input_val : str = " "
Theme . refresh ( )
if not cls . background :
cls . background = f ' { THEME . inactive_fg } ' + Fx . uncolor ( f ' { Draw . saved_buffer ( ) } ' ) + f ' { Term . fg } '
option_items : Dict [ str , List [ str ] ] = {
" color_theme " : [
' Set color theme. ' ,
' ' ,
' Choose from all theme files in ' ,
' " /usr/[local/]share/bpytop/themes " and ' ,
' " ~/.config/bpytop/themes " . ' ,
' ' ,
' " Default " for builtin default theme. ' ,
' User themes are prefixed by a plus sign " + " . ' ,
' ' ,
' For theme updates see: ' ,
' https://github.com/aristocratos/bpytop ' ] ,
2020-08-23 10:37:34 +00:00
" theme_background " : [
' If the theme set background should be shown. ' ,
' ' ,
' Set to False if you want terminal background ' ,
' transparency. ' ] ,
2020-08-02 18:10:42 +00:00
" mini_mode " : [
' Enable bpytop mini mode at start. ' ,
' ' ,
' Disables net and mem boxes and lowers height ' ,
' of cpu box. ' ] ,
" update_ms " : [
' Update time in milliseconds. ' ,
' ' ,
' Recommended 2000 ms or above for better sample ' ,
' times for graphs. ' ,
' ' ,
' Min value: 100 ms ' ,
' Max value: 86400000 ms = 24 hours. ' ] ,
" proc_sorting " : [
' Processes sorting option. ' ,
' ' ,
' Possible values: " pid " , " program " , " arguments " , ' ,
' " threads " , " user " , " memory " , " cpu lazy " and ' ,
' " cpu responsive " . ' ,
' ' ,
' " cpu lazy " updates top process over time, ' ,
' " cpu responsive " updates top process directly. ' ] ,
" proc_reversed " : [
' Reverse processes sorting order. ' ,
' ' ,
' True or False. ' ] ,
" proc_tree " : [
' Processes tree view. ' ,
' ' ,
' Set true to show processes grouped by parents, ' ,
' with lines drawn between parent and child ' ,
' process. ' ] ,
" proc_colors " : [
' Enable colors in process view. ' ,
' ' ,
' Uses the cpu graph gradient colors. ' ] ,
" proc_gradient " : [
' Enable process view gradient fade. ' ,
' ' ,
' Fades from top or current selection. ' ,
' Max fade value is equal to current themes ' ,
' " inactive_fg " color value. ' ] ,
" proc_per_core " : [
' Process usage per core. ' ,
' ' ,
' If process cpu usage should be of the core ' ,
' it \' s running on or usage of the total ' ,
' available cpu power. ' ,
' ' ,
' If true and process is multithreaded ' ,
' cpu usage can reach over 100 % . ' ] ,
2020-08-09 12:45:03 +00:00
" proc_mem_bytes " : [
' Show memory as bytes in process list. ' ,
' ' ,
' True or False. '
] ,
2020-08-02 18:10:42 +00:00
" check_temp " : [
' Enable cpu temperature reporting. ' ,
' ' ,
' True or False. ' ] ,
" draw_clock " : [
' Draw a clock at top of screen. ' ,
' ' ,
' Formatting according to strftime, empty ' ,
' string to disable. ' ,
' ' ,
' Examples: ' ,
' " %X " locale HH:MM:SS ' ,
' " % H " 24h hour, " % I " 12h hour ' ,
' " % M " minute, " % S " second ' ,
' " %d " day, " % m " month, " % y " year ' ] ,
" background_update " : [
' Update main ui when menus are showing. ' ,
' ' ,
' True or False. ' ,
' ' ,
' Set this to false if the menus is flickering ' ,
' too much for a comfortable experience. ' ] ,
" custom_cpu_name " : [
' Custom cpu model name in cpu percentage box. ' ,
' ' ,
' Empty string to disable. ' ] ,
" disks_filter " : [
' Optional filter for shown disks. ' ,
' ' ,
' Should be last folder in path of a mountpoint, ' ,
' " root " replaces " / " , separate multiple values ' ,
' with a comma. ' ,
' Begin line with " exclude= " to change to exclude ' ,
' filter. ' ,
' Oterwise defaults to " most include " filter. ' ,
' ' ,
' Example: disks_filter= " exclude=boot, home " ' ] ,
" mem_graphs " : [
' Show graphs for memory values. ' ,
' ' ,
' True or False. ' ] ,
" show_swap " : [
' If swap memory should be shown in memory box. ' ,
' ' ,
' True or False. ' ] ,
" swap_disk " : [
' Show swap as a disk. ' ,
' ' ,
' Ignores show_swap value above. ' ,
' Inserts itself after first disk. ' ] ,
" show_disks " : [
' Split memory box to also show disks. ' ,
' ' ,
' True or False. ' ] ,
2020-08-15 15:39:45 +00:00
" net_download " : [
' Fixed network graph download value. ' ,
2020-08-09 14:20:00 +00:00
' ' ,
2020-08-15 15:39:45 +00:00
' Default " 10M " = 10 MibiBytes. ' ,
2020-08-09 14:20:00 +00:00
' Possible units: ' ,
2020-08-15 15:39:45 +00:00
' " K " (KiB), " M " (MiB), " G " (GiB). ' ,
2020-08-09 14:20:00 +00:00
' ' ,
2020-08-15 15:39:45 +00:00
' Append " bit " for bits instead of bytes, ' ,
' i.e " 100Mbit " ' ,
2020-08-09 14:20:00 +00:00
' ' ,
2020-08-15 15:39:45 +00:00
' Can be toggled with auto button. ' ] ,
" net_upload " : [
' Fixed network graph upload value. ' ,
' ' ,
' Default " 10M " = 10 MibiBytes. ' ,
2020-08-09 14:20:00 +00:00
' Possible units: ' ,
2020-08-15 15:39:45 +00:00
' " K " (KiB), " M " (MiB), " G " (GiB). ' ,
' ' ,
' Append " bit " for bits instead of bytes, ' ,
' i.e " 100Mbit " ' ,
2020-08-09 14:20:00 +00:00
' ' ,
2020-08-15 15:39:45 +00:00
' Can be toggled with auto button. ' ] ,
" net_auto " : [
2020-08-09 14:20:00 +00:00
' Start in network graphs auto rescaling mode. ' ,
' ' ,
' Ignores any values set above at start and ' ,
2020-08-15 16:07:44 +00:00
' rescales down to 10KibiBytes at the lowest. ' ,
2020-08-09 14:20:00 +00:00
' ' ,
' True or False. ' ] ,
2020-08-15 15:39:45 +00:00
" net_color_fixed " : [
' Set network graphs color gradient to fixed. ' ,
' ' ,
' If True the network graphs color is based ' ,
' on the total bandwidth usage instead of ' ,
' the current autoscaling. ' ,
' ' ,
' The bandwidth usage is based on the ' ,
' " net_download " and " net_upload " values set ' ,
' above. ' ] ,
2020-08-02 18:10:42 +00:00
" show_init " : [
' Show init screen at startup. ' ,
' ' ,
' The init screen is purely cosmetical and ' ,
' slows down start to show status messages. ' ] ,
" update_check " : [
' Check for updates at start. ' ,
' ' ,
' Checks for latest version from: ' ,
' https://github.com/aristocratos/bpytop ' ] ,
" log_level " : [
' Set loglevel for error.log ' ,
' ' ,
' Levels are: " ERROR " " WARNING " " INFO " " DEBUG " . ' ,
' The level set includes all lower levels, ' ,
' i.e. " DEBUG " will show all logging info. ' ]
}
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 )
color_i : int
while not cls . close :
key = " "
if cls . resized :
y = 9 if Term . height < option_len + 10 else Term . height / / 2 - option_len / / 2 + 4
out_misc = ( f ' { Banner . draw ( y - 7 , center = True ) } { Mv . d ( 1 ) } { Mv . l ( 46 ) } { Colors . black_bg } { Colors . default } { Fx . b } ← esc '
f ' { Mv . r ( 30 ) } { Fx . i } Version: { VERSION } { Fx . ui } { Fx . ub } { Term . bg } { Term . fg } ' )
x = Term . width / / 2 - 38
x2 = x + 27
h , w , w2 = Term . height - 2 - y , 26 , 50
h - = h % 2
color_i = list ( Theme . themes ) . index ( THEME . current )
if option_len > h :
pages = ceil ( option_len / h )
else :
h = option_len
pages = 0
page = 1
selected_int = 0
out_misc + = create_box ( x , y , w , h + 2 , " options " , line_color = THEME . div_line )
redraw = True
cls . resized = False
2020-07-16 00:51:55 +00:00
2020-08-02 18:10:42 +00:00
if redraw :
out = " "
cy = 0
selected = list ( option_items ) [ selected_int ]
if pages :
out + = ( f ' { Mv . to ( y + h + 1 , x + 11 ) } { THEME . div_line ( Symbol . title_left ) } { Fx . b } { THEME . title ( " pg " ) } { Fx . ub } { THEME . main_fg ( Symbol . up ) } { Fx . b } { THEME . title } { page } / { pages } '
f ' pg { Fx . ub } { THEME . main_fg ( Symbol . down ) } { THEME . div_line ( Symbol . title_right ) } ' )
#out += f'{Mv.to(y+1, x+1)}{THEME.title}{Fx.b}{"Keys:":^20}Description:{THEME.main_fg}'
for n , opt in enumerate ( option_items ) :
if pages and n < ( page - 1 ) * ceil ( h / 2 ) : continue
value = getattr ( CONFIG , opt )
t_color = f ' { THEME . selected_bg } { THEME . selected_fg } ' if opt == selected else f ' { THEME . title } '
v_color = " " if opt == selected else f ' { THEME . title } '
d_quote = ' " ' if isinstance ( value , str ) else " "
if opt == " color_theme " :
counter = f ' { color_i + 1 } / { len ( Theme . themes ) } '
elif opt == " proc_sorting " :
counter = f ' { sorting_i + 1 } / { len ( CONFIG . sorting_options ) } '
elif opt == " log_level " :
counter = f ' { loglevel_i + 1 } / { len ( CONFIG . log_levels ) } '
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 " ] :
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 } '
else :
out + = ( ( f ' { t_color } { Symbol . left } { v_color } ' if type ( value ) is int else " " ) +
f ' { str ( value ) + " " + Symbol . enter : ^20.20 } ' + ( f ' { t_color } { Symbol . right } ' if type ( value ) is int else " " ) )
else :
out + = f ' { d_quote + str ( value ) + d_quote : ^24.24 } '
out + = f ' { Term . bg } '
if opt == selected :
h2 = len ( option_items [ opt ] ) + 2
y2 = y + ( selected_int * 2 ) - ( ( page - 1 ) * h )
if y2 + h2 > Term . height : y2 = Term . height - h2
out + = f ' { create_box ( x2 , y2 , w2 , h2 , " description " , line_color = THEME . div_line ) } { THEME . main_fg } '
for n , desc in enumerate ( option_items [ opt ] ) :
out + = f ' { Mv . to ( y2 + 1 + n , x2 + 2 ) } { desc : .48 } '
cy + = 2
if cy > = h : break
if cy < h :
for i in range ( h - cy ) :
out + = f ' { Mv . to ( y + 1 + cy + i , x + 1 ) } { " " * ( w - 2 ) } '
if not skip or redraw :
Draw . now ( f ' { cls . background } { out_misc } { out } ' )
skip = redraw = False
if Key . input_wait ( Timer . left ( ) ) :
key = Key . get ( )
redraw = True
has_sel = False
if key == " mouse_click " and not inputting :
mx , my = Key . get_mouse ( )
if mx > x and mx < x + w and my > y and my < y + h + 2 :
mouse_sel = ceil ( ( my - y ) / 2 ) - 1 + ceil ( ( page - 1 ) * ( h / 2 ) )
if pages and my == y + h + 1 and mx > x + 11 and mx < x + 16 :
key = " page_up "
elif pages and my == y + h + 1 and mx > x + 19 and mx < x + 24 :
key = " page_down "
elif my == y + h + 1 :
pass
elif mouse_sel == selected_int :
if mx < x + 6 :
key = " left "
elif mx > x + 19 :
key = " right "
else :
key = " enter "
elif mouse_sel < len ( option_items ) :
selected_int = mouse_sel
has_sel = True
else :
key = " escape "
if inputting :
if key in [ " escape " , " mouse_click " ] :
inputting = False
elif key == " enter " :
inputting = False
if str ( getattr ( CONFIG , selected ) ) != input_val :
if selected == " update_ms " :
if not input_val or int ( input_val ) < 100 :
CONFIG . update_ms = 100
elif int ( input_val ) > 86399900 :
CONFIG . update_ms = 86399900
else :
CONFIG . update_ms = int ( input_val )
elif isinstance ( getattr ( CONFIG , selected ) , str ) :
setattr ( CONFIG , selected , input_val )
2020-08-09 14:20:00 +00:00
if selected . startswith ( " net_ " ) :
NetCollector . net_min = { " download " : - 1 , " upload " : - 1 }
2020-08-22 18:20:58 +00:00
elif selected == " draw_clock " :
Box . clock_on = True if len ( CONFIG . draw_clock ) > 0 else False
if not Box . clock_on : Draw . clear ( " clock " , saved = True )
2020-08-02 18:10:42 +00:00
Term . refresh ( force = True )
cls . resized = False
elif key == " backspace " and len ( input_val ) > 0 :
input_val = input_val [ : - 1 ]
elif key == " delete " :
input_val = " "
elif isinstance ( getattr ( CONFIG , selected ) , str ) and len ( key ) == 1 :
input_val + = key
elif isinstance ( getattr ( CONFIG , selected ) , int ) and key . isdigit ( ) :
input_val + = key
elif key == " q " :
clean_quit ( )
2020-08-11 15:09:05 +00:00
elif key in [ " escape " , " o " , " M " , " f2 " ] :
2020-08-02 18:10:42 +00:00
cls . close = True
break
2020-08-15 15:39:45 +00:00
elif key == " enter " and selected in [ " update_ms " , " disks_filter " , " custom_cpu_name " , " net_download " , " net_upload " , " draw_clock " ] :
2020-08-02 18:10:42 +00:00
inputting = True
input_val = str ( getattr ( CONFIG , selected ) )
elif key == " left " and selected == " update_ms " and CONFIG . update_ms - 100 > = 100 :
CONFIG . update_ms - = 100
Box . draw_update_ms ( )
elif key == " right " and selected == " update_ms " and CONFIG . update_ms + 100 < = 86399900 :
CONFIG . update_ms + = 100
Box . draw_update_ms ( )
elif key in [ " left " , " right " ] and isinstance ( getattr ( CONFIG , selected ) , bool ) :
setattr ( CONFIG , selected , not getattr ( CONFIG , selected ) )
if selected == " check_temp " :
if CONFIG . check_temp :
CpuCollector . get_sensors ( )
else :
CpuCollector . sensor_method = " "
CpuCollector . got_sensors = False
2020-08-15 15:39:45 +00:00
if selected in [ " net_auto " , " net_color_fixed " ] :
2020-08-15 16:07:44 +00:00
if selected == " net_auto " : NetCollector . auto_min = CONFIG . net_auto
2020-08-15 15:39:45 +00:00
NetBox . redraw = True
2020-08-23 10:37:34 +00:00
if selected == " theme_background " :
Term . bg = THEME . main_bg if CONFIG . theme_background else " \033 [49m "
Draw . now ( Term . bg )
2020-08-02 18:10:42 +00:00
Term . refresh ( force = True )
cls . resized = False
elif key in [ " left " , " right " ] and selected == " color_theme " and len ( Theme . themes ) > 1 :
if key == " left " :
color_i - = 1
if color_i < 0 : color_i = len ( Theme . themes ) - 1
elif key == " right " :
color_i + = 1
if color_i > len ( Theme . themes ) - 1 : color_i = 0
CONFIG . color_theme = list ( Theme . themes ) [ color_i ]
THEME ( CONFIG . color_theme )
Term . refresh ( force = True )
Timer . finish ( )
elif key in [ " left " , " right " ] and selected == " proc_sorting " :
ProcCollector . sorting ( key )
elif key in [ " left " , " right " ] and selected == " log_level " :
if key == " left " :
loglevel_i - = 1
if loglevel_i < 0 : loglevel_i = len ( CONFIG . log_levels ) - 1
elif key == " right " :
loglevel_i + = 1
if loglevel_i > len ( CONFIG . log_levels ) - 1 : loglevel_i = 0
CONFIG . log_level = CONFIG . log_levels [ loglevel_i ]
errlog . setLevel ( getattr ( logging , CONFIG . log_level ) )
errlog . info ( f ' Loglevel set to { CONFIG . log_level } ' )
elif key == " up " :
selected_int - = 1
if selected_int < 0 : selected_int = len ( option_items ) - 1
page = floor ( selected_int * 2 / h ) + 1
elif key == " down " :
selected_int + = 1
if selected_int > len ( option_items ) - 1 : selected_int = 0
page = floor ( selected_int * 2 / h ) + 1
elif key in [ " mouse_scroll_up " , " page_up " ] and pages :
page - = 1
if page < 1 : page = pages
selected_int = ( page - 1 ) * ceil ( h / 2 )
elif key in [ " mouse_scroll_down " , " page_down " ] and pages :
page + = 1
if page > pages : page = 1
selected_int = ( page - 1 ) * ceil ( h / 2 )
elif has_sel :
pass
else :
redraw = False
if Timer . not_zero ( ) and not cls . resized :
skip = True
else :
Collector . collect ( )
Collector . collect_done . wait ( 1 )
if CONFIG . background_update : cls . background = f ' { THEME . inactive_fg } ' + Fx . uncolor ( f ' { Draw . saved_buffer ( ) } ' ) + f ' { Term . fg } '
Timer . stamp ( )
if main_active :
cls . close = False
return
Draw . now ( f ' { Draw . saved_buffer ( ) } ' )
cls . background = " "
cls . active = False
cls . close = False
2020-07-11 22:54:52 +00:00
2020-07-20 22:52:34 +00:00
class Timer :
timestamp : float
2020-07-24 01:44:11 +00:00
return_zero = False
2020-07-20 22:52:34 +00:00
@classmethod
def stamp ( cls ) :
cls . timestamp = time ( )
@classmethod
def not_zero ( cls ) - > bool :
2020-07-24 01:44:11 +00:00
if cls . return_zero :
cls . return_zero = False
return False
2020-07-20 22:52:34 +00:00
if cls . timestamp + ( CONFIG . update_ms / 1000 ) > time ( ) :
return True
else :
return False
@classmethod
def left ( cls ) - > float :
return cls . timestamp + ( CONFIG . update_ms / 1000 ) - time ( )
@classmethod
def finish ( cls ) :
2020-07-24 01:44:11 +00:00
cls . return_zero = True
2020-07-20 22:52:34 +00:00
cls . timestamp = time ( ) - ( CONFIG . update_ms / 1000 )
2020-07-24 01:44:11 +00:00
Key . break_wait ( )
2020-07-11 22:54:52 +00:00
2020-08-09 15:43:36 +00:00
class UpdateChecker :
version : str = VERSION
thread : threading . Thread
2020-07-09 00:56:03 +00:00
2020-08-09 15:43:36 +00:00
@classmethod
def run ( cls ) :
cls . thread = threading . Thread ( target = cls . _checker )
cls . thread . start ( )
@classmethod
def _checker ( cls ) :
try :
with urllib . request . urlopen ( " https://github.com/aristocratos/bpytop/raw/master/bpytop.py " , timeout = 5 ) as source : # type: ignore
for line in source :
line = line . decode ( " utf-8 " )
if line . startswith ( " VERSION: str = " ) :
cls . version = line [ ( line . index ( " = " ) + 1 ) : ] . strip ( ' " \n ' )
break
except Exception as e :
errlog . exception ( f ' { e } ' )
else :
if cls . version != VERSION and which ( " notify-send " ) :
try :
subprocess . run ( [ " notify-send " , " -u " , " normal " , " BpyTop Update! " ,
f ' New version of BpyTop available! \n Current version: { VERSION } \n New version: { cls . version } \n Download at github.com/aristocratos/bpytop ' ,
" -i " , " update-notifier " , " -t " , " 10000 " ] , stdout = subprocess . DEVNULL , stderr = subprocess . DEVNULL )
except Exception as e :
errlog . exception ( f ' { e } ' )
2020-07-09 00:56:03 +00:00
2020-08-22 12:02:11 +00:00
class Init :
running : bool = True
initbg_colors : List [ str ] = [ ]
initbg_data : List [ int ]
initbg_up : Graph
initbg_down : Graph
resized = False
2020-08-22 18:20:58 +00:00
@classmethod
def start ( cls ) :
Draw . buffer ( " init " , z = 1 )
Draw . buffer ( " initbg " , z = 10 )
for i in range ( 51 ) :
for _ in range ( 2 ) : cls . initbg_colors . append ( Color . fg ( i , i , i ) )
Draw . buffer ( " banner " , ( f ' { Banner . draw ( Term . height / / 2 - 10 , center = True ) } { Mv . d ( 1 ) } { Mv . l ( 11 ) } { Colors . black_bg } { Colors . default } '
f ' { Fx . b } { Fx . i } Version: { VERSION } { Fx . ui } { Fx . ub } { Term . bg } { Term . fg } { Color . fg ( " #50 " ) } ' ) , z = 2 )
for _i in range ( 7 ) :
perc = f ' { str ( round ( ( _i + 1 ) * 14 + 2 ) ) + " % " : >5 } '
Draw . buffer ( " +banner " , f ' { Mv . to ( Term . height / / 2 - 2 + _i , Term . width / / 2 - 28 ) } { Fx . trans ( perc ) } { Symbol . v_line } ' )
Draw . out ( " banner " )
Draw . buffer ( " +init! " , f ' { Color . fg ( " #cc " ) } { Fx . b } { Mv . to ( Term . height / / 2 - 2 , Term . width / / 2 - 21 ) } { Mv . save } ' )
cls . initbg_data = [ randint ( 0 , 100 ) for _ in range ( Term . width * 2 ) ]
cls . initbg_up = Graph ( Term . width , Term . height / / 2 , cls . initbg_colors , cls . initbg_data , invert = True )
cls . initbg_down = Graph ( Term . width , Term . height / / 2 , cls . initbg_colors , cls . initbg_data , invert = False )
@classmethod
def success ( cls ) :
if not CONFIG . show_init or cls . resized : return
cls . draw_bg ( 5 )
Draw . buffer ( " +init! " , f ' { Mv . restore } { Symbol . ok } \n { Mv . r ( Term . width / / 2 - 22 ) } { Mv . save } ' )
2020-08-22 12:02:11 +00:00
@staticmethod
def fail ( err ) :
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Symbol . fail } ' )
sleep ( 2 )
errlog . exception ( f ' { err } ' )
clean_quit ( 1 , errmsg = f ' Error during init! See { CONFIG_DIR } /error.log for more information. ' )
@classmethod
def draw_bg ( cls , times : int = 5 ) :
for _ in range ( times ) :
sleep ( 0.05 )
x = randint ( 0 , 100 )
Draw . buffer ( " initbg " , f ' { Fx . ub } { Mv . to ( 0 , 0 ) } { cls . initbg_up ( x ) } { Mv . to ( Term . height / / 2 , 0 ) } { cls . initbg_down ( x ) } ' )
Draw . out ( " initbg " , " banner " , " init " )
@classmethod
def done ( cls ) :
cls . running = False
if not CONFIG . show_init : return
if cls . resized :
Draw . now ( Term . clear )
else :
cls . draw_bg ( 10 )
Draw . clear ( " initbg " , " banner " , " init " , saved = True )
if cls . resized : return
del cls . initbg_up , cls . initbg_down , cls . initbg_data , cls . initbg_colors
2020-07-09 00:56:03 +00:00
#? Functions ------------------------------------------------------------------------------------->
def get_cpu_name ( ) - > str :
''' 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 "
2020-07-14 00:20:40 +00:00
try :
cmd_out = subprocess . check_output ( " LANG=C " + command , shell = True , universal_newlines = True )
except :
pass
2020-07-09 00:56:03 +00:00
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 ( " " )
2020-08-03 15:02:34 +00:00
if " Xeon " in name and " CPU " in name :
2020-07-09 00:56:03 +00:00
name = nlist [ nlist . index ( " CPU " ) + 1 ]
elif " Ryzen " in name :
name = " " . join ( nlist [ nlist . index ( " Ryzen " ) : nlist . index ( " Ryzen " ) + 3 ] )
2020-08-23 11:17:16 +00:00
elif " Duo " in name and " @ " in name :
name = " " . join ( nlist [ : nlist . index ( " @ " ) ] )
2020-08-03 15:02:34 +00:00
elif " CPU " in name and not nlist [ 0 ] == " CPU " :
2020-07-09 00:56:03 +00:00
name = nlist [ nlist . index ( " CPU " ) - 1 ]
2020-08-24 05:56:28 +00:00
name = " " . join ( name . split ( ) )
2020-08-23 11:17:16 +00:00
return name . replace ( " Processor " , " " ) . replace ( " CPU " , " " ) . replace ( " (R) " , " " ) . replace ( " (TM) " , " " ) . replace ( " Intel " , " " )
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
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 :
2020-07-09 00:56:03 +00:00
''' 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
2020-07-20 22:52:34 +00:00
if not title_color : title_color = THEME . title
2020-07-09 00:56:03 +00:00
#* Get values from box class if given
if box :
x = box . x
y = box . y
width = box . width
height = box . height
title = box . name
hlines : Tuple [ int , int ] = ( y , y + height - 1 )
out + = f ' { line_color } '
#* Draw all horizontal lines
for hpos in hlines :
out + = f ' { Mv . to ( hpos , x ) } { Symbol . h_line * ( width - 1 ) } '
2020-08-02 18:10:42 +00:00
#* Draw all vertical lines and fill if enabled
for hpos in range ( hlines [ 0 ] + 1 , hlines [ 1 ] ) :
out + = f ' { Mv . to ( hpos , x ) } { Symbol . v_line } { " " * ( width - 2 ) if fill else Mv . r ( width - 2 ) } { Symbol . v_line } '
2020-07-09 00:56:03 +00:00
#* Draw corners
out + = f ' { Mv . to ( y , x ) } { Symbol . left_up } \
{ Mv . to ( y , x + width - 1 ) } { Symbol . right_up } \
{ Mv . to ( y + height - 1 , x ) } { Symbol . left_down } \
{ Mv . to ( y + height - 1 , x + width - 1 ) } { Symbol . right_down } '
#* Draw titles if enabled
if title :
2020-07-20 22:52:34 +00:00
out + = f ' { Mv . to ( y , x + 2 ) } { Symbol . title_left } { title_color } { Fx . b } { title } { Fx . ub } { line_color } { Symbol . title_right } '
2020-07-09 00:56:03 +00:00
if title2 :
2020-08-02 18:10:42 +00:00
out + = f ' { Mv . to ( hlines [ 1 ] , x + 2 ) } { Symbol . title_left } { title_color } { Fx . b } { title2 } { Fx . ub } { line_color } { Symbol . title_right } '
2020-07-09 00:56:03 +00:00
return f ' { out } { Term . fg } { Mv . to ( y + 1 , x + 1 ) } '
def now_sleeping ( signum , frame ) :
""" Reset terminal settings and stop background input read before putting to sleep """
Key . stop ( )
Collector . stop ( )
2020-08-09 15:56:06 +00:00
Draw . now ( Term . clear , Term . normal_screen , Term . show_cursor , Term . mouse_off , Term . mouse_direct_off , Term . title ( ) )
2020-07-09 00:56:03 +00:00
Term . echo ( True )
os . kill ( os . getpid ( ) , signal . SIGSTOP )
def now_awake ( signum , frame ) :
""" Set terminal settings and restart background input read """
2020-08-09 15:56:06 +00:00
Draw . now ( Term . alt_screen , Term . clear , Term . hide_cursor , Term . mouse_on , Term . title ( " BpyTOP " ) )
2020-07-09 00:56:03 +00:00
Term . echo ( False )
Key . start ( )
2020-07-20 22:52:34 +00:00
Term . refresh ( )
Box . calc_sizes ( )
Box . draw_bg ( )
2020-07-09 00:56:03 +00:00
Collector . start ( )
2020-07-20 22:52:34 +00:00
#Draw.out()
2020-07-09 00:56:03 +00:00
def quit_sigint ( signum , frame ) :
""" SIGINT redirection to clean_quit() """
clean_quit ( )
2020-07-16 00:51:55 +00:00
def clean_quit ( errcode : int = 0 , errmsg : str = " " , thread : bool = False ) :
2020-07-09 00:56:03 +00:00
""" Stop background input read, save current config and reset terminal settings before quitting """
2020-07-16 00:51:55 +00:00
global THREAD_ERROR
if thread :
THREAD_ERROR = errcode
interrupt_main ( )
return
if THREAD_ERROR : errcode = THREAD_ERROR
2020-07-09 00:56:03 +00:00
Key . stop ( )
Collector . stop ( )
if not errcode : CONFIG . save_config ( )
2020-08-09 15:56:06 +00:00
Draw . now ( Term . clear , Term . normal_screen , Term . show_cursor , Term . mouse_off , Term . mouse_direct_off , Term . title ( ) )
2020-07-09 00:56:03 +00:00
Term . echo ( True )
if errcode == 0 :
errlog . info ( f ' Exiting. Runtime { timedelta ( seconds = round ( time ( ) - SELF_START , 0 ) ) } \n ' )
else :
errlog . warning ( f ' Exiting with errorcode ( { errcode } ). Runtime { timedelta ( seconds = round ( time ( ) - SELF_START , 0 ) ) } \n ' )
2020-07-18 01:16:01 +00:00
if not errmsg : errmsg = f ' Bpytop exited with errorcode ( { errcode } ). See { CONFIG_DIR } /error.log for more information! '
2020-07-09 00:56:03 +00:00
if errmsg : print ( errmsg )
2020-07-16 00:51:55 +00:00
2020-07-09 00:56:03 +00:00
raise SystemExit ( errcode )
def floating_humanizer ( value : Union [ float , int ] , bit : bool = False , per_second : bool = False , start : int = 0 , short : bool = False ) - > str :
''' Scales up in steps of 1024 to highest possible unit and returns string with unit suffixed
* bit = True or defaults to bytes
* start = int to set 1024 multiplier starting unit
* short = True always returns 0 decimals and shortens unit to 1 character
'''
out : str = " "
unit : Tuple [ str , . . . ] = UNITS [ " bit " ] if bit else UNITS [ " byte " ]
selector : int = start if start else 0
mult : int = 8 if bit else 1
if value < = 0 : value = 0
if isinstance ( value , float ) : value = round ( value * 100 * mult )
elif value > 0 : value * = 100 * mult
2020-07-27 01:13:13 +00:00
else : value = 0
2020-07-09 00:56:03 +00:00
while len ( f ' { value } ' ) > 5 and value > = 102400 :
value >> = 10
if value < 100 :
out = f ' { value } '
break
selector + = 1
else :
if len ( f ' { value } ' ) < 5 and len ( f ' { value } ' ) > = 2 and selector > 0 :
decimals = 5 - len ( f ' { value } ' )
out = f ' { value } ' [ : - 2 ] + " . " + f ' { value } ' [ - decimals : ]
elif len ( f ' { value } ' ) > = 2 :
out = f ' { value } ' [ : - 2 ]
2020-07-20 22:52:34 +00:00
else :
out = f ' { value } '
2020-07-09 00:56:03 +00:00
2020-08-09 12:45:03 +00:00
if short :
out = out . split ( " . " ) [ 0 ]
if len ( out ) > 3 :
out = f ' { int ( out [ 0 ] ) + 1 } '
selector + = 1
2020-07-09 00:56:03 +00:00
out + = f ' { " " if short else " " } { unit [ selector ] [ 0 ] if short else unit [ selector ] } '
2020-07-20 22:52:34 +00:00
if per_second : out + = " ps " if bit else " /s "
2020-07-09 00:56:03 +00:00
return out
2020-08-09 14:20:00 +00:00
def units_to_bytes ( value : str ) - > int :
if not value : return 0
out : int = 0
mult : int = 0
2020-08-15 15:39:45 +00:00
bit : bool = False
2020-08-09 14:20:00 +00:00
value_i : int = 0
2020-08-15 15:39:45 +00:00
units : Dict [ str , int ] = { " k " : 1 , " m " : 2 , " g " : 3 }
2020-08-09 14:20:00 +00:00
try :
2020-08-15 15:39:45 +00:00
if value . lower ( ) . endswith ( " s " ) :
value = value [ : - 1 ]
if value . lower ( ) . endswith ( " bit " ) :
bit = True
value = value [ : - 3 ]
elif value . lower ( ) . endswith ( " byte " ) :
value = value [ : - 4 ]
if value [ - 1 ] . lower ( ) in units :
mult = units [ value [ - 1 ] . lower ( ) ]
2020-08-09 14:20:00 +00:00
value = value [ : - 1 ]
if " . " in value and value . replace ( " . " , " " ) . isdigit ( ) :
if mult > 0 :
value_i = round ( float ( value ) * 1024 )
mult - = 1
else :
value_i = round ( float ( value ) )
elif value . isdigit ( ) :
value_i = int ( value )
2020-08-15 15:39:45 +00:00
if bit : value_i = round ( value_i / 8 )
out = int ( value_i ) << ( 10 * mult )
2020-08-09 14:20:00 +00:00
except ValueError :
out = 0
return out
2020-07-20 22:52:34 +00:00
def process_keys ( ) :
2020-07-27 01:13:13 +00:00
mouse_pos : Tuple [ int , int ] = ( 0 , 0 )
filtered : bool = False
2020-07-20 22:52:34 +00:00
while Key . has_key ( ) :
key = Key . get ( )
2020-07-27 01:13:13 +00:00
if key in [ " mouse_scroll_up " , " mouse_scroll_down " , " mouse_click " ] :
2020-07-24 01:44:11 +00:00
mouse_pos = Key . get_mouse ( )
2020-07-30 02:29:40 +00:00
if mouse_pos [ 0 ] > = ProcBox . x and mouse_pos [ 1 ] > = ProcBox . current_y + 1 and mouse_pos [ 1 ] < ProcBox . current_y + ProcBox . current_h - 1 :
2020-07-24 01:44:11 +00:00
pass
2020-07-27 01:13:13 +00:00
elif key == " mouse_click " :
key = " mouse_unselect "
else :
key = " _null "
if ProcBox . filtering :
if key in [ " enter " , " mouse_click " , " mouse_unselect " ] :
ProcBox . filtering = False
Collector . collect ( ProcCollector , redraw = True , only_draw = True )
continue
elif key in [ " escape " , " delete " ] :
ProcCollector . search_filter = " "
ProcBox . filtering = False
elif len ( key ) == 1 :
ProcCollector . search_filter + = key
elif key == " backspace " and len ( ProcCollector . search_filter ) > 0 :
ProcCollector . search_filter = ProcCollector . search_filter [ : - 1 ]
else :
continue
Collector . collect ( ProcCollector , proc_interrupt = True , redraw = True )
if filtered : Collector . collect_done . wait ( 0.1 )
filtered = True
continue
2020-07-24 01:44:11 +00:00
if key == " _null " :
continue
elif key == " q " :
2020-07-20 22:52:34 +00:00
clean_quit ( )
2020-07-24 01:44:11 +00:00
elif key == " + " and CONFIG . update_ms + 100 < = 86399900 :
2020-07-20 22:52:34 +00:00
CONFIG . update_ms + = 100
Box . draw_update_ms ( )
2020-07-24 01:44:11 +00:00
elif key == " - " and CONFIG . update_ms - 100 > = 100 :
2020-07-20 22:52:34 +00:00
CONFIG . update_ms - = 100
Box . draw_update_ms ( )
2020-07-24 01:44:11 +00:00
elif key in [ " b " , " n " ] :
2020-07-20 22:52:34 +00:00
NetCollector . switch ( key )
2020-08-04 19:35:49 +00:00
elif key in [ " M " , " escape " ] :
2020-07-24 01:44:11 +00:00
Menu . main ( )
2020-08-02 18:10:42 +00:00
elif key in [ " o " , " f2 " ] :
Menu . options ( )
elif key in [ " h " , " f1 " ] :
Menu . help ( )
2020-07-24 01:44:11 +00:00
elif key == " z " :
NetCollector . reset = not NetCollector . reset
2020-08-09 14:20:00 +00:00
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 )
2020-07-24 01:44:11 +00:00
elif key in [ " left " , " right " ] :
ProcCollector . sorting ( key )
2020-08-15 22:53:51 +00:00
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 )
2020-08-04 19:35:49 +00:00
elif key == " e " :
2020-07-24 01:44:11 +00:00
CONFIG . proc_tree = not CONFIG . proc_tree
Collector . collect ( ProcCollector , interrupt = True , redraw = True )
2020-08-04 19:35:49 +00:00
elif key == " r " :
2020-07-24 01:44:11 +00:00
CONFIG . proc_reversed = not CONFIG . proc_reversed
Collector . collect ( ProcCollector , interrupt = True , redraw = True )
2020-08-04 19:35:49 +00:00
# elif key == "C":
# CONFIG.proc_colors = not CONFIG.proc_colors
# Collector.collect(ProcCollector, redraw=True, only_draw=True)
# elif key == "G":
# CONFIG.proc_gradient = not CONFIG.proc_gradient
# Collector.collect(ProcCollector, redraw=True, only_draw=True)
elif key == " c " :
2020-07-24 01:44:11 +00:00
CONFIG . proc_per_core = not CONFIG . proc_per_core
Collector . collect ( ProcCollector , interrupt = True , redraw = True )
2020-08-02 18:10:42 +00:00
elif key == " g " :
2020-07-24 01:44:11 +00:00
CONFIG . mem_graphs = not CONFIG . mem_graphs
2020-07-27 01:13:13 +00:00
Collector . collect ( MemCollector , interrupt = True , redraw = True )
2020-08-02 18:10:42 +00:00
elif key == " s " :
2020-07-24 01:44:11 +00:00
CONFIG . swap_disk = not CONFIG . swap_disk
2020-07-27 01:13:13 +00:00
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 )
2020-08-04 19:35:49 +00:00
elif key == " m " :
2020-07-30 02:29:40 +00:00
Box . mini_mode = not Box . mini_mode
Draw . clear ( saved = True )
Term . refresh ( force = True )
2020-08-04 19:52:21 +00:00
elif key . lower ( ) in [ " t " , " k " , " i " ] and ( ProcBox . selected > 0 or ProcCollector . detailed ) :
2020-08-02 18:10:42 +00:00
pid : int = ProcBox . selected_pid if ProcBox . selected > 0 else ProcCollector . detailed_pid # type: ignore
if psutil . pid_exists ( pid ) :
if key == " t " : sig = signal . SIGTERM
elif key == " k " : sig = signal . SIGKILL
elif key == " 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 } ' )
2020-07-27 01:13:13 +00:00
elif key == " delete " and ProcCollector . search_filter :
ProcCollector . search_filter = " "
Collector . collect ( ProcCollector , proc_interrupt = True , redraw = True )
2020-07-30 02:29:40 +00:00
elif key == " enter " :
if ProcBox . selected > 0 and ProcCollector . detailed_pid != ProcBox . selected_pid and psutil . pid_exists ( ProcBox . selected_pid ) :
2020-07-27 01:13:13 +00:00
ProcCollector . detailed = True
2020-08-05 19:56:13 +00:00
ProcBox . last_selection = ProcBox . selected
2020-07-30 02:29:40 +00:00
ProcBox . selected = 0
ProcCollector . detailed_pid = ProcBox . selected_pid
ProcBox . resized = True
elif ProcCollector . detailed :
2020-08-06 16:06:43 +00:00
ProcBox . selected = ProcBox . last_selection
2020-08-05 19:56:13 +00:00
ProcBox . last_selection = 0
2020-07-30 02:29:40 +00:00
ProcCollector . detailed = False
ProcCollector . detailed_pid = None
ProcBox . resized = True
2020-07-27 01:13:13 +00:00
else :
continue
ProcCollector . details = { }
ProcCollector . details_cpu = [ ]
2020-07-30 02:29:40 +00:00
ProcCollector . details_mem = [ ]
Graphs . detailed_cpu = NotImplemented
Graphs . detailed_mem = NotImplemented
2020-07-27 01:13:13 +00:00
Collector . collect ( ProcCollector , proc_interrupt = True , redraw = True )
2020-07-09 00:56:03 +00:00
2020-07-27 01:13:13 +00:00
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 )
2020-07-24 01:44:11 +00:00
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
#? Pre main -------------------------------------------------------------------------------------->
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
CPU_NAME : str = get_cpu_name ( )
2020-07-09 00:56:03 +00:00
2020-08-22 12:09:14 +00:00
THEME : Theme
2020-07-09 00:56:03 +00:00
2020-08-22 11:49:01 +00:00
def main ( ) :
2020-08-22 12:09:14 +00:00
global THEME
2020-07-09 00:56:03 +00:00
2020-08-23 12:59:38 +00:00
Term . width = os . get_terminal_size ( ) . columns
Term . height = os . get_terminal_size ( ) . lines
2020-07-09 00:56:03 +00:00
#? Init -------------------------------------------------------------------------------------->
2020-07-20 22:52:34 +00:00
if DEBUG : TimeIt . start ( " Init " )
2020-07-11 22:54:52 +00:00
2020-07-27 01:13:13 +00:00
#? Switch to alternate screen, clear screen, hide cursor, enable mouse reporting and disable input echo
2020-08-09 15:56:06 +00:00
Draw . now ( Term . alt_screen , Term . clear , Term . hide_cursor , Term . mouse_on , Term . title ( " BpyTOP " ) )
2020-07-09 00:56:03 +00:00
Term . echo ( False )
2020-08-12 14:57:34 +00:00
Term . refresh ( force = True )
2020-08-22 18:20:58 +00:00
#? Start a thread checking for updates while running init
2020-08-09 15:43:36 +00:00
if CONFIG . update_check : UpdateChecker . run ( )
2020-07-09 00:56:03 +00:00
#? Draw banner and init status
2020-08-22 18:20:58 +00:00
if CONFIG . show_init and not Init . resized :
Init . start ( )
2020-07-09 00:56:03 +00:00
#? Load theme
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Loading theme and creating colors... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
try :
2020-08-22 12:09:14 +00:00
THEME = Theme ( CONFIG . color_theme )
2020-07-09 00:56:03 +00:00
except Exception as e :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
#? Setup boxes
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Doing some maths and drawing... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
try :
2020-08-02 18:10:42 +00:00
if CONFIG . check_temp : CpuCollector . get_sensors ( )
2020-07-09 00:56:03 +00:00
Box . calc_sizes ( )
Box . draw_bg ( now = False )
except Exception as e :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
#? Setup signal handlers for SIGSTP, SIGCONT, SIGINT and SIGWINCH
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Setting up signal handlers... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
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 :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
#? Start a separate thread for reading keyboard input
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Starting input reader thread... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
try :
Key . start ( )
except Exception as e :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
#? Start a separate thread for data collection and drawing
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Starting data collection and drawer thread... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
try :
Collector . start ( )
except Exception as e :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
#? Collect data and draw to buffer
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Collecting data and drawing... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
try :
2020-07-20 22:52:34 +00:00
Collector . collect ( draw_now = False )
2020-07-09 00:56:03 +00:00
pass
except Exception as e :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
#? Draw to screen
2020-07-20 22:52:34 +00:00
if CONFIG . show_init :
Draw . buffer ( " +init! " , f ' { Mv . restore } { Fx . trans ( " Finishing up... " ) } { Mv . save } ' )
2020-07-09 00:56:03 +00:00
try :
2020-07-20 22:52:34 +00:00
Collector . collect_done . wait ( )
2020-07-09 00:56:03 +00:00
except Exception as e :
2020-07-11 22:54:52 +00:00
Init . fail ( e )
2020-07-09 00:56:03 +00:00
else :
2020-07-11 22:54:52 +00:00
Init . success ( )
2020-07-09 00:56:03 +00:00
2020-07-20 22:52:34 +00:00
Init . done ( )
Term . refresh ( )
Draw . out ( clear = True )
2020-08-02 18:10:42 +00:00
if CONFIG . draw_clock :
Box . clock_on = True
2020-07-20 22:52:34 +00:00
if DEBUG : TimeIt . stop ( " Init " )
2020-07-09 00:56:03 +00:00
2020-07-27 01:13:13 +00:00
#? Main loop ------------------------------------------------------------------------------------->
2020-08-22 11:45:21 +00:00
def run ( ) :
2020-07-27 01:13:13 +00:00
while not False :
Term . refresh ( )
Timer . stamp ( )
while Timer . not_zero ( ) :
2020-08-02 18:10:42 +00:00
if Key . input_wait ( Timer . left ( ) ) :
2020-07-27 01:13:13 +00:00
process_keys ( )
Collector . collect ( )
2020-07-09 00:56:03 +00:00
#? Start main loop
2020-07-27 01:13:13 +00:00
try :
2020-08-22 11:45:21 +00:00
run ( )
2020-07-27 01:13:13 +00:00
except Exception as e :
errlog . exception ( f ' { e } ' )
clean_quit ( 1 )
2020-07-09 00:56:03 +00:00
else :
#? Quit cleanly even if false starts being true...
clean_quit ( )
2020-08-22 11:49:01 +00:00
if __name__ == " __main__ " :
main ( )