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