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