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