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