mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-01 20:28:16 +02:00
508 lines
14 KiB
Bash
Executable File
508 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Generate themed config files from Omarchy templates
|
|
# omarchy:hidden=true
|
|
|
|
TEMPLATES_DIR="$OMARCHY_PATH/default/themed"
|
|
USER_TEMPLATES_DIR="$HOME/.config/omarchy/themed"
|
|
NEXT_THEME_DIR="$HOME/.config/omarchy/current/next-theme"
|
|
COLORS_FILE="$NEXT_THEME_DIR/colors.toml"
|
|
|
|
declare -A THEME_COLORS
|
|
|
|
# Convert hex color to decimal RGB (e.g., "#1e1e2e" -> "30,30,46")
|
|
hex_to_rgb() {
|
|
local hex="${1#\#}"
|
|
printf "%d,%d,%d" "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
|
|
}
|
|
|
|
# Mix two hex colors. Amount may be a fraction (0.30) or percentage (30%).
|
|
mix_color() {
|
|
local start="${1#\#}"
|
|
local end="${2#\#}"
|
|
local amount="$3"
|
|
|
|
awk -v start="$start" -v end="$end" -v amount="$amount" '
|
|
function hex_value(char) {
|
|
return index("0123456789abcdef", tolower(char)) - 1
|
|
}
|
|
|
|
function hex_pair_to_int(hex, idx) {
|
|
return hex_value(substr(hex, idx, 1)) * 16 + hex_value(substr(hex, idx + 1, 1))
|
|
}
|
|
|
|
BEGIN {
|
|
if (amount ~ /%$/) {
|
|
sub(/%$/, "", amount)
|
|
amount = amount / 100
|
|
} else {
|
|
amount += 0
|
|
if (amount > 1) amount = amount / 100
|
|
}
|
|
|
|
if (amount < 0) amount = 0
|
|
if (amount > 1) amount = 1
|
|
|
|
start_r = hex_pair_to_int(start, 1)
|
|
start_g = hex_pair_to_int(start, 3)
|
|
start_b = hex_pair_to_int(start, 5)
|
|
end_r = hex_pair_to_int(end, 1)
|
|
end_g = hex_pair_to_int(end, 3)
|
|
end_b = hex_pair_to_int(end, 5)
|
|
|
|
red = int(start_r * (1 - amount) + end_r * amount + 0.5)
|
|
green = int(start_g * (1 - amount) + end_g * amount + 0.5)
|
|
blue = int(start_b * (1 - amount) + end_b * amount + 0.5)
|
|
|
|
printf "#%02x%02x%02x\n", red, green, blue
|
|
}
|
|
'
|
|
}
|
|
|
|
trim() {
|
|
local value="$1"
|
|
|
|
value="${value#"${value%%[![:space:]]*}"}"
|
|
value="${value%"${value##*[![:space:]]}"}"
|
|
printf "%s" "$value"
|
|
}
|
|
|
|
resolve_theme_ref() {
|
|
local ref="$1"
|
|
local fallback="${2:-}"
|
|
|
|
if [[ -n ${THEME_COLORS[$ref]+_} ]]; then
|
|
printf "%s" "${THEME_COLORS[$ref]}"
|
|
elif [[ -n $fallback && -n ${THEME_COLORS[$fallback]+_} ]]; then
|
|
printf "%s" "${THEME_COLORS[$fallback]}"
|
|
elif [[ -n $fallback ]]; then
|
|
printf "%s" "$fallback"
|
|
else
|
|
printf "%s" "$ref"
|
|
fi
|
|
}
|
|
|
|
resolve_gradient_color() {
|
|
local color
|
|
|
|
color=$(trim "$1")
|
|
if [[ -n ${THEME_COLORS[$color]+_} ]]; then
|
|
color="${THEME_COLORS[$color]}"
|
|
fi
|
|
|
|
printf "%s" "$color"
|
|
}
|
|
|
|
parse_gradient() {
|
|
local spec="$1"
|
|
local part color
|
|
local -a parts
|
|
|
|
GRADIENT_COLORS=()
|
|
GRADIENT_ANGLE=""
|
|
|
|
read -ra parts <<<"$spec"
|
|
for part in "${parts[@]}"; do
|
|
[[ -n $part ]] || continue
|
|
|
|
if [[ $part =~ ^-?[0-9]+([.][0-9]+)?deg$ ]]; then
|
|
GRADIENT_ANGLE="${part%deg}"
|
|
else
|
|
color=$(resolve_gradient_color "$part")
|
|
GRADIENT_COLORS+=("$color")
|
|
fi
|
|
done
|
|
}
|
|
|
|
color_to_shell_hex() {
|
|
local color r g b
|
|
|
|
color=$(resolve_gradient_color "$1")
|
|
|
|
if [[ $color =~ ^#[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$ ]]; then
|
|
printf "#%s" "${color:1:6}"
|
|
elif [[ $color =~ ^[Rr][Gg][Bb][Aa]?\(([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?\)$ ]]; then
|
|
printf "#%s" "${BASH_REMATCH[1]}"
|
|
elif [[ $color =~ ^[Rr][Gg][Bb][Aa]?\(([0-9]+),([0-9]+),([0-9]+)(,[0-9.]+)?\)$ ]]; then
|
|
r=${BASH_REMATCH[1]}
|
|
g=${BASH_REMATCH[2]}
|
|
b=${BASH_REMATCH[3]}
|
|
(( r > 255 )) && r=255
|
|
(( g > 255 )) && g=255
|
|
(( b > 255 )) && b=255
|
|
printf "#%02x%02x%02x" "$r" "$g" "$b"
|
|
elif [[ $color =~ ^0x[0-9A-Fa-f]{8}$ ]]; then
|
|
printf "#%s" "${color:4:6}"
|
|
else
|
|
printf "%s" "$color"
|
|
fi
|
|
}
|
|
|
|
hypr_gradient_value() {
|
|
local spec color index
|
|
|
|
spec=$(resolve_theme_ref "$1" "${2:-}")
|
|
parse_gradient "$spec"
|
|
|
|
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
|
|
printf '"%s"' "$spec"
|
|
elif (( ${#GRADIENT_COLORS[@]} == 1 )); then
|
|
printf '"%s"' "${GRADIENT_COLORS[0]}"
|
|
else
|
|
printf '{ colors = {'
|
|
for index in "${!GRADIENT_COLORS[@]}"; do
|
|
(( index > 0 )) && printf ','
|
|
printf ' "%s"' "${GRADIENT_COLORS[$index]}"
|
|
done
|
|
printf ' }'
|
|
[[ -n $GRADIENT_ANGLE ]] && printf ', angle = %s' "$GRADIENT_ANGLE"
|
|
printf ' }'
|
|
fi
|
|
}
|
|
|
|
gradient_start_value() {
|
|
local spec
|
|
|
|
spec=$(resolve_theme_ref "$1" "${2:-}")
|
|
parse_gradient "$spec"
|
|
|
|
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
|
|
color_to_shell_hex "$spec"
|
|
else
|
|
color_to_shell_hex "${GRADIENT_COLORS[0]}"
|
|
fi
|
|
}
|
|
|
|
shell_gradient_value() {
|
|
local spec index
|
|
|
|
spec=$(resolve_theme_ref "$1" "${2:-}")
|
|
parse_gradient "$spec"
|
|
|
|
if (( ${#GRADIENT_COLORS[@]} == 0 )); then
|
|
printf "%s" "$spec"
|
|
return
|
|
fi
|
|
|
|
for index in "${!GRADIENT_COLORS[@]}"; do
|
|
(( index > 0 )) && printf " "
|
|
printf "%s" "${GRADIENT_COLORS[$index]}"
|
|
done
|
|
|
|
[[ -n $GRADIENT_ANGLE ]] && printf " %sdeg" "$GRADIENT_ANGLE"
|
|
}
|
|
|
|
add_template_value() {
|
|
local key="$1"
|
|
local value="$2"
|
|
local rgb
|
|
|
|
printf 's|{{ %s }}|%s|g\n' "$key" "$value" >>"$sed_script"
|
|
printf 's|{{ %s_strip }}|%s|g\n' "$key" "${value#\#}" >>"$sed_script"
|
|
|
|
if [[ $value =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
|
rgb=$(hex_to_rgb "$value")
|
|
printf 's|{{ %s_rgb }}|%s|g\n' "$key" "$rgb" >>"$sed_script"
|
|
fi
|
|
}
|
|
|
|
add_mix_value() {
|
|
local token="$1"
|
|
local content fn start_key end_key amount start end value
|
|
|
|
content="${token#\{\{}"
|
|
content="${content%\}\}}"
|
|
read -r fn start_key end_key amount <<<"$content"
|
|
|
|
start="${THEME_COLORS[$start_key]:-}"
|
|
end="${THEME_COLORS[$end_key]:-}"
|
|
|
|
[[ $start =~ ^#[0-9A-Fa-f]{6}$ && $end =~ ^#[0-9A-Fa-f]{6}$ ]] || return
|
|
|
|
value=$(mix_color "$start" "$end" "$amount")
|
|
|
|
case "$fn" in
|
|
mix)
|
|
;;
|
|
mix_strip)
|
|
value="${value#\#}"
|
|
;;
|
|
mix_rgb)
|
|
value=$(hex_to_rgb "$value")
|
|
;;
|
|
*)
|
|
return
|
|
;;
|
|
esac
|
|
|
|
printf 's|%s|%s|g\n' "$token" "$value" >>"$sed_script"
|
|
}
|
|
|
|
add_mix_values() {
|
|
local tpl token
|
|
local -A seen=()
|
|
|
|
for tpl in "${template_files[@]}"; do
|
|
while IFS= read -r token; do
|
|
[[ -n ${seen[$token]:-} ]] && continue
|
|
seen[$token]=1
|
|
add_mix_value "$token"
|
|
done < <(grep -hEo '\{\{[[:space:]]*mix(_strip|_rgb)?[[:space:]]+[A-Za-z0-9_]+[[:space:]]+[A-Za-z0-9_]+[[:space:]]+[0-9]+([.][0-9]+)?%?[[:space:]]*\}\}' "$tpl" 2>/dev/null || true)
|
|
done
|
|
}
|
|
|
|
add_gradient_function_value() {
|
|
local token="$1"
|
|
local content fn key fallback value
|
|
|
|
content="${token#\{\{}"
|
|
content="${content%\}\}}"
|
|
read -r fn key fallback <<<"$content"
|
|
|
|
case "$fn" in
|
|
hypr_gradient)
|
|
value=$(hypr_gradient_value "$key" "$fallback")
|
|
;;
|
|
gradient_start)
|
|
value=$(gradient_start_value "$key" "$fallback")
|
|
;;
|
|
shell_gradient)
|
|
value=$(shell_gradient_value "$key" "$fallback")
|
|
;;
|
|
*)
|
|
return
|
|
;;
|
|
esac
|
|
|
|
printf 's|%s|%s|g\n' "$token" "$value" >>"$sed_script"
|
|
}
|
|
|
|
add_gradient_function_values() {
|
|
local tpl token
|
|
local -A seen=()
|
|
|
|
for tpl in "${template_files[@]}"; do
|
|
while IFS= read -r token; do
|
|
[[ -n ${seen[$token]:-} ]] && continue
|
|
seen[$token]=1
|
|
add_gradient_function_value "$token"
|
|
done < <(grep -hEo '\{\{[[:space:]]*(hypr_gradient|gradient_start|shell_gradient)[[:space:]]+[^}]+[[:space:]]*\}\}' "$tpl" 2>/dev/null || true)
|
|
done
|
|
}
|
|
|
|
strip_shell_section_header() {
|
|
local section="$1"
|
|
local file="$2"
|
|
|
|
awk -v section="$section" '
|
|
BEGIN { skipping_header = 0 }
|
|
/^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
|
|
if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
|
|
skipping_header = 1
|
|
next
|
|
}
|
|
}
|
|
{ print }
|
|
' "$file"
|
|
}
|
|
|
|
apply_shell_section_override() {
|
|
local override="$1"
|
|
local section tmp body
|
|
|
|
[[ -f $NEXT_THEME_DIR/shell.toml ]] || return
|
|
[[ -f $override ]] || return
|
|
|
|
section=$(basename "$override")
|
|
section="${section#shell.}"
|
|
section="${section%.toml}"
|
|
[[ $section =~ ^[A-Za-z0-9_-]+$ ]] || return
|
|
|
|
tmp=$(mktemp)
|
|
body=$(mktemp)
|
|
|
|
strip_shell_section_header "$section" "$override" >"$body"
|
|
|
|
awk -v section="$section" -v body="$body" '
|
|
function emit_override() {
|
|
if (emitted) return
|
|
print "[" section "]"
|
|
while ((getline line < body) > 0) print line
|
|
close(body)
|
|
emitted = 1
|
|
}
|
|
|
|
/^[[:space:]]*\[[^]]+\][[:space:]]*($|#)/ {
|
|
if (in_section) {
|
|
emit_override()
|
|
in_section = 0
|
|
}
|
|
if ($0 ~ "^[[:space:]]*\\[" section "\\][[:space:]]*($|#)") {
|
|
in_section = 1
|
|
next
|
|
}
|
|
}
|
|
|
|
!in_section { print }
|
|
|
|
END {
|
|
if (in_section || !emitted) {
|
|
if (NR > 0) print ""
|
|
emit_override()
|
|
}
|
|
}
|
|
' "$NEXT_THEME_DIR/shell.toml" >"$tmp"
|
|
|
|
mv "$tmp" "$NEXT_THEME_DIR/shell.toml"
|
|
rm "$body"
|
|
}
|
|
|
|
apply_shell_section_overrides() {
|
|
local override
|
|
|
|
shopt -s nullglob
|
|
for override in "$NEXT_THEME_DIR"/shell.*.toml; do
|
|
[[ $(basename "$override") != "shell.toml" ]] || continue
|
|
apply_shell_section_override "$override"
|
|
done
|
|
}
|
|
|
|
alias_theme_color() {
|
|
local key="$1"
|
|
local fallback="$2"
|
|
|
|
[[ ${THEME_COLORS[$key]} ]] || THEME_COLORS[$key]="${THEME_COLORS[$fallback]}"
|
|
}
|
|
|
|
resolve_theme_mode() {
|
|
local bg_hex lum
|
|
|
|
[[ ${THEME_COLORS[mode]} ]] || THEME_COLORS[mode]="${THEME_COLORS[theme_type]}"
|
|
[[ ${THEME_COLORS[mode]} ]] && return
|
|
|
|
if [[ -f $NEXT_THEME_DIR/light.mode ]]; then
|
|
THEME_COLORS[mode]="light"
|
|
elif [[ ${THEME_COLORS[background]} =~ ^#[0-9A-Fa-f]{6}$ ]]; then
|
|
bg_hex="${THEME_COLORS[background]#\#}"
|
|
lum=$(( $(printf "%d" "0x${bg_hex:0:2}") + $(printf "%d" "0x${bg_hex:2:2}") + $(printf "%d" "0x${bg_hex:4:2}") ))
|
|
(( lum > 382 )) && THEME_COLORS[mode]="light" || THEME_COLORS[mode]="dark"
|
|
else
|
|
THEME_COLORS[mode]="dark"
|
|
fi
|
|
}
|
|
|
|
# Only generate dynamic templates for themes with a colors.toml definition
|
|
if [[ -f $COLORS_FILE ]]; then
|
|
sed_script=$(mktemp)
|
|
|
|
shopt -s nullglob
|
|
template_files=("$USER_TEMPLATES_DIR"/*.tpl "$TEMPLATES_DIR"/*.tpl)
|
|
|
|
while IFS='=' read -r key value; do
|
|
key="${key//[\"\' ]/}" # strip quotes and spaces from key
|
|
[[ $key && $key != \#* ]] || continue # skip empty lines and comments
|
|
value="${value#*[\"\']}"
|
|
value="${value%%[\"\']*}" # extract value between quotes (ignores inline comments)
|
|
|
|
THEME_COLORS[$key]="$value"
|
|
done <"$COLORS_FILE"
|
|
|
|
# Legacy compatibility: map ANSI color0..color15 and raw bg/fg to semantic names
|
|
declare -A legacy_alias=(
|
|
[bg]=background
|
|
[fg]=foreground
|
|
[red]=color1
|
|
[green]=color2
|
|
[yellow]=color3
|
|
[blue]=color4
|
|
[magenta]=color5
|
|
[cyan]=color6
|
|
[bright_red]=color9
|
|
[bright_green]=color10
|
|
[bright_yellow]=color11
|
|
[bright_blue]=color12
|
|
[bright_magenta]=color13
|
|
[bright_cyan]=color14
|
|
)
|
|
for key in "${!legacy_alias[@]}"; do
|
|
alias_theme_color "$key" "${legacy_alias[$key]}"
|
|
done
|
|
alias_theme_color magenta purple
|
|
alias_theme_color bright_magenta bright_purple
|
|
|
|
[[ ${THEME_COLORS[light_fg]} ]] || THEME_COLORS[light_fg]="${THEME_COLORS[color7]:-${THEME_COLORS[foreground]}}"
|
|
[[ ${THEME_COLORS[bright_fg]} ]] || THEME_COLORS[bright_fg]="${THEME_COLORS[color15]:-${THEME_COLORS[foreground]}}"
|
|
[[ ${THEME_COLORS[lighter_bg]} ]] || THEME_COLORS[lighter_bg]="${THEME_COLORS[color0]:-${THEME_COLORS[background]}}"
|
|
[[ ${THEME_COLORS[dark_fg]} ]] || THEME_COLORS[dark_fg]="${THEME_COLORS[color8]:-${THEME_COLORS[foreground]}}"
|
|
[[ ${THEME_COLORS[muted]} ]] || THEME_COLORS[muted]="${THEME_COLORS[color8]:-${THEME_COLORS[dark_fg]}}"
|
|
[[ ${THEME_COLORS[selection]} ]] || THEME_COLORS[selection]="${THEME_COLORS[selection_background]:-${THEME_COLORS[color8]:-${THEME_COLORS[color0]}}}"
|
|
[[ ${THEME_COLORS[orange]} ]] || THEME_COLORS[orange]="${THEME_COLORS[yellow]}"
|
|
[[ ${THEME_COLORS[brown]} ]] || THEME_COLORS[brown]=$(mix_color "${THEME_COLORS[orange]}" "#000000" 50%)
|
|
|
|
# Auto-derive shades from base accents when not defined and not aliased from colorN
|
|
[[ ${THEME_COLORS[dark_bg]} ]] || THEME_COLORS[dark_bg]=$(mix_color "${THEME_COLORS[background]}" "#000000" 25%)
|
|
[[ ${THEME_COLORS[darker_bg]} ]] || THEME_COLORS[darker_bg]=$(mix_color "${THEME_COLORS[background]}" "#000000" 50%)
|
|
[[ ${THEME_COLORS[bright_red]} ]] || THEME_COLORS[bright_red]=$(mix_color "${THEME_COLORS[red]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_yellow]} ]] || THEME_COLORS[bright_yellow]=$(mix_color "${THEME_COLORS[yellow]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_green]} ]] || THEME_COLORS[bright_green]=$(mix_color "${THEME_COLORS[green]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_cyan]} ]] || THEME_COLORS[bright_cyan]=$(mix_color "${THEME_COLORS[cyan]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_blue]} ]] || THEME_COLORS[bright_blue]=$(mix_color "${THEME_COLORS[blue]}" "#ffffff" 20%)
|
|
[[ ${THEME_COLORS[bright_magenta]} ]] || THEME_COLORS[bright_magenta]=$(mix_color "${THEME_COLORS[magenta]}" "#ffffff" 20%)
|
|
alias_theme_color purple magenta
|
|
alias_theme_color bright_purple bright_magenta
|
|
|
|
# Keep semantic themes compatible with user templates that still reference
|
|
# the legacy ANSI placeholders directly.
|
|
declare -A ansi_alias=(
|
|
[color0]=bg
|
|
[color1]=red
|
|
[color2]=green
|
|
[color3]=yellow
|
|
[color4]=blue
|
|
[color5]=magenta
|
|
[color6]=cyan
|
|
[color7]=fg
|
|
[color8]=muted
|
|
[color9]=bright_red
|
|
[color10]=bright_green
|
|
[color11]=bright_yellow
|
|
[color12]=bright_blue
|
|
[color13]=bright_magenta
|
|
[color14]=bright_cyan
|
|
[color15]=bright_fg
|
|
)
|
|
for key in "${!ansi_alias[@]}"; do
|
|
alias_theme_color "$key" "${ansi_alias[$key]}"
|
|
done
|
|
|
|
# Resolve theme mode (dark|light) with this precedence:
|
|
# 1. `mode` key in colors.toml
|
|
# 2. legacy `light.mode` file shipped beside the theme
|
|
# 3. background luminance auto-detect
|
|
# `theme_type` is kept as an alias so existing templates ({{ theme_type }}) keep working.
|
|
resolve_theme_mode
|
|
THEME_COLORS[theme_type]="${THEME_COLORS[mode]}"
|
|
|
|
for key in "${!THEME_COLORS[@]}"; do
|
|
add_template_value "$key" "${THEME_COLORS[$key]}"
|
|
done
|
|
|
|
add_mix_values
|
|
add_gradient_function_values
|
|
|
|
# Process user templates first, then built-in templates (user overrides built-in)
|
|
for tpl in "${template_files[@]}"; do
|
|
filename=$(basename "$tpl" .tpl)
|
|
output_path="$NEXT_THEME_DIR/$filename"
|
|
|
|
# Don't overwrite configs already exists in the output directory (copied from theme specific folder)
|
|
if [[ ! -f $output_path ]]; then
|
|
sed -f "$sed_script" "$tpl" >"$output_path"
|
|
fi
|
|
done
|
|
|
|
rm "$sed_script"
|
|
fi
|
|
|
|
apply_shell_section_overrides
|