Make flake self-contained - consolidate installer-replication

BREAKING CHANGE: Remove external dots-hyprland dependency

- Imported all essential configs from dots-hyprland/installer-replication
- Added complete configs/ directory with:
  - hypr/ - Hyprland configuration
  - quickshell/ - Quickshell widgets and config
  - applications/ - Application configurations
  - scripts/ - Utility scripts
  - matugen/ - Material You theming
- Updated flake.nix to use local ./configs instead of external repo
- Simplified update-flake script (removed external repo management)
- Updated README to reflect self-contained architecture
- All builds pass with local configurations

Benefits:
- No external repository dependencies
- Faster builds (no network dependencies)
- Version controlled configs in single repo
- Easier maintenance and development
- Complete installer replication in one place
This commit is contained in:
Celes Renata
2025-08-08 22:26:47 -07:00
parent 22b65891ac
commit ac6d3adeb9
710 changed files with 81319 additions and 115 deletions
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
QUICKSHELL_CONFIG_NAME="ii"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME"
CACHE_DIR="$XDG_CACHE_HOME/quickshell"
STATE_DIR="$XDG_STATE_HOME/quickshell"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
term_alpha=100 #Set this to < 100 make all your terminals transparent
# sleep 0 # idk i wanted some delay or colors dont get applied properly
if [ ! -d "$STATE_DIR"/user/generated ]; then
mkdir -p "$STATE_DIR"/user/generated
fi
cd "$CONFIG_DIR" || exit
colornames=''
colorstrings=''
colorlist=()
colorvalues=()
colornames=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f1)
colorstrings=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d ";" -f1)
IFS=$'\n'
colorlist=($colornames) # Array of color names
colorvalues=($colorstrings) # Array of color values
apply_term() {
# Check if terminal escape sequence template exists
if [ ! -f "$SCRIPT_DIR/terminal/sequences.txt" ]; then
echo "Template file not found for Terminal. Skipping that."
return
fi
# Copy template
mkdir -p "$STATE_DIR"/user/generated/terminal
cp "$SCRIPT_DIR/terminal/sequences.txt" "$STATE_DIR"/user/generated/terminal/sequences.txt
# Apply colors
for i in "${!colorlist[@]}"; do
sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$STATE_DIR"/user/generated/terminal/sequences.txt
done
sed -i "s/\$alpha/$term_alpha/g" "$STATE_DIR/user/generated/terminal/sequences.txt"
for file in /dev/pts/*; do
if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then
{
cat "$STATE_DIR"/user/generated/terminal/sequences.txt >"$file"
} & disown || true
fi
done
}
apply_qt() {
sh "$CONFIG_DIR/scripts/kvantum/materialQT.sh" # generate kvantum theme
python "$CONFIG_DIR/scripts/kvantum/changeAdwColors.py" # apply config colors
}
# Check if terminal theming is enabled in config
CONFIG_FILE="$XDG_CONFIG_HOME/illogical-impulse/config.json"
if [ -f "$CONFIG_FILE" ]; then
enable_terminal=$(jq -r '.appearance.wallpaperTheming.enableTerminal' "$CONFIG_FILE")
if [ "$enable_terminal" = "true" ]; then
apply_term &
fi
else
echo "Config file not found at $CONFIG_FILE. Applying terminal theming by default."
apply_term &
fi
# apply_qt & # Qt theming is already handled by kde-material-colors
@@ -0,0 +1,181 @@
#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@""
import argparse
import math
import json
from PIL import Image
from materialyoucolor.quantize import QuantizeCelebi
from materialyoucolor.score.score import Score
from materialyoucolor.hct import Hct
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
from materialyoucolor.utils.color_utils import (rgba_from_argb, argb_from_rgb, argb_from_rgba)
from materialyoucolor.utils.math_utils import (sanitize_degrees_double, difference_degrees, rotation_direction)
parser = argparse.ArgumentParser(description='Color generation script')
parser.add_argument('--path', type=str, default=None, help='generate colorscheme from image')
parser.add_argument('--size', type=int , default=128 , help='bitmap image size')
parser.add_argument('--color', type=str, default=None, help='generate colorscheme from color')
parser.add_argument('--mode', type=str, choices=['dark', 'light'], default='dark', help='dark or light mode')
parser.add_argument('--scheme', type=str, default='vibrant', help='material scheme to use')
parser.add_argument('--smart', action='store_true', default=False, help='decide scheme type based on image color')
parser.add_argument('--transparency', type=str, choices=['opaque', 'transparent'], default='opaque', help='enable transparency')
parser.add_argument('--termscheme', type=str, default=None, help='JSON file containg the terminal scheme for generating term colors')
parser.add_argument('--harmony', type=float , default=0.8, help='(0-1) Color hue shift towards accent')
parser.add_argument('--harmonize_threshold', type=float , default=100, help='(0-180) Max threshold angle to limit color hue shift')
parser.add_argument('--term_fg_boost', type=float , default=0.35, help='Make terminal foreground more different from the background')
parser.add_argument('--blend_bg_fg', action='store_true', default=False, help='Shift terminal background or foreground towards accent')
parser.add_argument('--cache', type=str, default=None, help='file path to store the generated color')
parser.add_argument('--debug', action='store_true', default=False, help='debug mode')
args = parser.parse_args()
rgba_to_hex = lambda rgba: "#{:02X}{:02X}{:02X}".format(rgba[0], rgba[1], rgba[2])
argb_to_hex = lambda argb: "#{:02X}{:02X}{:02X}".format(*map(round, rgba_from_argb(argb)))
hex_to_argb = lambda hex_code: argb_from_rgb(int(hex_code[1:3], 16), int(hex_code[3:5], 16), int(hex_code[5:], 16))
display_color = lambda rgba : "\x1B[38;2;{};{};{}m{}\x1B[0m".format(rgba[0], rgba[1], rgba[2], "\x1b[7m \x1b[7m")
def calculate_optimal_size (width: int, height: int, bitmap_size: int) -> (int, int):
image_area = width * height;
bitmap_area = bitmap_size ** 2
scale = math.sqrt(bitmap_area/image_area) if image_area > bitmap_area else 1
new_width = round(width * scale)
new_height = round(height * scale)
if new_width == 0:
new_width = 1
if new_height == 0:
new_height = 1
return new_width, new_height
def harmonize (design_color: int, source_color: int, threshold: float = 35, harmony: float = 0.5) -> int:
from_hct = Hct.from_int(design_color)
to_hct = Hct.from_int(source_color)
difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue)
rotation_degrees = min(difference_degrees_ * harmony, threshold)
output_hue = sanitize_degrees_double(
from_hct.hue + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue)
)
return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int()
def boost_chroma_tone (argb: int, chroma: float = 1, tone: float = 1) -> int:
hct = Hct.from_int(argb)
return Hct.from_hct(hct.hue, hct.chroma * chroma, hct.tone * tone).to_int()
darkmode = (args.mode == 'dark')
transparent = (args.transparency == 'transparent')
if args.path is not None:
image = Image.open(args.path)
if image.format == "GIF":
image.seek(1)
if image.mode in ["L", "P"]:
image = image.convert('RGB')
wsize, hsize = image.size
wsize_new, hsize_new = calculate_optimal_size(wsize, hsize, args.size)
if wsize_new < wsize or hsize_new < hsize:
image = image.resize((wsize_new, hsize_new), Image.Resampling.BICUBIC)
colors = QuantizeCelebi(list(image.getdata()), 128)
argb = Score.score(colors)[0]
if args.cache is not None:
with open(args.cache, 'w') as file:
file.write(argb_to_hex(argb))
hct = Hct.from_int(argb)
if(args.smart):
if(hct.chroma < 20):
args.scheme = 'neutral'
elif args.color is not None:
argb = hex_to_argb(args.color)
hct = Hct.from_int(argb)
if args.scheme == 'scheme-fruit-salad':
from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme
elif args.scheme == 'scheme-expressive':
from materialyoucolor.scheme.scheme_expressive import SchemeExpressive as Scheme
elif args.scheme == 'scheme-monochrome':
from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome as Scheme
elif args.scheme == 'scheme-rainbow':
from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow as Scheme
elif args.scheme == 'scheme-tonal-spot':
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme
elif args.scheme == 'scheme-neutral':
from materialyoucolor.scheme.scheme_neutral import SchemeNeutral as Scheme
elif args.scheme == 'scheme-fidelity':
from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity as Scheme
elif args.scheme == 'scheme-content':
from materialyoucolor.scheme.scheme_content import SchemeContent as Scheme
elif args.scheme == 'scheme-vibrant':
from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant as Scheme
else:
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme
# Generate
scheme = Scheme(hct, darkmode, 0.0)
material_colors = {}
term_colors = {}
for color in vars(MaterialDynamicColors).keys():
color_name = getattr(MaterialDynamicColors, color)
if hasattr(color_name, "get_hct"):
rgba = color_name.get_hct(scheme).to_rgba()
material_colors[color] = rgba_to_hex(rgba)
# Extended material
if darkmode == True:
material_colors['success'] = '#B5CCBA'
material_colors['onSuccess'] = '#213528'
material_colors['successContainer'] = '#374B3E'
material_colors['onSuccessContainer'] = '#D1E9D6'
else:
material_colors['success'] = '#4F6354'
material_colors['onSuccess'] = '#FFFFFF'
material_colors['successContainer'] = '#D1E8D5'
material_colors['onSuccessContainer'] = '#0C1F13'
# Terminal Colors
if args.termscheme is not None:
with open(args.termscheme, 'r') as f:
json_termscheme = f.read()
term_source_colors = json.loads(json_termscheme)['dark' if darkmode else 'light']
primary_color_argb = hex_to_argb(material_colors['primary_paletteKeyColor'])
for color, val in term_source_colors.items():
if(args.scheme == 'monochrome') :
term_colors[color] = val
continue
if args.blend_bg_fg and color == "term0":
harmonized = boost_chroma_tone(hex_to_argb(material_colors['surfaceContainerLow']), 1.2, 0.95)
elif args.blend_bg_fg and color == "term15":
harmonized = boost_chroma_tone(hex_to_argb(material_colors['onSurface']), 3, 1)
else:
harmonized = harmonize(hex_to_argb(val), primary_color_argb, args.harmonize_threshold, args.harmony)
harmonized = boost_chroma_tone(harmonized, 1, 1 + (args.term_fg_boost * (1 if darkmode else -1)))
term_colors[color] = argb_to_hex(harmonized)
if args.debug == False:
print(f"$darkmode: {darkmode};")
print(f"$transparent: {transparent};")
for color, code in material_colors.items():
print(f"${color}: {code};")
for color, code in term_colors.items():
print(f"${color}: {code};")
else:
if args.path is not None:
print('\n--------------Image properties-----------------')
print(f"Image size: {wsize} x {hsize}")
print(f"Resized image: {wsize_new} x {hsize_new}")
print('\n---------------Selected color------------------')
print(f"Dark mode: {darkmode}")
print(f"Scheme: {args.scheme}")
print(f"Accent color: {display_color(rgba_from_argb(argb))} {argb_to_hex(argb)}")
print(f"HCT: {hct.hue:.2f} {hct.chroma:.2f} {hct.tone:.2f}")
print('\n---------------Material colors-----------------')
for color, code in material_colors.items():
rgba = rgba_from_argb(hex_to_argb(code))
print(f"{color.ljust(32)} : {display_color(rgba)} {code}")
print('\n----------Harmonize terminal colors------------')
for color, code in term_colors.items():
rgba = rgba_from_argb(hex_to_argb(code))
code_source = term_source_colors[color]
rgba_source = rgba_from_argb(hex_to_argb(code_source))
print(f"{color.ljust(6)} : {display_color(rgba_source)} {code_source} --> {display_color(rgba)} {code}")
print('-----------------------------------------------')
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
get_pictures_dir() {
if command -v xdg-user-dir &> /dev/null; then
xdg-user-dir PICTURES
return
fi
local config_file="${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs"
if [ -f "$config_file" ]; then
local pictures_path
pictures_path=$(source "$config_file" >/dev/null 2>&1; echo "$XDG_PICTURES_DIR")
echo "${pictures_path/#\$HOME/$HOME}"
return
fi
echo "$HOME/Pictures"
}
QUICKSHELL_CONFIG_NAME="ii"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
PICTURES_DIR=$(get_pictures_dir)
CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME"
CACHE_DIR="$XDG_CACHE_HOME/quickshell"
STATE_DIR="$XDG_STATE_HOME/quickshell"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
mkdir -p "$PICTURES_DIR/Wallpapers"
page=$((1 + RANDOM % 1000));
response=$(curl "https://konachan.net/post.json?tags=rating%3Asafe&limit=1&page=$page")
link=$(echo "$response" | jq '.[0].file_url' -r);
ext=$(echo "$link" | awk -F. '{print $NF}')
downloadPath="$PICTURES_DIR/Wallpapers/konachan_random_image.$ext"
illogicalImpulseConfigPath="$HOME/.config/illogical-impulse/config.json"
currentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath)
if [ "$downloadPath" == "$currentWallpaperPath" ]; then
downloadPath="$PICTURES_DIR/Wallpapers/konachan_random_image-1.$ext"
fi
curl "$link" -o "$downloadPath"
"$SCRIPT_DIR/switchwall.sh" --image "$downloadPath"
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import sys
import cv2
import numpy as np
# Allowed scheme types
SCHEMES = [
"scheme-content",
"scheme-expressive",
"scheme-fidelity",
"scheme-fruit-salad",
"scheme-monochrome",
"scheme-neutral",
"scheme-rainbow",
"scheme-tonal-spot"
]
def image_colorfulness(image):
# Based on Hasler and Süsstrunk's colorfulness metric
(B, G, R) = cv2.split(image.astype("float"))
rg = np.absolute(R - G)
yb = np.absolute(0.5 * (R + G) - B)
std_rg = np.std(rg)
std_yb = np.std(yb)
mean_rg = np.mean(rg)
mean_yb = np.mean(yb)
colorfulness = np.sqrt(std_rg ** 2 + std_yb ** 2) + (0.3 * np.sqrt(mean_rg ** 2 + mean_yb ** 2))
return colorfulness
# scheme-content respects the image's colors very well, but it might
# look too saturated, so we only use it for not very colorful images to be safe
def pick_scheme(colorfulness):
if colorfulness < 10:
# return "scheme-monochrome"
return "scheme-content"
elif colorfulness < 20:
return "scheme-content"
elif colorfulness < 50:
return "scheme-neutral"
else:
return "scheme-tonal-spot"
def main():
colorfulness_mode = False
args = sys.argv[1:]
if '--colorfulness' in args:
colorfulness_mode = True
args.remove('--colorfulness')
if len(args) < 1:
print("scheme-tonal-spot")
sys.exit(1)
img_path = args[0]
img = cv2.imread(img_path)
if img is None:
print("scheme-tonal-spot")
sys.exit(1)
colorfulness = image_colorfulness(img)
if colorfulness_mode:
print(f"{colorfulness}")
else:
scheme = pick_scheme(colorfulness)
print(scheme)
if __name__ == "__main__":
main()
+403
View File
@@ -0,0 +1,403 @@
#!/usr/bin/env bash
QUICKSHELL_CONFIG_NAME="ii"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME"
CACHE_DIR="$XDG_CACHE_HOME/quickshell"
STATE_DIR="$XDG_STATE_HOME/quickshell"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SHELL_CONFIG_FILE="$XDG_CONFIG_HOME/illogical-impulse/config.json"
MATUGEN_DIR="$XDG_CONFIG_HOME/matugen"
terminalscheme="$SCRIPT_DIR/terminal/scheme-base.json"
handle_kde_material_you_colors() {
# Check if Qt app theming is enabled in config
if [ -f "$SHELL_CONFIG_FILE" ]; then
enable_qt_apps=$(jq -r '.appearance.wallpaperTheming.enableQtApps' "$SHELL_CONFIG_FILE")
if [ "$enable_qt_apps" == "false" ]; then
return
fi
fi
# Map $type_flag to allowed scheme variants for kde-material-you-colors-wrapper.sh
local kde_scheme_variant=""
case "$type_flag" in
scheme-content|scheme-expressive|scheme-fidelity|scheme-fruit-salad|scheme-monochrome|scheme-neutral|scheme-rainbow|scheme-tonal-spot)
kde_scheme_variant="$type_flag"
;;
*)
kde_scheme_variant="scheme-tonal-spot" # default
;;
esac
"$XDG_CONFIG_HOME"/matugen/templates/kde/kde-material-you-colors-wrapper.sh --scheme-variant "$kde_scheme_variant"
}
pre_process() {
local mode_flag="$1"
# Set GNOME color-scheme if mode_flag is dark or light
if [[ "$mode_flag" == "dark" ]]; then
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3-dark'
elif [[ "$mode_flag" == "light" ]]; then
gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'
gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3'
fi
if [ ! -d "$CACHE_DIR"/user/generated ]; then
mkdir -p "$CACHE_DIR"/user/generated
fi
}
post_process() {
local screen_width="$1"
local screen_height="$2"
local wallpaper_path="$3"
handle_kde_material_you_colors &
# Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in
# if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then
# echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/"
# else
# "$MATUGEN_DIR/scripts/least_busy_region.py" \
# --screen-width "$screen_width" --screen-height "$screen_height" \
# --width 300 --height 200 \
# "$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json
# fi
}
check_and_prompt_upscale() {
local img="$1"
min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" # max monitor width
min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" # max monitor height
if command -v identify &>/dev/null && [ -f "$img" ]; then
local img_width img_height
if is_video "$img"; then # Not check resolution for videos, just let em pass
img_width=$min_width_desired
img_height=$min_height_desired
else
img_width=$(identify -format "%w" "$img" 2>/dev/null)
img_height=$(identify -format "%h" "$img" 2>/dev/null)
fi
if [[ "$img_width" -lt "$min_width_desired" || "$img_height" -lt "$min_height_desired" ]]; then
action=$(notify-send "Upscale?" \
"Image resolution (${img_width}x${img_height}) is lower than screen resolution (${min_width_desired}x${min_height_desired})" \
-A "open_upscayl=Open Upscayl"\
-a "Wallpaper switcher")
if [[ "$action" == "open_upscayl" ]]; then
if command -v upscayl &>/dev/null; then
nohup upscayl > /dev/null 2>&1 &
else
action2=$(notify-send \
-a "Wallpaper switcher" \
-c "im.error" \
-A "install_upscayl=Install Upscayl (Arch)" \
"Install Upscayl?" \
"yay -S upscayl-bin")
if [[ "$action2" == "install_upscayl" ]]; then
kitty -1 yay -S upscayl-bin
if command -v upscayl &>/dev/null; then
nohup upscayl > /dev/null 2>&1 &
fi
fi
fi
fi
fi
fi
}
CUSTOM_DIR="$XDG_CONFIG_HOME/hypr/custom"
RESTORE_SCRIPT_DIR="$CUSTOM_DIR/scripts"
RESTORE_SCRIPT="$RESTORE_SCRIPT_DIR/__restore_video_wallpaper.sh"
THUMBNAIL_DIR="$RESTORE_SCRIPT_DIR/mpvpaper_thumbnails"
VIDEO_OPTS="no-audio loop hwdec=auto scale=bilinear interpolation=no video-sync=display-resample panscan=1.0 video-scale-x=1.0 video-scale-y=1.0 video-align-x=0.5 video-align-y=0.5 load-scripts=no"
is_video() {
local extension="${1##*.}"
[[ "$extension" == "mp4" || "$extension" == "webm" || "$extension" == "mkv" || "$extension" == "avi" || "$extension" == "mov" ]] && return 0 || return 1
}
kill_existing_mpvpaper() {
pkill -f -9 mpvpaper || true
}
create_restore_script() {
local video_path=$1
cat > "$RESTORE_SCRIPT.tmp" << EOF
#!/bin/bash
# Generated by switchwall.sh - Don't modify it by yourself.
# Time: $(date)
pkill -f -9 mpvpaper
for monitor in \$(hyprctl monitors -j | jq -r '.[] | .name'); do
mpvpaper -o "$VIDEO_OPTS" "\$monitor" "$video_path" &
sleep 0.1
done
EOF
mv "$RESTORE_SCRIPT.tmp" "$RESTORE_SCRIPT"
chmod +x "$RESTORE_SCRIPT"
}
remove_restore() {
cat > "$RESTORE_SCRIPT.tmp" << EOF
#!/bin/bash
# The content of this script will be generated by switchwall.sh - Don't modify it by yourself.
EOF
mv "$RESTORE_SCRIPT.tmp" "$RESTORE_SCRIPT"
}
set_wallpaper_path() {
local path="$1"
if [ -f "$SHELL_CONFIG_FILE" ]; then
jq --arg path "$path" '.background.wallpaperPath = $path' "$SHELL_CONFIG_FILE" > "$SHELL_CONFIG_FILE.tmp" && mv "$SHELL_CONFIG_FILE.tmp" "$SHELL_CONFIG_FILE"
fi
}
set_thumbnail_path() {
local path="$1"
if [ -f "$SHELL_CONFIG_FILE" ]; then
jq --arg path "$path" '.background.thumbnailPath = $path' "$SHELL_CONFIG_FILE" > "$SHELL_CONFIG_FILE.tmp" && mv "$SHELL_CONFIG_FILE.tmp" "$SHELL_CONFIG_FILE"
fi
}
switch() {
imgpath="$1"
mode_flag="$2"
type_flag="$3"
color_flag="$4"
color="$5"
read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs)
cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960
cursorposx=$(bc <<< "scale=0; ($cursorposx - $screenx) * $scale / 1")
cursorposy=$(hyprctl cursorpos -j | jq '.y' 2>/dev/null) || cursorposy=540
cursorposy=$(bc <<< "scale=0; ($cursorposy - $screeny) * $scale / 1")
cursorposy_inverted=$((screensizey - cursorposy))
if [[ "$color_flag" == "1" ]]; then
matugen_args=(color hex "$color")
generate_colors_material_args=(--color "$color")
else
if [[ -z "$imgpath" ]]; then
echo 'Aborted'
exit 0
fi
check_and_prompt_upscale "$imgpath" &
kill_existing_mpvpaper
if is_video "$imgpath"; then
mkdir -p "$THUMBNAIL_DIR"
missing_deps=()
if ! command -v mpvpaper &> /dev/null; then
missing_deps+=("mpvpaper")
fi
if ! command -v ffmpeg &> /dev/null; then
missing_deps+=("ffmpeg")
fi
if [ ${#missing_deps[@]} -gt 0 ]; then
echo "Missing deps: ${missing_deps[*]}"
echo "Arch: sudo pacman -S ${missing_deps[*]}"
action=$(notify-send \
-a "Wallpaper switcher" \
-c "im.error" \
-A "install_arch=Install (Arch)" \
"Can't switch to video wallpaper" \
"Missing dependencies: ${missing_deps[*]}")
if [[ "$action" == "install_arch" ]]; then
kitty -1 sudo pacman -S "${missing_deps[*]}"
if command -v mpvpaper &>/dev/null && command -v ffmpeg &>/dev/null; then
notify-send 'Wallpaper switcher' 'Alright, try again!' -a "Wallpaper switcher"
fi
fi
exit 0
fi
# Set wallpaper path
set_wallpaper_path "$imgpath"
# Set video wallpaper
local video_path="$imgpath"
monitors=$(hyprctl monitors -j | jq -r '.[] | .name')
for monitor in $monitors; do
mpvpaper -o "$VIDEO_OPTS" "$monitor" "$video_path" &
sleep 0.1
done
# Extract first frame for color generation
thumbnail="$THUMBNAIL_DIR/$(basename "$imgpath").jpg"
ffmpeg -y -i "$imgpath" -vframes 1 "$thumbnail" 2>/dev/null
# Set thumbnail path
set_thumbnail_path "$thumbnail"
if [ -f "$thumbnail" ]; then
matugen_args=(image "$thumbnail")
generate_colors_material_args=(--path "$thumbnail")
create_restore_script "$video_path"
else
echo "Cannot create image to colorgen"
remove_restore
exit 1
fi
else
matugen_args=(image "$imgpath")
generate_colors_material_args=(--path "$imgpath")
# Update wallpaper path in config
set_wallpaper_path "$imgpath"
remove_restore
fi
fi
# Determine mode if not set
if [[ -z "$mode_flag" ]]; then
current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d "'")
if [[ "$current_mode" == "prefer-dark" ]]; then
mode_flag="dark"
else
mode_flag="light"
fi
fi
[[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag")
[[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag")
generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg)
generate_colors_material_args+=(--cache "$STATE_DIR/user/generated/color.txt")
pre_process "$mode_flag"
# Check if app and shell theming is enabled in config
if [ -f "$SHELL_CONFIG_FILE" ]; then
enable_apps_shell=$(jq -r '.appearance.wallpaperTheming.enableAppsAndShell' "$SHELL_CONFIG_FILE")
if [ "$enable_apps_shell" == "false" ]; then
echo "App and shell theming disabled, skipping matugen and color generation"
return
fi
fi
matugen "${matugen_args[@]}"
source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate"
python3 "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \
> "$STATE_DIR"/user/generated/material_colors.scss
"$SCRIPT_DIR"/applycolor.sh
deactivate
# Pass screen width, height, and wallpaper path to post_process
max_width_desired="$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)"
max_height_desired="$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)"
post_process "$max_width_desired" "$max_height_desired" "$imgpath"
}
main() {
imgpath=""
mode_flag=""
type_flag=""
color_flag=""
color=""
noswitch_flag=""
get_type_from_config() {
jq -r '.appearance.palette.type' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "auto"
}
detect_scheme_type_from_image() {
local img="$1"
"$SCRIPT_DIR"/scheme_for_image.py "$img" 2>/dev/null | tr -d '\n'
}
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
mode_flag="$2"
shift 2
;;
--type)
type_flag="$2"
shift 2
;;
--color)
color_flag="1"
if [[ "$2" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then
color="$2"
shift 2
else
color=$(hyprpicker --no-fancy)
shift
fi
;;
--image)
imgpath="$2"
shift 2
;;
--noswitch)
noswitch_flag="1"
imgpath=$(jq -r '.background.wallpaperPath' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "")
shift
;;
*)
if [[ -z "$imgpath" ]]; then
imgpath="$1"
fi
shift
;;
esac
done
# If type_flag is not set, get it from config
if [[ -z "$type_flag" ]]; then
type_flag="$(get_type_from_config)"
fi
# Validate type_flag (allow 'auto' as well)
allowed_types=(scheme-content scheme-expressive scheme-fidelity scheme-fruit-salad scheme-monochrome scheme-neutral scheme-rainbow scheme-tonal-spot auto)
valid_type=0
for t in "${allowed_types[@]}"; do
if [[ "$type_flag" == "$t" ]]; then
valid_type=1
break
fi
done
if [[ $valid_type -eq 0 ]]; then
echo "[switchwall.sh] Warning: Invalid type '$type_flag', defaulting to 'auto'" >&2
type_flag="auto"
fi
# Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set
if [[ -z "$imgpath" && -z "$color_flag" && -z "$noswitch_flag" ]]; then
cd "$(xdg-user-dir PICTURES)/Wallpapers/showcase" 2>/dev/null || cd "$(xdg-user-dir PICTURES)/Wallpapers" 2>/dev/null || cd "$(xdg-user-dir PICTURES)" || return 1
imgpath="$(kdialog --getopenfilename . --title 'Choose wallpaper')"
fi
# If type_flag is 'auto', detect scheme type from image (after imgpath is set)
if [[ "$type_flag" == "auto" ]]; then
if [[ -n "$imgpath" && -f "$imgpath" ]]; then
detected_type="$(detect_scheme_type_from_image "$imgpath")"
# Only use detected_type if it's valid
valid_detected=0
for t in "${allowed_types[@]}"; do
if [[ "$detected_type" == "$t" && "$detected_type" != "auto" ]]; then
valid_detected=1
break
fi
done
if [[ $valid_detected -eq 1 ]]; then
type_flag="$detected_type"
else
echo "[switchwall] Warning: Could not auto-detect a valid scheme, defaulting to 'scheme-tonal-spot'" >&2
type_flag="scheme-tonal-spot"
fi
else
echo "[switchwall] Warning: No image to auto-detect scheme from, defaulting to 'scheme-tonal-spot'" >&2
type_flag="scheme-tonal-spot"
fi
fi
switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color"
}
main "$@"
@@ -0,0 +1,38 @@
{
"dark": {
"term0" : "#282828",
"term1" : "#CC241D",
"term2" : "#98971A",
"term3" : "#D79921",
"term4" : "#458588",
"term5" : "#B16286",
"term6" : "#689D6A",
"term7" : "#A89984",
"term8" : "#928374",
"term9" : "#FB4934",
"term10" : "#B8BB26",
"term11" : "#FABD2F",
"term12" : "#83A598",
"term13" : "#D3869B",
"term14" : "#8EC07C",
"term15" : "#EBDBB2"
},
"light": {
"term0" : "#FDF9F3",
"term1" : "#FF6188",
"term2" : "#A9DC76",
"term3" : "#FC9867",
"term4" : "#FFD866",
"term5" : "#F47FD4",
"term6" : "#78DCE8",
"term7" : "#333034",
"term8" : "#121212",
"term9" : "#FF6188",
"term10" : "#A9DC76",
"term11" : "#FC9867",
"term12" : "#FFD866",
"term13" : "#F47FD4",
"term14" : "#78DCE8",
"term15" : "#333034"
}
}
@@ -0,0 +1 @@
]4;0;#$term0 #\]1;0;#$term0 #\]4;1;#$term1 #\]4;2;#$term2 #\]4;3;#$term3 #\]4;4;#$term4 #\]4;5;#$term5 #\]4;6;#$term6 #\]4;7;#$term7 #\]4;8;#$term8 #\]4;9;#$term9 #\]4;10;#$term10 #\]4;11;#$term11 #\]4;12;#$term12 #\]4;13;#$term13 #\]4;14;#$term14 #\]4;15;#$term15 #\]10;#$term7 #\]11;[100]#$term0 #\]12;#$term7 #\]13;#$term7 #\]17;#$term7 #\]19;#$term0 #\]4;232;#$term7 #\]4;256;#$term7 #\]708;[100]#$term0 #\]11;#$term0 #\