forked from Shinonome/alt-illogical-impulse
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:
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Get the list, skip the header, and extract the first column (model names)
|
||||
model_names=$(ollama list | tail -n +2 | awk '{print $1}')
|
||||
|
||||
# Build a JSON array
|
||||
json_array="["
|
||||
for name in $model_names; do
|
||||
json_array+="\"$name\","
|
||||
done
|
||||
|
||||
# Remove trailing comma and close the array
|
||||
json_array="${json_array%,}]"
|
||||
|
||||
# Output the JSON array
|
||||
echo "$json_array"
|
||||
@@ -0,0 +1,17 @@
|
||||
[general]
|
||||
mode = waves
|
||||
framerate = 60
|
||||
autosens = 1
|
||||
bars = 50
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
channels = mono
|
||||
mono_option = average
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 20
|
||||
|
||||
|
||||
+72
@@ -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('-----------------------------------------------')
|
||||
@@ -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
@@ -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
@@ -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 #\
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@""
|
||||
import argparse
|
||||
import re
|
||||
import os
|
||||
from os.path import expandvars as os_expandvars
|
||||
from typing import Dict, List
|
||||
|
||||
TITLE_REGEX = "#+!"
|
||||
HIDE_COMMENT = "[hidden]"
|
||||
MOD_SEPARATORS = ['+', ' ']
|
||||
COMMENT_BIND_PATTERN = "#/#"
|
||||
|
||||
parser = argparse.ArgumentParser(description='Hyprland keybind reader')
|
||||
parser.add_argument('--path', type=str, default="$HOME/.config/hypr/hyprland.conf", help='path to keybind file (sourcing isn\'t supported)')
|
||||
args = parser.parse_args()
|
||||
content_lines = []
|
||||
reading_line = 0
|
||||
|
||||
# Little Parser made for hyprland keybindings conf file
|
||||
Variables: Dict[str, str] = {}
|
||||
|
||||
|
||||
class KeyBinding(dict):
|
||||
def __init__(self, mods, key, dispatcher, params, comment) -> None:
|
||||
self["mods"] = mods
|
||||
self["key"] = key
|
||||
self["dispatcher"] = dispatcher
|
||||
self["params"] = params
|
||||
self["comment"] = comment
|
||||
|
||||
class Section(dict):
|
||||
def __init__(self, children, keybinds, name) -> None:
|
||||
self["children"] = children
|
||||
self["keybinds"] = keybinds
|
||||
self["name"] = name
|
||||
|
||||
|
||||
def read_content(path: str) -> str:
|
||||
if (not os.access(os.path.expanduser(os.path.expandvars(path)), os.R_OK)):
|
||||
return ("error")
|
||||
with open(os.path.expanduser(os.path.expandvars(path)), "r") as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
def autogenerate_comment(dispatcher: str, params: str = "") -> str:
|
||||
match dispatcher:
|
||||
|
||||
case "resizewindow":
|
||||
return "Resize window"
|
||||
|
||||
case "movewindow":
|
||||
if(params == ""):
|
||||
return "Move window"
|
||||
else:
|
||||
return "Window: move in {} direction".format({
|
||||
"l": "left",
|
||||
"r": "right",
|
||||
"u": "up",
|
||||
"d": "down",
|
||||
}.get(params, "null"))
|
||||
|
||||
case "pin":
|
||||
return "Window: pin (show on all workspaces)"
|
||||
|
||||
case "splitratio":
|
||||
return "Window split ratio {}".format(params)
|
||||
|
||||
case "togglefloating":
|
||||
return "Float/unfloat window"
|
||||
|
||||
case "resizeactive":
|
||||
return "Resize window by {}".format(params)
|
||||
|
||||
case "killactive":
|
||||
return "Close window"
|
||||
|
||||
case "fullscreen":
|
||||
return "Toggle {}".format(
|
||||
{
|
||||
"0": "fullscreen",
|
||||
"1": "maximization",
|
||||
"2": "fullscreen on Hyprland's side",
|
||||
}.get(params, "null")
|
||||
)
|
||||
|
||||
case "fakefullscreen":
|
||||
return "Toggle fake fullscreen"
|
||||
|
||||
case "workspace":
|
||||
if params == "+1":
|
||||
return "Workspace: focus right"
|
||||
elif params == "-1":
|
||||
return "Workspace: focus left"
|
||||
return "Focus workspace {}".format(params)
|
||||
|
||||
case "movefocus":
|
||||
return "Window: move focus {}".format(
|
||||
{
|
||||
"l": "left",
|
||||
"r": "right",
|
||||
"u": "up",
|
||||
"d": "down",
|
||||
}.get(params, "null")
|
||||
)
|
||||
|
||||
case "swapwindow":
|
||||
return "Window: swap in {} direction".format(
|
||||
{
|
||||
"l": "left",
|
||||
"r": "right",
|
||||
"u": "up",
|
||||
"d": "down",
|
||||
}.get(params, "null")
|
||||
)
|
||||
|
||||
case "movetoworkspace":
|
||||
if params == "+1":
|
||||
return "Window: move to right workspace (non-silent)"
|
||||
elif params == "-1":
|
||||
return "Window: move to left workspace (non-silent)"
|
||||
return "Window: move to workspace {} (non-silent)".format(params)
|
||||
|
||||
case "movetoworkspacesilent":
|
||||
if params == "+1":
|
||||
return "Window: move to right workspace"
|
||||
elif params == "-1":
|
||||
return "Window: move to right workspace"
|
||||
return "Window: move to workspace {}".format(params)
|
||||
|
||||
case "togglespecialworkspace":
|
||||
return "Workspace: toggle special"
|
||||
|
||||
case "exec":
|
||||
return "Execute: {}".format(params)
|
||||
|
||||
case _:
|
||||
return ""
|
||||
|
||||
def get_keybind_at_line(line_number, line_start = 0):
|
||||
global content_lines
|
||||
line = content_lines[line_number]
|
||||
_, keys = line.split("=", 1)
|
||||
keys, *comment = keys.split("#", 1)
|
||||
|
||||
mods, key, dispatcher, *params = list(map(str.strip, keys.split(",", 4)))
|
||||
params = "".join(map(str.strip, params))
|
||||
|
||||
# Remove empty spaces
|
||||
comment = list(map(str.strip, comment))
|
||||
# Add comment if it exists, else generate it
|
||||
if comment:
|
||||
comment = comment[0]
|
||||
if comment.startswith("[hidden]"):
|
||||
return None
|
||||
else:
|
||||
comment = autogenerate_comment(dispatcher, params)
|
||||
|
||||
if mods:
|
||||
modstring = mods + MOD_SEPARATORS[0] # Add separator at end to ensure last mod is read
|
||||
mods = []
|
||||
p = 0
|
||||
for index, char in enumerate(modstring):
|
||||
if(char in MOD_SEPARATORS):
|
||||
if(index - p > 1):
|
||||
mods.append(modstring[p:index])
|
||||
p = index+1
|
||||
else:
|
||||
mods = []
|
||||
|
||||
return KeyBinding(mods, key, dispatcher, params, comment)
|
||||
|
||||
def get_binds_recursive(current_content, scope):
|
||||
global content_lines
|
||||
global reading_line
|
||||
# print("get_binds_recursive({0}, {1}) [@L{2}]".format(current_content, scope, reading_line + 1))
|
||||
while reading_line < len(content_lines): # TODO: Adjust condition
|
||||
line = content_lines[reading_line]
|
||||
heading_search_result = re.search(TITLE_REGEX, line)
|
||||
# print("Read line {0}: {1}\tisHeading: {2}".format(reading_line + 1, content_lines[reading_line], "[{0}, {1}, {2}]".format(heading_search_result.start(), heading_search_result.start() == 0, ((heading_search_result != None) and (heading_search_result.start() == 0))) if heading_search_result != None else "No"))
|
||||
if ((heading_search_result != None) and (heading_search_result.start() == 0)): # Found title
|
||||
# Determine scope
|
||||
heading_scope = line.find('!')
|
||||
# Lower? Return
|
||||
if(heading_scope <= scope):
|
||||
reading_line -= 1
|
||||
return current_content
|
||||
|
||||
section_name = line[(heading_scope+1):].strip()
|
||||
# print("[[ Found h{0} at line {1} ]] {2}".format(heading_scope, reading_line+1, content_lines[reading_line]))
|
||||
reading_line += 1
|
||||
current_content["children"].append(get_binds_recursive(Section([], [], section_name), heading_scope))
|
||||
|
||||
elif line.startswith(COMMENT_BIND_PATTERN):
|
||||
keybind = get_keybind_at_line(reading_line, line_start=len(COMMENT_BIND_PATTERN))
|
||||
if(keybind != None):
|
||||
current_content["keybinds"].append(keybind)
|
||||
|
||||
elif line == "" or not line.lstrip().startswith("bind"): # Comment, ignore
|
||||
pass
|
||||
|
||||
else: # Normal keybind
|
||||
keybind = get_keybind_at_line(reading_line)
|
||||
if(keybind != None):
|
||||
current_content["keybinds"].append(keybind)
|
||||
|
||||
reading_line += 1
|
||||
|
||||
return current_content;
|
||||
|
||||
def parse_keys(path: str) -> Dict[str, List[KeyBinding]]:
|
||||
global content_lines
|
||||
content_lines = read_content(path).splitlines()
|
||||
if content_lines[0] == "error":
|
||||
return "error"
|
||||
return get_binds_recursive(Section([], [], ""), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
|
||||
ParsedKeys = parse_keys(args.path)
|
||||
print(json.dumps(ParsedKeys))
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import cv2
|
||||
import json
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
DEFAULT_IMAGE_PATH = '/tmp/quickshell/media/screenshot/image'
|
||||
|
||||
def iou(boxA, boxB):
|
||||
# Compute intersection over union for two boxes
|
||||
xA = max(boxA['x'], boxB['x'])
|
||||
yA = max(boxA['y'], boxB['y'])
|
||||
xB = min(boxA['x'] + boxA['width'], boxB['x'] + boxB['width'])
|
||||
yB = min(boxA['y'] + boxA['height'], boxB['y'] + boxB['height'])
|
||||
interW = max(0, xB - xA)
|
||||
interH = max(0, yB - yA)
|
||||
interArea = interW * interH
|
||||
boxAArea = boxA['width'] * boxA['height']
|
||||
boxBArea = boxB['width'] * boxB['height']
|
||||
iou = interArea / float(boxAArea + boxBArea - interArea) if (boxAArea + boxBArea - interArea) > 0 else 0
|
||||
return iou
|
||||
|
||||
def non_max_suppression(regions, iou_threshold=0.7):
|
||||
# Sort by area (largest first)
|
||||
regions = sorted(regions, key=lambda r: r['width'] * r['height'], reverse=True)
|
||||
keep = []
|
||||
while regions:
|
||||
current = regions.pop(0)
|
||||
keep.append(current)
|
||||
regions = [r for r in regions if iou(current, r) < iou_threshold]
|
||||
return keep
|
||||
|
||||
def find_regions(image_path, min_width, min_height, max_width=None, max_height=None, quality=False, k=150, min_size=20, sigma=0.8, resize_factor=1.0):
|
||||
image = cv2.imread(image_path)
|
||||
if image is None:
|
||||
print(f'Error: Could not load image {image_path}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
orig_h, orig_w = image.shape[:2]
|
||||
if resize_factor != 1.0:
|
||||
image = cv2.resize(image, (int(orig_w * resize_factor), int(orig_h * resize_factor)), interpolation=cv2.INTER_AREA)
|
||||
ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()
|
||||
ss.setBaseImage(image)
|
||||
if quality:
|
||||
ss.switchToSelectiveSearchQuality(k, min_size, sigma)
|
||||
else:
|
||||
ss.switchToSelectiveSearchFast(k, min_size, sigma)
|
||||
rects = ss.process()
|
||||
regions = []
|
||||
for (x, y, w, h) in rects:
|
||||
# Scale regions back to original image size if resized
|
||||
if resize_factor != 1.0:
|
||||
x = int(x / resize_factor)
|
||||
y = int(y / resize_factor)
|
||||
w = int(w / resize_factor)
|
||||
h = int(h / resize_factor)
|
||||
# Filter out region that is exactly the same size as the original image
|
||||
if w == orig_w and h == orig_h and x == 0 and y == 0:
|
||||
continue
|
||||
if w > min_width and h > min_height:
|
||||
if (max_width is None or w < max_width) and (max_height is None or h < max_height):
|
||||
regions.append({'x': int(x), 'y': int(y), 'width': int(w), 'height': int(h)})
|
||||
# Remove duplicates/overlaps
|
||||
regions = non_max_suppression(regions, iou_threshold=0.7)
|
||||
return regions, cv2.imread(image_path) # Return original image for drawing
|
||||
|
||||
def draw_regions(image, regions, output_path):
|
||||
for region in regions:
|
||||
if 'x' in region:
|
||||
x, y, w, h = region['x'], region['y'], region['width'], region['height']
|
||||
elif 'at' in region and 'size' in region:
|
||||
x, y = region['at']
|
||||
w, h = region['size']
|
||||
else:
|
||||
continue
|
||||
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
|
||||
cv2.imwrite(output_path, image)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Find regions of interest in an image using selective search.')
|
||||
parser.add_argument('-i', '--image', default=DEFAULT_IMAGE_PATH, help='Path to input image')
|
||||
parser.add_argument('-do', '--debug-output', help='Path to save debug image with rectangles')
|
||||
parser.add_argument('--min-width', type=int, default=200, help='Minimum width of detected region')
|
||||
parser.add_argument('--min-height', type=int, default=100, help='Minimum height of detected region')
|
||||
parser.add_argument('--max-width', type=int, help='Maximum width of detected region')
|
||||
parser.add_argument('--max-height', type=int, help='Maximum height of detected region')
|
||||
parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region')
|
||||
parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)')
|
||||
parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)')
|
||||
parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)')
|
||||
parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)')
|
||||
parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)')
|
||||
parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}')
|
||||
args = parser.parse_args()
|
||||
|
||||
regions, image = find_regions(
|
||||
args.image,
|
||||
min_width=args.min_width,
|
||||
min_height=args.min_height,
|
||||
max_width=args.max_width,
|
||||
max_height=args.max_height,
|
||||
quality=args.quality,
|
||||
k=args.k,
|
||||
min_size=args.min_size,
|
||||
sigma=args.sigma,
|
||||
resize_factor=args.resize_factor
|
||||
)
|
||||
if args.single and regions:
|
||||
largest = max(regions, key=lambda r: r['width'] * r['height'])
|
||||
regions = [largest]
|
||||
if args.hyprctl:
|
||||
regions = [{"at": [r['x'], r['y']], "size": [r['width'], r['height']]} for r in regions]
|
||||
print(json.dumps(regions))
|
||||
if args.debug_output:
|
||||
draw_regions(image, regions, args.debug_output)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
#!/usr/bin/env python3
|
||||
# Disclaimer: This script was ai-generated and went through minimal revision.
|
||||
|
||||
import os
|
||||
os.environ["OPENCV_LOG_LEVEL"] = "SILENT"
|
||||
import cv2
|
||||
import numpy as np
|
||||
import argparse
|
||||
import json
|
||||
|
||||
def center_crop(img, target_w, target_h):
|
||||
h, w = img.shape[:2]
|
||||
if w == target_w and h == target_h:
|
||||
return img
|
||||
x1 = max(0, (w - target_w) // 2)
|
||||
y1 = max(0, (h - target_h) // 2)
|
||||
x2 = x1 + target_w
|
||||
y2 = y1 + target_h
|
||||
return img[y1:y2, x1:x2]
|
||||
|
||||
def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", horizontal_padding=50, vertical_padding=50):
|
||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape
|
||||
scale = 1.0
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
if verbose:
|
||||
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
if verbose:
|
||||
print(f"Cropped image to {screen_width}x{screen_height}")
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using original image size: {orig_w}x{orig_h}")
|
||||
arr = img.astype(np.float64)
|
||||
h, w = arr.shape
|
||||
# Use OpenCV's integral for fast computation
|
||||
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
|
||||
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
|
||||
def region_sum(ii, x1, y1, x2, y2):
|
||||
total = ii[y2, x2]
|
||||
if x1 > 0:
|
||||
total -= ii[y2, x1-1]
|
||||
if y1 > 0:
|
||||
total -= ii[y1-1, x2]
|
||||
if x1 > 0 and y1 > 0:
|
||||
total += ii[y1-1, x1-1]
|
||||
return total
|
||||
min_var = None
|
||||
min_coords = (0, 0)
|
||||
area = region_width * region_height
|
||||
x_start = horizontal_padding
|
||||
y_start = vertical_padding
|
||||
x_end = w - region_width - horizontal_padding + 1
|
||||
y_end = h - region_height - vertical_padding + 1
|
||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||
x1, y1 = x, y
|
||||
x2, y2 = x + region_width - 1, y + region_height - 1
|
||||
s = region_sum(integral, x1, y1, x2, y2)
|
||||
s2 = region_sum(integral_sq, x1, y1, x2, y2)
|
||||
mean = s / area
|
||||
var = (s2 / area) - (mean ** 2)
|
||||
if (min_var is None) or (var < min_var):
|
||||
min_var = var
|
||||
min_coords = (x, y)
|
||||
return min_coords, min_var
|
||||
|
||||
def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, horizontal_padding=50, vertical_padding=50):
|
||||
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape
|
||||
scale = 1.0
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
if verbose:
|
||||
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
if verbose:
|
||||
print(f"Cropped image to {screen_width}x{screen_height}")
|
||||
else:
|
||||
if verbose:
|
||||
print(f"Using original image size: {orig_w}x{orig_h}")
|
||||
arr = img.astype(np.float64)
|
||||
h, w = arr.shape
|
||||
# Use OpenCV's integral for fast computation
|
||||
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
|
||||
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
|
||||
def region_sum(ii, x1, y1, x2, y2):
|
||||
total = ii[y2, x2]
|
||||
if x1 > 0:
|
||||
total -= ii[y2, x1-1]
|
||||
if y1 > 0:
|
||||
total -= ii[y1-1, x2]
|
||||
if x1 > 0 and y1 > 0:
|
||||
total += ii[y1-1, x1-1]
|
||||
return total
|
||||
min_size = 10
|
||||
max_size = min(h, int(w / aspect_ratio)) if aspect_ratio >= 1.0 else min(int(h * aspect_ratio), w)
|
||||
best = None
|
||||
best_size = min_size
|
||||
while min_size <= max_size:
|
||||
mid = (min_size + max_size) // 2
|
||||
if aspect_ratio >= 1.0:
|
||||
region_h = mid
|
||||
region_w = int(mid * aspect_ratio)
|
||||
else:
|
||||
region_w = mid
|
||||
region_h = int(mid / aspect_ratio)
|
||||
if region_w > w or region_h > h:
|
||||
max_size = mid - 1
|
||||
continue
|
||||
found = False
|
||||
x_start = horizontal_padding
|
||||
y_start = vertical_padding
|
||||
x_end = w - region_w - horizontal_padding + 1
|
||||
y_end = h - region_h - vertical_padding + 1
|
||||
for y in range(y_start, max(y_end, y_start+1), stride):
|
||||
for x in range(x_start, max(x_end, x_start+1), stride):
|
||||
x1, y1 = x, y
|
||||
x2, y2 = x + region_w - 1, y + region_h - 1
|
||||
s = region_sum(integral, x1, y1, x2, y2)
|
||||
s2 = region_sum(integral_sq, x1, y1, x2, y2)
|
||||
area = region_w * region_h
|
||||
mean = s / area
|
||||
var = (s2 / area) - (mean ** 2)
|
||||
if var <= threshold:
|
||||
found = True
|
||||
best = (x, y, region_w, region_h, var)
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if found:
|
||||
best_size = mid
|
||||
min_size = mid + 1
|
||||
else:
|
||||
max_size = mid - 1
|
||||
if best:
|
||||
x, y, region_w, region_h, var = best
|
||||
center_x = x + region_w // 2
|
||||
center_y = y + region_h // 2
|
||||
return (center_x, center_y), (region_w, region_h), var
|
||||
else:
|
||||
return None, (0, 0), None
|
||||
|
||||
def draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
x, y = coords
|
||||
cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3)
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Saved output image with rectangle at {output_path}")
|
||||
|
||||
def draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
cx, cy = center
|
||||
region_w, region_h = size
|
||||
x1 = cx - region_w // 2
|
||||
y1 = cy - region_h // 2
|
||||
x2 = cx + region_w // 2 - 1
|
||||
y2 = cy + region_h // 2 - 1
|
||||
cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3)
|
||||
cv2.imwrite(output_path, img)
|
||||
print(f"Saved output image with largest region at {output_path}")
|
||||
|
||||
def get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode="fill"):
|
||||
img = cv2.imread(image_path)
|
||||
if img is None:
|
||||
raise FileNotFoundError(f"Image not found: {image_path}")
|
||||
orig_h, orig_w = img.shape[:2]
|
||||
if screen_width is not None and screen_height is not None:
|
||||
scale_w = screen_width / orig_w
|
||||
scale_h = screen_height / orig_h
|
||||
if screen_mode == "fill":
|
||||
scale = max(scale_w, scale_h)
|
||||
else:
|
||||
scale = min(scale_w, scale_h)
|
||||
new_w = int(orig_w * scale)
|
||||
new_h = int(orig_h * scale)
|
||||
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
|
||||
img = center_crop(img, screen_width, screen_height)
|
||||
# Ensure region is within bounds
|
||||
x = max(0, x)
|
||||
y = max(0, y)
|
||||
w = max(1, min(w, img.shape[1] - x))
|
||||
h = max(1, min(h, img.shape[0] - y))
|
||||
region = img[y:y+h, x:x+w]
|
||||
if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0:
|
||||
return [0, 0, 0]
|
||||
region = region.reshape((-1, 3))
|
||||
# Filter out black pixels (optional, improves accuracy for some images)
|
||||
non_black = region[np.any(region > 10, axis=1)]
|
||||
if non_black.shape[0] == 0:
|
||||
non_black = region
|
||||
region = np.float32(non_black)
|
||||
if region.shape[0] < 3:
|
||||
return [int(x) for x in np.mean(region, axis=0)]
|
||||
# K-means to find dominant color
|
||||
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
|
||||
K = min(3, region.shape[0])
|
||||
_, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
|
||||
counts = np.bincount(labels.flatten())
|
||||
dominant = centers[np.argmax(counts)]
|
||||
return [int(x) for x in dominant]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.")
|
||||
parser.add_argument("image_path", help="Path to the input image")
|
||||
parser.add_argument("--width", type=int, default=300, help="Region width")
|
||||
parser.add_argument("--height", type=int, default=200, help="Region height")
|
||||
parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle")
|
||||
parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling")
|
||||
parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling")
|
||||
parser.add_argument("--stride", type=int, default=10, help="Step size for sliding window (higher is faster, less precise)")
|
||||
parser.add_argument("--screen-mode", choices=["fill", "fit"], default="fill", help="Wallpaper scaling mode: 'fill' (default) or 'fit'")
|
||||
parser.add_argument("--verbose", action="store_true", help="Print verbose output")
|
||||
parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center")
|
||||
parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode")
|
||||
parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode")
|
||||
parser.add_argument("--horizontal-padding", "-hp", type=int, default=50, help="Minimum horizontal distance from region to image edge")
|
||||
parser.add_argument("--vertical-padding", "-vp", type=int, default=50, help="Minimum vertical distance from region to image edge")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.largest_region:
|
||||
center, size, var = find_largest_region(
|
||||
args.image_path,
|
||||
screen_width=args.screen_width,
|
||||
screen_height=args.screen_height,
|
||||
verbose=args.verbose,
|
||||
stride=args.stride,
|
||||
screen_mode=args.screen_mode,
|
||||
threshold=args.variance_threshold,
|
||||
aspect_ratio=args.aspect_ratio,
|
||||
horizontal_padding=args.horizontal_padding,
|
||||
vertical_padding=args.vertical_padding
|
||||
)
|
||||
if center:
|
||||
if args.visual_output:
|
||||
draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||
# Extract dominant color
|
||||
cx, cy = center
|
||||
region_w, region_h = size
|
||||
x1 = cx - region_w // 2
|
||||
y1 = cy - region_h // 2
|
||||
dominant_color = get_dominant_color(
|
||||
args.image_path, x1, y1, region_w, region_h,
|
||||
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
|
||||
)
|
||||
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
|
||||
print(json.dumps({
|
||||
"center_x": center[0],
|
||||
"center_y": center[1],
|
||||
"width": size[0],
|
||||
"height": size[1],
|
||||
"variance": var,
|
||||
"dominant_color": dominant_color_hex
|
||||
}))
|
||||
else:
|
||||
print(json.dumps({"error": "No region found under the threshold."}))
|
||||
return
|
||||
|
||||
coords, variance = find_least_busy_region(
|
||||
args.image_path,
|
||||
region_width=args.width,
|
||||
region_height=args.height,
|
||||
screen_width=args.screen_width,
|
||||
screen_height=args.screen_height,
|
||||
verbose=args.verbose,
|
||||
stride=args.stride,
|
||||
screen_mode=args.screen_mode,
|
||||
horizontal_padding=args.horizontal_padding,
|
||||
vertical_padding=args.vertical_padding
|
||||
)
|
||||
if args.visual_output:
|
||||
draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
|
||||
# Output JSON with center point
|
||||
center_x = coords[0] + args.width // 2
|
||||
center_y = coords[1] + args.height // 2
|
||||
dominant_color = get_dominant_color(
|
||||
args.image_path, coords[0], coords[1], args.width, args.height,
|
||||
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
|
||||
)
|
||||
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
|
||||
print(json.dumps({
|
||||
"center_x": center_x,
|
||||
"center_y": center_y,
|
||||
"width": args.width,
|
||||
"height": args.height,
|
||||
"variance": variance,
|
||||
"dominant_color": dominant_color_hex
|
||||
}))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
def read_scss(file_path):
|
||||
"""Reads an SCSS file and returns a dictionary of color variables."""
|
||||
colors = {}
|
||||
with open(file_path, 'r') as file:
|
||||
for line in file:
|
||||
match = re.match(r'\$(\w+):\s*(#[0-9A-Fa-f]{6});', line.strip())
|
||||
if match:
|
||||
variable_name, color = match.groups()
|
||||
colors[variable_name] = color
|
||||
return colors
|
||||
|
||||
def update_svg_colors(svg_path, old_to_new_colors, output_path):
|
||||
"""
|
||||
Updates the colors in an SVG file based on the provided color map.
|
||||
|
||||
:param svg_path: Path to the SVG file.
|
||||
:param old_to_new_colors: Dictionary mapping old colors to new colors.
|
||||
:param output_path: Path to save the updated SVG file.
|
||||
"""
|
||||
# Read the SVG content
|
||||
with open(svg_path, 'r') as file:
|
||||
svg_content = file.read()
|
||||
|
||||
# Replace old colors with new colors
|
||||
for old_color, new_color in old_to_new_colors.items():
|
||||
svg_content = re.sub(old_color, new_color, svg_content, flags=re.IGNORECASE)
|
||||
|
||||
# Write the updated SVG content to the output file
|
||||
with open(output_path, 'w') as file:
|
||||
file.write(svg_content)
|
||||
|
||||
print(f"SVG colors have been updated and saved to {output_path}!")
|
||||
|
||||
def main():
|
||||
xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||
xdg_state_home = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
|
||||
|
||||
scss_file = os.path.join(xdg_state_home, "quickshell", "user", "generated", "material_colors.scss")
|
||||
svg_path = os.path.join(xdg_config_home, "Kvantum", "Colloid", "Colloid.svg")
|
||||
output_path = os.path.join(xdg_config_home, "Kvantum", "MaterialAdw", "MaterialAdw.svg")
|
||||
|
||||
# Read colors from the SCSS file
|
||||
color_data = read_scss(scss_file)
|
||||
|
||||
# Specify the old colors and map them to new colors from the SCSS file
|
||||
old_to_new_colors = {
|
||||
#'#cccccc': color_data['surfaceDim'], # Map old SVG color to new SCSS color
|
||||
#'#666666': color_data['surfaceDim'],
|
||||
'#3c84f7': color_data['primary'],
|
||||
#'#5a5a5a': color_data['neutral_paletteKeyColor'],
|
||||
'#000000': color_data['shadow'],
|
||||
'#f04a50': color_data['error'],
|
||||
'#4285f4': color_data['primaryFixedDim'],
|
||||
'#f2f2f2': color_data['background'],
|
||||
#'#dfdfdf': color_data['surfaceContainerLow'],
|
||||
'#ffffff': color_data['background'],
|
||||
'#1e1e1e': color_data['onPrimaryFixed'],
|
||||
#'#b6b6b6': color_data['surfaceContainer'],
|
||||
'#333': color_data['inverseSurface'],
|
||||
'#212121': color_data['onSecondaryFixed'],
|
||||
'#5b9bf8': color_data['secondaryContainer'],
|
||||
'#26272a': color_data['term7'],
|
||||
#'#b3b3b3': color_data['surfaceBright'],
|
||||
#'#b74aff': color_data['tertiary'],
|
||||
#'#989898': color_data['surfaceContainerHighest'],
|
||||
#'#c1c1c1': color_data['surfaceContainerHigh'],
|
||||
'#444444': color_data['onBackground'],
|
||||
'#333333': color_data['onPrimaryFixed'],
|
||||
}
|
||||
|
||||
# Update the SVG colors
|
||||
update_svg_colors(svg_path, old_to_new_colors, output_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
def read_scss(file_path):
|
||||
"""Reads an SCSS file and returns a dictionary of color variables."""
|
||||
colors = {}
|
||||
with open(file_path, 'r') as file:
|
||||
for line in file:
|
||||
match = re.match(r'\$(\w+):\s*(#[0-9A-Fa-f]{6});', line.strip())
|
||||
if match:
|
||||
variable_name, color = match.groups()
|
||||
colors[variable_name] = color
|
||||
return colors
|
||||
|
||||
def update_svg_colors(svg_path, old_to_new_colors, output_path):
|
||||
"""
|
||||
Updates the colors in an SVG file based on the provided color map.
|
||||
|
||||
:param svg_path: Path to the SVG file.
|
||||
:param old_to_new_colors: Dictionary mapping old colors to new colors.
|
||||
:param output_path: Path to save the updated SVG file.
|
||||
"""
|
||||
# Read the SVG content
|
||||
with open(svg_path, 'r') as file:
|
||||
svg_content = file.read()
|
||||
|
||||
# Replace old colors with new colors
|
||||
for old_color, new_color in old_to_new_colors.items():
|
||||
svg_content = re.sub(old_color, new_color, svg_content, flags=re.IGNORECASE)
|
||||
|
||||
# Write the updated SVG content to the output file
|
||||
with open(output_path, 'w') as file:
|
||||
file.write(svg_content)
|
||||
|
||||
print(f"SVG colors have been updated and saved to {output_path}!")
|
||||
|
||||
def main():
|
||||
xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||
xdg_state_home = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
|
||||
|
||||
scss_file = os.path.join(xdg_state_home, "quickshell", "user", "generated", "material_colors.scss")
|
||||
svg_path = os.path.join(xdg_config_home, "Kvantum", "Colloid", "ColloidDark.svg")
|
||||
output_path = os.path.join(xdg_config_home, "Kvantum", "MaterialAdw", "MaterialAdw.svg")
|
||||
|
||||
# Read colors from the SCSS file
|
||||
color_data = read_scss(scss_file)
|
||||
|
||||
# Specify the old colors and map them to new colors from the SCSS file
|
||||
old_to_new_colors = {
|
||||
#'#525252': color_data['surfaceDim'], # Map old SVG color to new SCSS color
|
||||
#'#666666': color_data['surfaceDim'],
|
||||
'#31363b': color_data['background'],
|
||||
#'#eff0f1': color_data['neutral_paletteKeyColor'],
|
||||
'#000000': color_data['shadow'],
|
||||
'#5b9bf8': color_data['primary'],
|
||||
'#93cee9': color_data['onSecondaryContainer'],
|
||||
'#3daee9': color_data['secondary'],
|
||||
#'#fff': color_data['term10'],
|
||||
#'#5a5a5a': color_data['surfaceVariant'],
|
||||
#'#acb1bc': color_data['onPrimaryFixed'],
|
||||
'#ffffff': color_data['term11'],
|
||||
'#5a616e': color_data['surfaceVariant'],
|
||||
'#f04a50': color_data['error'],
|
||||
'#4285f4': color_data['secondary'],
|
||||
'#242424': color_data['background'],
|
||||
'#2c2c2c': color_data['background'],
|
||||
#'#dfdfdf': color_data['onSurfaceVariant'],
|
||||
#'#646464': color_data['surfaceContainerHighest'],
|
||||
#'#989898': color_data['surfaceContainerHigh'],
|
||||
#'#c1c1c1': color_data['primaryFixedDim'],
|
||||
'#1e1e1e': color_data['background'],
|
||||
'#3c3c3c': color_data['background'],
|
||||
'#26272a': color_data['surfaceBright'],
|
||||
'#000000': color_data['shadow'],
|
||||
'#b74aff': color_data['tertiary'],
|
||||
#'#b6b6b6': color_data['onSurfaceVariant'],
|
||||
'#1a1a1a': color_data['background'],
|
||||
'#333': color_data['term0'],
|
||||
'#212121': color_data['background'],
|
||||
}
|
||||
|
||||
# Update the SVG colors
|
||||
update_svg_colors(svg_path, old_to_new_colors, output_path)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
def get_colors_from_scss(scss_file):
|
||||
colors = {}
|
||||
with open(scss_file, 'r') as file:
|
||||
for line in file:
|
||||
match = re.match(r'\$(\w+):\s*(#[0-9A-Fa-f]{6});', line)
|
||||
if match:
|
||||
colors[match.group(1)] = match.group(2)
|
||||
return colors
|
||||
|
||||
def update_config_colors(config_file, colors, mappings):
|
||||
with open(config_file, 'r') as file:
|
||||
config_content = file.read()
|
||||
|
||||
for key, variable in mappings.items():
|
||||
if variable in colors:
|
||||
color = colors[variable]
|
||||
pattern = rf'({key}=)#?\w+\b'
|
||||
new_line = f'\\1{color}'
|
||||
if re.search(pattern, config_content):
|
||||
config_content = re.sub(pattern, new_line, config_content)
|
||||
else:
|
||||
config_content += f"\n{key}={color}"
|
||||
|
||||
with open(config_file, 'w') as file:
|
||||
file.write(config_content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
xdg_config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
|
||||
xdg_state_home = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
|
||||
|
||||
config_file = os.path.join(xdg_config_home, "Kvantum", "MaterialAdw", "MaterialAdw.kvconfig")
|
||||
scss_file = os.path.join(xdg_state_home, "quickshell", "user", "generated", "material_colors.scss")
|
||||
|
||||
# Define your mappings here
|
||||
mappings = {
|
||||
'window.color': 'background',
|
||||
'base.color': 'background',
|
||||
'alt.base.color': 'background',
|
||||
'button.color': 'surfaceContainer',
|
||||
'light.color': 'surfaceContainerLow',
|
||||
'mid.light.color': 'surfaceContainer',
|
||||
'dark.color': 'surfaceContainerHighest',
|
||||
'mid.color': 'surfaceContainerHigh',
|
||||
'highlight.color': 'primary',
|
||||
'inactive.highlight.color': 'primary',
|
||||
'text.color': 'onBackground',
|
||||
'window.text.color': 'onBackground',
|
||||
'button.text.color': 'onBackground',
|
||||
'disabled.text.color': 'onBackground',
|
||||
'tooltip.text.color': 'onBackground',
|
||||
'highlight.text.color': 'onSurface',
|
||||
'link.color': 'tertiary',
|
||||
'link.visited.color': 'tertiaryFixed',
|
||||
'progress.indicator.text.color': 'onBackground',
|
||||
'text.normal.color': 'onBackground',
|
||||
'text.focus.color': 'onBackground',
|
||||
'text.press.color': 'onsecondarycontainer',
|
||||
'text.toggle.color': 'onsecondarycontainer',
|
||||
'text.disabled.color': 'surfaceDim',
|
||||
|
||||
|
||||
# Add more mappings as needed
|
||||
}
|
||||
|
||||
colors = get_colors_from_scss(scss_file)
|
||||
update_config_colors(config_file, colors, mappings)
|
||||
print("Config colors updated successfully!")
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#!/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)"
|
||||
|
||||
get_light_dark() {
|
||||
current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d "'")
|
||||
if [[ "$current_mode" == "prefer-dark" ]]; then
|
||||
echo "dark"
|
||||
else
|
||||
echo "light"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_qt() {
|
||||
# Check if the theme exists
|
||||
FOLDER_PATH="$XDG_CONFIG_HOME/Kvantum/Colloid/"
|
||||
|
||||
if [ ! -d "$FOLDER_PATH" ]; then
|
||||
# Send a notification
|
||||
notify-send "Colloid-kde theme required" " The folder '$FOLDER_PATH' does not exist."
|
||||
exit 1 # Exit the function if the folder does not exist
|
||||
fi
|
||||
|
||||
lightdark=$(get_light_dark)
|
||||
if [ "$lightdark" = "light" ]; then
|
||||
# apply ligght colors
|
||||
cp "$XDG_CONFIG_HOME/Kvantum/Colloid/Colloid.kvconfig" "$XDG_CONFIG_HOME/Kvantum/MaterialAdw/MaterialAdw.kvconfig"
|
||||
python "$CONFIG_DIR/scripts/kvantum/adwsvg.py"
|
||||
|
||||
else
|
||||
#apply dark colors
|
||||
cp "$XDG_CONFIG_HOME/Kvantum/Colloid/ColloidDark.kvconfig" "$XDG_CONFIG_HOME/Kvantum/MaterialAdw/MaterialAdw.kvconfig"
|
||||
python "$CONFIG_DIR/scripts/kvantum/adwsvgDark.py"
|
||||
fi
|
||||
}
|
||||
|
||||
apply_qt
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env -S\_/bin/sh\_-xc\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@""
|
||||
|
||||
# From https://github.com/stwa/wayland-idle-inhibitor
|
||||
# License: WTFPL Version 2
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from signal import SIGINT, SIGTERM, signal
|
||||
from threading import Event
|
||||
import setproctitle
|
||||
|
||||
from pywayland.client.display import Display
|
||||
from pywayland.protocol.idle_inhibit_unstable_v1.zwp_idle_inhibit_manager_v1 import (
|
||||
ZwpIdleInhibitManagerV1,
|
||||
)
|
||||
from pywayland.protocol.wayland.wl_compositor import WlCompositor
|
||||
from pywayland.protocol.wayland.wl_registry import WlRegistryProxy
|
||||
from pywayland.protocol.wayland.wl_surface import WlSurface
|
||||
|
||||
|
||||
@dataclass
|
||||
class GlobalRegistry:
|
||||
surface: WlSurface | None = None
|
||||
inhibit_manager: ZwpIdleInhibitManagerV1 | None = None
|
||||
|
||||
|
||||
def handle_registry_global(
|
||||
wl_registry: WlRegistryProxy, id_num: int, iface_name: str, version: int
|
||||
) -> None:
|
||||
global_registry: GlobalRegistry = wl_registry.user_data or GlobalRegistry()
|
||||
|
||||
if iface_name == "wl_compositor":
|
||||
compositor = wl_registry.bind(id_num, WlCompositor, version)
|
||||
global_registry.surface = compositor.create_surface() # type: ignore
|
||||
elif iface_name == "zwp_idle_inhibit_manager_v1":
|
||||
global_registry.inhibit_manager = wl_registry.bind(
|
||||
id_num, ZwpIdleInhibitManagerV1, version
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
done = Event()
|
||||
signal(SIGINT, lambda _, __: done.set())
|
||||
signal(SIGTERM, lambda _, __: done.set())
|
||||
|
||||
global_registry = GlobalRegistry()
|
||||
|
||||
display = Display()
|
||||
display.connect()
|
||||
|
||||
registry = display.get_registry() # type: ignore
|
||||
registry.user_data = global_registry
|
||||
registry.dispatcher["global"] = handle_registry_global
|
||||
|
||||
def shutdown() -> None:
|
||||
display.dispatch()
|
||||
display.roundtrip()
|
||||
display.disconnect()
|
||||
|
||||
display.dispatch()
|
||||
display.roundtrip()
|
||||
|
||||
if global_registry.surface is None or global_registry.inhibit_manager is None:
|
||||
print("Wayland seems not to support idle_inhibit_unstable_v1 protocol.")
|
||||
shutdown()
|
||||
sys.exit(1)
|
||||
|
||||
inhibitor = global_registry.inhibit_manager.create_inhibitor( # type: ignore
|
||||
global_registry.surface
|
||||
)
|
||||
|
||||
display.dispatch()
|
||||
display.roundtrip()
|
||||
|
||||
print("Inhibiting idle...")
|
||||
done.wait()
|
||||
print("Shutting down...")
|
||||
|
||||
inhibitor.destroy()
|
||||
|
||||
shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
setproctitle.setproctitle("wayland-idle-inhibitor.py")
|
||||
main()
|
||||
Reference in New Issue
Block a user