1#!/bin/sh 2# 3# pfetch - Simple POSIX sh fetch script. 4 5log() { 6 # The 'log()' function handles the printing of information. 7 # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info 8 # happen independently of each other. 9 # 10 # The size of the ascii art is stored and the ascii is printed first. 11 # Once the ascii is printed, the cursor is located right below the art 12 # (See marker $[1]). 13 # 14 # Using the stored ascii size, the cursor is then moved to marker $[2]. 15 # This is simply a cursor up escape sequence using the "height" of the 16 # ascii art. 17 # 18 # 'log()' then moves the cursor to the right the "width" of the ascii art 19 # with an additional amount of padding to add a gap between the art and 20 # the information (See marker $[3]). 21 # 22 # When 'log()' has executed, the cursor is then located at marker $[4]. 23 # When 'log()' is run a second time, the next line of information is 24 # printed, moving the cursor to marker $[5]. 25 # 26 # Markers $[4] and $[5] repeat all the way down through the ascii art 27 # until there is no more information left to print. 28 # 29 # Every time 'log()' is called the script keeps track of how many lines 30 # were printed. When printing is complete the cursor is then manually 31 # placed below the information and the art according to the "heights" 32 # of both. 33 # 34 # The math is simple: move cursor down $((ascii_height - info_height)). 35 # If the aim is to move the cursor from marker $[5] to marker $[6], 36 # plus the ascii height is 8 while the info height is 2 it'd be a move 37 # of 6 lines downwards. 38 # 39 # However, if the information printed is "taller" (takes up more lines) 40 # than the ascii art, the cursor isn't moved at all! 41 # 42 # Once the cursor is at marker $[6], the script exits. This is the gist 43 # of how this "dynamic" printing and layout works. 44 # 45 # This method allows ascii art to be stored without markers for info 46 # and it allows for easy swapping of info order and amount. 47 # 48 # $[2] ___ $[3] goldie@KISS 49 # $[4](.· | $[5] os KISS Linux 50 # (<> | 51 # / __ \ 52 # ( / \ /| 53 # _/\ __)/_) 54 # \/-____\/ 55 # $[1] 56 # 57 # $[6] /home/goldie $ 58 59 # End here if no data was found. 60 [ "$2" ] || return 61 62 # Store the value of '$1' as we reset the argument list below. 63 name=$1 64 65 # Use 'set --' as a means of stripping all leading and trailing 66 # white-space from the info string. This also normalizes all 67 # white-space inside of the string. 68 # 69 # Disable the shellcheck warning for word-splitting 70 # as it's safe and intended ('set -f' disables globbing). 71 # shellcheck disable=2046,2086 72 { 73 set -f 74 set +f -- $2 75 info=$* 76 } 77 78 # Move the cursor to the right, the width of the ascii art with an 79 # additional gap for text spacing. 80 printf '[%sC' "${ascii_width--1}" 81 82 # Print the info name and color the text. 83 printf '[3%s;1m%s[m' "${PF_COL1-4}" "$name" 84 85 # Print the info name and info data separator. 86 printf %s "$PF_SEP" 87 88 # Move the cursor backward the length of the *current* info name and 89 # then move it forwards the length of the *longest* info name. This 90 # aligns each info data line. 91 printf '[%sD[%sC' "${#name}" "${PF_ALIGN-$info_length}" 92 93 # Print the info data, color it and strip all leading whitespace 94 # from the string. 95 printf '[3%sm%s[m\n' "${PF_COL2-7}" "$info" 96 97 # Keep track of the number of times 'log()' has been run. 98 info_height=$((${info_height:-0} + 1)) 99} 100 101get_title() { 102 # Username is retrieved by first checking '$USER' with a fallback 103 # to the 'id -un' command. 104 user=${USER:-$(id -un)} 105 106 # Hostname is retrieved by first checking '$HOSTNAME' with a fallback 107 # to the 'hostname' command. 108 # 109 # Disable the warning about '$HOSTNAME' being undefined in POSIX sh as 110 # the intention for using it is allowing the user to overwrite the 111 # value on invocation. 112 # shellcheck disable=SC2039 113 hostname=${HOSTNAME:-${hostname:-$(hostname)}} 114 115 log "[3${PF_COL3:-1}m${user}${c7}@[3${PF_COL3:-1}m${hostname}" " " >&6 116} 117 118get_os() { 119 # This function is called twice, once to detect the distribution name 120 # for the purposes of picking an ascii art early and secondly to display 121 # the distribution name in the info output (if enabled). 122 # 123 # On first run, this function displays _nothing_, only on the second 124 # invocation is 'log()' called. 125 [ "$distro" ] && { 126 log os "$distro" >&6 127 return 128 } 129 130 case $os in 131 Linux*) 132 # Some Linux distributions (which are based on others) 133 # fail to identify as they **do not** change the upstream 134 # distribution's identification packages or files. 135 # 136 # It is senseless to add a special case in the code for 137 # each and every distribution (which _is_ technically no 138 # different from what it is based on) as they're either too 139 # lazy to modify upstream's identification files or they 140 # don't have the know-how (or means) to ship their own 141 # lsb-release package. 142 # 143 # This causes users to think there's a bug in system detection 144 # tools like neofetch or pfetch when they technically *do* 145 # function correctly. 146 # 147 # Exceptions are made for distributions which are independent, 148 # not based on another distribution or follow different 149 # standards. 150 # 151 # This applies only to distributions which follow the standard 152 # by shipping unmodified identification files and packages 153 # from their respective upstreams. 154 if command -v lsb_release; then 155 distro=$(lsb_release -sd) 156 157 # Android detection works by checking for the existence of 158 # the follow two directories. I don't think there's a simpler 159 # method than this. 160 elif [ -d /system/app ] && [ -d /system/priv-app ]; then 161 distro="Android $(getprop ro.build.version.release)" 162 163 else 164 # This used to be a simple '. /etc/os-release' but I believe 165 # this is insecure as we blindly executed whatever was in the 166 # file. This parser instead simply handles 'key=val', treating 167 # the file contents as plain-text. 168 while IFS='=' read -r key val; do 169 case $key in 170 PRETTY_NAME) distro=$val ;; 171 esac 172 done < /etc/os-release 173 fi 174 175 # 'os-release' and 'lsb_release' sometimes add quotes 176 # around the distribution name, strip them. 177 distro=${distro##[\"\']} 178 distro=${distro%%[\"\']} 179 180 # Special cases for (independent) distributions which 181 # don't follow any os-release/lsb standards whatsoever. 182 command -v crux && distro=$(crux) 183 command -v guix && distro='Guix System' 184 185 # Check to see if we're running Bedrock Linux which is 186 # very unique. This simply checks to see if the user's 187 # PATH contais a Bedrock specific value. 188 case $PATH in 189 */bedrock/cross/*) distro='Bedrock Linux' 190 esac 191 192 # Check to see if Linux is running in Windows 10 under 193 # WSL1 (Windows subsystem for Linux [version 1]) and 194 # append a string accordingly. 195 # 196 # If the kernel version string ends in "-Microsoft", 197 # we're very likely running under Windows 10 in WSL1. 198 [ "${kernel%%*-Microsoft}" ] || 199 distro="$distro on Windows 10 [WSL1]" 200 201 # Check to see if Linux is running in Windows 10 under 202 # WSL2 (Windows subsystem for Linux [version 2]) and 203 # append a string accordingly. 204 # 205 # This checks to see if '$WSLENV' is defined. This 206 # appends the Windows 10 string even if '$WSLENV' is 207 # empty. We only need to check that is has been _exported_. 208 distro="${distro}${WSLENV+ on Windows 10 [WSL2]}" 209 ;; 210 211 Darwin*) 212 # Parse the SystemVersion.plist file to grab the macOS 213 # version. The file is in the following format: 214 # 215 # <key>ProductVersion</key> 216 # <string>10.14.6</string> 217 # 218 # 'IFS' is set to '<>' to enable splitting between the 219 # keys and a second 'read' is used to operate on the 220 # next line directly after a match. 221 # 222 # '_' is used to nullify a field. '_ _ line _' basically 223 # says "populate $line with the third field's contents". 224 while IFS='<>' read -r _ _ line _; do 225 case $line in 226 # Match 'ProductVersion' and read the next line 227 # directly as it contains the key's value. 228 ProductVersion) 229 IFS='<>' read -r _ _ mac_version _ 230 break 231 ;; 232 esac 233 done < /System/Library/CoreServices/SystemVersion.plist 234 235 # Use the ProductVersion to determine which macOS/OS X codename 236 # the system has. As far as I'm aware there's no "dynamic" way 237 # of grabbing this information. 238 case $mac_version in 239 10.4*) distro='Mac OS X Tiger' ;; 240 10.5*) distro='Mac OS X Leopard' ;; 241 10.6*) distro='Mac OS X Snow Leopard' ;; 242 10.7*) distro='Mac OS X Lion' ;; 243 10.8*) distro='OS X Mountain Lion' ;; 244 10.9*) distro='OS X Mavericks' ;; 245 10.10*) distro='OS X Yosemite' ;; 246 10.11*) distro='OS X El Capitan' ;; 247 10.12*) distro='macOS Sierra' ;; 248 10.13*) distro='macOS High Sierra' ;; 249 10.14*) distro='macOS Mojave' ;; 250 10.15*) distro='macOS Catalina' ;; 251 *) distro='macOS' ;; 252 esac 253 254 distro="$distro $mac_version" 255 ;; 256 257 Haiku) 258 # Haiku uses 'uname -v' for version information 259 # instead of 'uname -r' which only prints '1'. 260 distro=$(uname -sv) 261 ;; 262 263 Minix|DragonFly) 264 distro="$os $kernel" 265 266 # Minix and DragonFly don't support the escape 267 # sequences used, clear the exit trap. 268 trap '' EXIT 269 ;; 270 271 SunOS) 272 # Grab the first line of the '/etc/release' file 273 # discarding everything after '('. 274 IFS='(' read -r distro _ < /etc/release 275 ;; 276 277 *) 278 # Catch all to ensure '$distro' is never blank. 279 # This also handles the BSDs. 280 distro="$os $kernel" 281 ;; 282 esac 283} 284 285get_kernel() { 286 case $os in 287 # Don't print kernel output on some systems as the 288 # OS name includes it. 289 *BSD*|Haiku|Minix) 290 return 291 ;; 292 esac 293 294 # '$kernel' is the cached output of 'uname -r'. 295 log kernel "$kernel" >&6 296} 297 298get_host() { 299 case $os in 300 Linux*) 301 # Despite what these files are called, version doesn't 302 # always contain the version nor does name always contain 303 # the name. 304 read -r name < /sys/devices/virtual/dmi/id/product_name 305 read -r version < /sys/devices/virtual/dmi/id/product_version 306 read -r model < /sys/firmware/devicetree/base/model 307 308 host="$name $version $model" 309 ;; 310 311 Darwin*|FreeBSD*|DragonFly*) 312 host=$(sysctl -n hw.model) 313 ;; 314 315 NetBSD*) 316 host=$(sysctl -n machdep.dmi.system-vendor \ 317 machdep.dmi.system-product) 318 ;; 319 320 *BSD*|Minix) 321 host=$(sysctl -n hw.vendor hw.product) 322 ;; 323 esac 324 325 # Turn the host string into an argument list so we can iterate 326 # over it and remove OEM strings and other information which 327 # shouldn't be displayed. 328 # 329 # Disable the shellcheck warning for word-splitting 330 # as it's safe and intended ('set -f' disables globbing). 331 # shellcheck disable=2046,2086 332 { 333 set -f 334 set +f -- $host 335 host= 336 } 337 338 # Iterate over the host string word by word as a means of stripping 339 # unwanted and OEM information from the string as a whole. 340 # 341 # This could have been implemented using a long 'sed' command with 342 # a list of word replacements, however I want to show that something 343 # like this is possible in pure sh. 344 # 345 # This string reconstruction is needed as some OEMs either leave the 346 # identification information as "To be filled by OEM", "Default", 347 # "undefined" etc and we shouldn't print this to the screen. 348 for word; do 349 # This works by reconstructing the string by excluding words 350 # found in the "blacklist" below. Only non-matches are appended 351 # to the final host string. 352 case $word in 353 To | [Bb]e | [Ff]illed | [Bb]y | O.E.M. | OEM |\ 354 Not | Applicable | Specified | System | Product | Name |\ 355 Version | Undefined | Default | string | INVALID | � | os ) 356 continue 357 ;; 358 esac 359 360 host="$host$word " 361 done 362 363 # '$arch' is the cached output from 'uname -m'. 364 log host "${host:-$arch}" >&6 365} 366 367get_uptime() { 368 # Uptime works by retrieving the data in total seconds and then 369 # converting that data into days, hours and minutes using simple 370 # math. 371 case $os in 372 Linux*|Minix*) 373 IFS=. read -r s _ < /proc/uptime 374 ;; 375 376 Darwin*|*BSD*|DragonFly*) 377 s=$(sysctl -n kern.boottime) 378 379 # Extract the uptime in seconds from the following output: 380 # [...] { sec = 1271934886, usec = 667779 } Thu Apr 22 12:14:46 2010 381 s=${s#*=} 382 s=${s%,*} 383 384 # The uptime format from 'sysctl' needs to be subtracted from 385 # the current time in seconds. 386 s=$(($(date +%s) - s)) 387 ;; 388 389 Haiku) 390 # The boot time is returned in microseconds, convert it to 391 # regular seconds. 392 s=$(($(system_time) / 1000000)) 393 ;; 394 395 SunOS) 396 # Split the output of 'kstat' on '.' and any white-space 397 # which exists in the command output. 398 # 399 # The output is as follows: 400 # unix:0:system_misc:snaptime 14809.906993005 401 # 402 # The parser extracts: ^^^^^ 403 IFS=' .' read -r _ s _ <<-EOF 404 $(kstat -p unix:0:system_misc:snaptime) 405 EOF 406 ;; 407 408 IRIX) 409 # Grab the uptime in a pretty format. Usually, 410 # 00:00:00 from the 'ps' command. 411 t=$(LC_ALL=POSIX ps -o etime= -p 1) 412 413 # Split the pretty output into days or hours 414 # based on the uptime. 415 case $t in 416 *-*) d=${t%%-*} t=${t#*-} ;; 417 *:*:*) h=${t%%:*} t=${t#*:} ;; 418 esac 419 420 h=${h#0} t=${t#0} 421 422 # Convert the split pretty fields back into 423 # seconds so we may re-convert them to our format. 424 s=$((${d:-0}*86400 + ${h:-0}*3600 + ${t%%:*}*60 + ${t#*:})) 425 ;; 426 esac 427 428 # Convert the uptime from seconds into days, hours and minutes. 429 d=$((s / 60 / 60 / 24)) 430 h=$((s / 60 / 60 % 24)) 431 m=$((s / 60 % 60)) 432 433 # Only append days, hours and minutes if they're non-zero. 434 [ "$d" = 0 ] || uptime="${uptime}${d}d " 435 [ "$h" = 0 ] || uptime="${uptime}${h}h " 436 [ "$m" = 0 ] || uptime="${uptime}${m}m " 437 438 log uptime "${uptime:-0m}" >&6 439} 440 441get_pkgs() { 442 # This is just a simple wrapper around 'command -v' to avoid 443 # spamming '>/dev/null' throughout this function. 444 has() { command -v "$1" >/dev/null; } 445 446 # This works by first checking for which package managers are 447 # installed and finally by printing each package manager's 448 # package list with each package one per line. 449 # 450 # The output from this is then piped to 'wc -l' to count each 451 # line, giving us the total package count of whatever package 452 # managers are installed. 453 # 454 # Backticks are *required* here as '/bin/sh' on macOS is 455 # 'bash 3.2' and it can't handle the following: 456 # 457 # var=$( 458 # code here 459 # ) 460 # 461 # shellcheck disable=2006 462 packages=` 463 case $os in 464 Linux*) 465 # Commands which print packages one per line. 466 has bonsai && bonsai list 467 has crux && pkginfo -i 468 has pacman-key && pacman -Qq 469 has dpkg && dpkg-query -f '.\n' -W 470 has rpm && rpm -qa 471 has xbps-query && xbps-query -l 472 has apk && apk info 473 has guix && guix package --list-installed 474 has opkg && opkg list-installed 475 476 # Directories containing packages. 477 has kiss && printf '%s\n' /var/db/kiss/installed/*/ 478 has brew && printf '%s\n' "$(brew --cellar)/"* 479 has emerge && printf '%s\n' /var/db/pkg/*/*/ 480 has pkgtool && printf '%s\n' /var/log/packages/* 481 has eopkg && printf '%s\n' /var/lib/eopkg/package/* 482 483 # 'nix' requires two commands. 484 has nix-store && { 485 nix-store -q --requisites /run/current-system/sw 486 nix-store -q --requisites ~.nix-profile 487 } 488 ;; 489 490 Darwin*) 491 # Commands which print packages one per line. 492 has pkgin && pkgin list 493 494 # Directories containing packages. 495 has brew && printf '%s\n' /usr/local/Cellar/* 496 497 # 'port' prints a single line of output to 'stdout' 498 # when no packages are installed and exits with 499 # success causing a false-positive of 1 package 500 # installed. 501 # 502 # 'port' should really exit with a non-zero code 503 # in this case to allow scripts to cleanly handle 504 # this behavior. 505 has port && { 506 pkg_list=$(port installed) 507 508 [ "$pkg_list" = "No ports are installed." ] || 509 printf '%s\n' "$pkg_list" 510 } 511 ;; 512 513 FreeBSD*|DragonFly*) 514 pkg info 515 ;; 516 517 OpenBSD*) 518 printf '%s\n' /var/db/pkg/*/ 519 ;; 520 521 NetBSD*) 522 pkg_info 523 ;; 524 525 Haiku) 526 printf '%s\n' /boot/system/package-links/* 527 ;; 528 529 Minix) 530 printf '%s\n' /usr/pkg/var/db/pkg/*/ 531 ;; 532 533 SunOS) 534 has pkginfo && pkginfo -i 535 has pkg && pkg list 536 ;; 537 538 IRIX) 539 versions -b 540 ;; 541 esac | wc -l 542 ` 543 544 case $os in 545 # IRIX's package manager adds 3 lines of extra 546 # output which we must account for here. 547 IRIX) packages=$((packages - 3)) ;; 548 esac 549 550 [ "$packages" -gt 1 ] && log pkgs "$packages" >&6 551} 552 553get_memory() { 554 case $os in 555 # Used memory is calculated using the following "formula": 556 # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable 557 # Source: https://github.com/KittyKatt/screenFetch/issues/386 558 Linux*) 559 # Parse the '/proc/meminfo' file splitting on ':' and 'k'. 560 # The format of the file is 'key: 000kB' and an additional 561 # split is used on 'k' to filter out 'kB'. 562 while IFS=':k ' read -r key val _; do 563 case $key in 564 MemTotal) 565 mem_used=$((mem_used + val)) 566 mem_full=$val 567 ;; 568 569 Shmem) 570 mem_used=$((mem_used + val)) 571 ;; 572 573 MemFree|Buffers|Cached|SReclaimable) 574 mem_used=$((mem_used - val)) 575 ;; 576 esac 577 done < /proc/meminfo 578 579 mem_used=$((mem_used / 1024)) 580 mem_full=$((mem_full / 1024)) 581 ;; 582 583 # Used memory is calculated using the following "formula": 584 # (wired + active + occupied) * 4 / 1024 585 Darwin*) 586 mem_full=$(($(sysctl -n hw.memsize) / 1024 / 1024)) 587 588 # Parse the 'vmstat' file splitting on ':' and '.'. 589 # The format of the file is 'key: 000.' and an additional 590 # split is used on '.' to filter it out. 591 while IFS=:. read -r key val; do 592 case $key in 593 *' wired'*|*' active'*|*' occupied'*) 594 mem_used=$((mem_used + ${val:-0})) 595 ;; 596 esac 597 598 # Using '<<-EOF' is the only way to loop over a command's 599 # output without the use of a pipe ('|'). 600 # This ensures that any variables defined in the while loop 601 # are still accessible in the script. 602 done <<-EOF 603 $(vm_stat) 604 EOF 605 606 mem_used=$((mem_used * 4 / 1024)) 607 ;; 608 609 OpenBSD*) 610 mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024)) 611 612 # This is a really simpler parser for 'vmstat' which grabs 613 # the used memory amount in a lazy way. 'vmstat' prints 3 614 # lines of output with the needed value being stored in the 615 # final line. 616 # 617 # This loop simply grabs the 3rd element of each line until 618 # the EOF is reached. Each line overwrites the value of the 619 # previous one so we're left with what we wanted. This isn't 620 # slow as only 3 lines are parsed. 621 while read -r _ _ line _; do 622 mem_used=${line%%M} 623 624 # Using '<<-EOF' is the only way to loop over a command's 625 # output without the use of a pipe ('|'). 626 # This ensures that any variables defined in the while loop 627 # are still accessible in the script. 628 done <<-EOF 629 $(vmstat) 630 EOF 631 ;; 632 633 # Used memory is calculated using the following "formula": 634 # mem_full - ((inactive + free + cache) * page_size / 1024) 635 FreeBSD*|DragonFly*) 636 mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024)) 637 638 # Use 'set --' to store the output of the command in the 639 # argument list. POSIX sh has no arrays but this is close enough. 640 # 641 # Disable the shellcheck warning for word-splitting 642 # as it's safe and intended ('set -f' disables globbing). 643 # shellcheck disable=2046 644 { 645 set -f 646 set +f -- $(sysctl -n hw.pagesize \ 647 vm.stats.vm.v_inactive_count \ 648 vm.stats.vm.v_free_count \ 649 vm.stats.vm.v_cache_count) 650 } 651 652 # Calculate the amount of used memory. 653 # $1: hw.pagesize 654 # $2: vm.stats.vm.v_inactive_count 655 # $3: vm.stats.vm.v_free_count 656 # $4: vm.stats.vm.v_cache_count 657 mem_used=$((mem_full - (($2 + $3 + $4) * $1 / 1024 / 1024))) 658 ;; 659 660 NetBSD*) 661 mem_full=$(($(sysctl -n hw.physmem64) / 1024 / 1024)) 662 663 # NetBSD implements a lot of the Linux '/proc' filesystem, 664 # this uses the same parser as the Linux memory detection. 665 while IFS=':k ' read -r key val _; do 666 case $key in 667 MemFree) 668 mem_free=$((val / 1024)) 669 break 670 ;; 671 esac 672 done < /proc/meminfo 673 674 mem_used=$((mem_full - mem_free)) 675 ;; 676 677 Haiku) 678 # Read the first line of 'sysinfo -mem' splitting on 679 # '(', ' ', and ')'. The needed information is then 680 # stored in the 5th and 7th elements. Using '_' "consumes" 681 # an element allowing us to proceed to the next one. 682 # 683 # The parsed format is as follows: 684 # 3501142016 bytes free (used/max 792645632 / 4293787648) 685 IFS='( )' read -r _ _ _ _ mem_used _ mem_full <<-EOF 686 $(sysinfo -mem) 687 EOF 688 689 mem_used=$((mem_used / 1024 / 1024)) 690 mem_full=$((mem_full / 1024 / 1024)) 691 ;; 692 693 Minix) 694 # Minix includes the '/proc' filesystem though the format 695 # differs from Linux. The '/proc/meminfo' file is only a 696 # single line with space separated elements and elements 697 # 2 and 3 contain the total and free memory numbers. 698 read -r _ mem_full mem_free _ < /proc/meminfo 699 700 mem_used=$(((mem_full - mem_free) / 1024)) 701 mem_full=$(( mem_full / 1024)) 702 ;; 703 704 SunOS) 705 hw_pagesize=$(pagesize) 706 707 # 'kstat' outputs memory in the following format: 708 # unix:0:system_pages:pagestotal 1046397 709 # unix:0:system_pages:pagesfree 885018 710 # 711 # This simply uses the first "element" (white-space 712 # separated) as the key and the second element as the 713 # value. 714 # 715 # A variable is then assigned based on the key. 716 while read -r key val; do 717 case $key in 718 *total) pages_full=$val ;; 719 *free) pages_free=$val ;; 720 esac 721 done <<-EOF 722 $(kstat -p unix:0:system_pages:pagestotal \ 723 unix:0:system_pages:pagesfree) 724 EOF 725 726 mem_full=$((pages_full * hw_pagesize / 1024 / 1024)) 727 mem_free=$((pages_free * hw_pagesize / 1024 / 1024)) 728 mem_used=$((mem_full - mem_free)) 729 ;; 730 731 IRIX) 732 # Read the memory information from the 'top' command. Parse 733 # and split each line until we reach the line starting with 734 # "Memory". 735 # 736 # Example output: Memory: 160M max, 147M avail, ..... 737 while IFS=' :' read -r label mem_full _ mem_free _; do 738 case $label in 739 Memory) 740 mem_full=${mem_full%M} 741 mem_free=${mem_free%M} 742 break 743 ;; 744 esac 745 done <<-EOF 746 $(top -n) 747 EOF 748 749 mem_used=$((mem_full - mem_free)) 750 ;; 751 esac 752 753 log memory "${mem_used:-?}M / ${mem_full:-?}M" >&6 754} 755 756get_wm() { 757 case $os in 758 # Don't display window manager on macOS. 759 Darwin*) ;; 760 761 *) 762 # xprop can be used to grab the window manager's properties 763 # which contains the window manager's name under '_NET_WM_NAME'. 764 # 765 # The upside to using 'xprop' is that you don't need to hardcode 766 # a list of known window manager names. The downside is that 767 # not all window managers conform to setting the '_NET_WM_NAME' 768 # atom.. 769 # 770 # List of window managers which fail to set the name atom: 771 # catwm, fvwm, dwm, 2bwm, monster, wmaker and sowm [mine! ;)]. 772 # 773 # The final downside to this approach is that it does _not_ 774 # support Wayland environments. The only solution which supports 775 # Wayland is the 'ps' parsing mentioned below. 776 # 777 # A more naive implementation is to parse the last line of 778 # '~/.xinitrc' to extract the second white-space separated 779 # element. 780 # 781 # The issue with an approach like this is that this line data 782 # does not always equate to the name of the window manager and 783 # could in theory be _anything_. 784 # 785 # This also fails when the user launches xorg through a display 786 # manager or other means. 787 # 788 # 789 # Another naive solution is to parse 'ps' with a hardcoded list 790 # of window managers to detect the current window manager (based 791 # on what is running). 792 # 793 # The issue with this approach is the need to hardcode and 794 # maintain a list of known window managers. 795 # 796 # Another issue is that process names do not always equate to 797 # the name of the window manager. False-positives can happen too. 798 # 799 # This is the only solution which supports Wayland based 800 # environments sadly. It'd be nice if some kind of standard were 801 # established to identify Wayland environments. 802 # 803 # pfetch's goal is to remain _simple_, if you'd like a "full" 804 # implementation of window manager detection use 'neofetch'. 805 # 806 # Neofetch use a combination of 'xprop' and 'ps' parsing to 807 # support all window managers (including non-conforming and 808 # Wayland) though it's a lot more complicated! 809 810 # Don't display window manager if X isn't running. 811 [ "$DISPLAY" ] || return 812 813 # This is a two pass call to xprop. One call to get the window 814 # manager's ID and another to print its properties. 815 command -v xprop && { 816 # The output of the ID command is as follows: 817 # _NET_SUPPORTING_WM_CHECK: window id # 0x400000 818 # 819 # To extract the ID, everything before the last space 820 # is removed. 821 id=$(xprop -root -notype _NET_SUPPORTING_WM_CHECK) 822 id=${id##* } 823 824 # The output of the property command is as follows: 825 # _NAME 8t 826 # _NET_WM_PID = 252 827 # _NET_WM_NAME = "bspwm" 828 # _NET_SUPPORTING_WM_CHECK: window id # 0x400000 829 # WM_CLASS = "wm", "Bspwm" 830 # 831 # To extract the name, everything before '_NET_WM_NAME = \"' 832 # is removed and everything after the next '"' is removed. 833 wm=$(xprop -id "$id" -notype -len 25 -f _NET_WM_NAME 8t) 834 835 # Handle cases of a window manager _not_ populating the 836 # '_NET_WM_NAME' atom. Display nothing in this case. 837 case $wm in 838 *'_NET_WM_NAME = '*) 839 wm=${wm##*_NET_WM_NAME = \"} 840 wm=${wm%%\"*} 841 ;; 842 843 *) 844 # Fallback to checking the process list 845 # for the select few window managers which 846 # don't set '_NET_WM_NAME'. 847 # 848 # TODO: This is currently limited to 'grep' 849 # implementations providing the '-o' 850 # flag. This needs to be replaced with 851 # a command which searches for a list 852 # of strings and returns _only_ the 853 # first match's contents (also ensuring 854 # the search itself isn't matched). 855 # 856 # A generic parser isn't possible as 857 # the output of 'ps' is _not_ the same 858 # between implementations and across 859 # operating systems. 860 # 861 # The simple search method above works 862 # regardless of 'ps' implementation. 863 # 864 # Disable the shellcheck warning about using 865 # 'pgrep' instead of 'ps | grep' as 'pgrep' 866 # is not always available. 867 # shellcheck disable=2009 868 wm=$(ps x | grep -o \ 869 -e '[c]atwm' \ 870 -e '[f]vwm' \ 871 -e '[d]wm' \ 872 -e '[2]bwm' \ 873 -e '[m]onsterwm' \ 874 -e '[w]maker' \ 875 -e '[s]owm') 876 ;; 877 esac 878 } 879 ;; 880 esac 881 882 log wm "$wm" >&6 883} 884 885 886get_de() { 887 # This only supports Xorg related desktop environments though 888 # this is fine as knowing the desktop envrionment on Windows, 889 # macOS etc is useless (they'll always report the same value). 890 # 891 # Display the value of '$XDG_CURRENT_DESKTOP', if it's empty, 892 # display the value of '$DESKTOP_SESSION'. 893 log de "${XDG_CURRENT_DESKTOP:-$DESKTOP_SESSION}" >&6 894} 895 896get_shell() { 897 # Display the basename of the '$SHELL' environment variable. 898 log shell "${SHELL##*/}" >&6 899} 900 901get_editor() { 902 # Display the value of '$VISUAL', if it's empty, display the 903 # value of '$EDITOR'. 904 log editor "${VISUAL:-$EDITOR}" >&6 905} 906 907get_palette() { 908 # Print the first 8 terminal colors. This uses the existing 909 # sequences to change text color with a sequence prepended 910 # to reverse the foreground and background colors. 911 # 912 # This allows us to save hardcoding a second set of sequences 913 # for background colors. 914 palette="[7m$c1 $c1 $c2 $c2 $c3 $c3 $c4 $c4 $c5 $c5 $c6 $c6 [m" 915 916 # Print the palette with a new-line before and afterwards. 917 printf '\n' >&6 918 log "$palette 919 " " " >&6 920} 921 922get_ascii() { 923 # This is a simple function to read the contents of 924 # an ascii file from 'stdin'. It allows for the use 925 # of '<<-EOF' to prevent the break in indentation in 926 # this source code. 927 # 928 # This function also sets the text colors according 929 # to the ascii color. 930 read_ascii() { 931 # 'PF_COL1': Set the info name color according to ascii color. 932 # 'PF_COL3': Set the title color to some other color. ¯\_(ツ)_/¯ 933 PF_COL1=${PF_COL1:-${1:-7}} 934 PF_COL3=${PF_COL3:-$((${1:-7}%8+1))} 935 936 # POSIX sh has no 'var+=' so 'var=${var}append' is used. What's 937 # interesting is that 'var+=' _is_ supported inside '$(())' 938 # (arithmetic) though there's no support for 'var++/var--'. 939 # 940 # There is also no $'\n' to add a "literal"(?) newline to the 941 # string. The simplest workaround being to break the line inside 942 # the string (though this has the caveat of breaking indentation). 943 while IFS= read -r line; do 944 ascii="$ascii$line 945" 946 done 947 } 948 949 # This checks for ascii art in the following order: 950 # '$1': Argument given to 'get_ascii()' directly. 951 # '$PF_ASCII': Environment variable set by user. 952 # '$distro': The detected distribution name. 953 # '$os': The name of the operating system/kernel. 954 # 955 # NOTE: Each ascii art below is indented using tabs, this 956 # allows indentation to continue naturally despite 957 # the use of '<<-EOF'. 958 case ${1:-${PF_ASCII:-${distro:-$os}}} in 959 [Aa]lpine*) 960 read_ascii 4 <<-EOF 961 ${c4} /\\ /\\ 962 /${c7}/ ${c4}\\ \\ 963 /${c7}/ ${c4}\\ \\ 964 /${c7}// ${c4}\\ \\ 965 ${c7}// ${c4}\\ \\ 966 ${c4}\\ 967 EOF 968 ;; 969 970 [Aa]ndroid*) 971 read_ascii 2 <<-EOF 972 ${c2} ;, ,; 973 ${c2} ';,.-----.,;' 974 ${c2} ,' ', 975 ${c2} / O O \\ 976 ${c2}| | 977 ${c2}'-----------------' 978 EOF 979 ;; 980 981 [Aa]rch*) 982 read_ascii 4 <<-EOF 983 ${c6} /\\ 984 ${c6} / \\ 985 ${c6} /\\ \\ 986 ${c4} / \\ 987 ${c4} / ,, \\ 988 ${c4} / | | -\\ 989 ${c4} /_-'' ''-_\\ 990 EOF 991 ;; 992 993 [Aa]rco*) 994 read_ascii 4 <<-EOF 995 ${c4} /\\ 996 ${c4} / \\ 997 ${c4} / /\\ \\ 998 ${c4} / / \\ \\ 999 ${c4} / / \\ \\ 1000 ${c4} / / _____\\ \\ 1001 ${c4}/_/ \`----.\\_\\ 1002 EOF 1003 ;; 1004 1005 [Aa]rtix*) 1006 read_ascii 6 <<-EOF 1007 ${c4} /\\ 1008 ${c4} / \\ 1009 ${c4} /\`'.,\\ 1010 ${c4} / ', 1011 ${c4} / ,\`\\ 1012 ${c4} / ,.'\`. \\ 1013 ${c4}/.,'\` \`'.\\ 1014 EOF 1015 ;; 1016 1017 [Bb]edrock*) 1018 read_ascii 4 <<-EOF 1019 ${c7}__ 1020 ${c7}\\ \\___ 1021 ${c7} \\ _ \\ 1022 ${c7} \\___/ 1023 EOF 1024 ;; 1025 1026 [Cc]ent[Oo][Ss]*) 1027 read_ascii 5 <<-EOF 1028 ${c2} ____${c3}^${c5}____ 1029 ${c2} |\\ ${c3}|${c5} /| 1030 ${c2} | \\ ${c3}|${c5} / | 1031 ${c5}<---- ${c4}----> 1032 ${c4} | / ${c2}|${c3} \\ | 1033 ${c4} |/__${c2}|${c3}__\\| 1034 ${c2} v 1035 EOF 1036 ;; 1037 1038 [Dd]ebian*) 1039 read_ascii 1 <<-EOF 1040 ${c1} _____ 1041 ${c1} / __ \\ 1042 ${c1}| / | 1043 ${c1}| \\___- 1044 ${c1}-_ 1045 ${c1} --_ 1046 EOF 1047 ;; 1048 1049 [Dd]ragon[Ff]ly*) 1050 read_ascii 1 <<-EOF 1051 ,${c1}_${c7}, 1052 ('-_${c1}|${c7}_-') 1053 >--${c1}|${c7}--< 1054 (_-'${c1}|${c7}'-_) 1055 ${c1}| 1056 ${c1}| 1057 ${c1}| 1058 EOF 1059 ;; 1060 1061 [Ee]lementary*) 1062 read_ascii <<-EOF 1063 ${c7} _______ 1064 ${c7} / ____ \\ 1065 ${c7}/ | / /\\ 1066 ${c7}|__\\ / / | 1067 ${c7}\\ /__/ / 1068 ${c7}\\_______/ 1069 EOF 1070 ;; 1071 1072 [Ee]ndeavour*) 1073 read_ascii 4 <<-EOF 1074 ${c1}/${c4}\\ 1075 ${c1}/${c4}/ \\${c6}\\ 1076 ${c1}/${c4}/ \\ ${c6}\\ 1077 ${c1}/ ${c4}/ _) ${c6}) 1078 ${c1}/_${c4}/___-- ${c6}__- 1079 ${c6}/____-- 1080 EOF 1081 ;; 1082 1083 [Ff]edora*) 1084 read_ascii 4 <<-EOF 1085 ${c7} _____ 1086 / __)${c4}\\${c7} 1087 | / ${c4}\\ \\${c7} 1088 ${c4}__${c7}_| |_${c4}_/ /${c7} 1089 ${c4}/ ${c7}(_ _)${c4}_/${c7} 1090 ${c4}/ /${c7} | | 1091 ${c4}\\ \\${c7}__/ | 1092 ${c4}\\${c7}(_____/ 1093 EOF 1094 ;; 1095 1096 [Ff]ree[Bb][Ss][Dd]*) 1097 read_ascii 1 <<-EOF 1098 ${c1}/\\,-'''''-,/\\ 1099 ${c1}\\_) (_/ 1100 ${c1}| | 1101 ${c1}| | 1102 ${c1}; ; 1103 ${c1}'-_____-' 1104 EOF 1105 ;; 1106 1107 [Gg]entoo*) 1108 read_ascii 5 <<-EOF 1109 ${c5} _-----_ 1110 ${c5}( \\ 1111 ${c5}\\ 0 \\ 1112 ${c7} \\ ) 1113 ${c7} / _/ 1114 ${c7}( _- 1115 ${c7}\\____- 1116 EOF 1117 ;; 1118 1119 [Gg]uix[Ss][Dd]*|[Gg]uix*) 1120 read_ascii 3 <<-EOF 1121 ${c3}|.__ __.| 1122 ${c3}|__ \\ / __| 1123 ${c3}\\ \\ / / 1124 ${c3}\\ \\ / / 1125 ${c3}\\ \\ / / 1126 ${c3}\\ \\/ / 1127 ${c3}\\__/ 1128 EOF 1129 ;; 1130 1131 [Hh]aiku*) 1132 read_ascii 3 <<-EOF 1133 ${c3} ,^, 1134 ${c3} / \\ 1135 ${c3}*--_ ; ; _--* 1136 ${c3}\\ '" "' / 1137 ${c3}'. .' 1138 ${c3}.-'" "'-. 1139 ${c3}'-.__. .__.-' 1140 ${c3}|_| 1141 EOF 1142 ;; 1143 1144 [Hh]yperbola*) 1145 read_ascii <<-EOF 1146 ${c7} |\`__.\`/ 1147 ${c7} \____/ 1148 ${c7} .--. 1149 ${c7} / \\ 1150 ${c7} / ___ \\ 1151 ${c7}/ .\` \`.\\ 1152 ${c7}/.\` \`.\\ 1153 EOF 1154 ;; 1155 1156 [Ii][Rr][Ii][Xx]*) 1157 read_ascii 1 <<-EOF 1158 ${c1} __ 1159 ${c1} \\ \\ __ 1160 ${c1} \\ \\ / / 1161 ${c1} \\ v / 1162 ${c1} / . \\ 1163 ${c1} /_/ \\ \\ 1164 ${c1} \\_\\ 1165 EOF 1166 ;; 1167 1168 [Ll]inux*[Ll]ite*|[Ll]ite*) 1169 read_ascii 3 <<-EOF 1170 ${c3} /\\ 1171 ${c3} / \\ 1172 ${c3} / ${c7}/ ${c3}/ 1173 ${c3}> ${c7}/ ${c3}/ 1174 ${c3}\\ ${c7}\\ ${c3}\\ 1175 ${c3}\\_${c7}\\${c3}_\\ 1176 ${c7} \\ 1177 EOF 1178 ;; 1179 1180 [Ll]inux*[Mm]int*|[Mm]int) 1181 read_ascii 2 <<-EOF 1182 ${c2} ___________ 1183 ${c2}|_ \\ 1184 ${c2}| ${c7}| _____ ${c2}| 1185 ${c2}| ${c7}| | | | ${c2}| 1186 ${c2}| ${c7}| | | | ${c2}| 1187 ${c2}| ${c7}\\__${c7}___/ ${c2}| 1188 ${c2}\\_________/ 1189 EOF 1190 ;; 1191 1192 1193 [Ll]inux*) 1194 read_ascii 4 <<-EOF 1195 ${c4} ___ 1196 ${c4}(${c7}.. ${c4}| 1197 ${c4}(${c5}<> ${c4}| 1198 ${c4}/ ${c7}__ ${c4}\\ 1199 ${c4}( ${c7}/ \\ ${c4}/| 1200 ${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4}) 1201 ${c5}\/${c4}-____${c5}\/ 1202 EOF 1203 ;; 1204 1205 [Mm]ac[Oo][Ss]*|[Dd]arwin*) 1206 read_ascii 1 <<-EOF 1207 ${c1} .:' 1208 ${c1} _ :'_ 1209 ${c2} .'\`_\`-'_\`\`. 1210 ${c2}:________.-' 1211 ${c3}:_______: 1212 ${c4} :_______\`-; 1213 ${c5} \`._.-._.' 1214 EOF 1215 ;; 1216 1217 [Mm]ageia*) 1218 read_ascii 2 <<-EOF 1219 ${c6} * 1220 ${c6} * 1221 ${c6} ** 1222 ${c7} /\\__/\\ 1223 ${c7}/ \\ 1224 ${c7}\\ / 1225 ${c7} \\____/ 1226 EOF 1227 ;; 1228 1229 [Mm]anjaro*) 1230 read_ascii 2 <<-EOF 1231 ${c2}||||||||| |||| 1232 ${c2}||||||||| |||| 1233 ${c2}|||| |||| 1234 ${c2}|||| |||| |||| 1235 ${c2}|||| |||| |||| 1236 ${c2}|||| |||| |||| 1237 ${c2}|||| |||| |||| 1238 EOF 1239 ;; 1240 1241 [Mm]inix*) 1242 read_ascii 4 <<-EOF 1243 ${c4} ,, ,, 1244 ${c4};${c7},${c4} ', ,' ${c7},${c4}; 1245 ${c4}; ${c7}',${c4} ',,' ${c7},'${c4} ; 1246 ${c4}; ${c7}',${c4} ${c7},'${c4} ; 1247 ${c4}; ${c7};, '' ,;${c4} ; 1248 ${c4}; ${c7};${c4};${c7}',,'${c4};${c7};${c4} ; 1249 ${c4}', ${c7};${c4};; ;;${c7};${c4} ,' 1250 ${c4} '${c7};${c4}' '${c7};${c4}' 1251 EOF 1252 ;; 1253 1254 [Mm][Xx]*) 1255 read_ascii <<-EOF 1256 ${c7} \\\\ / 1257 ${c7} \\\\/ 1258 ${c7} \\\\ 1259 ${c7} /\\/ \\\\ 1260 ${c7} / \\ /\\ 1261 ${c7} / \\/ \\ 1262 ${c7}/__________\\ 1263 EOF 1264 ;; 1265 1266 [Nn]et[Bb][Ss][Dd]*) 1267 read_ascii 3 <<-EOF 1268 ${c7}\\\\${c3}\`-______,----__ 1269 ${c7} \\\\ ${c3}__,---\`_ 1270 ${c7} \\\\ ${c3}\`.____ 1271 ${c7} \\\\${c3}-______,----\`- 1272 ${c7} \\\\ 1273 ${c7} \\\\ 1274 ${c7} \\\\ 1275 EOF 1276 ;; 1277 1278 [Nn]ix[Oo][Ss]*) 1279 read_ascii 4 <<-EOF 1280 ${c4} \\\\ \\\\ // 1281 ${c4} ==\\\\__\\\\/ // 1282 ${c4} // \\\\// 1283 ${c4}==// //== 1284 ${c4} //\\\\___// 1285 ${c4}// /\\\\ \\\\== 1286 ${c4} // \\\\ \\\\ 1287 EOF 1288 ;; 1289 1290 [Oo]pen[Bb][Ss][Dd]*) 1291 read_ascii 3 <<-EOF 1292 ${c3} _____ 1293 ${c3} \\- -/ 1294 ${c3} \\_/ \\ 1295 ${c3} | ${c7}O O${c3} | 1296 ${c3} |_ < ) 3 ) 1297 ${c3} / \\ / 1298 ${c3} /-_____-\\ 1299 EOF 1300 ;; 1301 1302 [Oo]pen[Ss][Uu][Ss][Ee]*|[Oo]pen*SUSE*|SUSE*|suse*) 1303 read_ascii 2 <<-EOF 1304 ${c2} _______ 1305 ${c2}__| __ \\ 1306 ${c2} / .\\ \\ 1307 ${c2} \\__/ | 1308 ${c2} _______| 1309 ${c2} \\_______ 1310 ${c2}__________/ 1311 EOF 1312 ;; 1313 1314 [Oo]pen[Ww]rt*) 1315 read_ascii 1 <<-EOF 1316 ${c1} _______ 1317 ${c1}| |.-----.-----.-----. 1318 ${c1}| - || _ | -__| | 1319 ${c1}|_______|| __|_____|__|__| 1320 ${c1} ________|__| __ 1321 ${c1}| | | |.----.| |_ 1322 ${c1}| | | || _|| _| 1323 ${c1}|________||__| |____| 1324 EOF 1325 ;; 1326 1327 [Pp]arabola*) 1328 read_ascii 5 <<-EOF 1329 ${c5} __ __ __ _ 1330 ${c5}.\`_//_//_/ / \`. 1331 ${c5} / .\` 1332 ${c5} / .\` 1333 ${c5} /.\` 1334 ${c5} /\` 1335 EOF 1336 ;; 1337 1338 [Pp]op!_[Oo][Ss]*) 1339 read_ascii 6 <<-EOF 1340 ${c6}______ 1341 ${c6}\\ _ \\ __ 1342 ${c6}\\ \\ \\ \\ / / 1343 ${c6}\\ \\_\\ \\ / / 1344 ${c6}\\ ___\\ /_/ 1345 ${c6} \\ \\ _ 1346 ${c6} __\\_\\__(_)_ 1347 ${c6}(___________) 1348 EOF 1349 ;; 1350 1351 [Pp]ure[Oo][Ss]*) 1352 read_ascii <<-EOF 1353 ${c7} _____________ 1354 ${c7}| _________ | 1355 ${c7}| | | | 1356 ${c7}| | | | 1357 ${c7}| |_________| | 1358 ${c7}|_____________| 1359 EOF 1360 ;; 1361 1362 [Ss]lackware*) 1363 read_ascii 4 <<-EOF 1364 ${c4} ________ 1365 ${c4} / ______| 1366 ${c4} | |______ 1367 ${c4} \\______ \\ 1368 ${c4} ______| | 1369 ${c4}| |________/ 1370 ${c4}|____________ 1371 EOF 1372 ;; 1373 1374 [Ss]un[Oo][Ss]|[Ss]olaris*) 1375 read_ascii 3 <<-EOF 1376 ${c3} . .; . 1377 ${c3} . :; :: ;: . 1378 ${c3} .;. .. .. .;. 1379 ${c3}.. .. .. .. 1380 ${c3} .;, ,;. 1381 EOF 1382 ;; 1383 1384 [Uu]buntu*) 1385 read_ascii 3 <<-EOF 1386 ${c3} _ 1387 ${c3} ---(_) 1388 ${c3} _/ --- \\ 1389 ${c3}(_) | | 1390 ${c3} \\ --- _/ 1391 ${c3} ---(_) 1392 EOF 1393 ;; 1394 1395 [Vv]oid*) 1396 read_ascii 2 <<-EOF 1397 ${c2} _______ 1398 ${c2} _ \\______ - 1399 ${c2}| \\ ___ \\ | 1400 ${c2}| | / \ | | 1401 ${c2}| | \___/ | | 1402 ${c2}| \\______ \\_| 1403 ${c2} -_______\\ 1404 EOF 1405 ;; 1406 1407 *) 1408 # On no match of a distribution ascii art, this function calls 1409 # itself again, this time to look for a more generic OS related 1410 # ascii art (KISS Linux -> Linux). 1411 [ "$1" ] || { 1412 get_ascii "$os" 1413 return 1414 } 1415 1416 printf 'error: %s is not currently supported.\n' "$os" >&6 1417 printf 'error: Open an issue for support to be added.\n' >&6 1418 exit 1 1419 ;; 1420 esac 1421 1422 # Store the "width" (longest line) and "height" (number of lines) 1423 # of the ascii art for positioning. This script prints to the screen 1424 # *almost* like a TUI does. It uses escape sequences to allow dynamic 1425 # printing of the information through user configuration. 1426 # 1427 # Iterate over each line of the ascii art to retrieve the above 1428 # information. The 'sed' is used to strip '[3Xm' color codes from 1429 # the ascii art so they don't affect the width variable. 1430 while read -r line; do 1431 ascii_height=$((${ascii_height:-0} + 1)) 1432 1433 # This was a ternary operation but they aren't supported in 1434 # Minix's shell. 1435 [ "${#line}" -gt "${ascii_width:-0}" ] && 1436 ascii_width=${#line} 1437 1438 # Using '<<-EOF' is the only way to loop over a command's 1439 # output without the use of a pipe ('|'). 1440 # This ensures that any variables defined in the while loop 1441 # are still accessible in the script. 1442 done <<-EOF 1443 $(printf %s "$ascii" | sed 's/\[3.m//g') 1444 EOF 1445 1446 # Add a gap between the ascii art and the information. 1447 ascii_width=$((ascii_width + 4)) 1448 1449 # Print the ascii art and position the cursor back where we 1450 # started prior to printing it. 1451 # '[1m': Print the ascii in bold. 1452 # '[m': Clear bold. 1453 # '[%sA': Move the cursor up '$ascii_height' amount of lines. 1454 printf '[1m%s[m[%sA' "$ascii" "$ascii_height" >&6 1455} 1456 1457main() { 1458 # Hide 'stderr' unless the first argument is '-v'. This saves 1459 # polluting the script with '2>/dev/null'. 1460 [ "$1" = -v ] || exec 2>/dev/null 1461 1462 # Hide 'stdout' and selectively print to it using '>&6'. 1463 # This gives full control over what it displayed on the screen. 1464 exec 6>&1 >/dev/null 1465 1466 # Allow the user to execute their own script and modify or 1467 # extend pfetch's behavior. 1468 # shellcheck source=/dev/null 1469 . "${PF_SOURCE:-/dev/null}" ||: 1470 1471 # Ensure that the 'TMPDIR' is writable as heredocs use it and 1472 # fail without the write permission. This was found to be the 1473 # case on Android where the temporary directory requires root. 1474 [ -w "${TMPDIR:-/tmp}" ] || export TMPDIR=~ 1475 1476 # Generic color list. 1477 # Disable warning about unused variables. 1478 # shellcheck disable=2034 1479 { 1480 c1='[31m'; c2='[32m' 1481 c3='[33m'; c4='[34m' 1482 c5='[35m'; c6='[36m' 1483 c7='[37m'; c8='[38m' 1484 } 1485 1486 # Avoid text-wrapping from wrecking the program output. 1487 # 1488 # Some terminals don't support these sequences, nor do they 1489 # silently conceal them if they're printed resulting in 1490 # partial sequences being printed to the terminal! 1491 [ "$TERM" = dumb ] || 1492 [ "$TERM" = minix ] || 1493 [ "$TERM" = cons25 ] || { 1494 # Disable line-wrapping. 1495 printf '[?7l' >&6 1496 1497 # Enable line-wrapping again on exit. 1498 trap 'printf [?7h >&6' EXIT 1499 } 1500 1501 # Store the output of 'uname' to avoid calling it multiple times 1502 # throughout the script. 'read <<EOF' is the simplest way of reading 1503 # a command into a list of variables. 1504 read -r os kernel arch <<-EOF 1505 $(uname -srm) 1506 EOF 1507 1508 # Always run 'get_os' for the purposes of detecting which ascii 1509 # art to display. 1510 get_os 1511 1512 # Allow the user to specify the order and inclusion of information 1513 # functions through the 'PF_INFO' environment variable. 1514 # shellcheck disable=2086 1515 { 1516 # Disable globbing and set the positional parameters to the 1517 # contents of 'PF_INFO'. 1518 set -f 1519 set +f ${PF_INFO-ascii title os host kernel uptime pkgs memory} 1520 1521 # Iterate over the info functions to determine the lengths of the 1522 # "info names" for output alignment. The option names and subtitles 1523 # match 1:1 so this is thankfully simple. 1524 for info; do 1525 command -v "get_$info" >/dev/null || continue 1526 1527 # This was a ternary operation but they aren't supported in 1528 # Minix's shell. 1529 [ "${#info}" -gt "${info_length:-0}" ] && 1530 info_length=${#info} 1531 done 1532 1533 # Add an additional space of length to act as a gap. 1534 info_length=$((info_length + 1)) 1535 1536 # Iterate over the above list and run any existing "get_" functions. 1537 for info; do "get_$info"; done 1538 } 1539 1540 # Position the cursor below both the ascii art and information lines 1541 # according to the height of both. If the information exceeds the ascii 1542 # art in height, don't touch the cursor (0/unset), else move it down 1543 # N lines. 1544 # 1545 # This was a ternary operation but they aren't supported in Minix's shell. 1546 [ "${info_height:-0}" -lt "${ascii_height:-0}" ] && 1547 cursor_pos=$((ascii_height - info_height)) 1548 1549 # Print '$cursor_pos' amount of newlines to correctly position the 1550 # cursor. This used to be a 'printf $(seq X X)' however 'seq' is only 1551 # typically available (by default) on GNU based systems! 1552 while [ "${i:=0}" -le "${cursor_pos:-0}" ]; do 1553 printf '\n' 1554 i=$((i + 1)) 1555 done >&6 1556} 1557 1558main "$@" 1559