From 2cfe2651090c5d3580e538b32424171fef36d47c Mon Sep 17 00:00:00 2001 From: schmeeve Date: Mon, 31 Aug 2026 07:58:12 -0700 Subject: [PATCH] preparing for da public --- README.md | 111 +++++++++++++++ bootstrap-fastfetch.sh | 144 +++++++++++++++++++ ff-logo | 308 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 563 insertions(+) create mode 100755 bootstrap-fastfetch.sh create mode 100755 ff-logo diff --git a/README.md b/README.md index e69de29..39176c0 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/bootstrap-fastfetch.sh b/bootstrap-fastfetch.sh new file mode 100755 index 0000000..7a7b41d --- /dev/null +++ b/bootstrap-fastfetch.sh @@ -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." diff --git a/ff-logo b/ff-logo new file mode 100755 index 0000000..ffec214 --- /dev/null +++ b/ff-logo @@ -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 <&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" </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"