Compare commits

..
11 Commits
36 changed files with 559 additions and 185 deletions
+126 -8
View File
@@ -3,7 +3,9 @@
# blob-boot - boot splash and bootloader management. # blob-boot - boot splash and bootloader management.
# #
# blob-boot [image] Apply a Plymouth boot splash image (default action) # blob-boot [image] Apply a Plymouth boot splash image (default action)
# blob-boot grub Migrate from Limine back to GRUB, detecting every OS # blob-boot grub Install GRUB and detect every other OS on the machine.
# Migrates off Limine when it is there; on a machine
# that never had it, only GRUB is installed
# --purge removes Limine in the same run instead of # --purge removes Limine in the same run instead of
# keeping it for one fallback boot # keeping it for one fallback boot
# blob-boot detect Rescan for other operating systems, rebuild the menu # blob-boot detect Rescan for other operating systems, rebuild the menu
@@ -16,8 +18,12 @@
set -euo pipefail set -euo pipefail
DEFAULT_IMAGE="$HOME/Documents/dotfiles/branding/boot_flash.png" BLOB_PATH="${BLOB_PATH:-$HOME/.local/share/blob}"
PLYMOUTH_LOGO="/usr/share/plymouth/themes/blob/logo.png" DEFAULT_IMAGE="$BLOB_PATH/branding/boot_flash.png"
THEME_SOURCE="$BLOB_PATH/default/plymouth"
THEME_DIR="/usr/share/plymouth/themes/blob"
PLYMOUTH_LOGO="$THEME_DIR/logo.png"
MKINITCPIO_DROPIN="/etc/mkinitcpio.conf.d/blob-plymouth.conf"
log() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; } log() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
info() { printf ' %s\n' "$*"; } info() { printf ' %s\n' "$*"; }
@@ -43,17 +49,24 @@ apply_splash() {
fi fi
[ -f "$image_path" ] || die "File '$image_path' does not exist." [ -f "$image_path" ] || die "File '$image_path' does not exist."
[ -d "$(dirname "$PLYMOUTH_LOGO")" ] || die "Plymouth blob theme is not installed." command -v plymouth-set-default-theme >/dev/null \
|| die "The 'plymouth' package is not installed. Install it with: sudo pacman -S plymouth"
log "Applying boot splash image: $image_path" log "Applying boot splash image: $image_path"
require_sudo require_sudo
sudo cp "$image_path" "$PLYMOUTH_LOGO" install_theme "$image_path"
sudo chmod 644 "$PLYMOUTH_LOGO" install_initramfs_hook
enable_plymouth_units
log "Setting the default Plymouth theme"
sudo plymouth-set-default-theme blob
log "Rebuilding initramfs" log "Rebuilding initramfs"
rebuild_initramfs rebuild_initramfs
report_kernel_cmdline
# Under GRUB the menu references the initramfs by path, so it only needs # Under GRUB the menu references the initramfs by path, so it only needs
# regenerating if the file set changed - but it is cheap and keeps the menu # regenerating if the file set changed - but it is cheap and keeps the menu
# honest after a kernel or os-prober change. # honest after a kernel or os-prober change.
@@ -65,6 +78,80 @@ apply_splash() {
ok "Boot splash successfully updated!" ok "Boot splash successfully updated!"
} }
install_theme() {
local image_path=$1
[ -d "$THEME_SOURCE" ] || die "Theme source '$THEME_SOURCE' is missing from the checkout."
sudo install -d -m 0755 "$THEME_DIR"
sudo install -m 0644 "$THEME_SOURCE/blob.plymouth" "$THEME_DIR/blob.plymouth"
sudo install -m 0644 "$THEME_SOURCE/blob.script" "$THEME_DIR/blob.script"
sudo install -m 0644 "$image_path" "$PLYMOUTH_LOGO"
info "installed the blob theme to $THEME_DIR"
}
# A drop-in rather than an edit of HOOKS: mkinitcpio.conf.d is sourced after
# the main config, so the hook can be inserted without rewriting a line the
# user or another tool owns. plymouth has to come after udev, which starts the
# devices it draws on.
install_initramfs_hook() {
sudo install -d -m 0755 "$(dirname "$MKINITCPIO_DROPIN")"
sudo tee "$MKINITCPIO_DROPIN" >/dev/null <<'DROPIN'
# Written by blob-boot. Adds the plymouth hook after udev, once.
if [[ " ${HOOKS[*]} " != *" plymouth "* ]]; then
_hooks=()
for _hook in "${HOOKS[@]}"; do
_hooks+=("$_hook")
[[ $_hook == "udev" || $_hook == "systemd" ]] && _hooks+=("plymouth")
done
HOOKS=("${_hooks[@]}")
unset _hooks _hook
fi
DROPIN
info "wrote $MKINITCPIO_DROPIN"
}
# install.sh masks these so an unconfigured plymouth cannot hold the console and
# leave the machine with no greeter and no TTY. A configured one needs them: if
# plymouth-quit-wait stays masked the splash never hands the display back.
enable_plymouth_units() {
local unit
for unit in plymouth-start.service plymouth-quit-wait.service; do
if [ "$(systemctl is-enabled "$unit" 2>/dev/null)" = "masked" ]; then
sudo systemctl unmask "$unit" >/dev/null
info "unmasked $unit"
fi
done
}
# plymouth only draws when the kernel was told to be quiet and to splash. The
# bootloader owns that line, so report rather than rewrite anything but GRUB's.
report_kernel_cmdline() {
local params="quiet splash vt.global_cursor_default=0"
if grep -qw splash /proc/cmdline; then
info "kernel command line already carries 'splash'"
return 0
fi
log "One step left: the kernel command line"
if using_grub; then
warn "GRUB is in charge. Add to GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub:"
warn " $params"
warn "then: sudo grub-mkconfig -o /boot/grub/grub.cfg"
elif [ -f /boot/limine.conf ]; then
warn "Limine is in charge. Add to the cmdline in /boot/limine.conf:"
warn " $params"
elif [ -d /boot/loader/entries ]; then
warn "systemd-boot is in charge. Add to the options line in /boot/loader/entries/*.conf:"
warn " $params"
else
warn "Add these to your bootloader's kernel command line:"
warn " $params"
fi
warn "Without them the splash is drawn over by kernel messages."
}
# Call mkinitcpio directly: limine-mkinitcpio-hook installs a wrapper at # Call mkinitcpio directly: limine-mkinitcpio-hook installs a wrapper at
# /usr/local/bin/mkinitcpio that stops to ask whether to rebuild Limine # /usr/local/bin/mkinitcpio that stops to ask whether to rebuild Limine
# entries, which is useless in a script. While Limine is still the active # entries, which is useless in a script. While Limine is still the active
@@ -82,6 +169,26 @@ using_grub() {
[ -f /boot/grub/grub.cfg ] && [ -f /boot/EFI/GRUB/grubx64.efi ] [ -f /boot/grub/grub.cfg ] && [ -f /boot/EFI/GRUB/grubx64.efi ]
} }
# Everything Limine-specific below is skipped on a machine that never had it:
# reclaiming its space, removing its hooks, and rewriting the mkinitcpio preset
# its hooks needed. Rewriting that preset elsewhere would drop the fallback
# initramfs for no reason.
limine_present() {
pacman -Qq limine &>/dev/null || [ -f /boot/limine.conf ] || [ -d /boot/EFI/limine ]
}
install_grub_packages() {
local missing=()
command -v grub-install >/dev/null || missing+=(grub)
command -v os-prober >/dev/null || missing+=(os-prober)
(( ${#missing[@]} == 0 )) && return 0
log "Installing ${missing[*]}"
sudo pacman -S --needed --noconfirm "${missing[@]}" \
|| die "Could not install: ${missing[*]}"
}
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Limine -> GRUB migration # Limine -> GRUB migration
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@@ -92,10 +199,9 @@ migrate_to_grub() {
[ -d /sys/firmware/efi ] || die "Not booted in UEFI mode; this script assumes UEFI." [ -d /sys/firmware/efi ] || die "Not booted in UEFI mode; this script assumes UEFI."
mountpoint -q /boot || die "/boot is not mounted." mountpoint -q /boot || die "/boot is not mounted."
command -v grub-install >/dev/null || die "The 'grub' package is not installed."
command -v os-prober >/dev/null || die "The 'os-prober' package is not installed."
require_sudo require_sudo
install_grub_packages
local machine_id esp_uuid backup local machine_id esp_uuid backup
machine_id=$(</etc/machine-id) machine_id=$(</etc/machine-id)
@@ -111,8 +217,14 @@ migrate_to_grub() {
sudo efibootmgr -v | sudo tee "$backup/efibootmgr-before.txt" >/dev/null 2>&1 || true sudo efibootmgr -v | sudo tee "$backup/efibootmgr-before.txt" >/dev/null 2>&1 || true
info "saved." info "saved."
if limine_present; then
reclaim_esp_space "$machine_id" "$backup" reclaim_esp_space "$machine_id" "$backup"
restore_standard_initramfs restore_standard_initramfs
else
info "Limine is not installed; leaving the initramfs layout alone"
log "Building the initramfs"
rebuild_initramfs
fi
configure_grub_scripts configure_grub_scripts
install_grub "$esp_uuid" install_grub "$esp_uuid"
fix_failing_boot_units fix_failing_boot_units
@@ -529,6 +641,12 @@ set_boot_order() {
# Delete every trace of Limine and hand its EFI fallback path to GRUB. # Delete every trace of Limine and hand its EFI fallback path to GRUB.
purge_limine() { purge_limine() {
if ! limine_present; then
log "Claiming the removable EFI fallback path for GRUB"
sudo grub-install --target=x86_64-efi --efi-directory=/boot --bootloader-id=GRUB --removable --recheck
return 0
fi
log "Removing Limine" log "Removing Limine"
drop_limine_pkgs limine limine-mkinitcpio-hook limine-snapper-sync drop_limine_pkgs limine limine-mkinitcpio-hook limine-snapper-sync
+4 -2
View File
@@ -12,9 +12,11 @@ LOGO_FILE="$HOME/.config/blob/branding/about.txt"
FIT_FILE="$HOME/.local/state/blob/windows/about.fit" FIT_FILE="$HOME/.local/state/blob/windows/about.fit"
# A user-level fastfetch config can relocate or restyle the logo in ways this # A user-level fastfetch config can relocate or restyle the logo in ways this
# measurement cannot see, so leave sizing to the float rule in that case. # measurement cannot see, so leave sizing to the float rule in that case. Blob's
# own config is the symlink the installer makes into the current theme, and the
# measurements below are written against it.
custom_fastfetch_config() { custom_fastfetch_config() {
[[ -f $HOME/.config/fastfetch/config.jsonc ]] [[ -f $HOME/.config/fastfetch/config.jsonc && ! -L $HOME/.config/fastfetch/config.jsonc ]]
} }
# wc -L counts display columns only in a UTF-8 locale. A session that never set # wc -L counts display columns only in a UTF-8 locale. A session that never set
+1 -1
View File
@@ -10,4 +10,4 @@ source blob-restart-gum
cmd="$*" cmd="$*"
presentation_script="blob-show-logo; $cmd; if (( \$? != 130 )); then blob-show-done; fi" presentation_script="blob-show-logo; $cmd; if (( \$? != 130 )); then blob-show-done; fi"
exec setsid uwsm-app -- xdg-terminal-exec --app-id=org.blob.terminal --title=Blob -e bash -c "$presentation_script" exec setsid uwsm-app -- blob-terminal-exec --app-id=org.blob.terminal --title=Blob -e bash -c "$presentation_script"
+1 -1
View File
@@ -16,7 +16,7 @@ if blob-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then
fi fi
focused=$(blob-hypr-monitor-focused) focused=$(blob-hypr-monitor-focused)
terminal=$(xdg-terminal-exec --print-id) terminal=$(blob-terminal-exec --print-id)
case $terminal in case $terminal in
*Alacritty* | *ghostty* | *foot* | *kitty*) ;; *Alacritty* | *ghostty* | *foot* | *kitty*) ;;
+1 -10
View File
@@ -3,13 +3,4 @@
# blob:summary=Launch the default terminal # blob:summary=Launch the default terminal
# blob:args=[command [args...]] # blob:args=[command [args...]]
if blob-cmd-present xdg-terminal-exec; then exec setsid uwsm-app -- blob-terminal-exec "$@"
exec setsid uwsm-app -- xdg-terminal-exec "$@"
fi
for terminal in foot kitty; do
blob-cmd-present "$terminal" && exec setsid uwsm-app -- "$terminal" "$@"
done
blob-notify-send "No terminal installed" "Install xdg-terminal-exec, foot or kitty."
exit 1
+1 -1
View File
@@ -10,4 +10,4 @@ else
APP_ID="org.blob.$(basename $1)" APP_ID="org.blob.$(basename $1)"
fi fi
exec setsid uwsm-app -- xdg-terminal-exec --app-id=$APP_ID -e "$1" "${@:2}" exec setsid uwsm-app -- blob-terminal-exec --app-id=$APP_ID -e "$1" "${@:2}"
+1 -1
View File
@@ -67,7 +67,7 @@ desktop_name="${desktop_file##*/}"
desktop_name="${desktop_name%.desktop}" desktop_name="${desktop_name%.desktop}"
exec_line="$(sed -n 's/^Exec=//p' "$desktop_file" | head -1)" exec_line="$(sed -n 's/^Exec=//p' "$desktop_file" | head -1)"
if [[ $exec_line =~ (^|[[:space:]])(\$TERMINAL|xdg-terminal-exec)[[:space:]].*-e([[:space:]]|$) ]]; then if [[ $exec_line =~ (^|[[:space:]])(\$TERMINAL|xdg-terminal-exec|blob-terminal-exec)[[:space:]].*-e([[:space:]]|$) ]]; then
BLOB_REMOVE_NOTIFY=false blob-tui-remove "$desktop_name" BLOB_REMOVE_NOTIFY=false blob-tui-remove "$desktop_name"
exit 0 exit 0
fi fi
+1 -1
View File
@@ -22,7 +22,7 @@ effect_args() {
) )
printf '%s\n' -i "$input" --frame-rate 120 --canvas-width 0 --canvas-height 0 \ printf '%s\n' -i "$input" --frame-rate 120 --canvas-width 0 --canvas-height 0 \
--anchor-canvas c --anchor-text c --no-color \ --anchor-canvas c --anchor-text c \
"${effects[RANDOM % ${#effects[@]}]}" "${effects[RANDOM % ${#effects[@]}]}"
} }
+67
View File
@@ -0,0 +1,67 @@
#!/bin/bash
# blob:summary=Run a command in the default terminal
# blob:args=[--print-id] [--app-id=<app-id>] [--title=<title>] [-e] <command> [args...]
# blob:hidden=true
terminal=""
for candidate in xdg-terminal-exec foot kitty; do
if command -v "$candidate" &>/dev/null; then
terminal=$candidate
break
fi
done
if [[ -z $terminal ]]; then
blob-notify-send "No terminal installed" "Install xdg-terminal-exec, foot or kitty."
exit 1
fi
if [[ $terminal == "xdg-terminal-exec" ]]; then
exec xdg-terminal-exec "$@"
fi
app_id=""
title=""
command_args=()
while (( $# > 0 )); do
case "$1" in
--print-id)
printf '%s\n' "$terminal"
exit 0
;;
--app-id=*)
app_id=${1#--app-id=}
shift
;;
--title=*)
title=${1#--title=}
shift
;;
-e)
shift
command_args+=("$@")
break
;;
*)
command_args+=("$1")
shift
;;
esac
done
launch=("$terminal")
case "$terminal" in
foot)
[[ -n $app_id ]] && launch+=("--app-id=$app_id")
;;
kitty)
[[ -n $app_id ]] && launch+=("--class=$app_id")
;;
esac
[[ -n $title ]] && launch+=("--title=$title")
exec "${launch[@]}" "${command_args[@]}"
+3 -3
View File
@@ -21,7 +21,7 @@ current_mode() {
} }
list_theme_rows() { list_theme_rows() {
printf 'Dynamic (from wallpaper)\tblob-dynamic\n' printf '\tDynamic (from wallpaper)\tblob-dynamic\n'
{ {
find "$user_themes_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null find "$user_themes_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null
@@ -30,8 +30,8 @@ list_theme_rows() {
[ -f "$theme_dir/colors.toml" ] || continue [ -f "$theme_dir/colors.toml" ] || continue
slug=$(basename "$theme_dir") slug=$(basename "$theme_dir")
[ "$slug" = "blob-dynamic" ] && continue [ "$slug" = "blob-dynamic" ] && continue
printf '%s\t%s\n' "$(prettify "$slug")" "$slug" printf '\t%s\t%s\n' "$(prettify "$slug")" "$slug"
done | sort -u -t$'\t' -k2,2 done | sort -u -t$'\t' -k3,3
} }
print_current_colors() { print_current_colors() {
-1
View File
@@ -108,7 +108,6 @@ set_theme_background() {
local new_background new_background_snapshot local new_background new_background_snapshot
if ! choose_theme_background; then if ! choose_theme_background; then
blob-notify-send "No background was found for theme" -t 2000
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
return return
fi fi
+1
View File
@@ -203,6 +203,7 @@ add_template_value() {
if [[ $value =~ ^#[0-9A-Fa-f]{6}$ ]]; then if [[ $value =~ ^#[0-9A-Fa-f]{6}$ ]]; then
rgb=$(hex_to_rgb "$value") rgb=$(hex_to_rgb "$value")
printf 's|{{ %s_rgb }}|%s|g\n' "$key" "$rgb" >>"$sed_script" printf 's|{{ %s_rgb }}|%s|g\n' "$key" "$rgb" >>"$sed_script"
printf 's|{{ %s_ansi }}|38;2;%s|g\n' "$key" "${rgb//,/;}" >>"$sed_script"
fi fi
} }
+1 -1
View File
@@ -84,7 +84,7 @@ cat >"$DESKTOP_FILE" <<EOF
Version=1.0 Version=1.0
Name=$APP_NAME Name=$APP_NAME
Comment=$APP_NAME Comment=$APP_NAME
Exec=xdg-terminal-exec --app-id=$APP_CLASS -e $APP_EXEC Exec=blob-terminal-exec --app-id=$APP_CLASS -e $APP_EXEC
Terminal=false Terminal=false
Type=Application Type=Application
Icon=$ICON_VALUE Icon=$ICON_VALUE
+1 -1
View File
@@ -12,7 +12,7 @@ DESKTOP_DIR="$HOME/.local/share/applications/"
if (( $# == 0 )); then if (( $# == 0 )); then
# Find all TUIs # Find all TUIs
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
if grep -qE '^Exec=.*(\$TERMINAL|xdg-terminal-exec).*-e' "$file"; then if grep -qE '^Exec=.*(\$TERMINAL|xdg-terminal-exec|blob-terminal-exec).*-e' "$file"; then
TUIS+=("$(basename "${file%.desktop}")") TUIS+=("$(basename "${file%.desktop}")")
fi fi
done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0) done < <(find "$DESKTOP_DIR" -name '*.desktop' -print0)
+1 -1
View File
@@ -12,7 +12,7 @@ echo "Scanning for TUIs in $APP_DIR..."
tui_desktop_files=() tui_desktop_files=()
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
if grep -q "Exec=xdg-terminal-exec --app-id=TUI\." "$file" 2>/dev/null; then if grep -qE "Exec=(xdg|blob)-terminal-exec --app-id=TUI\." "$file" 2>/dev/null; then
tui_desktop_files+=("$file") tui_desktop_files+=("$file")
fi fi
done < <(find "$APP_DIR" -maxdepth 1 -name "*.desktop" -print0 2>/dev/null) done < <(find "$APP_DIR" -maxdepth 1 -name "*.desktop" -print0 2>/dev/null)
+21 -22
View File
@@ -1,26 +1,25 @@
@@@@@@@@@@@@ @@ @@ @@
@@@@ @@@@ @@ @@ @@
@@@ @@@ @@ @@ @@
@@@ @@@ @@ @@ @@
@@@ @@ @@ @@ @@
@@ @@ @@ @@
@ @@ @@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ @@ ##### @ @ @ @@ @@ @@
@ @ ##### @ @ ##### @ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@ @ ### @ @ ###### @ @@ @@ @@@@@@
@ @ @ @ ######@@ @@ @@ @@ @@
@@ @@@ @@@ @@ @@@ @ @@ @@ @@
@@ @@ @@ @@ @@
@@ @@ @@ @@ @@
@@ @ @@ @@@@@@@
@ @@
@@ @@ @@ @@
@@ @@ @@ @@
@ @@
@@ @@ @@ @@
@@ @@ @@ @@
@@@ @@ @@@@@@@@@@@@@@@@
@@@ @@@
@@@ @@@
@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@ @@ @@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<rect x="8.6" y="1.6" width="1.3" height="4.6" rx="0.65" fill="#000000" transform="rotate(-12 9.25 3.9)"/>
<rect x="12.4" y="2.4" width="1.3" height="4.6" rx="0.65" fill="#000000" transform="rotate(12 13.05 4.7)"/>
<path d="M17.2 11 a3.4 3.4 0 0 1 0 6 v-1.7 a1.7 1.7 0 0 0 0 -2.6 z" fill="#000000"/>
<path d="M4 9 h14 l-2.5 9.2 a1.6 1.6 0 0 1 -1.55 1.2 h-5.9 a1.6 1.6 0 0 1 -1.55 -1.2 z" fill="#000000"/>
<rect x="3" y="20.4" width="18" height="2" rx="1" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 612 B

+4 -4
View File
@@ -254,8 +254,8 @@
"setup.direct-boot": {"icon":"","label":"Direct Boot","action":"blob-launch-floating blob-setup-direct-boot"}, "setup.direct-boot": {"icon":"","label":"Direct Boot","action":"blob-launch-floating blob-setup-direct-boot"},
// Install // Install
"install.package": {"icon":"󰣇","label":"Package","action":"xdg-terminal-exec --app-id=org.blob.terminal blob-pkg-install"}, "install.package": {"icon":"󰣇","label":"Package","action":"blob-terminal-exec --app-id=org.blob.terminal blob-pkg-install"},
"install.aur": {"icon":"󰣇","label":"AUR","action":"xdg-terminal-exec --app-id=org.blob.terminal blob-pkg-aur-install"}, "install.aur": {"icon":"󰣇","label":"AUR","action":"blob-terminal-exec --app-id=org.blob.terminal blob-pkg-aur-install"},
"install.tui": {"icon":"","label":"TUI","action":"blob-launch-floating blob-tui-install"}, "install.tui": {"icon":"","label":"TUI","action":"blob-launch-floating blob-tui-install"},
"install.style": {"icon":"","label":"Style"}, "install.style": {"icon":"","label":"Style"},
"install.style.theme": {"icon":"󰸌","label":"Theme","action":"blob-launch-floating blob-theme-install"}, "install.style.theme": {"icon":"󰸌","label":"Theme","action":"blob-launch-floating blob-theme-install"},
@@ -269,8 +269,8 @@
"install.style.font.iosevka": {"icon":"","label":"Iosevka","action":"blob-install-font Iosevka ttf-iosevka-nerd 'Iosevka Nerd Font Mono'"}, "install.style.font.iosevka": {"icon":"","label":"Iosevka","action":"blob-install-font Iosevka ttf-iosevka-nerd 'Iosevka Nerd Font Mono'"},
// Remove // Remove
"remove.package": {"icon":"󰣇","label":"Package","action":"xdg-terminal-exec --app-id=org.blob.terminal blob-pkg-remove"}, "remove.package": {"icon":"󰣇","label":"Package","action":"blob-terminal-exec --app-id=org.blob.terminal blob-pkg-remove"},
"remove.tui": {"icon":"","label":"TUI","when":"grep -qE '^Exec=.*(\\$TERMINAL|xdg-terminal-exec).*-e' $HOME/.local/share/applications/*.desktop","action":"blob-tui-remove"}, "remove.tui": {"icon":"","label":"TUI","when":"grep -qE '^Exec=.*(\\$TERMINAL|xdg-terminal-exec|blob-terminal-exec).*-e' $HOME/.local/share/applications/*.desktop","action":"blob-tui-remove"},
"remove.theme": {"icon":"󰸌","label":"Theme","action":"blob-theme-remove"}, "remove.theme": {"icon":"󰸌","label":"Theme","action":"blob-theme-remove"},
// Update // Update
+8
View File
@@ -0,0 +1,8 @@
[Plymouth Theme]
Name=Blob
Description=Blob boot splash
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/blob
ScriptFile=/usr/share/plymouth/themes/blob/blob.script
+70
View File
@@ -0,0 +1,70 @@
Window.SetBackgroundTopColor(0.059, 0.063, 0.071);
Window.SetBackgroundBottomColor(0.059, 0.063, 0.071);
screen.width = Window.GetWidth();
screen.height = Window.GetHeight();
logo.image = Image("logo.png");
logo.width = logo.image.GetWidth();
logo.height = logo.image.GetHeight();
scale_x = screen.width / logo.width;
scale_y = screen.height / logo.height;
scale = scale_x;
if (scale_y < scale_x) {
scale = scale_y;
}
if (scale != 1) {
logo.image = logo.image.Scale(logo.width * scale, logo.height * scale);
logo.width = logo.image.GetWidth();
logo.height = logo.image.GetHeight();
}
logo.sprite = Sprite(logo.image);
logo.sprite.SetX((screen.width - logo.width) / 2);
logo.sprite.SetY((screen.height - logo.height) / 2);
logo.sprite.SetZ(0);
prompt.sprite = Sprite();
prompt.sprite.SetZ(10);
message.sprite = Sprite();
message.sprite.SetZ(10);
fun draw_prompt(text) {
if (text == "") {
prompt.sprite.SetImage(Image.Text(" ", 1, 1, 1));
return;
}
prompt.image = Image.Text(text, 0.8, 0.82, 0.85);
prompt.sprite.SetImage(prompt.image);
prompt.sprite.SetX((screen.width - prompt.image.GetWidth()) / 2);
prompt.sprite.SetY(screen.height * 0.82);
}
fun display_password_callback(prompt_text, bullets) {
dots = "";
index = 0;
while (index < bullets) {
dots += "*";
index++;
}
draw_prompt(prompt_text + " " + dots);
}
fun display_normal_callback() {
draw_prompt("");
}
fun message_callback(text) {
message.image = Image.Text(text, 0.8, 0.82, 0.85);
message.sprite.SetImage(message.image);
message.sprite.SetX((screen.width - message.image.GetWidth()) / 2);
message.sprite.SetY(screen.height * 0.9);
}
Plymouth.SetDisplayPasswordFunction(display_password_callback);
Plymouth.SetDisplayNormalFunction(display_normal_callback);
Plymouth.SetMessageFunction(message_callback);
+50
View File
@@ -0,0 +1,50 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
"type": "file",
"source": "~/.config/blob/branding/about.txt",
"padding": {
"top": 2,
"left": 2,
"right": 6
}
},
"display": {
"separator": " ",
"key": {
"width": 9
},
"color": {
"title": "{{ accent_ansi }}",
"keys": "{{ accent_ansi }}",
"output": "{{ foreground_ansi }}"
}
},
"modules": [
{
"type": "title",
"format": "{user-name}@{host-name}"
},
"break",
{ "type": "os", "key": "os" },
{ "type": "kernel", "key": "kernel" },
{ "type": "uptime", "key": "uptime" },
{ "type": "packages", "key": "packages" },
{ "type": "shell", "key": "shell" },
"break",
{ "type": "wm", "key": "wm" },
{ "type": "terminal", "key": "term" },
{ "type": "display", "key": "display" },
"break",
{ "type": "cpu", "key": "cpu" },
{ "type": "gpu", "key": "gpu" },
{ "type": "memory", "key": "memory" },
{ "type": "disk", "key": "disk" },
"break",
{
"type": "colors",
"symbol": "circle",
"paddingLeft": 9
}
]
}
+2 -2
View File
@@ -1,6 +1,6 @@
add_newline = true add_newline = true
command_timeout = 200 command_timeout = 200
format = "[$directory$git_branch$git_status]($style)$character" format = "$directory$git_branch$git_status$character"
palette = "blob" palette = "blob"
[palettes.blob] [palettes.blob]
@@ -27,7 +27,7 @@ format = "[$branch]($style) "
style = "italic accent" style = "italic accent"
[git_status] [git_status]
format = '[$all_status]($style)' format = '[$all_status ]($style)'
style = "warning" style = "warning"
ahead = "⇡${count} " ahead = "⇡${count} "
diverged = "⇕⇡${ahead_count}⇣${behind_count} " diverged = "⇕⇡${ahead_count}⇣${behind_count} "
+1
View File
@@ -533,6 +533,7 @@ of the `blob` listing.
| Command | Does | Arguments | | Command | Does | Arguments |
| --- | --- | --- | | --- | --- | --- |
| `blob-terminal-exec` | Run a command in the default terminal (hidden) | [--print-id] [--app-id=<app-id>] [--title=<title>] [-e] <command> [args...] |
| `blob-theme-browser` | Apply the current theme color to Chromium, Chrome, Edge, and Brave (hidden) | - | | `blob-theme-browser` | Apply the current theme color to Chromium, Chrome, Edge, and Brave (hidden) | - |
| `blob-theme-browser-policy` | Write the current theme color into the browser policy directories (hidden) | <rrggbb> | | `blob-theme-browser-policy` | Write the current theme color into the browser policy directories (hidden) | <rrggbb> |
| `blob-theme-color` | Resolve semantic colors from an Blob theme colors.toml (hidden) | [--file <colors.toml>] (--all \| --raw \| <key> [fallback]) | | `blob-theme-color` | Resolve semantic colors from an Blob theme colors.toml (hidden) | [--file <colors.toml>] (--all \| --raw \| <key> [fallback]) |
+28 -7
View File
@@ -42,10 +42,8 @@ with `--check`.
panels come up full of blanks. `fonts.conf` binds `monospace` to panels come up full of blanks. `fonts.conf` binds `monospace` to
`JetBrainsMono Nerd Font`; `blob font set` rewrites the same file, and the `JetBrainsMono Nerd Font`; `blob font set` rewrites the same file, and the
installer then reports it as a local change and keeps your choice. installer then reports it as a local change and keeps your choice.
`~/.bashrc` gets one line sourcing `session/bashrc`, which is what starts `~/.bashrc` gets one line sourcing `session/bashrc`. The line is added once,
the starship prompt and puts `blob` on `PATH` in an interactive shell. The a `.bak` is kept, and a `~/.bashrc` that already has it is left alone.
line is added once, a `.bak` is kept, and a `~/.bashrc` that already has it
is left alone.
`session/profile.sh` also goes to `/etc/profile.d/blob.sh`, which sources `session/profile.sh` also goes to `/etc/profile.d/blob.sh`, which sources
`session/env-bootstrap` and is what puts `blob` on `PATH` in a login shell. `session/env-bootstrap` and is what puts `blob` on `PATH` in a login shell.
Without it `BLOB_PATH` and `$BLOB_PATH/bin` are only set by Without it `BLOB_PATH` and `$BLOB_PATH/bin` are only set by
@@ -76,9 +74,10 @@ with `--check`.
avahi, docker, power-profiles-daemon, and oomd. avahi, docker, power-profiles-daemon, and oomd.
`NetworkManager-wait-online.service` is masked so a slow DHCP lease cannot `NetworkManager-wait-online.service` is masked so a slow DHCP lease cannot
hold up the login screen, and `plymouth-start.service` and hold up the login screen, and `plymouth-start.service` and
`plymouth-quit-wait.service` are masked because Blob configures no boot `plymouth-quit-wait.service` are masked because an unconfigured plymouth
splash: an unconfigured plymouth holds the console and leaves the machine holds the console and leaves the machine with neither a greeter nor a TTY.
with neither a greeter nor a TTY. See [packages.md](packages.md). `blob boot` unmasks them when it sets the splash up. See
[packages.md](packages.md#the-boot-splash).
9. **The login screen** is SDDM. It has to be installed first: if 9. **The login screen** is SDDM. It has to be installed first: if
`sddm.service` is not there, the installer says whether the package is `sddm.service` is not there, the installer says whether the package is
missing or whether systemd has simply not re-read its units yet, and carries missing or whether systemd has simply not re-read its units yet, and carries
@@ -146,6 +145,28 @@ with `--check`.
| `--autologin` | skip the SDDM prompt for this user | | `--autologin` | skip the SDDM prompt for this user |
| `--no-launch` | leave the login screen for the next reboot | | `--no-launch` | leave the login screen for the next reboot |
## The interactive shell
`session/bashrc` is sourced from `~/.bashrc` and returns immediately in a
non-interactive shell, so it cannot disturb `scp` or `rsync`. It does three
things, each only when the tool is installed:
| Line | Effect |
| --- | --- |
| `session/env-bootstrap` | `BLOB_PATH` and `$BLOB_PATH/bin` on `PATH`, so `blob` resolves |
| `starship init bash` | the themed prompt, see [themes.md](themes.md#the-prompt) |
| `zoxide init bash --cmd cd` | `cd` learns the directories you actually use |
`--cmd cd` replaces `cd` rather than adding a `z` command, so
`cd blomarchy` jumps to the last matching directory you have visited from
anywhere, while `cd ./relative/path`, `cd -` and `cd` with no argument keep
behaving exactly as the builtin does. `cdi` picks from the matches
interactively through `fzf`, and `zoxide query -ls` prints the database.
The database is built as you go: it learns a directory the first time you `cd`
there, so the jumping is only as good as the history behind it and a fresh
install knows nothing yet.
## The lock screen ## The lock screen
The lock plugin watches `/etc/pam.d/blob-lock-password` and refuses to lock at The lock plugin watches `/etc/pam.d/blob-lock-password` and refuses to lock at
+22 -15
View File
@@ -58,8 +58,11 @@ than usual.
Two are worth knowing about: Two are worth knowing about:
- `xdg-terminal-exec` is load-bearing. Every TUI launcher's `Exec=` line calls - `xdg-terminal-exec` used to be load-bearing: every TUI launcher, the floating
it, so if its AUR build ever fails, TUI shortcuts stop opening. launcher behind most of the menu, and `$TERMINAL` called it by name, so a
failed AUR build silently broke about half the menu. They all go through
`blob-terminal-exec` now, which prefers it and falls back to `foot`, then
`kitty`, both from the official repositories.
- `aether` is in the AUR at a version newer than the one the repository shipped. - `aether` is in the AUR at a version newer than the one the repository shipped.
`quickshell` never came from the Omarchy repository - it is in official `extra`. `quickshell` never came from the Omarchy repository - it is in official `extra`.
@@ -69,7 +72,7 @@ Two are worth knowing about:
`install.sh` installs what is missing before it writes any config: `install.sh` installs what is missing before it writes any config:
``` ```
packages/blob.packages 121 packages from core, extra, multilib packages/blob.packages 123 packages from core, extra, multilib
packages/blob-aur.packages 11 packages with no official equivalent packages/blob-aur.packages 11 packages with no official equivalent
``` ```
@@ -133,23 +136,27 @@ Omarchy's own package list, because they were installed by hand on this machine:
`playerctl` is in official `extra`, so it sits in the repo list. `playerctl` is in official `extra`, so it sits in the repo list.
## No boot splash ## The boot splash
`plymouth` is deliberately not on either list. Blob configures no splash: there `plymouth` is installed but left inert, and the splash is opt-in through one
is no mkinitcpio hook, no kernel command line, and `default/plymouth/` ships no command:
theme assets, so `blob-plymouth-set` has nothing to publish either. An installed
but unconfigured plymouth is not harmless - it takes the console at boot and
never hands it back, and `getty@tty1` waits on `plymouth-quit-wait.service`, so
the machine reaches neither the greeter nor a TTY.
`install.sh` masks `plymouth-start.service` and `plymouth-quit-wait.service` for
that reason, which un-breaks a machine that already has the package. To go
further:
``` ```
sudo pacman -Rns plymouth blob boot # branding/boot_flash.png
blob boot ~/some/image.png # or any other image
``` ```
The reason it is opt-in is that an installed but *unconfigured* plymouth is not
harmless: it takes the console at boot and never hands it back, and
`getty@tty1` waits on `plymouth-quit-wait.service`, so the machine reaches
neither the greeter nor a TTY. `install.sh` therefore masks
`plymouth-start.service` and `plymouth-quit-wait.service`, and `blob boot`
unmasks them as part of configuring everything else - the theme in
`default/plymouth/`, the `plymouth` mkinitcpio hook, and the initramfs rebuild.
It finishes by printing the kernel parameters your bootloader still needs
(`quiet splash vt.global_cursor_default=0`), because that line is the one thing
it will not rewrite for a bootloader other than GRUB.
## Updating ## Updating
``` ```
+25 -6
View File
@@ -9,7 +9,7 @@ retints the running desktop.
| File | Used by | | File | Used by |
| --- | --- | | --- | --- |
| `colors.toml` | required; the palette everything else derives from | | `colors.toml` | required; the palette everything else derives from |
| `backgrounds/` | the background switcher | | `backgrounds/` | the background switcher; a theme without one keeps the current wallpaper |
| `shell.lock.toml` | lock screen surface tokens | | `shell.lock.toml` | lock screen surface tokens |
| `neovim.lua` | the editor colorscheme | | `neovim.lua` | the editor colorscheme |
| `icons.theme` | GTK icon theme name | | `icons.theme` | GTK icon theme name |
@@ -30,13 +30,14 @@ the shell and the CLI never disagree about what a slot means.
Each rendered file is picked up differently: `foot.ini` and `kitty.conf` by an Each rendered file is picked up differently: `foot.ini` and `kitty.conf` by an
`include` in the shipped terminal config, `hyprland.lua` through Hyprland's Lua `include` in the shipped terminal config, `hyprland.lua` through Hyprland's Lua
module path, `shell.toml` and `chromium.theme` read straight from the state module path, `shell.toml` and `chromium.theme` read straight from the state
directory, and `btop.theme`, `neovim.lua`, `gtk.css` and `starship.toml` directory, and `btop.theme`, `neovim.lua`, `gtk.css`, `starship.toml` and
through symlinks the installer makes into those apps' own config trees. `fastfetch.jsonc` through symlinks the installer makes into those apps' own
config trees.
`default/themed/*.tpl` are rendered per theme into `default/themed/*.tpl` are rendered per theme into
`~/.local/state/blob/current/theme/`. Eleven ship: foot, kitty, btop, chromium, `~/.local/state/blob/current/theme/`. Twelve ship: foot, kitty, btop, chromium,
hyprland, the screenshare picker, neovim, `shell.toml`, zen, `gtk.css`, and hyprland, the screenshare picker, neovim, `shell.toml`, zen, `gtk.css`,
`starship.toml`. `starship.toml`, and `fastfetch.jsonc`.
An app picks its colors up in one of two ways. Most include the generated file An app picks its colors up in one of two ways. Most include the generated file
directly, so nothing has to run: directly, so nothing has to run:
@@ -66,6 +67,24 @@ so an app that was already open keeps its old colors until it is restarted.
Adding a template for another app means dropping a `.tpl` in `default/themed/` Adding a template for another app means dropping a `.tpl` in `default/themed/`
and, if it cannot include a file, adding an applier to that list. and, if it cannot include a file, adding an applier to that list.
## Tokens
A template may reference `{{ key }}` for any resolved palette key, plus three
derived forms: `{{ key_strip }}` without the leading `#`, `{{ key_rgb }}` as
`R,G,B`, and `{{ key_ansi }}` as `38;2;R;G;B`, which is what a terminal program
that takes a raw escape sequence rather than a hex color needs.
## About
`fastfetch.jsonc` is linked to `~/.config/fastfetch/config.jsonc`. It draws
`~/.config/blob/branding/about.txt` as the logo, with 2 columns of padding to
its left and 6 between it and the modules - the exact numbers
`blob-launch-about` measures the About window against, so replacing the logo
resizes the window to fit it. Title and keys take the theme's accent.
A config of your own at that path is a regular file rather than the symlink, and
`blob-launch-about` leaves the window sizing alone when it finds one.
## The prompt ## The prompt
`starship.toml` is linked to `~/.config/starship.toml`. It carries a `starship.toml` is linked to `~/.config/starship.toml`. It carries a
+1
View File
@@ -134,6 +134,7 @@ link_app_themes() {
link_theme_file gtk.css "$config_dir/gtk-3.0/gtk.css" "gtk-3.0 theme" link_theme_file gtk.css "$config_dir/gtk-3.0/gtk.css" "gtk-3.0 theme"
link_theme_file gtk.css "$config_dir/gtk-4.0/gtk.css" "gtk-4.0 theme" link_theme_file gtk.css "$config_dir/gtk-4.0/gtk.css" "gtk-4.0 theme"
link_theme_file starship.toml "$config_dir/starship.toml" "starship prompt" link_theme_file starship.toml "$config_dir/starship.toml" "starship prompt"
link_theme_file fastfetch.jsonc "$config_dir/fastfetch/config.jsonc" "fastfetch"
if [[ -d $config_dir/nvim/lua/plugins ]]; then if [[ -d $config_dir/nvim/lua/plugins ]]; then
link_theme_file neovim.lua "$config_dir/nvim/lua/plugins/theme.lua" "neovim theme" link_theme_file neovim.lua "$config_dir/nvim/lua/plugins/theme.lua" "neovim theme"
+1
View File
@@ -22,6 +22,7 @@ wl-clipboard
wtype wtype
socat socat
inotify-tools inotify-tools
plymouth
polkit polkit
# Shell widget backends # Shell widget backends
+2
View File
@@ -6,3 +6,5 @@ esac
[ -r "$HOME/.local/share/blob/session/env-bootstrap" ] && . "$HOME/.local/share/blob/session/env-bootstrap" [ -r "$HOME/.local/share/blob/session/env-bootstrap" ] && . "$HOME/.local/share/blob/session/env-bootstrap"
command -v starship >/dev/null 2>&1 && eval "$(starship init bash)" command -v starship >/dev/null 2>&1 && eval "$(starship init bash)"
command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init bash --cmd cd)"
+1 -1
View File
@@ -1,2 +1,2 @@
export TERMINAL=xdg-terminal-exec export TERMINAL=blob-terminal-exec
export EDITOR="blob-launch-editor --inline" export EDITOR="blob-launch-editor --inline"
+1 -1
View File
@@ -3,7 +3,7 @@
if [ -f "${BLOB_PATH%/}/session/uwsm/default" ]; then if [ -f "${BLOB_PATH%/}/session/uwsm/default" ]; then
. "${BLOB_PATH%/}/session/uwsm/default" . "${BLOB_PATH%/}/session/uwsm/default"
else else
export TERMINAL=xdg-terminal-exec export TERMINAL=blob-terminal-exec
export EDITOR="blob-launch-editor --inline" export EDITOR="blob-launch-editor --inline"
fi fi
+2 -2
View File
@@ -8,7 +8,7 @@ BarWidget {
id: root id: root
moduleName: "blob.menu" moduleName: "blob.menu"
readonly property string iconPath: Quickshell.env("HOME") + "/.config/blob/branding/blob_icon.svg" readonly property string iconPath: Quickshell.env("HOME") + "/.config/blob/branding/chai.svg"
readonly property int iconSize: Math.round(Style.font.body * 1.35) readonly property int iconSize: Math.round(Style.font.body * 1.35)
property string iconSvg: "" property string iconSvg: ""
@@ -48,7 +48,7 @@ BarWidget {
horizontalMargin: 7.5 horizontalMargin: 7.5
onPressed: function(button) { onPressed: function(button) {
if (!root.bar) return if (!root.bar) return
if (button === Qt.RightButton) root.bar.run("xdg-terminal-exec") if (button === Qt.RightButton) root.bar.run("blob-terminal-exec")
else root.bar.run("blob-shell shell toggle blob.menu '{\"menu\":\"root\"}'") else root.bar.run("blob-shell shell toggle blob.menu '{\"menu\":\"root\"}'")
} }
+1
View File
@@ -1243,6 +1243,7 @@ Item {
font.family: row.iconFont.length > 0 ? row.iconFont : root.fontFamily font.family: row.iconFont.length > 0 ? row.iconFont : root.fontFamily
font.pixelSize: Style.font.iconLarge font.pixelSize: Style.font.iconLarge
width: Style.space(36) width: Style.space(36)
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
anchors.left: parent.left anchors.left: parent.left
@@ -192,13 +192,10 @@ Item {
onClicked: root.dismiss() onClicked: root.dismiss()
} }
Row {
anchors.top: parent.top
anchors.topMargin: Style.spaceReal(10) + root.barInset
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.spaceReal(12)
WeatherCard { WeatherCard {
anchors.top: controlCentre.top
anchors.right: controlCentre.left
anchors.rightMargin: Style.spaceReal(12)
available: root.weatherAvailable available: root.weatherAvailable
glyph: root.weatherGlyph glyph: root.weatherGlyph
place: root.weatherPlace place: root.weatherPlace
@@ -208,6 +205,9 @@ Item {
Card { Card {
id: controlCentre id: controlCentre
anchors.top: parent.top
anchors.topMargin: Style.spaceReal(10) + root.barInset
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: Style.spaceReal(400) implicitWidth: Style.spaceReal(400)
fillColor: Util.alpha(Color.background, Style.panelFillAlpha) fillColor: Util.alpha(Color.background, Style.panelFillAlpha)
spacing: Style.spaceReal(12) spacing: Style.spaceReal(12)
@@ -272,7 +272,6 @@ Item {
} }
} }
} }
}
onOpenedChanged: if (root.opened) root.refreshState() onOpenedChanged: if (root.opened) root.refreshState()
} }
+30 -22
View File
@@ -1,23 +1,31 @@
accent = "#7babe3" mode = "dark"
cursor = "#cbd2d9"
foreground = "#cbd2d9"
background = "#0f1012"
selection_foreground = "#0f1012"
selection_background = "#7babe3"
color0 = "#000000" accent = "#5fa3e8"
color1 = "#d53838" selection = "#1e2228"
color2 = "#38d4d5" muted = "#5c656e"
color3 = "#d58638"
color4 = "#d53886" background = "#0f1012"
color5 = "#38d586" dark_background = "#0a0b0c"
color6 = "#3886d5" darker_background = "#050607"
color7 = "#d4d4d4" lighter_background = "#191c20"
color8 = "#949494"
color9 = "#d53838" foreground = "#cbd2d9"
color10 = "#38d4d5" dark_foreground = "#78828c"
color11 = "#d58638" light_foreground = "#dde3e9"
color12 = "#d53886" bright_foreground = "#f0f4f8"
color13 = "#38d586"
color14 = "#3886d5" red = "#d53838"
color15 = "#d4d4d4" yellow = "#d58638"
orange = "#d56338"
green = "#38d586"
cyan = "#38d4d5"
blue = "#3886d5"
magenta = "#d53886"
brown = "#6a321c"
bright_red = "#dd6060"
bright_yellow = "#dd9e60"
bright_green = "#60dd9e"
bright_cyan = "#60dddd"
bright_blue = "#609edd"
bright_magenta = "#dd609e"
+1
View File
@@ -114,6 +114,7 @@ for lua in hyprland.lua monitors.lua input.lua looknfeel.lua bindings.lua autost
done done
[[ -L $config_dir/starship.toml ]] && restore_or_remove "$config_dir/starship.toml" "starship.toml" [[ -L $config_dir/starship.toml ]] && restore_or_remove "$config_dir/starship.toml" "starship.toml"
[[ -L $config_dir/fastfetch/config.jsonc ]] && restore_or_remove "$config_dir/fastfetch/config.jsonc" "fastfetch/config.jsonc"
for gtk in gtk-3.0 gtk-4.0; do for gtk in gtk-3.0 gtk-4.0; do
[[ -L $config_dir/$gtk/gtk.css ]] && restore_or_remove "$config_dir/$gtk/gtk.css" "$gtk/gtk.css" [[ -L $config_dir/$gtk/gtk.css ]] && restore_or_remove "$config_dir/$gtk/gtk.css" "$gtk/gtk.css"