From 65ec0b682f6dc73205b26cc2c7d0cfc934b95766 Mon Sep 17 00:00:00 2001 From: aristocratos Date: Sat, 28 Mar 2020 11:04:34 +0100 Subject: [PATCH] repo init --- README.md | 191 ++++ bashtop | 3208 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3399 insertions(+) create mode 100644 README.md create mode 100755 bashtop diff --git a/README.md b/README.md new file mode 100644 index 0000000..12083f9 --- /dev/null +++ b/README.md @@ -0,0 +1,191 @@ +# bashtop + +**Version:** 0.6.2 +**Usage:** Tmp +**Language:** Bash & Python + +## Description + +Internet speeds are tested against random servers from speedtest.net at an timed interval (defined by user). +If slow speed (defined by user) is detected, then runs a number of download and upload test with optional route tests to servers and writes to a logfile. + +## Screenshots + +Main UI with graph on. + +![Screenshot 1]() + +## Features + +* Function for checking current bandwidth usage to block false positives. Can optionally collect bandwidth usage from a router running linux with a ssh server enabled. +* Scrollable graph showing speeds over time. +* All output in the UI is also scrollabe with UP, DOWN, PAGE_UP, PAGE_DOWN, HOME, END keys working as you would expect. +* Option to have the timer reset on any mouse or keyboard activity in XServer. +* Option to have the timer auto pause when monitor is on and unpause when monitor is off. +* Unit used can be switched between Mbits and MB/s. +* Function for splitting log files at a defined size, old log files can also be automatically compressed. + +## Configurability + +Config files stored in "$HOME/.config/spdtest" folder + +#### spdtest.cfg: (auto generated if not found) +```bash +net_device="auto" #* Network interface to get current speed from, set to "auto" to get default interface from "ip route" command +unit="megabit" #* Default speed value to use, valid values are "megabit" and "megabyte" +slowspeed="30" #* Download speed in unit defined above that triggers more tests, recommended set to 10%-40% of your max speed +numservers="30" #* How many of the closest servers to get from speedtest.net, used as random pool of servers to test against +slowretry="1" #* When speed is below slowspeed, how many retries of random servers before running full tests +max_err_retry="3" #* Max servers to test if an error is encountered in slowcheck +numslowservers="8" #* How many of the closest servers from list to test if slow speed has been detected, tests all if not set +precheck="true" #* Check current bandwidth usage before slowcheck, blocks if speed is higher then values set below +precheck_samplet="5" #* Time in seconds to sample bandwidth usage, defaults to 5 if not set +precheck_down="50" #* Download speed in unit defined above that blocks slowcheck +precheck_up="50" #* Upload speed in unit defined above that blocks slowcheck +precheck_ssh_host="192.168.1.1" #* If set, precheck will fetch data from /proc/net/dev over SSH, for example from a router running linux + #* remote machine needs to have: "/proc/net/dev" and be able to run commands "ip route" and "grep" + #* copy SSH keys to remote machine if you don't want to be asked for password at start, guide: https://www.ssh.com/ssh/copy-id +precheck_ssh_user="admin" #* Username for ssh connection +precheck_ssh_nd="auto" #* Network interface on remote machine to get speeds from, set to "auto" if unsure +waittime="00:20:00" #* Default wait timer between slowchecks, format: "HH:MM:SS" +slowwait="00:10:00" #* Time between tests when slow speed has been detected, uses wait timer if unset, format: "HH:MM:SS" +idle="false" #* If "true", resets timer if keyboard or mouse activity is detected in XServer +# idletimer="00:30:00" #* If set and idle="true", the script uses this timer until first test, then uses standard wait time, + #* any X Server activity resets back to idletimer, format: "HH:MM:SS" +displaypause="false" #* If "true" automatically pauses timer when display is on, unpauses when off, overrides idle="true" if set, needs xset to work +paused="false" #* If "true", the timer is paused at startup, ignored if displaypause="true" +startuptest="false" #* If "true" and paused="false", tests speed at startup before timer starts +main_menu_start="shown" #* The status of the main menu at start, possible values: "shown", "hidden" +graph_start="shown" #* The status of the speed graph at start, possible values: "shown", "hidden" +loglevel="2" #* 0 : No logging + #* 1 : Log only when slow speed has been detected + #* 2 : Also log slow speed check + #* 3 : Also log server updates + #* 4 : Log all including forced tests +logdir="$HOME/spdtest-logs" #* Logfile save directory +quiet_start="true" #* If "true", don't print serverlist and routelist at startup +maxlogsize="1024" #* Max logsize (in kilobytes) before log is split +logcompress="gzip" #* Command for compressing logs, only log splits beyond the last split is compressed, disabled if not set +# custom_log="" #* Custom logfile (full path), if a custom logfile is set log splitting is disabled +max_buffer="1000" #* Max number of lines to buffer in internal scroll buffer +buffer_save="true" #* Save buffer to disk on exit and restore on start +mtr="true" #* Set "false" to disable route testing with mtr, automatically set to "false" if mtr is not found in PATH +mtr_internal="true" #* Use hosts from full test with speeds below $slowspeed in mtr test +mtr_internal_ok="false" #* Use hosts from full test with speeds above $slowspeed in mtr test +# mtr_internal_max="" #* Set max hosts to add from full test +mtr_external="false" #* Use hosts from route.cfg, see route.cfg.sample for formatting +mtrpings="25" #* Number of pings sent with mtr +testonly="false" #* If "true", never enter UI mode, always run full tests and quit +testnum="1" #* Number of times to loop full tests in testonly mode + +ookla_speedtest="speedtest" #* Command or full path to official speedtest client + +trace_errors="true" #* In event of error print line number of offending command to $HOME/.config/spdtest/errors +``` + +#### route.cfg.sample: (rename to route.cfg to use additional hosts in route test) +```bash +#? List of routes to test with mtr +#? Format: +#? routelista+=("host") +#? routelistdesc["host"]=("Name") +#? routelistport["host"]=("port") 'Set port to "auto" if you don't want to set a custom port!' + +routelista+=("google.com") +routelistdesc["google.com"]="Google" +routelistport["google.com"]="auto" + +routelista+=("reddit.com") +routelistdesc["reddit.com"]="Reddit" +routelistport["reddit.com"]="auto" + +routelista+=("twitch.tv") +routelistdesc["twitch.tv"]="Twitch" +routelistport["twitch.tv"]="auto" + +routelista+=("amazon.com") +routelistdesc["amazon.com"]="Amazon" +routelistport["amazon.com"]="auto" +``` + +#### Command line options: (to be updated) +``` +USAGE: ./spdtest.sh [OPTIONS] + +OPTIONS: + -t, --test [num] Runs full test 1 or number of times and quits + -u, --unit megabit/megabyte Which unit to show speed in, [default: megabit] + -s, --slow-speed speed Defines what speed in defined unit that will trigger more tests + -n, --num-servers num How many of the closest servers to get from speedtest.net + -i, --interface name Network interface being used [default: auto] + -l, --loglevel 0-3 0 No logging + 1 Log only when slow speed has been detected + 2 Also log slow speed check and server update + 3 Log all including forced tests + -lf, --log-file file Full path to custom logfile, no log rotation is done on custom logfiles + -p, --paused Sets timer to paused state at startup + -wt, --wait-time HH:MM:SS Time between tests when NO slowdown is detected [default: 00:10:00] + -st, --slow-time HH:MM:SS Time between tests when slowdown has been detected, uses wait timer if unset + -x, --x-reset [HH:MM:SS] Reset timer if keyboard or mouse activity is detected in X Server + If HH:MM:SS is included, the script uses this timer until first test, then uses + standard wait time, any activity resets to idle timer [default: unset] + -d, --display-pause Automatically pauses timer when display is on, unpauses when off + -gs, --gen-server-cfg num Writes number of the closest servers to "server.cfg" and quits + Servers aren't updated automatically at start if "server.cfg" exists + -sc, --server-config file Reads server config from [default: server.cfg] + If used in combination with -gs a new file is created + -h, --help Shows help information +CONFIG: + Note: All config files are stored in: $HOME/.config/spdtest + spdtest.cfg Automatically created with default values if removed + [server.cfg] Stores server id's to use with speedtest, delete to refresh servers on start + [route.cfg] Additional hosts to test with mtr, see route.cfg.sample for formatting +LOG: + Logfile location can be changed in config file + Currently: $HOME/spdtest-logs +``` + +## Dependencies + +**bash** (v4.4 or later) Script functionality might brake with earlier versions. + +**[Python 3](https://www.python.org/downloads)** (v3.7 or later) Needed for speedtest-cli, grc and getIdle. + +**[speedtest](https://www.speedtest.net/apps/cli)** Official speedtest client from Ookla, needs to be in path or defined in config. + +**[jq](https://stedolan.github.io/jq/)** Needed for json parsing. + +## Included + +**[speedtest-cli](https://github.com/sivel/speedtest-cli)** Used to get serverlist, since official speedtest client from Ookla is limited to 10 servers. +Modified and heavily stripped down, based on version 2.1.2. Python code included in the script. + +**[grc](https://github.com/garabik/grc)** For making text output in the UI pretty. +Modified version of grcat. Python code included in the script. + +**getIdle** Get XServer idle time. Python code included in script. + +## Optionals + +**[mtr](https://github.com/traviscross/mtr)** Needed if you want to check routes to slow servers. + +**[less](http://www.greenwoodsoftware.com/less/)** Needed if you want option to view logfile from UI. + +## TODO + + +- [ ] TODO Fix argument parsing and error messages +- [ ] TODO Change slowtest to multiple servers and compare results +- [ ] TODO fix wrong keypress in inputwait, esc codes etc +- [ ] TODO fix up README.md +- [x] TODO extern config and save to config? +- [x] TODO ssh controlmaster, server, client for precheck_speed +- [ ] TODO buffer logview +- [ ] TODO route test menu, choose host to test +- [ ] TODO windows: help, options, route +- [x] TODO plot speedgraphs overtime in UI +- [ ] TODO stat file +- [ ] Everything else... + +## LICENSE +[Apache License 2.0](LICENSE) diff --git a/bashtop b/bashtop new file mode 100755 index 0000000..5d2cce2 --- /dev/null +++ b/bashtop @@ -0,0 +1,3208 @@ +#!/usr/bin/env bash +# indent type=tab +# tab size=4 +# shellcheck disable=SC2034 #Unused variables +# shellcheck disable=SC2068 #Double quote array warning +# shellcheck disable=SC2086 # Double quote warning +## shellcheck disable=SC2120 +# shellcheck disable=SC2162 #Read without -r +# shellcheck disable=SC2206 #Word split warning +# shellcheck disable=SC2178 #Array to string warning +# shellcheck disable=SC2102 #Ranges only match single +# shellcheck disable=SC2004 #arithmetic brackets warning +# shellcheck disable=SC2207 #split array warning + +declare -a banner banner_colors + +banner=( +"██████╗ █████╗ ███████╗██╗ ██╗████████╗ ██████╗ ██████╗ " +"██╔══██╗██╔══██╗██╔════╝██║ ██║╚══██╔══╝██╔═══██╗██╔══██╗" +"██████╔╝███████║███████╗███████║ ██║ ██║ ██║██████╔╝" +"██╔══██╗██╔══██║╚════██║██╔══██║ ██║ ██║ ██║██╔═══╝ " +"██████╔╝██║ ██║███████║██║ ██║ ██║ ╚██████╔╝██║ " +"╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ") +declare version="0.6.2" +declare banner_width=${#banner[0]} +banner_colors=("#E62525" "#CD2121" "#B31D1D" "#9A1919" "#801414") + +#? Start default variables------------------------------------------------------------------------------> +#? These values are used to create "$HOME/.config/bashtop/bashtop.cfg" +#? Any changes made here will be ignored if config file exists +aaa_config() { : ; } #! Do not remove this line! + +#* Update time in milliseconds, increases automatically if set below internal loops processing time, recommended 2000 ms or above for better sample times for graphs +update_ms="2500" + +#* Processes sorting, "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive" +#* "cpu lazy" upates sorting over time, "cpu responsive" updates sorting directly at a cpu usage cost +proc_sorting="cpu lazy" + +#* Reverse sorting order, "true" or "false" +proc_reversed="false" + +#* Check cpu temperature, only works if "sensors" command is available and have values for "Package" and "Core" +check_temp="true" + +#* Draw a clock at top of screen, formatting according to strftime, empty string to disable +draw_clock="%X" + +#* Custom cpu model name, empty string to disable +custom_cpu_name="" + +#* Enable error logging to "$HOME/.config/bashtop/error.log", "true" or "false" +error_logging="true" + +aaz_config() { : ; } #! Do not remove this line! +#? End default variables--------------------------------------------------------------------------------> + +declare -a menu_options menu_help menu_quit + +menu_options=( +"┌─┐┌─┐┌┬┐┬┌─┐┌┐┌┌─┐" +"│ │├─┘ │ ││ ││││└─┐" +"└─┘┴ ┴ ┴└─┘┘└┘└─┘") +menu_help=( +"┬ ┬┌─┐┬ ┌─┐" +"├─┤├┤ │ ├─┘" +"┴ ┴└─┘┴─┘┴ ") +menu_quit=( +"┌─┐ ┬ ┬ ┬┌┬┐" +"│─┼┐│ │ │ │ " +"└─┘└└─┘ ┴ ┴ ") + +menu_options_selected=( +"╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗" +"║ ║╠═╝ ║ ║║ ║║║║╚═╗" +"╚═╝╩ ╩ ╩╚═╝╝╚╝╚═╝") +menu_help_selected=( +"╦ ╦╔═╗╦ ╔═╗" +"╠═╣║╣ ║ ╠═╝" +"╩ ╩╚═╝╩═╝╩ ") +menu_quit_selected=( +"╔═╗ ╦ ╦ ╦╔╦╗ " +"║═╬╗║ ║ ║ ║ " +"╚═╝╚╚═╝ ╩ ╩ ") + +declare -A cpu mem swap proc net box +declare -a cpu_usage cpu_graph_a cpu_graph_b color_meter color_temp color_cpu color_cpu_graph cpu_history color_mem_graph color_swap_graph +declare -a mem_history swap_history net_history_download net_history_upload mem_graph swap_graph proc_array download_graph upload_graph trace_array +declare resized=1 size_error clock tty_width tty_height hex="16#" cpu_p_box swap_on=1 draw_out esc_character boxes_out last_screen clock_out update_string +declare -a options_array=("update_ms" "proc_sorting" "proc_reversed" "check_temp" "draw_clock" "error_logging" "custom_cpu_name") +declare -a save_array=("${options_array[@]}") +declare -a sorting=( "pid" "program" "arguments" "threads" "user" "memory" "cpu lazy" "cpu responsive" ) +declare -a pid_history detail_graph detail_history detail_mem_history +declare time_left timestamp_start timestamp_end timestamp_input_start timestamp_input_end time_string mem_out proc_misc prev_screen pause_screen filter input_to_filter +declare no_epoch proc_det proc_misc2 sleeping=0 cursor detail_mem_graph proc_det2 +declare esc_character tab backspace update_sec saved_key +declare -a disks_free disks_total disks_name disks_free_percent +printf -v esc_character "\u1b" +printf -v tab "\u09" +printf -v backspace "\u7F" +printf -v enter_key "\x0a" + +tty_width="$(tput cols)" +tty_height="$(tput lines)" + +declare -a graph_symbol +graph_symbol=(" " "⡀" "⣀" "⣄" "⣤" "⣦" "⣴" "⣶" "⣷" "⣾" "⣿") +graph_symbol+=( " " "⣿" "⢿" "⡿" "⠿" "⠻" "⠟" "⠛" "⠙" "⠉" "⠈") +box[boxes]="cpu mem net processes" +box[cpu_color]="#3d7b46" +box[mem_color]="#8a882e" +box[net_color]="#423ba5" +box[processes_color]="#923535" + +cpu[threads]=0 + +#* Symbols for subscript function +subscript=("₀" "₁" "₂" "₃" "₄" "₅" "₆" "₇" "₈" "₉") + +#* Symbols for create_box function +box[single_hor_line]="─" +box[single_vert_line]="│" +box[single_left_corner_up]="┌" +box[single_right_corner_up]="┐" +box[single_left_corner_down]="└" +box[single_right_corner_down]="┘" +box[single_title_left]="├" +box[single_title_right]="┤" + +box[double_hor_line]="═" +box[double_vert_line]="║" +box[double_left_corner_up]="╔" +box[double_right_corner_up]="╗" +box[double_left_corner_down]="╚" +box[double_right_corner_down]="╝" +box[double_title_left]="╟" +box[double_title_right]="╢" + +#* If using bash version 5, set timestamps with EPOCHREALTIME variable +if [[ -n $EPOCHREALTIME ]]; then + get_ms() { #? Set given variable to current epoch millisecond with EPOCHREALTIME varialble + local -n ms_out=$1 + ms_out=$((${EPOCHREALTIME/,/}/1000)) + } + +#* If not, use date command +else + get_ms() { #? Set given variable to current epoch millisecond with date command + local -n ms_out=$1 + ms_out="$(date +%s%3N)" + } +fi + + +init_() { #? Collect needed information and set options before startig main loop + local i + #* Set terminal options, save and clear screen + tput smcup; stty -echo #; tput civis + print -v cursor -m 2 $((tty_width-1)) -fg 00 -t " " -l 1 + + #* Check if "sensors" command is available, if not, disable temperature collection + if [[ $check_temp != false ]] && command -v sensors >/dev/null 2>&1; then check_temp="true"; else check_temp="false"; fi + + #* Get number of cores and cpu threads + get_cpu_info + + #* Get processor BCLK + local param_var + param_var="$(70)); then + red=$((red+6)); blue=$((blue-6)); green=$((green-8)) + elif ((i>40)); then + blue=$((blue-2)); red=$((red+5)); green=$((green+2)) + elif ((i>20)); then + green=$((green+3)); ((blue--)) + else ((red++)); ((green++)); ((blue--)) + fi + if ((red>255)); then red=255; fi + if ((green>255)); then green=255; fi + if ((blue<20)); then blue=20; fi + done + + #* Create values for cpu meter color array + local red=80 green=240 blue=150 + for ((i=0;i<=100;i++)); do + color_cpu[i]="${red} ${green} ${blue}" + if ((i>75)); then + red=$((red+6)); blue=$((blue-4)); green=$((green-9)) + elif ((i>55)); then + blue=$((blue-2)); ((green++)) + elif ((i>35)); then + red=$((red+5)); ((green++)) + else + ((red++)); ((green--)); ((blue--)) + fi + if ((green<0)); then green=0; fi + if ((red>255)); then red=255; fi + if ((blue<20)); then blue=20; fi + done + + #* Create values for cpu graph color array + local red=80 green=240 blue=150 + for ((i=0;i<=100;i++)); do + color_cpu_graph[i]="${red} ${green} ${blue}" + if ((i>65)); then + red=$((red+6)); blue=$((blue-4)); green=$((green-9)) + + elif ((i>55)); then + blue=$((blue-2)); ((green++)) + elif ((i>20)); then + red=$((red+4)) + else + red=$((red+4)); ((green--)); ((blue--)) + fi + if ((green<0)); then green=0; fi + if ((red>255)); then red=255; fi + if ((blue<20)); then blue=20; fi + done + + #* Create values for free color array + local red=35 green=54 blue=4 + for ((i=0;i<=100;i++)); do + color_free_graph[i]="${red} ${green} ${blue}" + red=$((red+3)); green=$((green+3)); blue=$((blue+2)) + if ((green>255)); then green=255; fi + if ((red>255)); then red=255; fi + if ((blue>255)); then blue=255; fi + done + + #* Create values for cached color array + local red=15 green=34 blue=54 + for ((i=0;i<=100;i++)); do + color_cached_graph[i]="${red} ${green} ${blue}" + red=$((red+2)); green=$((green+3)); blue=$((blue+3)) + if ((green>255)); then green=255; fi + if ((red>255)); then red=255; fi + if ((blue>255)); then blue=255; fi + done + + #* Create values for available color array + local red=54 green=35 blue=15 + for ((i=0;i<=100;i++)); do + color_available_graph[i]="${red} ${green} ${blue}" + red=$((red+3)); green=$((green+3)); blue=$((blue+2)) + if ((green>255)); then green=255; fi + if ((red>255)); then red=255; fi + if ((blue>255)); then blue=255; fi + done + + #* Create values for used color array + local red=54 green=20 blue=15 + for ((i=0;i<=100;i++)); do + color_used_graph[i]="${red} ${green} ${blue}" + red=$((red+3)); green=$((green+2)); blue=$((blue+2)) + if ((green>255)); then green=255; fi + if ((red>255)); then red=255; fi + if ((blue>255)); then blue=255; fi + done + + #* Create values for download graph color array + local red=27 green=20 blue=75 + for ((i=0;i<=100;i++)); do + color_download_graph[i]="${red} ${green} ${blue}" + red=$((red+2)); green=$((green+2)); blue=$((blue+2)) + if ((green>255)); then green=255; fi + if ((red>255)); then red=255; fi + if ((blue>255)); then blue=255; fi + done + + #* Create values for upload graph color array + local red=58 green=4 blue=60 + for ((i=0;i<=100;i++)); do + color_upload_graph[i]="${red} ${green} ${blue}" + red=$((red+2)); green=$((green+2)); blue=$((blue+2)) + if ((green>255)); then green=255; fi + if ((red>255)); then red=255; fi + if ((blue>255)); then blue=255; fi + done + + #* Set up internals for quick processes sorting switching + for((i=0;i<${#sorting[@]};i++)); do + if [[ ${sorting[i]} == "${proc_sorting}" ]]; then + proc[sorting_int]=$i + break + fi + done + if [[ -z ${proc[sorting_int]} ]]; then + proc[sorting_int]=0 + proc_sorting="${sorting[0]}" + fi + + if [[ ${proc_reversed} == true ]]; then + proc[reverse]="+" + else + unset 'proc[reverse]' + fi + + #* Calculate sizes of boxes + calc_sizes + + #* Call init for processes data collection + proc[selected]=0 + proc[page]=1 + collect_processes init + +} + +quit_() { #? Clean exit + #* Restore terminal options and screen + tput cvvis + tput rmcup + stty echo + tput cnorm + + #* Save any changed values to config file + if [[ $config_file != "/dev/null" ]]; then + save_config "${save_array[@]}" + fi + + exit 0 +} + +traperr() { #? Function for reporting error line numbers + local match len trap_muted err="${BASH_LINENO[0]}" + + len=$((${#trace_array[@]})) + if ((len-->=1)); then + while ((len>=${#trace_array[@]}-2)); do + if [[ $err == "${trace_array[$((len--))]}" ]]; then ((++match)) ; fi + done + if ((match==2 & len != -2)); then return + elif ((match>=1)); then trap_muted="(MUTED!)" + fi + fi + if ((len>100)); then unset 'trace_array[@]'; fi + trace_array+=("$err") + printf "%(%X)T %s" "" "ERROR: On line $err $trap_muted" >> "${config_dir}/error.log" + +} + +resized() { #? Get new terminal size if terminal is resized + while (( tty_width!=$(tput cols) | tty_height!=$(tput lines) )); do + tty_width="$(tput cols)" + tty_height="$(tput lines)" + unset cursor + print -v cursor -m 2 $((tty_width-1)) -fg 00 -t " " -l 1 + resized=1 + sleep 0.1 + done + + if ((tty_width<80 | tty_height<25)); then size_error="true"; else size_error="false"; fi +} + +size_error_msg() { #? Shows error message if terminal size is below 80x25 + local width=$tty_width + local height=$tty_height + tput clear + create_box -full -title "warning" + print -rs -m $((tty_height/2-2)) 2 + print -c -fg cc -b "Resize window!" + print -d 1 -c "Current size: ${tty_height}x${tty_width}" + print -d 1 -c "Need to be atleast: 80x25" + echo -en "${cursor}" + while (( width==tty_width & height==tty_height )); do sleep 0.5 ; done + +} + +draw_banner() { #? Draw banner, usage: draw_banner [output variable] + local y letter b_color x_color xpos ypos=$1 banner_out + if [[ -n $2 ]]; then local -n banner_out=$2; fi + xpos=$(( (tty_width/2)-(banner_width/2) )) + + for banner_line in "${banner[@]}"; do + print -v banner_out -rs -move $((ypos+++y)) $xpos -t "${banner_line}" + done + + if [[ -z $2 ]]; then echo -en "${banner_out}"; fi +} + +create_config() { #? Creates a new config file with default values from above + local c_line c_read this_file + this_file="$(realpath "$0")" + echo "#? Config file for bashtop v. ${version}" > "$config_file" + while IFS= read -r c_line; do + if [[ $c_line =~ aaz_config() ]]; then break + elif [[ $c_read == "1" ]]; then echo "$c_line" >> "$config_file" + elif [[ $c_line =~ aaa_config() ]]; then c_read=1; fi + done < "$this_file" +} + +save_config() { #? Saves variables to config file if not same, usage: save_config "var1" ["var2"] ["var3"]... + if [[ -z $1 || $config_file == "/dev/null" ]]; then return; fi + local var tmp_conf tmp_value quote original new + tmp_conf="$(<"$config_file")" + for var in "$@"; do + if [[ ${tmp_conf} =~ ${var} ]]; then + get_value -v "tmp_value" -sv "tmp_conf" -k "${var}=" + if [[ ${tmp_value//\"/} != "${!var}" ]]; then + original="${var}=${tmp_value}" + new="${var}=\"${!var}\"" + sed -i "s/${original}/${new}/" "${config_file}" + fi + else + echo "${var}=\"${!var}\"" >> "$config_file" + fi + done +} + +set_font() { #? Take a string and generate a string of unicode characters of given font, usage; set_font "font-name [bold] [italic]" "string" + local i letter letter_hex new_hex add_hex start font="$1" string_in="$2" string_out hex="16#" + if [[ -z $font || -z $string_in ]]; then return; fi + case "$font" in + "sans-serif") lower_start="1D5BA"; upper_start="1D5A0"; digit_start="1D7E2";; + "sans-serif bold") lower_start="1D5EE"; upper_start="1D5D4"; digit_start="1D7EC";; + "sans-serif italic") lower_start="1D622"; upper_start="1D608"; digit_start="1D7E2";; + #"sans-serif bold italic") start="1D656"; upper_start="1D63C"; digit_start="1D7EC";; + "script") lower_start="1D4B6"; upper_start="1D49C"; digit_start="1D7E2";; + "script bold") lower_start="1D4EA"; upper_start="1D4D0"; digit_start="1D7EC";; + "fraktur") lower_start="1D51E"; upper_start="1D504"; digit_start="1D7E2";; + "fraktur bold") lower_start="1D586"; upper_start="1D56C"; digit_start="1D7EC";; + "monospace") lower_start="1D68A"; upper_start="1D670"; digit_start="1D7F6";; + "double-struck") lower_start="1D552"; upper_start="1D538"; digit_start="1D7D8";; + *) echo -n "${string_in}"; return;; + esac + + for((i=0;i<${#string_in};i++)); do + letter=${string_in:i:1} + if [[ $letter =~ [a-z] ]]; then #61 + printf -v letter_hex '%X\n' "'$letter" + printf -v add_hex '%X' "$((${hex}${letter_hex}-${hex}61))" + printf -v new_hex '%X' "$((${hex}${lower_start}+${hex}${add_hex}))" + string_out="${string_out}\U${new_hex}" + #if [[ $font =~ sans-serif && $letter =~ m|w ]]; then string_out="${string_out} "; fi + #\U205F + elif [[ $letter =~ [A-Z] ]]; then #41 + printf -v letter_hex '%X\n' "'$letter" + printf -v add_hex '%X' "$((${hex}${letter_hex}-${hex}41))" + printf -v new_hex '%X' "$((${hex}${upper_start}+${hex}${add_hex}))" + string_out="${string_out}\U${new_hex}" + #if [[ $font =~ sans-serif && $letter =~ M|W ]]; then string_out="${string_out} "; fi + elif [[ $letter =~ [0-9] ]]; then #30 + printf -v letter_hex '%X\n' "'$letter" + printf -v add_hex '%X' "$((${hex}${letter_hex}-${hex}30))" + printf -v new_hex '%X' "$((${hex}${digit_start}+${hex}${add_hex}))" + string_out="${string_out}\U${new_hex}" + else + string_out="${string_out} \e[1D${letter}" + fi + done + + echo -en "${string_out}" +} + +sort_array_int() { #? Copy and sort an array of integers from largest to smallest value, usage: sort_array_int "input array" "output array" + #* Return if given array has no values + if [[ -z ${!1} ]]; then return; fi + local start_n search_n tmp_array + + #* Create pointers to arrays + local -n in_arr="$1" + local -n out_arr="$2" + + #* Create local copy of array + local array=("${in_arr[@]}") + + #* Start sorting + for ((start_n=0;start_n<=${#array[@]}-1;++start_n)); do + for ((search_n=start_n+1;search_n<=${#array[@]}-1;++search_n)); do + if ((array[start_n] [-ps,-per-second] [-s,-start "1024 multiplier start"] [-v,-variable-output] + local value selector per_second unit_mult decimals out_var ext_var + local -a unit + until (($#==0)); do + case "$1" in + -b|-bit) unit=(bit Kib Mib Gib Tib Pib); unit_mult=8;; + -B|-Byte) unit=(Byte KiB MiB GiB TiB PiB); unit_mult=1;; + -ps|-per-second) per_second=1;; + -s|-start) selector="$2"; shift;; + -v|-variable-output) local -n out_var="$2"; ext_var=1; shift;; + *) if [[ $1 =~ ^[0-9]*$ ]]; then value=$1; break; fi;; + esac + shift + done + + if [[ -z $value || $value -lt 0 || -z $unit_mult ]]; then return; fi + + if ((per_second==1 & unit_mult==1)); then per_second="/s" + elif ((per_second==1)); then per_second="ps"; fi + + if ((value>0)); then + value=$((value*100*unit_mult)) + + until ((${#value}<6)); do + value=$((value>>10)) + ((++selector)) + done + + if ((${#value}<5 & ${#value}>=2 & selector>0)); then + decimals=$((5-${#value})) + value="${value::-2}.${value:(-${decimals})}" + elif ((${#value}>=2)); then + value="${value::-2}" + fi + fi + + out_var="${value} ${unit[$selector]}${per_second}" + if [[ -z $ext_var ]]; then echo -n "${out_var}"; fi +} + +get_cpu_info() { + local lscpu_var param_var + lscpu_var="$(lscpu)" + if [[ -z ${cpu[threads]} || -z ${cpu[cores]} ]]; then + get_value -v cpu[threads] -sv "lscpu_var" -k "CPU(s):" -i + get_value -v cpu[cores] -sv "lscpu_var" -k "Core(s)*:" -i + fi + if [[ -z $custom_cpu_name ]]; then + if ! get_value -v cpu[model] -sv "lscpu_var" -k "Model name:" -a -b -k "CPU" -mk -1; then + get_value -v cpu[model] -sv "lscpu_var" -k "Model name:" -r " " + fi + else + cpu[model]="${custom_cpu_name}" + fi +} + +get_value() { #? Get a value from a file, variable or array by searching for a non spaced "key name" on the same line + local match line_pos=1 int reg key all tmp_array input found input_line line_array line_val ext_var line_nr current_line match_key math removing ext_arr + local -a remove + until (($#==0)); do + until (($#==0)); do + case "$1" in + -k|-key) key="$2"; shift;; #? Key "string" on the same line as target value + -m|-match) match="$2"; shift;; #? If multiple matches on a line, match occurrence "x" + -mk|-match-key) match_key=$2; line_pos=0; shift;; #? Match in relation to key position, -1 for previous value, 1 for next value + -b|-break) shift; break;; #? Break up arguments for multiple searches + -a|-all) all=1;; #? Prints back found line including key + -l|-line) line_nr="$2"; shift;; #? Set target line if no key is available + -ss|-source-string) input="$2"; shift;; #? Argument string as source + -sf|-source-file) input="$(<"$2")"; shift;; #? File as source + -sv|-source-var) input="${!2}"; shift;; #? Variable as source + -sa|-source-array) local -n tmp_array=$2; input="${tmp_array[*]}"; shift;; #? Array as source + -fp|-floating-point) reg="[0-9]?(\.|\,)[0-9]*"; match=1;; #? Match floating point value + -math) math="$2"; shift;; #? Perform math on a integer value, "x" represents value, only works if "integer" argument is given + -i|-integer) reg="[0-9]+(\.|\,)?[0-9]*"; int=1; match=1;; #? Match integer value + -r|-remove) remove+=("$2"); shift;; #? Format output by removing entered regex, can be used multiple times + -v|-variable-out) local -n found="$2"; ext_var=1; shift;; #? Output to variable + -map|-map-array) local -n array_out="$2"; ext_var=1; ext_arr=1; shift;; #? Map output to array + esac + shift + done + + if [[ -z $input ]]; then return 1; fi + if [[ -z $line_nr && -z $key ]]; then line_nr=1; fi + + while IFS='' read -r input_line; do + ((++current_line)) + if [[ -n $line_nr && $current_line -eq $line_nr || -z $line_nr && -n $key && ${input_line/${key}/} != "$input_line" ]]; then + if [[ -n $all ]]; then + found="${input_line}" + break + + elif [[ -z $match && -z $match_key && -z $reg ]]; then + found="${input_line/${key}/}" + break + + else + line_array=(${input_line/${key}/${key// /}}) + + fi + + for line_val in "${line_array[@]}"; do + if [[ -n $match_key && $line_val == "${key// /}" ]]; then + if ((match_key<0 & line_pos+match_key>=0)) || ((match_key>=0 & line_pos+match_key<${#line_array[@]})); then + found="${line_array[$((line_pos+match_key))]}" + break 2 + else + return 1 + fi + + elif [[ -n $match_key ]]; then + ((++line_pos)) + + elif [[ -n $reg && $line_val =~ ^${reg}$ || -z $reg && -n $match ]]; then + if ((line_pos==match)); then + found=${line_val} + break 2 + fi + ((++line_pos)) + fi + done + fi + done <<<"${input}" + + if [[ -z $found ]]; then return 1; fi + + if [[ -n ${remove[*]} ]]; then + for removing in "${remove[@]}"; do + found="${found//${removing}/}" + done + fi + + if [[ -n $int && $found =~ . ]]; then + found=${found%.*} + fi + + if [[ -n $math && -n $int ]]; then + math="${math//x/$found}" + found=$((${math})) + fi + + if (($#>0)); then + input="${found}" + unset key match match_key all reg found int 'remove[@]' current_line + line_pos=1 + fi + + done + + if [[ -z $ext_var ]]; then echo "${found}"; fi + if [[ -n $ext_arr ]]; then array_out=(${found}); fi +} + +cur_pos() { #? Get cursor postion, argument "line" prints current line, argument "col" prints current column, no argument prints both in format "line column" + local line col + IFS=';' read -sdR -p $'\E[6n' line col + if [[ -z $1 || $1 == "line" ]]; then echo -n "${line#*[}${1:-" "}"; fi + if [[ -z $1 || $1 == "col" ]]; then echo -n "$col"; fi +} + +create_box() { #? Draw a box with an optional titlebar and title at given location + local width height col line title ltype hpos vpos i hlines vlines color line_color sm_title c_rev=0 box_out ext_var fill + until (($#==0)); do + case $1 in + -f|-full) col=1; line=1; width=$((tty_width)); height=$((tty_height));; #? Use full terminal size for box + -c|-col) if [[ $2 =~ ^[0-9]*$ ]]; then col=$2; shift; fi;; #? Column position to start box + -l|-line) if [[ $2 =~ ^[0-9]*$ ]]; then line=$2; shift; fi;; #? Line position to start box + -w|-width) if [[ $2 =~ ^[0-9]*$ ]]; then width=$2; shift; fi;; #? Width of box + -h|-height) if [[ $2 =~ ^[0-9]*$ ]]; then height=$2; shift; fi;; #? Height of box + -tb|-titlebar) if [[ -n $2 ]]; then title="$2"; shift; fi;; #? Draw titlebar and title + -t|-title) if [[ -n $2 ]]; then title="$2"; sm_title=1; shift; fi;; #? Draw title without titlebar + -s|-single) ltype="single";; #? Use single lines + -d|-double) ltype="double";; #? Use double lines + -lc|-line-color) line_color=$2; shift;; #? Color of the lines + -fill) fill=1;; + -v|-variable) local -n box_out=$2; ext_var=1; shift;; #? Output box to a variable + esac + shift + done + if [[ -z $col || -z $line || -z $width || -z $height ]]; then return; fi + + ltype=${ltype:-"single"} + vlines+=("$col" "$((col+width-1))") + hlines+=("$line" "$((line+height-1))") + + print -v box_out -rs + + #* Fill box if enabled + if [[ -n $fill ]]; then + for((i=line+1;i=width/2)); then c_rev=1; fi + elif ((c_rev==1)); then ((--color)); if ((color<=0)); then c_rev=0; fi; fi + done + print -v box_out -m $((line+1)) $((col+1)) -bg $((width/2)) 0 0 -fg ee -b -jc $((width-2)) -trans -t "$title" + + #* Draw small title without titlebar + elif [[ -n $title && -n $sm_title ]]; then + print -v box_out -m $line $((col+2)) -t "┤" -fg ee -b -t "$title" -rs -fg ${line_color:-66} -t "├" + fi + + print -v box_out -m $((line+1)) $((col+1)) -rs + + if [[ -z $ext_var ]]; then echo -en "${box_out}"; fi + + +} + +create_meter() { #? Create a horizontal percentage meter, usage; create_meter + #? Optional arguments: [-p, -place ] [-w, -width ] [-b, -block-size "small"|"big"] [-f, -fill-empty] + #? [-c, -color "array-name"] [-i, -invert-color] [-v, -variable "variable-name"] + if [[ -z $1 ]]; then return; fi + local val width colors color block_size block i fill_empty col line var ext_var out meter_var print_var invert + + #* Argument parsing + until (($#==0)); do + case $1 in + -p|-place) if [[ $2 =~ ^[0-9]+$ && $3 =~ ^[0-9]+$ ]]; then line=$2; col=$3; shift 2; fi;; #? Placement for meter + -w|-width) width=$2; shift;; #? Width of meter in columns + -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 + -i|-invert) invert=1;; #? Invert meter + -b|-block-size) block_size=$2; shift;; #? Size of blocks used, small or big + -f|-fill-empty) fill_empty=1;; #? Fill unused space with dark blocks + -v|-variable) local -n meter_var=$2; ext_var=1; shift;; #? Output meter to a variable + *) if [[ $1 =~ ^[0-9]+$ ]]; then val=$1; fi;; + esac + shift + done + + if [[ -z $val ]]; then return; fi + + #* Set default width if not given and set block size + width=${width:-10} + if [[ $block_size == "big" ]]; then block="⯀"; bg_color=30 + else block="■"; bg_color=30; fi + + #* If no color array was given, create a simple greyscale array + if [[ -z $colors ]]; then + for ((i=0,ic=50;i<=100;i++,ic=ic+2)); do + colors[i]="${ic} ${ic} ${ic}" + done + fi + + #* Create the meter + meter_var="" + if [[ -n $line && -n $col ]]; then print -v meter_var -rs -m $line $col + else print -v meter_var -rs; fi + + if [[ -n $invert ]]; then print -v meter_var -r $((width+1)); fi + for((i=1;i<=width;i++)); do + if [[ -n $invert ]]; then print -v meter_var -l 2; fi + + if ((val>=i*100/width)); then + print -v meter_var -fg ${colors[$((i*100/width))]} -t "${block}" + elif ((fill_empty==1)); then + if [[ -n $invert ]]; then print -v meter_var -l $((width-i)); fi + print -v meter_var -fg $bg_color -rp $((1+width-i)) -t "${block}"; break + else + if [[ -n $invert ]]; then break; print -v meter_var -l $((1+width-i)) + else print -v meter_var -r $((1+width-i)); break; fi + fi + done + if [[ -z $ext_var ]]; then echo -en "${meter_var}"; fi +} + +create_graph() { #? Create a graph from an array of percentage values, usage; create_graph + #? Create a graph from an array of non percentage values: create_graph <-max "max value"> + #? Add a value to existing graph; create_graph [-i, -invert] [-max "max value"] -add-value "graph_array" + #? Add last value from an array to existing graph; create_graph [-i, -invert] [-max "max value"] -add-last "graph_array" "value-array" + #? Options: < -d, -dimensions > [-i, -invert] [-n, -no-guide] [-c, -color "array-name"] [-o, -output-array "variable-name"] + if [[ -z $1 ]]; then return; fi + local val col s_col line s_line height s_height width s_width colors color i var ext_var out side_num side_nums=1 add add_array invert no_guide max + local -a graph_array input_array + + #* Argument parsing + until (($#==0)); do + case $1 in + -d|-dimensions) if [[ $2 =~ ^[0-9]+$ && $3 =~ ^[0-9]+$ && $3 =~ ^[0-9]+$ && $4 =~ ^[0-9]+$ ]]; then #? Graph dimensions + line=$2; col=$3; height=$4; width=$5; shift 4; fi;; + -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 + -o|-output-array) local -n output_array=$2; ext_var=1; shift;; #? Output meter to an array + -add-value) if [[ $3 =~ ^[0-9]+$ ]]; then #? Add a value to existing graph + local -n output_array=$2; add=$3; break + else return; fi;; + -add-last) local -n output_array=$2; local -n add_array=$3; add=${add_array[-1]}; break;; #? Add last value from array to existing graph + -i|-invert) invert=1;; #? Invert graph, drawing from top to bottom + -n|-no-guide) no_guide=1;; #? Don't print side and bottom guide lines + -max) if [[ $2 =~ ^[0-9]+$ ]]; then max=$2; shift; fi;; #? Needed max value for non percentage arrays + *) local -n tmp_in_array=$1; input_array=("${tmp_in_array[@]}");; + esac + shift + done + + if [[ -z $no_guide ]]; then + ((--height)) + else + if [[ -n $invert ]]; then ((line--)); fi + fi + + + if ((width<3)); then width=3; fi + if ((height<1)); then height=1; fi + + + #* If argument "add" was passed check for existing graph and make room for new value(s) + local add_start add_end + if [[ -n $add ]]; then + local cut_left search + if [[ -n ${input_array[0]} ]]; then return; fi + if [[ -n $output_array ]]; then + graph_array=("${output_array[@]}") + if [[ -z ${graph_array[0]} ]]; then return; fi + else + return + fi + height=$((${#graph_array[@]}-1)) + input_array[0]=${add} + + #* Remove last value in current graph + + for ((i=0;i=0;i--)); do + g_index+=($i) + done + + else + for((i=0;i<=height;i++)); do + g_index+=($i) + done + fi + + if [[ -n $no_guide ]]; then unset normal_vals + elif [[ -n $invert ]]; then g_char=(" ⡇" " ⡤" "⠤") + fi + + #* Set up graph array print side numbers and lines + print -v graph_array[0] -rs + print -v graph_array[0] -m $((line+g_index[0])) ${col} ${normal_vals:+-jr 3 -fg ee -b -t "${side_num[0]}" -rs -fg 90 -t "${g_char[0]}"} -fg ${colors[100]} + for((i=1;i=max)); then + input_array[i]=100 + else + input_array[i]=$((input_array[i]*100/max)) + fi + done + fi + + until ((y==done_val)); do + + #* Print spaces to right-justify graph if number of values is less than graph width + if [[ -z $add ]] && ((value_width0)); then + print -v graph_array[y] -rp ${count} -t " " + count=0 + fi + + #* Print current value in percent relative to graph size if current value is less than line percentage but greater than next line percentage + while ((x=next_value)); do + print -v graph_array[y] -t "${graph_symbol[${invert:+-}$(( (input_array[x]*virt_height/100)-next_value ))]}" + ((++x)) + done + + #* Print full block if current value is greater than percentage for current line + while ((xcur_value)); do + ((++count)) + ((++x)) + done + if ((count>0)); then + print -v graph_array[y] -rp ${count} -t "${graph_symbol[10]}" + count=0 + fi + done + + if [[ -n $invert ]]; then + ((y--)) || true + else + ((++y)) + fi + done + + #* Echo out graph if no argument for a output array was given + if [[ -z $ext_var && -z $add ]]; then echo -en "${graph_array[*]}" + else output_array=("${graph_array[@]}"); fi +} + +create_mini_graph() { #? Create a one line high graph from an array of percentage values, usage; create_mini_graph + #? Add a value to existing graph; create_mini_graph [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] -add-value "graph_variable" + #? Add last value from an array to existing graph; create_mini_graph [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] -add-last "graph_variable" "value-array" + #? Options: [-w, -width ] [-i, -invert] [-nc, -no-color] [-c, -color "array-name"] [-o, -output-variable "variable-name"] + if [[ -z $1 ]]; then return; fi + local val col s_col line s_line height s_height width s_width colors color i var ext_var out side_num side_nums=1 add invert no_guide graph_var no_color color_value + + #* Argument parsing + until (($#==0)); do + case $1 in + -w|-width) if [[ $2 =~ ^[0-9]+$ ]]; then width=$2; shift; fi;; #? Graph width + -c|-color) local -n colors=$2; shift;; #? Name of an array containing colors from index 0-100 + -nc|-no-color) no_color=1;; #? Set no color + -o|-output-variable) local -n output_var=$2; ext_var=1; shift;; #? Output graph to a variable + -add-value) if [[ $3 =~ ^[0-9]+$ ]]; then #? Add a value to existing graph + local -n output_var=$2; add=$3; break + else return; fi;; + -add-last) local -n output_var=$2 add_array=$3; add="${add_array[-1]}"; break;; #? Add last value from array to existing graph + -i|-invert) invert=1;; #? Invert graph, drawing from top to bottom + *) local -n input_array=$1;; + esac + shift + done + + if ((width<1)); then width=1; fi + + #* If argument "add" was passed check for existing graph and make room for new value(s) + local add_start add_end + if [[ -n $add ]]; then + local cut_left search + #if [[ -n ${input_array[0]} ]]; then return; fi + if [[ -n $output_var ]]; then + graph_var="${output_var}" + if [[ -z ${graph_var} ]]; then return; fi + else + return + fi + + declare -a input_array + input_array[0]=${add} + + #* Remove last value in current graph + if [[ -n ${graph_var} && -z $no_color ]]; then + if [[ ${graph_var::5} == "\e[1C" ]]; then + graph_var="${graph_var#'\e[1C'}" + else + cut_left="${graph_var%%m*}" + search=$((${#cut_left}+1)) + graph_var="${graph_var:$((search+1))}" + fi + elif [[ -n ${graph_var} && -n $no_color ]]; then + if [[ ${graph_var::5} == "\e[1C" ]]; then + #cut_left="${graph_var%%C*}" + #search=$((${#cut_left}+1)) + #graph_var="${graph_var:$((search))}" + graph_var="${graph_var#'\e[1C'}" + else + graph_var="${graph_var:1}" + fi + fi + fi + + + #* If no color array was given, create a simple greyscale array + if [[ -z $colors && -z $no_color ]]; then + for ((i=0,ic=50;i<=100;i++,ic=ic+2)); do + colors[i]="${ic} ${ic} ${ic}" + done + fi + + + #* Create the graph + local value_width x=0 y a cur_value virt_height=$((height*10)) offset=0 org_value + if [[ -n $add ]]; then + value_width=1 + elif ((${#input_array[@]}<=width)); then + value_width=${#input_array[@]}; + else + value_width=${width} + offset=$((${#input_array[@]}-width)) + fi + + #* Print spaces to right-justify graph if number of values is less than graph width + if [[ -z $add && -z $no_color ]] && ((value_width| ] [-bg, -background | ] [-rs, -reset] [-/+b, -/+bold] [-/+da, -/+dark] + #? [-/+ul, -/+underline] [-/+i, -/+italic] [-/+bl, -/+blink] [-f, -font "sans-serif|script|fraktur|monospace|double-struck"] + #? Manipulation: [-m, -move ] [-l, -left ] [-r, -right ] [-u, -up ] [-d, -down ] [-c, -center] [-sc, -save] [-rc, -restore] + #? [-jl, -justify-left ] [-jr, -justify-right ] [-jc, -justify-center ] [-rp, -repeat ] + #? Text: [-v, -variable "variable-name"] [-stdin] [-t, -text "string"] ["string"] + + #* Return if no arguments is given + if [[ -z $1 ]]; then return; fi + + #* Just echo and return if only one argument and not a valid option + if [[ $# -eq 1 && ${1::1} != "-" ]]; then echo -en "$1"; return; fi + + local effect color add_command text text2 esc center clear fgc bgc fg_bg_div tmp tmp_len bold italic custom_font val var out ext_var hex="16#" + local justify_left justify_right justify_center repeat r_tmp trans + + + #* Loop function until we are out of arguments + until (($#==0)); do + + #* Argument parsing + until (($#==0)); do + case $1 in + -t|-text) if [[ -n $2 ]]; then text="$2"; shift 2; break; fi;; #? String to print + -stdin) text="$( <0-255> <0-255>" + if [[ ${2::1} == "#" ]]; then val=${2:1}; else val=${2}; fi + if [[ $2 =~ ^[0-9]+$ && $3 =~ ^[0-9]+$ && $4 =~ ^[0-9]+$ ]]; then fgc="\e[38;2;$2;$3;$4m"; shift 3 + elif [[ ${#val} == 6 && $val =~ ^[0-9a-fA-F]*$ ]]; then fgc="\e[38;2;$((${hex}${val:0:2}));$((${hex}${val:2:2}));$((${hex}${val:4:2}))m"; shift + elif [[ ${#val} == 2 && $val =~ ^[0-9a-fA-F]*$ ]]; then fgc="\e[38;2;$((${hex}${val:0:2}));$((${hex}${val:0:2}));$((${hex}${val:0:2}))m"; shift + fi + ;; + -bg|-background) #? Set text background color, accepts either 6 digit hexadecimal "#RRGGBB", 2 digit hex (greyscale) or decimal RGB "<0-255> <0-255> <0-255>" + if [[ ${2::1} == "#" ]]; then val=${2:1}; else val=${2}; fi + if [[ $2 =~ ^[0-9]+$ && $3 =~ ^[0-9]+$ && $4 =~ ^[0-9]+$ ]]; then bgc="\e[48;2;$2;$3;$4m"; shift 3 + elif [[ ${#val} == 6 && $val =~ ^[0-9a-fA-F]*$ ]]; then bgc="\e[48;2;$((${hex}${val:0:2}));$((${hex}${val:2:2}));$((${hex}${val:4:2}))m"; shift + elif [[ ${#val} == 2 && $val =~ ^[0-9a-fA-F]*$ ]]; then bgc="\e[48;2;$((${hex}${val:0:2}));$((${hex}${val:0:2}));$((${hex}${val:0:2}))m"; shift + fi + ;; + -c|-center) center=1;; #? Center text horizontally on screen + -rs|-reset) effect="0${effect}";; #? Reset text colors and effects + -b|-bold) effect="${effect}${effect:+;}1"; bold=1;; #? Enable bold text + +b|+bold) effect="${effect}${effect:+;}21"; bold=0;; #? Disable bold text + -da|-dark) effect="${effect}${effect:+;}2";; #? Enable dark text + +da|+dark) effect="${effect}${effect:+;}22";; #? Disable dark text + -i|-italic) effect="${effect}${effect:+;}3"; italic=1;; #? Enable italic text + +i|+italic) effect="${effect}${effect:+;}23"; italic=0;; #? Disable italic text + -ul|-underline) effect="${effect}${effect:+;}4";; #? Enable underlined text + +ul|+underline) effect="${effect}${effect:+;}24";; #? Disable underlined text + -bl|-blink) effect="${effect}${effect:+;}5";; #? Enable blinking text + +bl|+blink) effect="${effect}${effect:+;}25";; #? Disable blinking text + -f|-font) if [[ $2 =~ ^(sans-serif|script|fraktur|monospace|double-struck)$ ]]; then custom_font="$2"; shift; fi;; #? Set custom font + -m|-move) if [[ $2 =~ ^[0-9\-]+$ && $3 =~ ^[0-9\-]+$ ]]; then add_command="${add_command}\e[${2};${3}f"; shift 2; fi;; #? Move to postion "LINE" "COLUMN" + -l|-left) if [[ $2 =~ ^[0-9\-]+$ ]]; then add_command="${add_command}\e[${2}D"; shift; fi;; #? Move left x columns + -r|-right) if [[ $2 =~ ^[0-9\-]+$ ]]; then add_command="${add_command}\e[${2}C"; shift; fi;; #? Move right x columns + -u|-up) if [[ $2 =~ ^[0-9\-]+$ ]]; then add_command="${add_command}\e[${2}A"; shift; fi;; #? Move up x lines + -d|-down) if [[ $2 =~ ^[0-9\-]+$ ]]; then add_command="${add_command}\e[${2}B"; shift; fi;; #? Move down x lines + -jl|-justify-left) if [[ $2 =~ ^[0-9\-]+$ ]]; then justify_left="${2}"; shift; fi;; #? Justify string left within given width + -jr|-justify-right) if [[ $2 =~ ^[0-9\-]+$ ]]; then justify_right="${2}"; shift; fi;; #? Justify string right within given width + -jc|-justify-center) if [[ $2 =~ ^[0-9\-]+$ ]]; then justify_center="${2}"; shift; fi;; #? Justify string center within given width + -rp|-repeat) if [[ $2 =~ ^[0-9]+$ ]]; then repeat=${2}; shift; fi;; #? Repeat next string x number of times + -sc|-save) add_command="\e[s${add_command}";; #? Save cursor position + -rc|-restore) add_command="${add_command}\e[u";; #? Restore cursor position + -trans) trans=1;; #? Make whitespace transparent + -v|-variable) local -n var=$2; ext_var=1; shift;; #? Send output to a variable, appending if not unset + *) text="$1"; shift; break;; #? Assumes text string if no argument is found + esac + shift + done + + #* Repeat string if repeat is enabled + if [[ -n $repeat ]]; then + printf -v r_tmp "%${repeat}s" "" + text="${r_tmp// /$text}" + fi + + #* Set correct placement for screen centered text + if ((center==1 & ${#text}>0 & ${#text}tty_width*2)); then + cpu_history=( "${cpu_history[@]:$tty_width}" "${cpu_usage[0]}") + else + cpu_history+=("${cpu_usage[0]}") + fi + + # threads=${box[testing]} #! For testing, remove <-------------- + + for((i=1;i<=threads;i++)); do + local -n cpu_core_history="cpu_core_history_$i" + if ((${#cpu_core_history[@]}>20)); then + cpu_core_history=( "${cpu_core_history[@]:10}" "${cpu_usage[$i]}") + else + cpu_core_history+=("${cpu_usage[$i]}") + fi + done + + + #* Get current cpu frequency from "/proc/cpuinfo" and convert to appropriate unit + if [[ -z ${cpu[no_cpu_info]} ]] && ! get_value -v cpu[freq] -sf "/proc/cpuinfo" -k "cpu MHz" -i; then + cpu[no_cpu_info]=1 + fi + + #* If getting cpu frequency from "proc/cpuinfo" was unsuccessfull try "/sys/devices/../../scaling_cur_freq" + if [[ -n ${cpu[no_cpu_info]} ]]; then + get_value -v cpu[freq] -sf "/sys/devices/system/cpu/cpu$((${cpu[threads]}-1))/cpufreq/scaling_cur_freq" -i -math "x/1000" + fi + + if ((${#cpu[freq]}>3)); then cpu[freq_string]="${cpu[freq]::-3}.${cpu[freq]:(-3):1} GHz" + else cpu[freq_string]="${cpu[freq]} MHz"; fi + + + #* Collect cpu temps if enabled + if [[ $check_temp == true ]]; then collect_cpu_temps; fi +} + +collect_cpu_temps() { #? Collect cpu temperatures + local unit i it c div threads=${cpu[threads]} sens_var + + #* Fetch output from "sensors" command to a variable + sens_var="$(sensors)" + + #* Get CPU package temp + if get_value -v cpu[temp_0] -sv "sens_var" -k "Package*:" -mk 1; then + + #* If successful get temperature unit, convert temp to integer and get high, crit and core temps + cpu[temp_unit]=${cpu[temp_0]:(-2)}; cpu[temp_0]=${cpu[temp_0]%.*}; if [[ ${cpu[temp_0]::1} == "+" ]]; then cpu[temp_0]=${cpu[temp_0]#+}; fi + if [[ -z ${cpu[temp_high]} ]]; then + get_value -v "cpu[temp_high]" -sv "sens_var" -k "Package*high =" -m 2 -r "[^0-9.]" -b -i + get_value -v "cpu[temp_crit]" -sv "sens_var" -k "Package*crit =" -m 2 -r "[^0-9.]" -b -i + fi + for((i=0,it=1;i15)); then + cpu_temp_history=( "${cpu_temp_history[@]:10}" "$(( (${cpu[temp_${i}]}-15)*100/(cpu[temp_crit]-15) ))") + else + cpu_temp_history+=("$(( (${cpu[temp_${i}]}-15)*100/(cpu[temp_crit]-15) ))") + fi + done + + + #* If unsuccessful turn off temperature checking + else + check_temp="false" + fi +} + +collect_mem() { #? Collect memory information from "/proc/meminfo" + ((++mem[counter])) + + if ((mem[counter]<10)); then return; fi + mem[counter]=0 + + local i tmp value array mem_info + local -a mem_array swap_array available=("mem") + + #* Get memory and swap information from "/proc/meminfo" and calculate percentages + mem_info="$(0)); then + get_value -v swap[free] -sv "mem_info" -k "SwapFree:" -i + swap[free_percent]=$((swap[free]*100/swap[total])) + + swap[used]=$((swap[total]-swap[free])) + swap[used_percent]=$((swap[used]*100/swap[total])) + + available+=("swap") + else + unset swap_on + fi + + #* Convert values to floating point and humanize + for array in ${available[@]}; do + for value in total used free available cached; do + if [[ $array == "swap" && $value == "available" ]]; then break 2; fi + local -n this_value="${array}[${value}]" this_string="${array}[${value}_string]" + floating_humanizer -v this_string -s 1 -B "${this_value}" + done + done + + #* Get disk information from "df" command + local df_array df_line line_array + unset 'disks_free[@]' 'disks_total[@]' 'disks_name[@]' 'disks_free_percent[@]' + readarray -t df_array < <(df -x squashfs -x tmpfs -x devtmpfs -x overlay) + for df_line in "${df_array[@]:1}"; do + line_array=(${df_line}) + + if [[ ${line_array[5]} == "/" ]]; then disks_name+=("root") + else disks_name+=("${line_array[5]##*/}"); fi + disks_total+=("$(floating_humanizer -s 1 -B ${line_array[1]})") + disks_used+=("$(floating_humanizer -s 1 -B ${line_array[2]})") + disks_used_percent+=("${line_array[4]%'%'}") + disks_free+=("$(floating_humanizer -s 1 -B ${line_array[3]})") + disks_free_percent+=("$((100-${line_array[4]%'%'}))") + + done + + + +} + +collect_processes() { #? Collect process information and calculate accurate cpu usage + local width=${box[processes_width]} height=${box[processes_height]} format_args format_cmd readline sort symbol="▼" cpu_title options pid_string tmp selected + local -a grep_array + + if [[ -n ${proc[reverse]} ]]; then symbol="▲"; fi + case ${proc_sorting} in + "pid") selected="Pid:"; sort="pid";; + "program") selected="Program:"; sort="comm";; + "arguments") selected="Arguments:"; sort="args";; + "threads") selected="Threads:"; sort="thcount";; + "user") selected="User:"; sort="euser";; + "memory") selected="Mem%"; sort="pmem";; + "cpu lazy"|"cpu responsive") sort="pcpu"; selected="Cpu%";; + esac + + + #* Collect output from ps command to array + if ((width>60)); then format_args=",args:$((width-57+proc[pid_len]))=Arguments:"; format_cmd=15 + else format_cmd=$((width-41+proc[pid_len])); fi + unset 'proc_array[@]' 'pid_array[@]' + + if ((proc[detailed]==0)) && [[ -n ${proc[detailed_name]} ]]; then + unset 'proc[detailed_name]' 'proc[detailed_killed]' 'proc[detailed_cpu_int]' 'proc[detailed_cmd]' + unset 'proc[detailed_mem]' 'proc[detailed_mem_int]' 'proc[detailed_user]' 'proc[detailed_threads]' + unset 'detail_graph[@]' 'detail_mem_graph' 'detail_history[@]' 'detail_mem_history[@]' + unset 'proc[detailed_runtime]' 'proc[detailed_mem_string]' 'proc[detailed_parent_pid]' 'proc[detailed_parent_name]' + fi + + unset 'proc[detailed_cpu]' + + # if [[ -z $filter && $proc_sorting != "cpu responsive" && ${proc[selected]} == 0 && ${proc[page]} -eq 1 ]]; then + # #options="-n $((height-2)) -t" + # options="-t" + if [[ -z $filter ]]; then + options="-t" + fi + + readarray ${options} proc_array < <(ps ax -o pid:${proc[pid_len]}=Pid:,comm:${format_cmd}=Program:${format_args},thcount:3=Tr:,euser:6=User:,pmem=Mem%,pcpu:10=Cpu% --sort ${proc[reverse]:--}${sort}) + + proc_array[0]="${proc_array[0]/ Tr:/ Threads:}" + proc_array[0]="${proc_array[0]/ ${selected}/${symbol}${selected}}" + + if [[ -n $filter ]]; then + grep_array[0]="${proc_array[0]}" + readarray -O 1 -t grep_array < <(echo -e "${proc_array[@]:1}" | grep "${filter}" || true) + proc_array=("${grep_array[@]}") + fi + + proc[pages]=$(( (${#proc_array[@]}-1)/(height-3)+1 )) + if ((proc[page]>proc[pages])); then proc[page]=${proc[pages]}; fi + + + #* Get accurate cpu usage by fetching and comparing values in /proc/"pid"/stat + local operations operation utime stime count time_elapsed cpu_percent_string rgb=231 step add proc_out tmp_value_array i pcpu_usage cpu_int tmp_percent + local -a cpu_percent statfile work_array + + #* Timestamp the values in milliseconds to accurately calculate cpu usage + get_ms proc[new_timestamp] + + for readline in "${proc_array[@]:1}"; do + ((++count)) + + if ((count==height-2)); then + if [[ -n $filter || $proc_sorting == "cpu responsive" || ${proc[selected]} -gt 0 || ${proc[page]} -gt 1 ]] || [[ ${proc[detailed]} -eq 1 && -z ${proc[detailed_killed]} ]]; then : + else break; fi + fi + + + work_array=(${readline}) + + pid="${work_array[0]}" + pcpu_usage="${work_array[-1]}" + + + if [[ ! ${pid_history[*]} =~ ${pid} ]]; then + pid_history+=("${pid}") + fi + + if [[ -n $filter || $proc_sorting == "cpu responsive" ]] && [[ ${proc_array[count]::$((proc[pid_len]+1))} != " " ]]; then + unset pid_string + print -v pid_string -jr ${proc[pid_len]} -t "${pid}" + proc_array[count]="${pid_string}${proc_array[count]#*$pid}" + fi + + if [[ -r "/proc/${pid}/stat" ]] && read -ra statfile /dev/null; then + + utime=${statfile[13]} + stime=${statfile[14]} + + proc[new_${pid}_ticks]=$((utime+stime)) + + + if [[ -n ${proc[old_${pid}_ticks]} ]]; then + + time_elapsed=$((proc[new_timestamp]-proc[old_timestamp])) + + #* Calculate current cpu usage for process, * 1000 (for conversion from ms to seconds) * 1000 (for conversion to floating point) + cpu_percent[count]=$(( ( ( ${proc[new_${pid}_ticks]}-${proc[old_${pid}_ticks]} ) * 1000 * 1000 ) / ( cpu[hz]*time_elapsed*cpu[threads] ) )) + + if ((cpu_percent[count]<0)); then cpu_percent[count]=0 + elif ((cpu_percent[count]>1000)); then cpu_percent[count]=1000; fi + + if ((${#cpu_percent[count]}<=3)); then + printf -v cpu_percent_string "%01d%s" "${cpu_percent[count]::-1}" ".${cpu_percent[count]:(-1)}" + else + cpu_percent_string=${cpu_percent[count]::-1} + fi + + printf -v cpu_percent_string "%5s" "${cpu_percent_string::4}" + + proc_array[count]="${proc_array[count]::-5}${cpu_percent_string}" + + + pid_graph="pid_${pid}_graph" + local -n pid_count="pid_${pid}_count" + + printf -v cpu_int "%01d" "${cpu_percent[count]::-1}" + + #* Get info for detailed box if enabled + if [[ ${pid} == "${proc[detailed_pid]}" ]]; then + if [[ -z ${proc[detailed_name]} ]]; then + local det_array get_mem + read -r -d '' proc[detailed_name] 900)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/10)) + elif ((proc[detailed_mem_int]>600)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/8)) + elif ((proc[detailed_mem_int]>300)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/5)) + elif ((proc[detailed_mem_int]>100)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]/2)) + elif ((proc[detailed_mem_int]<50)); then proc[detailed_mem_int]=$((proc[detailed_mem_int]*2)); fi + unset 'proc[detailed_mem_string]' + floating_humanizer -v proc[detailed_mem_string] -B -s 1 "$(ps -o rss:1 --no-headers -p ${pid} || true)" + if [[ -z ${proc[detailed_mem_string]} ]]; then proc[detailed_mem_string]="? Byte"; fi + fi + + #* Copy process cpu usage to history array and trim earlier entries + if ((${#detail_history[@]}>box[details_width]*2)); then + detail_history=( "${detail_history[@]:${box[details_width]}}" "$((cpu_int+4))") + else + detail_history+=("$((cpu_int+4))") + fi + + #* Copy process mem usage to history array and trim earlier entries + if ((${#detail_mem_history[@]}>box[details_width])); then + detail_mem_history=( "${detail_mem_history[@]:$((box[details_width]/2))}" "${proc[detailed_mem_int]}") + else + detail_mem_history+=("${proc[detailed_mem_int]}") + fi + fi + + #* Create small graphs for all visible processes using more than 1% cpu time + if [[ ${cpu_int} -gt 0 ]]; then pid_count=5; fi + + if [[ -z ${!pid_graph} && ${cpu_int} -gt 0 ]]; then + tmp_value_array=("$((cpu_int+4))") + create_mini_graph -o "pid_${pid}_graph" -nc -w 5 "tmp_value_array" + elif [[ ${pid_count} -gt 0 ]]; then + if [[ ${cpu_int} -gt 9 ]]; then + create_mini_graph -nc -add-value "pid_${pid}_graph" "$((cpu_int+20))" + else + create_mini_graph -nc -add-value "pid_${pid}_graph" "$((cpu_int+4))" + fi + + pid_count=$((${pid_count}-1)) + elif [[ ${pid_count} == "0" ]]; then + unset "pid_${pid}_graph" + unset "pid_${pid}_count" + fi + else + tmp_percent="${proc_array[count]:(-5)}"; tmp_percent="${tmp_percent// /}"; if [[ ${tmp_percent//./} != "$tmp_percent" ]]; then tmp_percent="${tmp_percent::-2}"; fi + if ((tmp_percent>100)); then + proc_array[count]="${proc_array[count]::-5} 100" + fi + fi + + proc[old_${pid}_ticks]=${proc[new_${pid}_ticks]} + + fi + done + + + proc[old_timestamp]=${proc[new_timestamp]} + + if ((proc[detailed]==1)) && [[ -z ${proc[detailed_cpu]} ]]; then proc[detailed_killed]=1 + else unset 'proc[detailed_killed]'; fi + + #* Sort output array based on cpu usage if "cpu responsive" is selected + if [[ ${proc_sorting} == "cpu responsive" ]]; then + local -a sort_array + if [[ -z ${proc[reverse]} ]]; then local sort_rev="-r"; fi + sort_array[0]="${proc_array[0]}" + readarray -O 1 -t sort_array < <(printf "%s\n" "${proc_array[@]:1}" | awk '{ print $NF, $0 }' | sort -n -k1 ${sort_rev}| sed 's/^[0-9\.]* //') + proc_array=("${sort_array[@]}") + fi + + #* Clear up memory by removing variables and graphs of no longer running processes + ((++proc[general_counter])) + if ((proc[general_counter]>100)); then + proc[general_counter]=0 + for ((i=0;i<${#pid_history[@]};i++)); do + if [[ -n ${pid_history[$i]} && ! -e /proc/${pid_history[$i]} ]]; then + unset "pid_${pid_history[i]}_graph" + unset "proc[new_${pid_history[i]}_ticks]" + unset "proc[old_${pid_history[i]}_ticks]" + unset "pid_history[${i}]" + fi + done + pid_history=(${pid_history[@]}) + fi + +} + +collect_net() { #? Collect information from "/proc/net/dev" + local operations operation direction index unit_selector speed speed_B total + local -a net_dev history_sorted history_last + + if [[ $1 == "init" ]]; then + for direction in "download" "upload"; do + net[${direction}_max]=0 + net[${direction}_new_low]=0 + net[${direction}_new_max]=0 + net[${direction}_max_current]=0 + net[${direction}_graph_max]=$((50<<10)) + done + fi + + #* Get the line with relevant net device from /proc/net/dev into array net_dev, index 1 is download, index 9 is upload + get_value -map net_dev -sf "/proc/net/dev" -k "${net[device]}" -a + + #* Timestamp the values to accurately calculate values in seconds + get_ms net[new_timestamp] + for direction in "download" "upload"; do + if [[ $direction == "download" ]]; then index=1 + else index=9; fi + + net[new_${direction}]=${net_dev[index]} + + if [[ -n ${net[old_${direction}]} ]]; then + #* Get total, convert to floating point and format string to best fitting unit in Bytes + floating_humanizer -Byte -v net[total_${direction}] ${net[new_${direction}]} + + #* Calculate current speeds: ("New value" - "Old value") * 1000(for ms to seconds) / ("new_timestamp" - "old_timestamp") + net[speed_${direction}]=$(( (${net[new_${direction}]}-${net[old_${direction}]})*1000/(net[new_timestamp]-net[old_timestamp]) )) + + #* Convert to floating point and format string to best fitting unit in Bytes and Bits per second + floating_humanizer -Byte -per-second -v net[speed_${direction}_byteps] ${net[speed_${direction}]} + floating_humanizer -bit -per-second -v net[speed_${direction}_bitps] ${net[speed_${direction}]} + + #* Update download and upload max values for graph + if ((${net[speed_${direction}]}>${net[${direction}_max]})); then + net[${direction}_max]=${net[speed_${direction}]} + fi + + if ((${net[speed_${direction}]}>${net[${direction}_graph_max]})); then + ((++net[${direction}_new_max])) + if ((net[${direction}_new_low]>0)); then ((net[${direction}_new_low]--)); fi + elif ((${net[${direction}_graph_max]}>10<<10 & ${net[speed_${direction}]}<${net[${direction}_graph_max]}/8)); then + ((++net[${direction}_new_low])) + if ((net[${direction}_new_max]>0)); then ((net[${direction}_new_max]--)); fi + # else + # net[${direction}_new_low]=0 + # net[${direction}_new_max]=0 + fi + + #* Copy download and upload speed to history arrays and trim earlier entries + local -n history="net_history_${direction}" + if ((${#history[@]}>box[net_width]*2)); then + history=( "${history[@]:${box[net_width]}}" "${net[speed_${direction}]}") + else + history+=("${net[speed_${direction}]}") + fi + + #* Check for new max value and set flag to adjust resolution of graph if needed + if ((${net[${direction}_new_max]}>=5)); then + net[${direction}_graph_max]=$((${net[${direction}_max]}+(${net[${direction}_max]}/2) )) + net[${direction}_redraw]=1 + net[${direction}_new_max]=0 + + #* If current max value isn't relevant, sort array to get the next largest value to set graph resolution + elif ((${net[${direction}_new_low]}>=5 & ${#history[@]}>5)); then + history_last=("${history[@]:(-5)}") + sort_array_int "history_last" "history_sorted" + net[${direction}_max]=${history_sorted[0]} + net[${direction}_graph_max]=$(( ${net[${direction}_max]}*3 )) + if ((${net[${direction}_graph_max]}<10<<10)); then net[${direction}_graph_max]=$((10<<10)); fi + net[${direction}_redraw]=1 + net[${direction}_new_low]=0 + fi + fi + + net[old_${direction}]=${net[new_${direction}]} + done + + net[old_timestamp]=${net[new_timestamp]} + +} + +calc_sizes() { #? Calculate width and height of all boxes + local pos calc_size calc_total percent threads=${cpu[threads]} + + #* Calculate heights + for pos in ${box[boxes]/processes/}; do + if [[ $pos = "cpu" ]]; then percent=32; + elif [[ $pos = "mem" ]]; then percent=40; + else percent=28; fi + + #* Multiplying with 10 to convert to floating point + calc_size=$(( (tty_height*10)*(percent*10)/100 )) + + #* Round down if last 2 digits of value is below "50" and round up if above + if ((${calc_size:(-2):1}==0)); then calc_size=$((calc_size+10)); fi + if ((${calc_size:(-2)}<50)); then + calc_size=$((${calc_size::-2})) + else + calc_size=$((${calc_size::-2}+1)) + fi + + #* Subtract from last value if the total of all rounded numbers is larger then terminal height + while ((calc_total+calc_size>tty_height)); do ((--calc_size)); done + calc_total=$((calc_total+calc_size)) + + #* Set calculated values in box array + box[${pos}_line]=$((calc_total-calc_size+1)) + box[${pos}_col]=1 + box[${pos}_height]=$calc_size + box[${pos}_width]=$tty_width + done + + + #* Calculate widths + unset calc_total + for pos in net processes; do + if [[ $pos = "net" ]]; then percent=45; else percent=55; fi + + #* Multiplying with 10 to convert to floating point + calc_size=$(( (tty_width*10)*(percent*10)/100 )) + + #* Round down if last 2 digits of value is below "50" and round up if above + if ((${calc_size:(-2)}<50)); then + calc_size=$((${calc_size::-2})) + else + calc_size=$((${calc_size::-2}+1)) + fi + + #* Subtract from last value if the total of all rounded numbers is larger then terminal width + while ((calc_total+calc_size>tty_width)); do ((--calc_size)); done + calc_total=$((calc_total+calc_size)) + + #* Set calculated values in box array + box[${pos}_col]=$((calc_total-calc_size+1)) + box[${pos}_width]=$calc_size + done + + #* Copy numbers around to get target layout + box[mem_width]=${box[net_width]} + box[processes_line]=${box[mem_line]} + box[processes_height]=$((box[mem_height]+box[net_height])) + + # threads=${box[testing]} #! For testing, remove <-------------- + + #* Recalculate size of process box if currently showing detailed process information + if ((proc[detailed]==1)); then + box[details_line]=${box[processes_line]} + box[details_col]=${box[processes_col]} + box[details_width]=${box[processes_width]} + box[details_height]=8 + box[processes_line]=$((box[processes_line]+box[details_height])) + box[processes_height]=$((box[processes_height]-box[details_height])) + fi + + #* Calculate number of columns and placement of cpu meter box + local cpu_line=$((box[cpu_line]+1)) cpu_width=$((box[cpu_width]-2)) cpu_height=$((box[cpu_height]-2)) + if ((threads>(cpu_height-3)*3 && tty_width>=200)); then box[p_width]=114; box[p_height]=$((threads/4+3)) + elif ((threads>(cpu_height-3)*2 && tty_width>=150)); then box[p_width]=84; box[p_height]=$((threads/3+4)) + elif ((threads>cpu_height-3 && tty_width>=100)); then box[p_width]=54; box[p_height]=$((threads/2+3)) + else box[p_width]=24; box[p_height]=$((threads+3)) + fi + + if [[ $check_temp == true ]]; then + box[p_width]=$(( box[p_width]+13+( ( ( box[p_width]-24 ) /24 ) *7 ) )) + fi + + if ((box[p_height]>cpu_height)); then box[p_height]=$cpu_height; fi + box[p_col]="$((cpu_width-box[p_width]+2))" + box[p_line]="$((cpu_line+(cpu_height/2)-(box[p_height]/2)+1))" + + #* Calculate placement of mem divider + local mem_line=$((box[mem_line]+1)) mem_width=$((box[mem_width]-2)) mem_height=$((box[mem_height]-2)) mem_col=$((box[mem_col]+1)) + box[m_width]=$((mem_width/2)) + box[m_width2]=${box[m_width]} + if ((box[m_width]+box[m_width2]9)); then box[n_height]=9 + else box[n_height]=$net_height; fi + box[n_col]="$((net_width-box[n_width]+2))" + box[n_line]="$((net_line+(net_height/2)-(box[n_height]/2)+1))" + + +} + +draw_bg() { #? Draw all box outlines + local this_box cpu_p_width i cpu_model_len + + unset boxes_out + for this_box in ${box[boxes]}; do + create_box -v boxes_out -col ${box[${this_box}_col]} -line ${box[${this_box}_line]} -width ${box[${this_box}_width]} -height ${box[${this_box}_height]} -fill -lc "${box[${this_box}_color]}" -title ${this_box} + done + + #* Misc cpu box + if [[ $check_temp == true ]]; then cpu_model_len=18; else cpu_model_len=9; fi + create_box -v boxes_out -col $((box[p_col]-1)) -line $((box[p_line]-1)) -width ${box[p_width]} -height ${box[p_height]} -lc 30 -t "${cpu[model]:0:${cpu_model_len}}" + print -v boxes_out -m ${box[cpu_line]} $((box[cpu_col]+10)) -rs \ + -fg ${box[cpu_color]} -t "┤" -b -fg "90" -t "m" -fg "#ee" -t "enu" -rs -fg ${box[cpu_color]} -t "├" + # -fg ${box[cpu_color]} -t "┤" -b -fg "90" -t "h" -fg "#ee" -t "elp" -rs -fg ${box[cpu_color]} -t "├"\ + # -fg ${box[cpu_color]} -t "┤" -b -fg "90" -t "q" -fg "#ee" -t "uit" -rs -fg ${box[cpu_color]} -t "├" + + + #* Misc mem + print -v boxes_out -m ${box[mem_line]} $((box[mem_col]+box[m_width]+2)) -rs -fg ${box[mem_color]} -t "┤" -fg ee -b -t "disks" -rs -fg ${box[mem_color]} -t "├" + print -v boxes_out -m ${box[mem_line]} $((box[mem_col]+box[m_width])) -rs -fg ${box[mem_color]} -t "┬" + print -v boxes_out -m $((box[mem_line]+box[mem_height]-1)) $((box[mem_col]+box[m_width])) -fg ${box[mem_color]} -t "┴" + for((i=1;i<=box[mem_height]-2;i++)); do + print -v boxes_out -m $((box[mem_line]+i)) $((box[mem_col]+box[m_width])) -fg 30 -t "│" + done + + + #* Misc net box + create_box -v boxes_out -col $((box[n_col]-1)) -line $((box[n_line]-1)) -width ${box[n_width]} -height ${box[n_height]} -lc 30 -t "Download" + print -v boxes_out -m $((box[n_line]+box[n_height]-2)) $((box[n_col]+1)) -rs -fg 30 -t "┤" -fg ee -b -t "Upload" -rs -fg 30 -t "├" + + + if [[ $1 == "quiet" ]]; then draw_out="${boxes_out}" + else echo -en "${boxes_out}"; fi + draw_update_string $1 +} + +draw_cpu() { #? Draw cpu and core graphs and print percentages + local cpu_out i name cpu_p_color temp_color y pt_line pt_col p_normal_color="#CC" threads=${cpu[threads]} + local meter meter_size meter_width temp_var cpu_out_var core_name temp_name temp_width + + #* Get variables from previous calculations + local col=$((box[cpu_col]+1)) line=$((box[cpu_line]+1)) width=$((box[cpu_width]-2)) height=$((box[cpu_height]-2)) + local p_width=${box[p_width]} p_height=${box[p_height]} p_col=${box[p_col]} p_line=${box[p_line]} + + #* If resized recreate cpu meter/graph box, cpu graph and core graphs + if ((resized>0)); then + local graph_a_size graph_b_size + graph_a_size=$((height/2)); graph_b_size=${graph_a_size} + if ((graph_a_size*224+temp_width)); then + name="CPU Total "; meter_width=$((p_width-17-temp_width)); meter_size="big" + elif ((i==0)); then meter_size="big" + fi + + + #* Create cpu usage meter + if ((i==0)); then + create_meter -v meter -b $meter_size -w $meter_width -f -c color_cpu ${cpu_usage[i]} + else + core_name="cpu_core_graph_$i" + meter="${!core_name}" + fi + + if ((p_width>84+temp_width & i>=(p_height-2)*3-2)); then pt_line=$((p_line+i-y*4)); pt_col=$((p_col+84+temp_width)) + elif ((p_width>54+temp_width & i>=(p_height-2)*2-1)); then pt_line=$((p_line+i-y*3)); pt_col=$((p_col+54+temp_width)) + elif ((p_width>24+temp_width & i>=p_height-2)); then pt_line=$((p_line+i-y*2)); pt_col=$((p_col+24+temp_width)) + else y=$i; fi + + print -v cpu_out_var -m $((pt_line+y)) $pt_col -rs -fg $p_normal_color -jl 7 -t "$name" -fg 30 "⡀⡀⡀⡀⡀⡀⡀⡀⡀⡀" -l 10 -t "$meter"\ + -fg $cpu_p_color -jr 4 -t "${cpu_usage[i]}" -fg $p_normal_color -t "%" + if [[ $check_temp == true ]]; then + print -v cpu_out_var -fg 30 " ⡀⡀⡀⡀⡀" -l 7 -t " ${!temp_name}" -fg $temp_color -jr 4 -t ${cpu[temp_${i}]} -fg $p_normal_color -t ${cpu[temp_unit]} + fi + + if (( i>(p_height-2)*( p_width/(24+temp_width) )-( p_width/(24+temp_width) )-1 )); then break; fi + done + + #* Print current CPU frequency right of the title in the meter box + print -v cpu_out_var -m $((p_line-1)) $((p_col+p_width-5-${#cpu[freq_string]})) -fg 30 -t "┤" -fg ee -b -t "${cpu[freq_string]}" -rs -fg 30 -t "├" + + #* Print created text, graph and meters to output variable + draw_out="${draw_out}${cpu_graph_a[*]}${cpu_graph_b[*]}${cpu_out_var}" + +} + +draw_mem() { #? Draw mem, swap and disk statistics + + if ((mem[counter]>0 & resized==0)); then return; fi + + local i swap_used_meter swap_free_meter mem_available_meter mem_free_meter mem_used_meter mem_cached_meter normal_color="#CC" value_text + local meter_mod_w meter_mod_pos value type m_title meter_options + local -a types=("mem") + unset mem_out + + if [[ -n $swap_on ]]; then types+=("swap"); fi + + #* Get variables from previous calculations + local col=$((box[mem_col]+1)) line=$((box[mem_line]+1)) width=$((box[mem_width]-2)) height=$((box[mem_height]-2)) + local m_width=${box[m_width]} m_height=${box[m_height]} m_col=${box[m_col]} m_line=${box[m_line]} mem_line=$((box[mem_col]+box[m_width])) + + #* Create text and meters for memory and swap and adapt sizes based on available height + local y_pos=$m_line v_height=8 list value meter inv_meter + for type in ${types[@]}; do + local -n type_name="$type" + if [[ $type == "mem" ]]; then + m_title="memory" + else + m_title="$type" + if ((height>14)); then ((y_pos++)); fi + fi + + #* Print name of type and total amount in humanized base 2 bytes + print -v mem_out -m $y_pos $m_col -rs -fg $normal_color -b -jl 9 -t "${m_title^}:" -m $((y_pos++)) $((mem_line-10)) -jr 9 -trans -t " ${type_name[total_string]::$((m_width-11))}" + + for value in "used" "available" "cached" "free"; do + if [[ $type == "swap" && $value == "available" ]]; then value="free" + elif [[ $type == "swap" && $value == "cached" ]]; then break 2; fi + + value_text="${value::$((m_width-12))}" + if ((height<14)); then value_text="${value_text::5}"; fi + + #* Print name of value and value amount in humanized base 2 bytes + print -v mem_out -m $y_pos $m_col -rs -fg $normal_color -jl 9 -t "${value_text^}:" -m $((y_pos++)) $((mem_line-10)) -jr 9 -trans -t " ${type_name[${value}_string]::$((m_width-11))}" + + #* Create meter for value and calculate size and placement depending on terminal size + if ((height>v_height++ | tty_width>100)); then + if ((height<=v_height & tty_width<150)); then + meter_mod_w=12 + meter_mod_pos=7 + ((y_pos--)) + elif ((height<=v_height)); then + print -v mem_out -m $((--y_pos)) $((m_col+5)) -jr 4 -t "${type_name[${value}_percent]}%" + meter_mod_w=14 + meter_mod_pos=10 + fi + create_meter -v ${type}_${value}_meter -w $((m_width-7-meter_mod_w)) -f -c color_${value}_graph ${type_name[${value}_percent]} + + meter="${type}_${value}_meter" + print -v mem_out -m $((y_pos++)) $((m_col+meter_mod_pos)) -t "${!meter}" -rs -fg $normal_color + + if [[ -z $meter_mod_w ]]; then print -v mem_out -jr 4 -t "${type_name[${value}_percent]}%"; fi + fi + done + done + + + #* Create text and meters for disks and adapt sizes based on available height + local disk_num disk_name disk_value v_height2 + y_pos=$m_line + m_col=$((m_col+m_width)) + m_width=${box[m_width2]} + v_height=$((${#disks_name[@]})) + unset meter_mod_w meter_mod_pos + + for disk_name in "${disks_name[@]}"; do + if ((y_pos>m_line+height-2)); then break; fi + + #* Print folder disk is mounted on and total size in humanized base 2 bytes + print -v mem_out -m $((y_pos++)) $m_col -rs -fg $normal_color -b -jl 9 -t "${disks_name[disk_num]::10}" -jr $((m_width-11)) -t "${disks_total[disk_num]::$((m_width-11))}" + + for value in "used" "free"; do + if ((height=v_height*5 | tty_width>100)); then + local -n disk_value_percent="disks_${value}_percent" + if ((height<=v_height*5 & tty_width<150)); then + meter_mod_w=12 + meter_mod_pos=7 + ((y_pos--)) + elif ((height<=v_height*5)); then + print -v mem_out -m $((--y_pos)) $((m_col+5)) -jr 4 -t "${disk_value_percent[disk_num]}%" + meter_mod_w=14 + meter_mod_pos=10 + fi + create_meter -v disk_${disk_num}_${value}_meter -w $((m_width-7-meter_mod_w)) -f -c color_${value}_graph ${disk_value_percent[disk_num]} + + meter="disk_${disk_num}_${value}_meter" + print -v mem_out -m $((y_pos++)) $((m_col+meter_mod_pos)) -t "${!meter}" -rs -fg $normal_color + + if [[ -z $meter_mod_w ]]; then print -v mem_out -jr 4 -t "${disk_value_percent[disk_num]}%"; fi + fi + if ((y_pos>m_line+height-1)); then break; fi + done + if ((height>=v_height*4 & height=v_height*6)); then ((y_pos++)); fi + ((++disk_num)) + done + + + #* Print created text, graph and meters to output variable + draw_out="${draw_out}${mem_graph[*]}${swap_graph[*]}${mem_out}" + +} + +draw_processes() { #? Draw processes and values to screen + local argument="$1" + local line=${box[processes_line]} col=${box[processes_col]} width=${box[processes_width]} height=${box[processes_height]} out_line y=1 rgb=231 step add proc_out checker=2 page_string + local reverse_string reverse_pos order_left="───────────┤" filter_string current_num detail_location det_no_add + local pid=0 pid_graph pid_r=13 pid_g=231 pid_b=86 p_step p_add bg_add bg_step rgb_d=70 d_step proc_start up_fg down_fg page_up_fg page_down_fg this_box=processes + local d_width=${box[details_width]} d_height=${box[details_height]} d_line=${box[details_line]} d_col=${box[details_col]} + local detail_graph_width=$((d_width/3+2)) detail_graph_height=$((d_height-1)) kill_fg det_mod + local right_width=$((d_width-detail_graph_width-2)) + local right_col=$((d_col+detail_graph_width+4)) + + add=$((200/height)) + p_add=$((110/height)) + d_add=$((rgb_d/height)) + + unset proc_out + + #* Details box + if ((proc[detailed_change]>0)) || ((proc[detailed]>0 & resized>0)); then + proc[detailed_change]=0 + proc[order_change]=1 + proc[page_change]=1 + if ((proc[detailed]==1)); then + unset proc_det + local enter_fg enter_a_fg i det_y=6 dets cmd_y + + if [[ ${#detail_history[@]} -eq 1 ]] || ((resized>0)); then + unset proc_det2 + #detail_mem_graph ${#detail_history[@]} -lt 2 + create_graph -o detail_graph -d $((d_line+1)) $((d_col+1)) ${detail_graph_height} ${detail_graph_width} -c color_cpu_graph -n detail_history + if ((tty_width>120)); then create_mini_graph -o detail_mem_graph -w $((right_width/3-3)) -nc detail_mem_history; fi + det_no_add=1 + + for detail_location in "${d_line}" "$((d_line+d_height))"; do + print -v proc_det2 -m ${detail_location} $((d_col+1)) -rs -fg ${box[processes_color]} -rp $((d_width-2)) -t "─" + done + for((i=1;i128)); then print -v proc_det2 -m -r 1 -t "┤" -fg "ee" -b -t "${proc[detailed_pid]}" -rs -fg ${box[processes_color]} -t "├"; fi + + + + if ((${#proc[detailed_cmd]}>(right_width-6)*2)); then ((det_y--)); dets=2 + elif ((${#proc[detailed_cmd]}>right_width-6)); then dets=1; fi + + + + #print -v proc_det2 -m $((d_line+det_y++)) ${right_col} -fg ee -b -jc $((right_width-4)) -t "Start command:" + print -v proc_det2 -fg ee -b + for i in C M D; do + print -v proc_det2 -m $((d_line+5+cmd_y++)) $right_col -t "$i" + done + + + print -v proc_det2 -m $((d_line+det_y++)) $((right_col+1)) -jc $((right_width-4)) -rs -fg ee -t "${proc[detailed_cmd]::$((right_width-6))}" + if ((dets>0)); then print -v proc_det2 -m $((d_line+det_y++)) $((right_col+1)) -jl $((right_width-4)) -t "${proc[detailed_cmd]:$((right_width-6)):$((right_width-6))}"; fi + if ((dets>1)); then print -v proc_det2 -m $((d_line+det_y)) $((right_col+1)) -jl $((right_width-4)) -t "${proc[detailed_cmd]:$(( (right_width-6)*2 )):$((right_width-6))}"; fi + + fi + + + if ((proc[selected]>0)); then enter_fg="50"; enter_a_fg="50"; else enter_fg="ee"; enter_a_fg="90"; fi + print -v proc_det -m ${d_line} $((d_col+d_width-11)) -fg ${box[processes_color]} -t "┤" -fg $enter_fg -b -t "close " -fg $enter_a_fg -t "↲" -rs -fg ${box[processes_color]} -t "├" + if ((tty_width<129)); then det_mod="-8"; fi + if [[ -z ${proc[detailed_killed]} ]]; then + print -v proc_det -m ${d_line} $((d_col+detail_graph_width+4+det_mod)) -t "┤" -fg $enter_a_fg -b -t "t" -fg $enter_fg -t "erminate" -rs -fg ${box[processes_color]} -t "├" + print -v proc_det -r 1 -t "┤" -fg $enter_a_fg -b -t "k" -fg $enter_fg -t "ill" -rs -fg ${box[processes_color]} -t "├" + if ((tty_width>104)); then print -v proc_det -r 1 -t "┤" -fg $enter_a_fg -b -t "i" -fg $enter_fg -t "nterrupt" -rs -fg ${box[processes_color]} -t "├"; fi + fi + + proc_det="${proc_det2}${proc_det}" + proc_out="${proc_det}" + + elif ((resized==0)); then + unset proc_det + create_box -v proc_out -col ${box[${this_box}_col]} -line ${box[${this_box}_line]} -width ${box[${this_box}_width]} -height ${box[${this_box}_height]} -fill -lc "${box[${this_box}_color]}" -title ${this_box} + fi + fi + #! Use ps for better detailed stats!!!!! + if [[ ${proc[detailed]} -eq 1 ]]; then + local det_status status_color + if [[ -z $det_no_add && $1 != "now" && -z ${proc[detailed_killed]} ]]; then + create_graph -add-last detail_graph detail_history + if ((tty_width>120)); then create_mini_graph -w $((right_width/3-3)) -nc -add-last detail_mem_graph detail_mem_history; fi + fi + + print -v proc_out -fg ee -b + cmd_y=0 + for i in C P U; do + print -v proc_out -m $((d_line+3+cmd_y++)) $((d_col+1)) -t "$i" + done + print -v proc_out -m $((d_line+1)) $((d_col+1)) -fg "dd" -t "${proc[detailed_cpu]}%" + + if [[ -n ${proc[detailed_killed]} ]]; then det_status="stopped"; status_color="ee3030" + else det_status="running"; status_color="30ee30"; fi + print -v proc_out -m $((d_line+1)) ${right_col} -fg ee -b -jc $((right_width/3-1)) -t "Status:" -jc $((right_width/3-1)) -t "User:" -jc $((right_width/3-1)) -t "Threads:" + print -v proc_out -m $((d_line+2)) ${right_col} -rs -fg ${status_color} -jc $((right_width/3-1)) -t "${det_status}" -jc $((right_width/3-1)) -fg ee -t "${proc[detailed_user]}" -jc $((right_width/3-1)) -t "${proc[detailed_threads]}" + + print -v proc_out -m $((d_line+4)) ${right_col} -fg ee -b -jr $((right_width/3+2)) -t "Memory: ${proc[detailed_mem]}%" -t " " + if ((tty_width>120)); then print -v proc_out -rs -fg 30 -rp $((right_width/3-3)) "⡀" -l $((right_width/3-3)) -fg 13 231 86 -t "${detail_mem_graph}" -t " "; fi + print -v proc_out -fg ee -b -t "${proc[detailed_mem_string]}" + #if ((tty_width>120)); then print -v proc_out -m $((d_line+4)) ${right_col} -r $((right_width/3-2)) -rs -t "${detail_mem_graph}"; fi + + fi + + #* proc[detailed_name] proc[detailed_cpu] proc[detailed_cpu_int] proc[detailed_cmd] detail_history + #* proc[detailed_mem] proc[detailed_mem_int] proc[detailed_user] proc[detailed_threads] + #* proc[detailed_runtime] proc[detailed_mem_string] proc[detailed_parent_pid] proc[detailed_parent_name] + + if ((proc[page]==1)); then proc_start=1 + else proc_start=$(( (height-3)*(proc[page]-1)+1 )); fi + + if ((proc_start+proc[selected]>${#proc_array[@]})); then proc[selected]=$((${#proc_array[@]}-proc_start)); fi + + if ((proc[selected]>1)); then + rgb="$(( 231-( add*(proc[selected]-1) ) ))" + rgb_d="$(( 70-( d_add*(proc[selected]-1) ) ))" + pid_g="$(( 231-( p_add*(proc[selected]-1) ) ))" + pid_b="$(( 86-( p_add*(proc[selected]-1) ) ))" + fi + + current_num=1 + + print -v proc_out -rs -m $((line+y++)) $((col+1)) -fg 235 235 235 -b -t "${proc_array[0]} " -rs + + + for out_line in "${proc_array[@]:$proc_start}"; do + pid="${out_line::${proc[pid_len]}}"; pid="${pid##* }" + pid_graph="pid_${pid}_graph" + + if ((current_num==proc[selected])); then print -v proc_out -bg "#7e2626" -b; proc[selected_pid]="$pid" + + else print -v proc_out -rs; fi + + print -v proc_out -m $((line+y)) $((col+1)) -fg $((rgb-step)) $((rgb-step)) $((rgb-step)) -t "${out_line} " + + if ((current_num==proc[selected])); then print -v proc_out -rs -bg "#7e2626"; fi + print -v proc_out -m $((line+y)) $((col+width-12)) -fg $((rgb_d-d_step)) $((rgb_d-d_step)) $((rgb_d-d_step)) -t "⡀⡀⡀⡀⡀" + + if [[ -n ${!pid_graph} ]]; then + print -v proc_out -m $((line+y)) $((col+width-12)) -fg $pid_r $((pid_g-p_step)) $(( pid_b-(p_step/2) )) -t "${!pid_graph}" + fi + + ((y++)) + ((current_num++)) + if ((y>height-2)); then break; fi + if ((current_num=proc[selected])); then + step=$((step+add)) + p_step=$((p_step+p_add)) + d_step=$((d_step+d_add)) + fi + + done + print -v proc_out -rs + while ((y<=height-2)); do + print -v proc_out -m $((line+y++)) $((col+1)) -rp $((width-2)) -t " " + done + + if ((proc[order_change]==1 | proc[filter_change]==1 | resized>0)); then + unset proc_misc + proc[order_change]=0 + proc[filter_change]=0 + proc[page_change]=1 + print -v proc_misc -m $line $((col+13)) -fg ${box[processes_color]} -rp $((box[processes_width]-14)) -t "─" -rs + + if ((proc[detailed]==1)); then + print -v proc_misc -m $((d_line+d_height)) $((d_col+detail_graph_width+2)) -fg ${box[processes_color]} -t "┴" -rs + fi + + if ((tty_width>100)); then + reverse_string="-fg ${box[processes_color]} -t ┤ -fg 90${proc[reverse]:+ -ul} -b -t r -fg ee -t everse -rs -fg ${box[processes_color]} -t ├" + reverse_pos=9 + #unset order_left + fi + print -v proc_misc -m $line $((col+width-${#proc_sorting}-8-reverse_pos)) -rs ${reverse_string}\ + -fg ${box[processes_color]} -t "┤" -fg 90 -b -t "‹" -fg ee -t " ${proc_sorting} " -fg 90 -t "›" -rs -fg ${box[processes_color]} -t "├" + + if [[ -z $filter && -z $input_to_filter ]]; then + print -v proc_misc -m $line $((col+14)) -fg ${box[processes_color]} -t "┤" -fg 90 -b -t "f" -fg ee -t "ilter" -rs -fg ${box[processes_color]} -t "├" + elif [[ -n $input_to_filter ]]; then + if [[ ${#filter} -le $((width-35-reverse_pos)) ]]; then filter_string="${filter}" + elif [[ ${#filter} -gt $((width-35-reverse_pos)) ]]; then filter_string="${filter: (-$((width-35-reverse_pos)))}" + fi + print -v proc_misc -m $line $((col+14)) -fg ${box[processes_color]} -t "┤" -fg ee -b -t "${filter_string}" -fg aa -bl -t "█" -rs -fg ${box[processes_color]} -t "├" + elif [[ -n $filter ]]; then + if [[ ${#filter} -le $((width-35-reverse_pos-4)) ]]; then filter_string="${filter}" + elif [[ ${#filter} -gt $((width-35-reverse_pos-4)) ]]; then filter_string="${filter::$((width-35-reverse_pos-4))}" + fi + print -v proc_misc -m $line $((col+14)) -fg ${box[processes_color]} -t "┤" -fg 90 -b -t "f" -fg ee -t " ${filter_string} " -fg 90 -t "c" -rs -fg ${box[processes_color]} -t "├" + fi + + + + proc_out="${proc_out}${proc_misc}" + fi + + if ((proc[page_change]==1 | resized>0)); then + unset proc_misc2 + proc[page_change]=0 + if ((proc[selected]>0)); then up_fg=bb; kill_fg=70; else up_fg=40; kill_fg=30; fi + if ((proc[selected]==${#proc_array[@]}-proc_start)); then down_fg=40; else down_fg=bb; fi + + if ((proc[page]>1)); then page_up_fg=bb; else page_up_fg=40; fi + if ((proc[page]100)); then print -v proc_misc2 -r 1 -t "┤" -fg $kill_fg -b -t "t" -fg $up_fg -t "erminate" -rs -fg ${box[processes_color]} -t "├"; fi + if ((tty_width>111)); then print -v proc_misc2 -r 1 -t "┤" -fg $kill_fg -b -t "k" -fg $up_fg -t "ill" -rs -fg ${box[processes_color]} -t "├"; fi + if ((tty_width>126)); then print -v proc_misc2 -r 1 -t "┤" -fg $kill_fg -b -t "i" -fg $up_fg -t "nterrupt" -rs -fg ${box[processes_color]} -t "├"; fi + + + + + proc_out="${proc_out}${proc_misc2}" + fi + + #page_string="1/$((${#proc_array[@]}/(height-2)))" + #print -v proc_out -m $line $((col+width-${#page_string}-6)) -rs -fg ${box[processes_color]} -t "──┤" -fg ee -b -t "${page_string}" -rs -fg ${box[processes_color]} -t "├" + + + + if [[ $argument == "now" ]]; then + echo -en "${detail_graph[*]}${proc_out}" + else + draw_out="${draw_out}${detail_graph[*]}${proc_out}" + fi + +} + +draw_net() { #? Draw net information and graphs to screen + local net_out + #* Get variables from previous calculations + local col=$((box[net_col]+1)) line=$((box[net_line]+1)) width=$((box[net_width]-2)) height=$((box[net_height]-2)) + local n_width=${box[n_width]} n_height=${box[n_height]} n_col=${box[n_col]} n_line=${box[n_line]} + + #* If resized recreate net meter box and net graphs + if ((resized>0)); then + local graph_a_size graph_b_size + graph_a_size=$(( (height)/2 )); graph_b_size=${graph_a_size} + if ((graph_a_size*20)); then + create_graph -o download_graph -d $line $col ${net[graph_a_size]} $((width-n_width-2)) -c color_download_graph -n -max "${net[download_graph_max]}" net_history_download + else + create_graph -max "${net[download_graph_max]}" -add-last download_graph net_history_download + fi + if ((net[upload_redraw]==1 | resized>0)); then + create_graph -o upload_graph -d $((line+net[graph_a_size])) $col ${net[graph_b_size]} $((width-n_width-2)) -c color_upload_graph -i -n -max "${net[upload_graph_max]}" net_history_upload + else + create_graph -max "${net[upload_graph_max]}" -i -add-last upload_graph net_history_upload + fi + + #* Create text depening on box height + local ypos=$n_line + + print -v net_out -fg cc -m $((ypos++)) $n_col -jl 10 -t "▼ Byte:" -jr 12 -t "${net[speed_download_byteps]}" + if ((height>4)); then print -v net_out -fg cc -m $((ypos++)) $n_col -jl 10 -t "▼ Bit:" -jr 12 -t "${net[speed_download_bitps]}"; fi + if ((height>6)); then print -v net_out -fg cc -m $((ypos++)) $n_col -jl 10 -t "▼ Total:" -jr 12 -t "${net[total_download]}"; fi + + if ((height>8)); then ((ypos++)); fi + print -v net_out -fg cc -m $((ypos++)) $n_col -jl 10 -t "▲ Byte:" -jr 12 -t "${net[speed_upload_byteps]}" + if ((height>7)); then print -v net_out -fg cc -m $((ypos++)) $n_col -jl 10 -t "▲ Bit:" -jr 12 -t "${net[speed_upload_bitps]}"; fi + if ((height>5)); then print -v net_out -fg cc -m $((ypos++)) $n_col -jl 10 -t "▲ Total:" -jr 12 -t "${net[total_upload]}"; fi + + + #* Print graphs and text to output variable + draw_out="${draw_out}${download_graph[*]}${upload_graph[*]}${net_out}" +} + +draw_clock() { #? Draw a clock at top of screen + if [[ -z $draw_clock ]]; then return; fi + local width=${box[cpu_width]} color=${box[cpu_color]} old_time_string="${time_string}" + #time_string="$(date ${draw_clock})" + printf -v time_string "%(${draw_clock})T" + if [[ $old_time_string != "$time_string" || $resized -gt 0 ]]; then + unset clock_out + print -v clock_out -m 1 $((width/2-${#time_string}/2)) -rs -fg ${color} -t "┤" -fg ee -b -t "${time_string}" -fg ${color} -t "├" + fi + if [[ $1 == "now" ]]; then echo -en "${clock_out}${cursor}"; fi + #print -v draw_out -t "${clock_out}" +} + +draw_update_string() { + unset update_string + update_sec="$(bc <<<"scale=3; ${update_ms}/1000")" + print -v update_string -m ${box[cpu_line]} $((box[cpu_col]+box[cpu_width]-${#update_ms}-14)) -rs -fg ${box[cpu_color]} -t "────┤" -fg "aa" -b -t "+" -fg "ee" -b -t " ${update_ms}ms " -fg "aa" -b -t "-" -rs -fg ${box[cpu_color]} -t "├" + if [[ $1 == "quiet" ]]; then draw_out="${draw_out}${update_string}" + else echo -en "${update_string}"; fi +} + +pause_() { #? Pause input and draw a darkened version of main ui + local pause_out ext_var + if [[ -n $1 && $1 != "off" ]]; then local -n pause_out=${1}; ext_var=1; fi + if [[ $1 != "off" ]]; then + prev_screen="${boxes_out}${proc_det}${last_screen}${mem_out}${proc_misc}${proc_misc2}${update_string}${clock_out}" + pause_screen="\e[1;38;5;236m$(echo -n "${prev_screen}" | sed -E 's/\\e\[[0-9;\-]*m//g')\e[0m" + + if [[ -z $ext_var ]]; then echo -en "${pause_screen}" + else pause_out="${pause_screen}"; fi + + elif [[ $1 == "off" ]]; then + echo -en "${prev_screen}" + unset pause_screen prev_screen + fi +} + +unpause_() { #? Unpause + pause_ off +} + +menu_() { #? Shows the main menu overlay + local menu i count keypress selected_int=0 selected up local_rez d_banner=1 menu_out bannerd skipped menu_pause out_out wait_string + local -a menus=("options" "help" "quit") color + + until false; do + draw_clock + pause_ menu_pause + unset draw_out + + if [[ -z ${bannerd} ]]; then + draw_banner "$((tty_height/2-10))" bannerd + unset d_banner + fi + if [[ -n ${keypress} || -z ${menu_out} ]]; then + unset menu_out + print -v menu_out -t "${bannerd}" + print -v menu_out -d 1 + selected="${menus[selected_int]}" + unset up + for menu in "${menus[@]}"; do + if [[ $menu == "$selected" ]]; then + local -n menu_array="menu_${menu}_selected" + color=("#c55e5e" "#c23d3d" "#a13030" "#8c2626") + else + local -n menu_array="menu_${menu}" + color=("#bb" "#aa" "#99" "#88") + fi + up=$((up+${#menu_array[@]})) + for((i=0;i<${#menu_array[@]};i++)); do + print -v menu_out -d 1 -fg ${color[i]} -c -trans -t "${menu_array[i]}" + done + done + print -v menu_out -u ${up} + fi + unset out_out + out_out="${menu_pause}${menu_out}" + echo -e "${out_out}${cursor}" + + + get_ms timestamp_end + time_left=$((timestamp_start+update_ms-timestamp_end)) + if ((time_left>1000)); then wait_string=1; time_left=$((time_left-1000)) + elif ((time_left>1)); then printf -v wait_string ".%03d" "${time_left}"; time_left=0 + else wait_string="0.001"; time_left=0; fi + + get_key -v keypress -w ${wait_string} + if ((resized>0)); then + while [[ $size_error == true ]] ; do size_error_msg; done + calc_sizes; draw_bg quiet; time_left=0; unset menu_out + unset bannerd + fi + + case "$keypress" in + up|shift_tab) if ((selected_int>0)); then ((selected_int--)); else selected_int=$((${#menus[@]}-1)); fi ;; + down|tab) if ((selected_int<${#menus[@]}-1)); then ((++selected_int)); else selected_int=0; fi ;; + enter|space) + case "$selected" in + options) options_ ;; + help) help_ ;; + quit) quit_ ;; + esac + ;; + m|M|escape|backspace) break ;; + q|Q) quit_ ;; + esac + + if ((time_left==0)); then get_ms timestamp_start; collect_and_draw; fi + if ((resized>0)); then resized=0; fi + + done + unpause_ + +} + +help_() { #? Shows the help overlay + local tmp from_menu col lineQ y i help_out help_pause redraw=1 wait_string + local -a shortcuts descriptions + + shortcuts=( + "(Esc, M, m)" + "(F2, O, o)" + "(F1, H, h)" + "(Ctrl-C, Q, q)" + "(+, A, a) (-, S, s)" + "(Up) (Down)" + "(Enter)" + "(Pg Up) (Pg Down)" + "(Home) (End)" + "(Left, Right)" + "(R, r)" + "(F, f)" + "(C, c)" + "(T, t)" + "(K, k)" + "(I, i)" + ) + descriptions=( + "Shows main menu." + "Shows options." + "Shows this window." + "Quits program." + "Add/Subtract 100ms to/from update timer." + "Select in process list." + "Show detailed information for selected process." + "Jump 1 page in process list." + "Jump to first or last entry in process list." + "Select previous/next sorting column." + "Reverse sorting order in processes box." + "Input a string to filter processes with." + "Clear any entered filter." + "Terminate selected process with SIGTERM - 15." + "Kill selected process with SIGKILL - 9." + "Interrupt selected process with SIGINT - 2." + ) + + if [[ -n $pause_screen ]]; then from_menu=1; fi + + until [[ -n $tmp ]]; do + if [[ -n $redraw ]]; then + col=$((tty_width/2-36)); line=$((tty_height/2-4)); y=1 + unset redraw help_out + draw_banner "$((tty_height/2-11))" help_out + print -d 1 + create_box -v help_out -w 72 -h $((${#shortcuts[@]}+3)) -l $((line++)) -c $((col++)) -fill -lc 40 -title "help" + ((++col)) + + print -v help_out -r 1 -fg ee -b -jl 20 -t "Key:" -jl 48 -t "Description:" -m $((line+y++)) $col + + for((i=0;i<${#shortcuts[@]};i++)); do + print -v help_out -fg cc -b -jl 20 -t "${shortcuts[i]}" -rs -fg ee -jl 48 -t "${descriptions[i]}" -m $((line+y++)) $col + done + fi + + draw_clock + pause_ help_pause + unset draw_out + echo -en "${help_pause}${help_out}${cursor}" + + get_ms timestamp_end + time_left=$((timestamp_start+update_ms-timestamp_end)) + if ((time_left>1000)); then wait_string=1; time_left=$((time_left-1000)) + elif ((time_left>0)); then printf -v wait_string ".%03d" "${time_left}"; time_left=0 + else wait_string="0.001"; time_left=0; fi + + get_key -v tmp -w "${wait_string}" + if ((resized>0)); then + while [[ $size_error == true ]] ; do size_error_msg; done + sleep 0.5 + calc_sizes; draw_bg quiet; redraw=1 + d_banner=1 + fi + if ((time_left==0)); then get_ms timestamp_start; collect_and_draw; fi + if ((resized>0)); then resized=0; fi + done + + if [[ -n $from_menu ]]; then pause_ + else unpause_; fi +} + +options_() { #? Shows the options overlay + local keypress from_menu col line y=1 i options_out selected_int=0 ypos option_string options_misc option_value bg fg skipped start_t end_t left_t changed_cpu_name + local desc_col right left enter lr inp valid updated_ms local_rez redraw_misc=1 desc_pos options_pause updated_proc inputting inputting_value inputting_key + desc_update_ms=( "Update time in milliseconds." + "Recommended 2000 ms or above for better sample" + "times for graphs." + " " + "Increases automatically if set below internal" + "loops processing time.") + desc_proc_sorting=( "Processes sorting." + "Valid values are \"pid\", \"program\", \"arguments\"," + "\"threads\", \"user\", \"memory\", \"cpu lazy\" and" + "\"cpu responsive\"." + " " + "\"cpu lazy\" uses ps commands internal sorting" + "and updates top process over a period of time." + " " + "\"cpu responsive\" updates sorting directly at a" + "significant cost of cpu time.") + desc_proc_reversed=("Reverse sorting order." + " " + "True or false.") + desc_check_temp=( "Check cpu temperature." + " " + "Only works if sensors command is available" + "and show values for Package and Core" + "temperatures.") + desc_draw_clock=( "Draw a clock at top of screen." + " " + "Formatting according to strftime, empty" + "string to disable." + " " + "\"%X\" locale HH:MM:SS" + "\"%H\" 24h hour, \"%I\" 12h hour" + "\"%M\" minute, \"%S\" second" + "\"%d\" day, \"%m\" month, \"%y\" year") + desc_custom_cpu_name=( "Custom cpu model name in cpu percentage box." + " " + "Empty string to disable.") + desc_error_logging=("Enable error logging to" + "\"\$HOME/.config/bashtop/error.log\"" + " " + "True or false." + "Takes effect after program restart.") + + + if [[ -n $pause_screen ]]; then from_menu=1; fi + + until false; do + draw_clock + pause_ options_pause + + if [[ -n $redraw_misc ]]; then + unset options_misc + col=$((tty_width/2-39)) + line=$((tty_height/2-4)) + desc_col=$((col+30)) + draw_banner "$((tty_height/2-11))" options_misc + create_box -v options_misc -w 29 -h $((${#options_array[@]}*2+2)) -l $line -c $((col-1)) -fill -lc 40 -title "options" + unset redraw_misc + fi + + + if [[ -n $keypress || -z $options_out ]]; then + unset options_out + selected="${options_array[selected_int]}" + local -n selected_desc="desc_${selected}" + unset lr inp valid + if ((selected_int*2+${#selected_desc[@]}<${#options_array[@]}*2)); then desc_pos=$((line+selected_int*2)) + else desc_pos=$((line+${#options_array[@]}*2-${#selected_desc[@]})); fi + create_box -v options_out -w 50 -h $((${#selected_desc[@]}+2)) -l $desc_pos -c $((desc_col-1)) -fill -lc 40 -title "description" + for((i=0,ypos=1;i<${#options_array[@]};i++,ypos=ypos+2)); do + option_string="${options_array[i]}" + if [[ -n $inputting && ${option_string} == "${selected}" ]]; then + if [[ ${#inputting_value} -gt 14 ]]; then option_value="${inputting_value:(-14)}_" + else option_value="${inputting_value}_"; fi + else + option_value="${!option_string}" + fi + + if [[ ${option_string} == "${selected}" ]]; then + if [[ $option_value =~ ^[0-9]+$ ]]; then + enter="↲"; inp=1 + fi + if [[ $option_value =~ ^[0-9]+$ || $option_value =~ true|false || $selected == "proc_sorting" ]] && [[ -z $inputting ]]; then + left="←"; right="→"; lr=1 + else + enter="↲"; inp=1 + fi + bg=" -bg #7e2626" + fg="ff" + fi + option_string="${option_string//_/ }" + print -v options_out -m $((line+ypos)) $((col+1)) -rs -fg ${fg:-ee}${bg} -b -jc 25 -t "${option_string^}:" + print -v options_out -m $((line+ypos+1)) $((col+1)) -rs -fg ${fg:-cc}${bg} -jc 25 -t "${enter:+ } ${left} \"${option_value::15}\" ${right} ${enter}" + unset right left enter bg fg + done + + for((i=0,ypos=1;i<${#selected_desc[@]};i++,ypos++)); do + print -v options_out -m $((desc_pos+ypos)) $((desc_col+1)) -rs -fg ee -jl 46 -t "${selected_desc[i]}" + done + fi + + echo -en "${options_pause}${options_misc}${options_out}${cursor}" + unset draw_out keypress + + + get_ms timestamp_end + time_left=$((timestamp_start+update_ms-timestamp_end)) + if ((time_left>1000)); then wait_string=1; time_left=$((time_left-1000)) + elif ((time_left>0)); then printf -v wait_string ".%03d" "${time_left}"; time_left=0 + else wait_string="0.001"; time_left=0; fi + + get_key -v keypress -w ${wait_string} + + if [[ -n $inputting ]]; then + case "$keypress" in + escape) unset inputting inputting_value ;; + enter|backspace) valid=1 ;; + *) if [[ ${#keypress} -eq 1 ]]; then valid=1; fi ;; + esac + else + case "$keypress" in + escape|q|backspace) break 1 ;; + down|tab) if ((selected_int<${#options_array[@]}-1)); then ((++selected_int)); else selected_int=0; fi ;; + up|shift_tab) if ((selected_int>0)); then ((selected_int--)); else selected_int=$((${#options_array[@]}-1)); fi ;; + left|right) if [[ -n $lr && -z $inputting ]]; then valid=1; fi ;; + enter) if [[ -n $inp ]]; then valid=1; fi ;; + esac + fi + + + if [[ -n $valid ]]; then + case "${selected} ${keypress}" in + "update_ms right") + update_ms=$((update_ms+100)) + updated_ms=1 + ;; + "update_ms left") + if ((update_ms>100)); then + update_ms=$((update_ms-100)) + updated_ms=1 + fi + ;; + "update_ms enter") + if [[ -z $inputting ]]; then inputting=1; inputting_value="${update_ms}" + else update_ms="${inputting_value:-0}"; unset inputting inputting_value; updated_ms=1; fi + ;; + "update_ms backspace") + if [[ ${#inputting_value} -gt 0 ]]; then + inputting_value="${inputting_value::-1}" + fi + ;; + "update_ms"*) + if [[ $keypress =~ ^[0-9]$ ]]; then + inputting_value="${inputting_value}${keypress}" + fi + ;; + "draw_clock enter") + if [[ -z $inputting ]]; then inputting=1; inputting_value="${draw_clock}" + else draw_clock="${inputting_value}"; unset inputting inputting_value clock_out; fi + ;; + "draw_clock backspace") + if [[ ${#inputting_value} -gt 0 ]]; then + inputting_value="${inputting_value::-1}" + fi + ;; + "draw_clock"*) + inputting_value="${inputting_value}${keypress//[^a-zA-Z0-9\.\:\,\-\_\/\@\%]/}" + ;; + "custom_cpu_name enter") + if [[ -z $inputting ]]; then inputting=1; inputting_value="${custom_cpu_name}" + else custom_cpu_name="${inputting_value}"; changed_cpu_name=1; unset inputting inputting_value; fi + ;; + "custom_cpu_name backspace") + if [[ ${#inputting_value} -gt 0 ]]; then + inputting_value="${inputting_value::-1}" + fi + ;; + "custom_cpu_name"*) + inputting_value="${inputting_value}${keypress//[^a-zA-Z0-9\.\:\,\-\_\/\@]/}" + ;; + "proc_reversed"*|"check_temp"*|"error_logging"*) + local -n selected_var=${selected} + if [[ ${selected_var} == "true" ]]; then + selected_var="false" + if [[ $selected == "proc_reversed" ]]; then proc[order_change]=1; unset 'proc[reverse]'; fi + else + selected_var="true" + if [[ $selected == "proc_reversed" ]]; then proc[order_change]=1; proc[reverse]="+"; fi + fi + if [[ $selected == "check_temp" ]]; then resized=1; fi + ;; + "proc_sorting right") + if ((proc[sorting_int]<${#sorting[@]}-1)); then ((++proc[sorting_int])) + else proc[sorting_int]=0; fi + proc_sorting="${sorting[proc[sorting_int]]}" + proc[order_change]=1 + ;; + "proc_sorting left") + if ((proc[sorting_int]>0)); then ((proc[sorting_int]--)) + else proc[sorting_int]=$((${#sorting[@]}-1)); fi + proc_sorting="${sorting[proc[sorting_int]]}" + proc[order_change]=1 + ;; + esac + + fi + + if [[ -n $changed_cpu_name ]]; then + changed_cpu_name=0 + get_cpu_info + draw_bg quiet + fi + + if ((resized>0)); then + while [[ $size_error == true ]] ; do size_error_msg; done + calc_sizes; draw_bg quiet + redraw_misc=1; time_left=0 + fi + + if ((time_left==0)); then get_ms timestamp_start; collect_and_draw; fi + if ((resized>0)); then resized=0; fi + + if [[ -n ${updated_ms} ]]; then + unset updated_ms + draw_update_string quiet + fi + + done + + if [[ -n $from_menu ]]; then pause_ + elif [[ -n ${pause_screen} ]]; then unpause_; draw_update_string; fi +} + +get_key() { #? Get one key from standard input and translate key code to readable format + local key key_out wait_time esc ext_out save + until (($#==0)); do + case "$1" in + -v|-variable) local -n key_out=$2; ext_out=1; shift;; #* Output variable + -w|-wait) wait_time="$2"; shift;; #* Time to wait for key + -s|-save) save=1;; #* Save key for later processing + esac + shift + done + + if [[ -n $save && -n $saved_key ]]; then return + elif [[ -n $saved_key ]]; then key="${saved_key}"; unset saved_key + else + if ! IFS= read -rst ${wait_time:-0.0001} -n 1 key >/dev/null 2>&1; then key_out=""; return; fi + + + #* Read 3 more characters if a leading escape character is detected + if [[ $key == "$esc_character" ]]; then esc=1; read -rsn3 -t 0.001 key || true; fi + if [[ -z $key && $esc -eq 0 ]]; then key="enter" + elif [[ -z $key && $esc -eq 1 ]]; then key="escape" + else + case "${key}" in + '[A') key="up" ;; + '[B') key="down" ;; + '[D') key="left" ;; + '[C') key="right" ;; + '[2~') key="insert" ;; + '[3~') key="delete" ;; + "${backspace}") key="backspace";; + #' ') key="space";; + '[H') key="home" ;; + '[F') key="end" ;; + '[5~') key="page_up" ;; + '[6~') key="page_down" ;; + "${tab}") key="tab" ;; + '[Z') key="shift_tab" ;; + 'OP') key="f1";; + 'OQ') key="f2";; + 'OR') key="f3";; + 'OS') key="f4";; + '[15') key="f5";; + '[17') key="f6";; + '[18') key="f7";; + '[19') key="f8";; + '[20') key="f9";; + '[21') key="f10";; + '[23') key="f11";; + '[24') key="f12";; + \\*) key="" ;; + esac + fi + + read -srd '' -t 0.0001 -n 10000 || true + + if [[ -n $save ]]; then saved_key="${key}"; return; fi + + fi + if [[ -n $ext_out ]]; then key_out="${key}" + else echo -n "${key}"; fi +} + +process_input() { #? Process keypresses for main ui + local wait_time="$1" keypress esc prev_screen anykey filter_change + + #* Wait while reading input + get_key -v keypress -w "${wait_time}" + if [[ -z $keypress ]]; then return; fi + + if [[ -n $input_to_filter ]]; then + filter_change=1 + case "$keypress" in + "enter") unset input_to_filter ;; + "backspace") if [[ ${#filter} -gt 0 ]]; then filter="${filter:: (-1)}"; else unset filter_change; fi ;; + "escape") unset input_to_filter filter ;; + *) if [[ ${#keypress} -eq 1 ]]; then filter="${filter}${keypress//[^a-zA-Z0-9\.\:\,\-\_\/\@]/}"; else unset filter_change; fi ;; + esac + + else + case "$keypress" in + left) #* Move left in processes sorting column + if ((proc[sorting_int]>0)); then ((proc[sorting_int]--)) + else proc[sorting_int]=$((${#sorting[@]}-1)); fi + proc_sorting="${sorting[proc[sorting_int]]}" + filter_change=1 + ;; + right) #* Move right in processes sorting column + if ((proc[sorting_int]<${#sorting[@]}-1)); then ((++proc[sorting_int])) + else proc[sorting_int]=0; fi + proc_sorting="${sorting[proc[sorting_int]]}" + filter_change=1 + ;; + up|shift_tab) + if [[ ${proc[selected]} -gt 0 ]]; then + #if ! ((--proc[selected] & proc[page]==1)); then proc[page_change]=1; fi + ((proc[selected]--)) + if ((proc[page]>1 & proc[selected]==0)); then ((--proc[page])); proc[selected]=$((box[processes_height]-3)); fi + proc[page_change]=1 + fi + ;; + down|tab) + if ! ((proc[page]==proc[pages] & proc[selected]>=box[processes_height]-3)); then + if ! ((proc[selected]++)); then collect_processes; proc[detailed_change]=1; fi + if ((proc[selected]>box[processes_height]-3)); then ((proc[page]++)); proc[selected]=1; fi + proc[page_change]=1 + fi + ;; + enter) + if ((proc[selected]>0 & proc[detailed_pid]!=proc[selected_pid])); then + proc[detailed]=1 + proc[detailed_change]=1 + proc[detailed_pid]=${proc[selected_pid]} + proc[page]=1 + proc[selected]=0 + unset 'proc[detailed_name]' 'detail_history[@]' 'detail_mem_history[@]' 'proc[detailed_killed]' + calc_sizes + collect_processes + elif ((proc[detailed]==1 & proc[detailed_pid]!=proc[selected_pid])); then + proc[detailed]=0 + proc[detailed_change]=1 + unset 'proc[detailed_pid]' + calc_sizes + fi + ;; + page_up) + if [[ ${proc[page]} -gt 1 ]]; then + ((--proc[page])) + proc[page_change]=1 + elif [[ ${proc[selected]} -gt 0 ]]; then + proc[selected]=0 + proc[page_change]=1 + fi + ;; + page_down) + if [[ ${proc[page]} -lt ${proc[pages]} ]]; then + if ((proc[page]++==1)); then collect_processes; fi + proc[page_change]=1 + elif [[ ${proc[selected]} -gt 0 ]]; then + proc[selected]=$((box[processes_height]-3)) + proc[page_change]=1 + fi + ;; + home) + #proc[selected]=1 + proc[page]=1 + proc[page_change]=1 + ;; + end) + if ((proc[selected]==0)); then collect_processes; fi + #proc[selected]=$((box[processes_height]-3)) + proc[page]=${proc[pages]} + proc[page_change]=1 + ;; + r|R) #* Reverse order of processes sorting column + if [[ -z ${proc[reverse]} ]]; then + proc[reverse]="+" + proc_reversed="true" + else + proc_reversed="false" + unset 'proc[reverse]' + fi + filter_change=1 + ;; + o|O|f2) #* Options + options_ + ;; + +|A|a) #* Add 100ms to update timer + update_ms=$((update_ms+100)) + draw_update_string + ;; + -|S|s) #* Subtract 100ms from update timer + if ((update_ms>100)); then + update_ms=$((update_ms-100)) + draw_update_string + fi + ;; + h|H|f1) #* Show help + help_ + ;; + q|Q) #* Quit + quit_ + ;; + m|M|escape) #* Show main menu + menu_ + ;; + f|F) #* Start process filtering input + input_to_filter=1 + filter_change=1 + #filter_proc init + ;; + c|C) #* Clear process filter + if [[ -n $filter ]]; then + unset input_to_filter filter + filter_change=1 + fi + # *) + # #if [[ $keypress == "" ]]; then keypress="enter"; fi + # printf -v letter_hex '%X\n' "'$keypress" + # echo "\"${keypress}\" \"${letter_hex}\"" >> "${config_dir}/error.log" + # ;; + esac + fi + + if [[ -n $filter_change ]]; then + unset filter_change + collect_processes + proc[filter_change]=1 + draw_processes now + elif [[ ${proc[page_change]} -eq 1 || ${proc[detailed_change]} == 1 ]]; then + if ((proc[selected]==0)); then unset 'proc[selected_pid]'; proc[detailed_change]=1; fi + draw_processes now + fi + + #* Subtract time since input start from time left if timer is interrupted + get_ms timestamp_input_end + time_left=$(( (timestamp_start+update_ms-1)-timestamp_input_end )) + + # #* Clear any remaining input from last keypress + # read -srd '' -t 0.001 -n 10000 || true + + return 0 +} + +collect_and_draw() { #? Run all collect and draw functions + + for task in processes cpu mem net; do + get_key -save + collect_${task} + draw_${task} + draw_clock "$1" + done + + last_screen="${draw_out}" +} + +#? ----------------------------------------------------------------------------------------------------------------------- ?# + +main_loop() { #? main loop... + local wait_time wait_string + + #* Timestamp for accurate timer + get_ms timestamp_start + + #* Loop a error message if terminal resized below 80x25 + while [[ $size_error == true ]] ; do size_error_msg; done + + if ((resized>0)); then + if (( tty_width!=$(tput cols) | tty_height!=$(tput lines) )); then + tty_width="$(tput cols)" + tty_height="$(tput lines)" + fi + calc_sizes + draw_bg + fi + + #* Run all collect and draw functions + collect_and_draw now + + #* Echo everyting out to screen in one command to get a smooth transition between updates + echo -en "${draw_out}${clock_out}${cursor}" + unset draw_out + + #* Reset resized variable if resized and all functions have finished redrawing + if ((resized>=3)); then resized=0; fi + + #* Compare timestamps to get exact time needed to wait until next loop + get_ms timestamp_end + time_left=$((timestamp_start+update_ms-timestamp_end)) + if ((time_left>0)); then + + #* Divide waiting time in chunks of 500ms and below to keep program responsive while reading input + while ((time_left>0 & resized==0)); do + #if ((resized>0)); then break; fi + #timestamp_input_start=${timestamp_end} + + #* If NOT waiting for input and time left is greater than 500ms, wait 500ms and loop + if [[ -z $input_to_filter ]] && ((time_left>=500)); then + wait_string="0.5" + time_left=$((time_left-500)) + + #* If waiting for input and time left is greater than "50 ms", wait 50ms and loop + elif [[ -n $input_to_filter ]] && ((time_left>=50)); then + wait_string="0.05" + time_left=$((time_left-50)) + + #* Else format wait string with padded zeroes if needed and break loop + else + printf -v wait_string ".%03d" "${time_left}" + time_left=0 + fi + + #* Wait while reading input + process_input "${wait_string}" + + #* Draw clock if set + draw_clock now + + done + + + + #* If time left is too low to process any input, add 100ms to update timer + else + update_ms=$((update_ms+100)) + draw_update_string + fi + +} + +sleep_() { + #tput cvvis + clear + tput cnorm + #tput rmcup + + resized=1 + stty echo + + + #sleeping=1 + + #trap 'sleeping=0' SIGCONT + #kill -USR1 $$ + + kill -STOP $$ + + #trap - SIGTSTP + #kill -s SIGTSTP $$ & + #sleep 1 + + # tput smcup + # tput civis + # stty -echo + # #trap 'sleep_' SIGTSTP + # resized=1 + +} + +resume_() { + + echo "resuming" + tput smcup + tput civis + stty -echo + #trap 'sleep_' SIGTSTP + resized=1 + +} + +#? Pre main loop + +#* Read config file or create if non existant +if [[ -d "$HOME/.config/bashtop" && -w "$HOME/.config/bashtop" ]] || mkdir -p "$HOME/.config/bashtop"; then + config_dir="$HOME/.config/bashtop" + config_file="${config_dir}/bashtop.cfg" + # shellcheck source=/dev/null + if [[ -e $config_file ]]; then + source "$config_file" + + #* If current config is from and older version recreate config file and save user changes + if [[ $(get_value -sf "${config_file}" -k "bashtop v." -mk 1) != "${version}" ]]; then + create_config + save_config "${save_array[@]}" + fi + else create_config; fi +else + #* If anything goes wrong turn off all writing to filesystem + echo "ERROR: Could not set config dir!" + config_dir="/dev/null" + config_file="/dev/null" + error_logging="false" + unset 'save_array[@]' +fi + +#* Set up traps for ctrl-c, soft kill, window resize and resume from ctrl-z +trap 'quit_' SIGINT SIGQUIT SIGTERM +trap 'resized' SIGWINCH +trap 'resized=1' SIGCONT + + +#* Set up error logging to file if enabled +if [[ $error_logging == true ]]; then + set -o errtrace + trap traperr ERR + ( echo " " ; echo "New instance of $0 Pid: $$" ) >> "${config_dir}/error.log" + exec 2>>"${config_dir}/error.log" + # exec 19>"${config_dir}/tracing.log" + # BASH_XTRACEFD=19 + # set -x +else + exec 2>/dev/null +fi + +#* Call init function +init_ + +#* Start infinite loop +until false; do main_loop; done + +#* Quit cleanly even if false starts being true... +quit_