preparing for da public

This commit is contained in:
2026-08-31 07:58:12 -07:00
parent 62c475c03e
commit 2cfe265109
3 changed files with 563 additions and 0 deletions

111
README.md
View File

@@ -0,0 +1,111 @@
# ff-logo
Pick a custom image logo for [fastfetch](https://github.com/fastfetch-cli/fastfetch) interactively, using fzf.
## Requirements
| Tool | Purpose |
|------|---------|
| `fastfetch` | System info display (must be built with image support) |
| `fzf` | Interactive selection |
| `imagemagick` | Image dimension detection (`identify`) |
| `chafa` or kitty terminal | Image rendering |
ff-logo will detect missing dependencies and offer to install them automatically.
## Platform support
- macOS (Homebrew)
- Arch Linux / CachyOS (pacman)
- Debian / Ubuntu (apt)
## Quick start
```sh
git clone https://github.com/youruser/ff-logo.git
cd ff-logo
./ff-logo
```
On first run, ff-logo checks that fastfetch is installed and has image support. If not, it offers to run `bootstrap-fastfetch.sh` for you.
### Install to PATH
```sh
./ff-logo --install
```
Choose between `~/.local/bin` (no sudo), `/usr/local/bin`, or `/usr/bin`.
## fastfetch image support
Most distro packages of fastfetch are compiled without chafa/ImageMagick support. If ff-logo warns you about this, build fastfetch from source with:
```sh
./bootstrap-fastfetch.sh
```
This installs build dependencies, compiles fastfetch, and places the binary at `/usr/local/bin/fastfetch`. On macOS it also symlinks the brew-installed chafa and ImageMagick libraries so fastfetch can find them at runtime.
> **CachyOS / Arch users:** the `fastfetch` package from the repos usually lacks image support. Run `bootstrap-fastfetch.sh` once.
## Image directory
ff-logo looks for images in this order:
1. `LOGO_DIR` set in `~/.ff-logo` (config file)
2. `~/.local/share/fastfetch-images` (XDG local dir)
3. `~/Pictures`
PNG, JPG, and JPEG files are supported.
## Configuration
```sh
ff-logo -e # create/open ~/.ff-logo in $EDITOR
```
Config file (`~/.ff-logo`) is a sourced bash file:
```bash
# Directory containing logo images
LOGO_DIR="$HOME/Pictures/fastfetch-logos"
# Maximum logo size in terminal columns/rows
MAX_W=83
MAX_H=44
# Skip the renderer prompt (chafa or kitty)
DEFAULT_RENDERER=chafa
```
If no config exists, ff-logo will offer to create one on first run.
## Usage
```
ff-logo [options]
Options:
-e, --edit Create/edit ~/.ff-logo in $EDITOR
--install Install ff-logo to a bin directory
-h, --help Show help
```
Running `ff-logo` with no arguments:
1. Checks dependencies (offers to install missing ones)
2. Verifies fastfetch has image support (offers to run bootstrap if not)
3. Shows an fzf picker of images in your logo directory
4. Prompts for a renderer (`chafa` or `kitty`) unless `DEFAULT_RENDERER` is set
5. Writes the selection to `~/.config/fastfetch/logo-local` and runs fastfetch
## Integrating with fastfetch
Add something like this to your fastfetch config or shell profile to use the saved logo:
```bash
# ~/.config/fastfetch/config.jsonc (logo-local is written by ff-logo)
```
Or call fastfetch directly referencing `logo-local` — see the fastfetch docs for config options.

144
bootstrap-fastfetch.sh Executable file
View File

@@ -0,0 +1,144 @@
#!/usr/bin/env bash
set -euo pipefail
# Bootstrap fastfetch with image support (chafa + ImageMagick)
# Builds from source so that --logo-type chafa/kitty works.
# Usage: ./bootstrap-fastfetch.sh
FASTFETCH_VER="2.62.1"
SRC_DIR="/tmp/fastfetch-src"
# ── Platform detection ─────────────────────────────────────────────────────────
MACOS=0
[[ "$(uname)" == "Darwin" ]] && MACOS=1
# ── Already installed from source? ────────────────────────────────────────────
# A source build goes to /usr/local/bin. Check if it's there AND has image support.
if command -v fastfetch &>/dev/null; then
FF_PATH=$(command -v fastfetch)
if [[ "$FF_PATH" == /usr/local/* ]]; then
if fastfetch --list-features 2>/dev/null | grep -qi 'chafa\|imagemagick'; then
echo "fastfetch is already built from source with image support."
exit 0
else
echo "fastfetch at $FF_PATH lacks image support — rebuilding..."
fi
else
echo "Installed fastfetch at $FF_PATH lacks source-build image support — rebuilding..."
fi
fi
echo "Building fastfetch $FASTFETCH_VER with image support..."
# ── Install build dependencies ─────────────────────────────────────────────────
if command -v brew &>/dev/null; then
brew install git cmake ninja chafa imagemagick
brew unlink fastfetch 2>/dev/null || true
elif command -v pacman &>/dev/null; then
sudo pacman -S --needed --noconfirm \
git base-devel cmake ninja chafa imagemagick
elif command -v apt &>/dev/null; then
sudo apt-get update -qq
sudo apt-get install -y \
git build-essential cmake ninja-build pkg-config \
libchafa-dev \
libmagickcore-dev libmagickwand-dev
else
echo "Unsupported package manager." >&2
echo "Install manually: git cmake ninja chafa imagemagick (dev headers)" >&2
exit 1
fi
# ── CPU count ──────────────────────────────────────────────────────────────────
if [[ $MACOS -eq 1 ]]; then
JOBS=$(sysctl -n hw.logicalcpu)
else
JOBS=$(nproc)
fi
# ── Clone source ───────────────────────────────────────────────────────────────
rm -rf "$SRC_DIR"
git clone --depth 1 --branch "$FASTFETCH_VER" \
https://github.com/fastfetch-cli/fastfetch.git "$SRC_DIR"
# ── Linux: patch sonames if the installed version doesn't match what fastfetch
# expects at runtime. Libraries live in multiarch dirs on Debian/Ubuntu
# (/usr/lib/x86_64-linux-gnu/ etc.) so we use ldconfig -p to locate them. ──
patch_soname() {
local libname="$1" # e.g. libchafa
local old_so="$2" # e.g. so.0
local new_so="$3" # e.g. so.1
# Find the real path of the old soname via the ldconfig cache
local old_path
old_path=$(ldconfig -p 2>/dev/null \
| grep -F "${libname}.${old_so}" \
| awk '{print $NF}' | head -1)
if [[ -z "$old_path" ]]; then
return 0 # old soname not present — nothing to patch
fi
local lib_dir new_path
lib_dir=$(dirname "$old_path")
new_path="${lib_dir}/${libname}.${new_so}"
if [[ ! -f "$new_path" ]] && [[ ! -L "$new_path" ]]; then
echo "Patching ${libname} soname (${old_so} → ${new_so}) in $lib_dir ..."
sudo ln -sf "$(basename "$old_path")" "$new_path"
sudo ldconfig
fi
}
if [[ $MACOS -eq 0 ]]; then
# chafa: Debian 12 ships libchafa.so.0; some fastfetch builds dlopen so.1
patch_soname libchafa so.0 so.1
# ImageMagick 7 soname bump (not present on Debian stable which ships IM6)
if pkg-config --exists MagickCore-7.Q16HDRI 2>/dev/null; then
patch_soname libMagickCore-7.Q16HDRI so.10 so.11
patch_soname libMagickWand-7.Q16HDRI so.10 so.11
fi
fi
# ── CMake configuration ────────────────────────────────────────────────────────
CMAKE_EXTRA_FLAGS=""
if [[ $MACOS -eq 1 ]]; then
BREW_PREFIX="$(brew --prefix)"
CMAKE_EXTRA_FLAGS="-DChafa_DIR=${BREW_PREFIX}/opt/chafa/lib/cmake/chafa \
-DCMAKE_PREFIX_PATH=${BREW_PREFIX}/opt/chafa:${BREW_PREFIX}/opt/imagemagick \
-DImageMagick_ROOT=${BREW_PREFIX}/opt/imagemagick"
fi
# shellcheck disable=SC2086 # word-split intended for CMAKE_EXTRA_FLAGS
cmake -S "$SRC_DIR" -B "$SRC_DIR/build" \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_VULKAN=OFF \
$CMAKE_EXTRA_FLAGS
cmake --build "$SRC_DIR/build" -j"$JOBS"
sudo cmake --install "$SRC_DIR/build" --prefix /usr/local
# ── macOS: symlink brew libs into /usr/local/lib so fastfetch's dlopen finds them
if [[ $MACOS -eq 1 ]]; then
BREW_PREFIX="$(brew --prefix)"
sudo mkdir -p /usr/local/lib
sudo ln -sf "${BREW_PREFIX}/opt/chafa/lib/libchafa.dylib" \
/usr/local/lib/libchafa.dylib
IM_LIB="${BREW_PREFIX}/opt/imagemagick/lib"
sudo ln -sf "${IM_LIB}/libMagickWand-7.Q16HDRI.dylib" \
/usr/local/lib/libMagickWand-7.Q16HDRI.dylib
sudo ln -sf "${IM_LIB}/libMagickCore-7.Q16HDRI.dylib" \
/usr/local/lib/libMagickCore-7.Q16HDRI.dylib
echo "Symlinked chafa + imagemagick into /usr/local/lib"
fi
echo
echo "Done — fastfetch with image support installed at /usr/local/bin/fastfetch"
fastfetch --list-features 2>/dev/null | grep -i 'chafa\|imagemagick' \
&& echo "Image support confirmed." \
|| echo "Warning: image features not listed — check cmake output above."

308
ff-logo Executable file
View File

@@ -0,0 +1,308 @@
#!/usr/bin/env bash
set -euo pipefail
export PATH="$HOME/.cargo/bin:$PATH"
CONFIG_FILE="$HOME/.ff-logo"
LOGO_LOCAL="${XDG_CONFIG_HOME:-$HOME/.config}/fastfetch/logo-local"
# ── Defaults (overridden by config) ──────────────────────────────────────────
MAX_W=83
MAX_H=44
LOGO_DIR=""
DEFAULT_RENDERER=""
# ── Flags ─────────────────────────────────────────────────────────────────────
EDIT_CONFIG=0
DO_INSTALL=0
usage() {
cat <<EOF
Usage: ff-logo [options]
Options:
-e, --edit Create/edit config file (~/.ff-logo) in \$EDITOR
--install Install ff-logo to a bin directory
-h, --help Show this help
Config file (~/.ff-logo) keys:
LOGO_DIR Path to image directory
MAX_W Max logo width in terminal columns (default: 83)
MAX_H Max logo height in terminal rows (default: 44)
DEFAULT_RENDERER chafa or kitty (skip the renderer prompt)
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-e|--edit) EDIT_CONFIG=1 ;;
--install) DO_INSTALL=1 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
esac
shift
done
# ── Config editing ─────────────────────────────────────────────────────────────
if [[ $EDIT_CONFIG -eq 1 ]]; then
if [[ ! -f "$CONFIG_FILE" ]]; then
cat > "$CONFIG_FILE" <<'CONF'
# ff-logo configuration
# Uncomment and edit values as needed.
# Directory containing logo images (PNG/JPG).
# If unset, ff-logo falls back to ~/.local/share/fastfetch-images then ~/Pictures.
# LOGO_DIR="$HOME/Pictures/fastfetch-logos"
# Maximum logo size in terminal columns/rows.
# MAX_W=83
# MAX_H=44
# Set a default renderer to skip the prompt each run (chafa or kitty).
# DEFAULT_RENDERER=chafa
CONF
echo "Created $CONFIG_FILE"
fi
"${EDITOR:-nano}" "$CONFIG_FILE"
exit 0
fi
# ── Load config ────────────────────────────────────────────────────────────────
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck source=/dev/null
source "$CONFIG_FILE"
fi
# ── Install ────────────────────────────────────────────────────────────────────
if [[ $DO_INSTALL -eq 1 ]]; then
SELF="$(realpath "$0")"
echo "Install ff-logo to:"
echo " 1) $HOME/.local/bin (user-local, no sudo)"
echo " 2) /usr/local/bin (system-wide, needs sudo)"
echo " 3) /usr/bin (system-wide, needs sudo)"
read -rp "Choice [1]: " _choice
_choice="${_choice:-1}"
case "$_choice" in
1)
mkdir -p "$HOME/.local/bin"
cp "$SELF" "$HOME/.local/bin/ff-logo"
chmod +x "$HOME/.local/bin/ff-logo"
echo "Installed to $HOME/.local/bin/ff-logo"
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
echo "Note: $HOME/.local/bin is not in your PATH. Add it to your shell profile."
fi
;;
2)
sudo cp "$SELF" /usr/local/bin/ff-logo
sudo chmod +x /usr/local/bin/ff-logo
echo "Installed to /usr/local/bin/ff-logo"
;;
3)
sudo cp "$SELF" /usr/bin/ff-logo
sudo chmod +x /usr/bin/ff-logo
echo "Installed to /usr/bin/ff-logo"
;;
*)
echo "Invalid choice." >&2; exit 1 ;;
esac
exit 0
fi
# ── Helpers ────────────────────────────────────────────────────────────────────
detect_pm() {
if command -v brew &>/dev/null; then echo brew
elif command -v pacman &>/dev/null; then echo pacman
elif command -v apt &>/dev/null; then echo apt
else echo unknown
fi
}
# Return the distro package name for a given logical dep + package manager
pkg_for() {
local dep="$1" pm="$2"
case "$dep/$pm" in
identify/apt) echo imagemagick ;;
identify/pacman) echo imagemagick ;;
identify/brew) echo imagemagick ;;
chafa/*) echo chafa ;;
fzf/*) echo fzf ;;
*) echo "$dep" ;;
esac
}
install_pkg() {
local pkg="$1" pm
pm=$(detect_pm)
case "$pm" in
brew) brew install "$pkg" ;;
pacman) sudo pacman -S --needed --noconfirm "$pkg" ;;
apt) sudo apt install -y "$pkg" ;;
*)
echo "Cannot auto-install '$pkg' — unsupported package manager." >&2
return 1
;;
esac
}
check_dep() {
local cmd="$1"
if ! command -v "$cmd" &>/dev/null; then
local pm pkg
pm=$(detect_pm)
pkg=$(pkg_for "$cmd" "$pm")
echo "Required tool '$cmd' is not installed."
read -rp " Install '$pkg' now? [Y/n] " _yn
_yn="${_yn:-Y}"
if [[ "$_yn" =~ ^[Yy] ]]; then
install_pkg "$pkg"
else
echo "Cannot continue without '$cmd'." >&2
exit 1
fi
fi
}
# ── Dependency checks ──────────────────────────────────────────────────────────
for _dep in fzf identify; do
check_dep "$_dep"
done
# ── fastfetch presence ─────────────────────────────────────────────────────────
BOOTSTRAP_HINT="Run bootstrap-fastfetch.sh from the ff-logo repo to build fastfetch with image support."
offer_bootstrap() {
# Try to find bootstrap-fastfetch.sh next to this script (works pre-install)
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local bootstrap="$script_dir/bootstrap-fastfetch.sh"
if [[ -f "$bootstrap" ]]; then
read -rp " Run bootstrap-fastfetch.sh now? [Y/n] " _yn
_yn="${_yn:-Y}"
if [[ "$_yn" =~ ^[Yy] ]]; then
bash "$bootstrap"
else
echo "$BOOTSTRAP_HINT" >&2
exit 1
fi
else
echo "$BOOTSTRAP_HINT" >&2
exit 1
fi
}
if ! command -v fastfetch &>/dev/null; then
echo "fastfetch is not installed."
offer_bootstrap
fi
# ── fastfetch image-support check ─────────────────────────────────────────────
# fastfetch --list-features prints compiled-in capabilities.
# If neither Chafa nor ImageMagick appear, image logos won't work.
if ! fastfetch --list-features 2>/dev/null | grep -qi 'chafa\|imagemagick'; then
echo "Your fastfetch does not have image support (Chafa / ImageMagick)."
echo "This is common with distro-packaged fastfetch (e.g. CachyOS, Debian)."
offer_bootstrap
fi
# ── Determine image directory ──────────────────────────────────────────────────
_LOCAL_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/fastfetch-images"
if [[ -z "$LOGO_DIR" ]]; then
if [[ -d "$_LOCAL_DIR" ]]; then
LOGO_DIR="$_LOCAL_DIR"
else
LOGO_DIR="$HOME/Pictures"
fi
fi
# ── Offer to create config (once, non-intrusively) ─────────────────────────────
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "No config found at $CONFIG_FILE."
echo " Current image dir : $LOGO_DIR"
echo " Max size : ${MAX_W}×${MAX_H} columns/rows"
read -rp " Create a config to customise these? [y/N] " _yn
_yn="${_yn:-N}"
if [[ "$_yn" =~ ^[Yy] ]]; then
cat > "$CONFIG_FILE" <<CONF
# ff-logo configuration
# LOGO_DIR="$LOGO_DIR"
# MAX_W=$MAX_W
# MAX_H=$MAX_H
# DEFAULT_RENDERER=chafa
CONF
echo "Created $CONFIG_FILE — edit it directly or run: ff-logo -e"
fi
fi
# ── Collect images ─────────────────────────────────────────────────────────────
logos=()
while IFS= read -r -d '' f; do
logos+=("$(basename "$f")")
done < <(find -L "$LOGO_DIR" -maxdepth 1 -type f \
\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) \
-print0 2>/dev/null | sort -z)
if [[ ${#logos[@]} -eq 0 ]]; then
echo "No images found in $LOGO_DIR" >&2
echo "Set LOGO_DIR in $CONFIG_FILE or add images to that directory." >&2
exit 1
fi
# ── Pick logo ──────────────────────────────────────────────────────────────────
fzf_args=(--prompt "Select logo > ")
if command -v fzf-preview.sh &>/dev/null; then
fzf_args+=(--preview "fzf-preview.sh '$LOGO_DIR/{}'")
fi
selected=$(printf '%s\n' "${logos[@]}" | fzf "${fzf_args[@]}")
if [[ -z "$selected" ]]; then
echo "No selection made." >&2
exit 1
fi
# ── Pick renderer ──────────────────────────────────────────────────────────────
if [[ -n "$DEFAULT_RENDERER" ]]; then
renderer="$DEFAULT_RENDERER"
else
renderer=$(printf 'chafa\nkitty' | fzf --prompt="Renderer > ")
fi
if [[ -z "$renderer" ]]; then
echo "No renderer selected." >&2
exit 1
fi
# ── Calculate dimensions ───────────────────────────────────────────────────────
calc_dims() {
local img="$1" pw ph
read -r pw ph < <(identify -format "%w %h\n" "$img" 2>/dev/null) || {
echo "30 18"
return
}
read -r new_w new_h < <(awk -v pw="$pw" -v ph="$ph" -v mw="$MAX_W" -v mh="$MAX_H" '
BEGIN {
ratio = pw / ph
w = mw
h = mw / ratio
if (h > mh) { h = mh; w = mh * ratio }
if (w < 3) w = 3
if (h < 3) h = 3
printf "%d %d\n", (w + 0.5), (h + 0.5)
}')
echo "$new_w $new_h"
}
read -r new_w new_h < <(calc_dims "$LOGO_DIR/$selected")
# ── Save state ─────────────────────────────────────────────────────────────────
mkdir -p "$(dirname "$LOGO_LOCAL")"
printf '%s\n%s\n%s\n' "$selected" "$renderer" "$new_w" > "$LOGO_LOCAL"
echo "Logo switched to: $selected ($new_w cols wide, renderer: $renderer)"
echo
fastfetch --pipe false \
--logo "$LOGO_DIR/$selected" \
--logo-type "$renderer" \
--logo-width "$new_w"