#!/bin/sh # # pfetch - Simple POSIX sh fetch script. log() { # The 'log()' function handles the printing of information. # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info # happen independently of each other. # # The size of the ascii art is stored and the ascii is printed first. # Once the ascii is printed, the cursor is located right below the art # (See marker $[1]). # # Using the stored ascii size, the cursor is then moved to marker $[2]. # This is simply a cursor up escape sequence using the "height" of the # ascii art. # # 'log()' then moves the cursor to the right the "width" of the ascii art # with an additional amount of padding to add a gap between the art and # the information (See marker $[3]). # # When 'log()' has executed the cursor is then located at marker $[4]. # When 'log()' is run a second time, the next line of information is # printed, moving the cursor to marker $[5]. # # Markers $[4] and $[5] repeat all the way down through the ascii art # until there is no more information left to print. # # Every time 'log()' is called the script keeps track of how many lines # were printed. When printing is complete the cursor is then manually # placed below the information and the art according to the "heights" # of both. # # The math is simple: move cursor down $((ascii_height - info_height)). # If the aim is to move the cursor from marker $[5] to marker $[6], # plus the ascii height is 8 while the info height is 2 it'd be a move # of 6 lines downwards. # # Once the cursor is at marker $[6], the script exits. This is the gist # of how this "dynamic" printing and layout works. # # This method allows ascii art to be stored without markers for info # and it allows for easy swapping of info order and amount. # # $[2] ___ $[3] goldie@KISS # $[4](.· | $[5] os KISS Linux # (<> | # / __ \ # ( / \ /| # _/\ __)/_) # \/-____\/ # $[1] # # $[6] /home/goldie $ # # '$PF_COL1': Control color of info name. # '$PF_SEP': Control the separator between info name and info data. # '$PF_COL2': Control color of info data. # '$PF_ALIGN': Control the alignment amount. # # '\033[14C': Move cursor 14 characters to the right. # TODO: Base this on ASCII art width. # # '\033[3%s': Color formatting. # '\033[m': Reset formatting. # # '\033[%sD': Move cursor '${#1}' characters to the left. # This allows for aligned info names and data. # # '\033[6C': Move cursor 6 characters to the right. # This aligns the info. # # This is ugly, I know! printf '\033[%sC\033[3%s;1m%s\033[m%s\033[3%sm\033[%sD\033[%sC%s\033[m\n' \ "${ascii_width:--1}" \ "${PF_COL1:-5}" \ "$1" \ "$PF_SEP" \ "${PF_COL2:-7}" \ "${#1}" \ "${PF_ALIGN:-$((info_length + 1))}" \ "$2" # Keep track of the number of times 'log()' has been run. info_height=$((info_height + 1)) } get_title() { case $os in Linux*|GNU*) read -r hostname < /proc/sys/kernel/hostname ;; esac # Username is retrieved by first checking '$USER' with a fallback # to the 'whoami' command. # # Hostname is retrieved by first checking '$HOSTNAME' with a fallback # to the OS specific detection above and finally an additional fallback # to the 'hostname' command. # # Disable the warning about '$HOSTNAME' being undefined in POSIX sh as # it is intended for allowing the user to overwrite the value on invocation. # shellcheck disable=SC2039 log "$c4${USER:-$(whoami)}$c7@$c4${HOSTNAME:-${hostname:-$(hostname)}}" } get_distro() { case $os in Linux*|GNU*) # Disable warning about shellcheck not being able # to read '/etc/os-release'. This is fine. # shellcheck source=/dev/null . /etc/os-release && distro=$PRETTY_NAME ;; esac log os "$distro" } get_kernel() { log kernel "$kernel" } get_host() { case $os in Linux*|GNU*) # Despite what these files are called, version doesn't # always contain the version nor does name always contain # the name. read -r name < /sys/devices/virtual/dmi/id/product_name read -r version < /sys/devices/virtual/dmi/id/product_version read -r model < /sys/firmware/devicetree/base/model host="$name $version $model" ;; esac log host "$host" } get_uptime() { # Uptime works by retrieving the data in total seconds and then # converting that data into days, hours and minutes using simple # math. case $os in Linux*|GNU*) IFS=. read -r s _ < /proc/uptime ;; esac # Convert the uptime from seconds into days, hours and minutes. d=$((s / 60 / 60 / 24)) h=$((s / 60 / 60 % 24)) m=$((s / 60 % 60)) # Only append days, hours and minutes if they're non-zero. [ "$d" = 0 ] || uptime="${uptime}${d}d " [ "$h" = 0 ] || uptime="${uptime}${h}h " [ "$m" = 0 ] || uptime="${uptime}${m}m " log uptime "${uptime:-0m}" } get_pkgs() { # Simple function to avoid '>/dev/null' spam. # This checks to see if a command is in '$PATH'. has() { command -v "$1" >/dev/null; } # This works by first checking for which package managers are # isntalled and finally by printing each package manager's # package list with each package one per line. # # The output from this is then piped to 'wc -l' to count each # line, giving us the total package count of whatever package # managers are installed. packages=$( case $os in Linux*|GNU*) # Commands which print packages one per line. has kiss && kiss l has bonsai && bonsai list has pacman-key && pacman -Qq has dpkg && dpkg-query -f '.\n' -W has rpm && rpm -qa has xbps-query && xbps-query -l has apk && apk info # Directories containing packages. has brew && printf '%s\n' "$(brew --cellar)/"* has emerge && printf '%s\n' /var/db/pkg/*/*/ ;; esac | wc -l ) log pkgs "$packages" } get_memory() { case $os in # Used memory is calculated using the following "formula" (Linux): # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable # Source: https://github.com/KittyKatt/screenFetch/issues/386 Linux*|GNU*) # Parse the '/proc/meminfo' file splitting on ':' and 'k'. # The format of the file is 'key: 000kB' and an additional # split is used on 'k' to filter out 'kB'. while IFS=:k read -r key val _; do case $key in MemTotal) mem_used=$((mem_used + val)) mem_total=$val ;; Shmem) mem_used=$((mem_used + val)) ;; MemFree|Buffers|Cached|SReclaimable) mem_used=$((mem_used - val)) ;; esac done < /proc/meminfo mem_used=$((mem_used / 1024)) mem_total=$((mem_total / 1024)) ;; esac log memory "${mem_used}MiB / ${mem_total}MiB" } get_ascii() { case $os in Linux*|GNU*) ascii="\ ${c4} ___ (${c7}.· ${c4}| (${c5}<> ${c4}| / ${c7}__ ${c4}\\ ( ${c7}/ \\ ${c4}/| ${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4}) ${c5}\/${c4}-____${c5}\/ " ;; *) printf 'error: %s is not currently supported.\n' "$os" printf 'error: Open an issue on GitHub for support to be added.\n' exit 1 ;; esac # Store the "width" (longest line) and "height" (number of lines) # of the ascii art for positioning. This script prints to the screen # *almost* like a TUI does. It uses escape sequences to allow dynamic # printing of the information through user configuration. # # Iterate over each line of the ascii art to retrieve the above # information. The 'sed' is used to strip '\033[3Xm' color codes from # the ascii art so they don't affect the width variable. # # The " " acts as the padding between the ascii art and the text as # it appends 3 spaces to the end of each line. while read -r line || [ -n "$line" ]; do ascii_height=$((ascii_height + 1)) ascii_width=$((${#line} > ascii_width ? ${#line} : ascii_width)) done <<-EOF $(printf %s "$ascii" | sed 's/\[3.m//g') EOF # Add a gap between the ascii art and the information. ascii_width=$((ascii_width + 4)) # Print the ascii art and position the cursor back where we # started prior to printing it. # '\033[?7l': Disable line-wrapping. # '\033[?25l': Hide the cursor. # '\033[1m': Print the ascii in bold. # '\033[m': Clear bold. # '\033[%sA: Move the cursor up '$ascii_height' amount of lines. printf '\033[?7l\033[?25l\033[1m%s\033[m\033[%sA' "$ascii" "$ascii_height" } main() { # Hide 'stderr' unless the first argument is '-v'. This saves # polluting the script with '2>/dev/null'. [ "$1" = -v ] || exec 2>/dev/null # Generic color list. # Disable warning about unused variables. # shellcheck disable=2034 { c1=''; c2='' c3=''; c4='' c5=''; c6='' c7=''; c8='' } # Store the output of 'uname' to avoid calling it multiple times # throughout the script. 'read </dev/null && info_length=$((${#info} > info_length ? ${#info} : info_length)) done # Iterate over the above list and run any existing "get_" functions. for info; do "get_$info" done } # Position the cursor below both the ascii art and information lines # according to the height of both. If the information exceeds the ascii # art in height, don't touch the cursor, else move it down N lines. # # 'log' contains the amount of 'get_' info lines that were printed. # # '\033[?7h': Enable line-wrapping. # '\033[%sB': Move the cursor down N lines. # '\033[?25h': Un-hide the cursor. printf '\033[?7h\033[%sB\n\033[?25h' \ "$((info_height > ascii_height ? 0 : ascii_height - info_height))" } main "$@"