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