better structure
This commit is contained in:
230
scripts/change-colortheme
Executable file
230
scripts/change-colortheme
Executable file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
|
||||
PALETTES = {
|
||||
"catppuccin-mocha": {
|
||||
"rosewater": "f5e0dc",
|
||||
"flamingo": "f2cdcd",
|
||||
"pink": "f5c2e7",
|
||||
"mauve": "cba6f7",
|
||||
"red": "f38ba8",
|
||||
"maroon": "eba0ac",
|
||||
"peach": "fab387",
|
||||
"yellow": "f9e2af",
|
||||
"green": "a6e3a1",
|
||||
"teal": "94e2d5",
|
||||
"sky": "89dceb",
|
||||
"sapphire": "74c7ec",
|
||||
"blue": "89b4fa",
|
||||
"lavender": "b4befe",
|
||||
},
|
||||
}
|
||||
|
||||
# CONFIG_DIR = ${dotfiles_root}/config
|
||||
CONFIG_DIR = Path(__file__).resolve().parent.resolve().parent.resolve() / "config"
|
||||
|
||||
# An application may have multiple scripts (e.g. due to config-switch)
|
||||
SCRIPTS = {
|
||||
"eww": [CONFIG_DIR / "eww" / "apply-color"],
|
||||
"fastfetch": [CONFIG_DIR / "fastfetch" / "apply-color"],
|
||||
"fuzzel": [CONFIG_DIR / "fuzzel" / "apply-color"],
|
||||
"hypr": [CONFIG_DIR / "hypr" / "apply-color"],
|
||||
"kvantum": [CONFIG_DIR / "fish" / "apply-color-kvantum"], # borrowing fish's directory
|
||||
"mako": [CONFIG_DIR / "mako" / "apply-color"],
|
||||
"niri": [CONFIG_DIR / "niri" / "apply-color"],
|
||||
"oh-my-posh": [CONFIG_DIR / "fish" / "apply-color-omp"], # borrowing fish's directory
|
||||
"quickshell": [CONFIG_DIR / "quickshell" / "apply-color"],
|
||||
"rofi": [CONFIG_DIR / "rofi" / "apply-color"],
|
||||
"waybar": [CONFIG_DIR / "waybar" / "apply-color"],
|
||||
"wlogout": [CONFIG_DIR / "wlogout" / "apply-color", CONFIG_DIR / "wlogout-niri" / "apply-color"],
|
||||
"yazi": [CONFIG_DIR / "yazi" / "apply-color"],
|
||||
}
|
||||
# or simply `find ${CONFIG_DIR} -type f -iname 'apply-color*'` to get all available scripts,
|
||||
# but I need the exact application names anyway, so hardcoding does make some sense
|
||||
|
||||
|
||||
def hex2rgb(hex_color: str) -> tuple[int, int, int]:
|
||||
return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4)) # type: ignore
|
||||
|
||||
|
||||
def extract_color(image_path: str) -> str:
|
||||
from colorthief import ColorThief
|
||||
return "#{:02x}{:02x}{:02x}".format(*ColorThief(image_path).get_color(quality=10))
|
||||
|
||||
|
||||
def match_color(color: str, palette: dict[str, str]) -> str:
|
||||
""" Matches a given color (rrggbb hex) to the closest color in the palette."""
|
||||
# HUE distance of the given and returned color must no<t exceed this value
|
||||
HUE_THRESHOLD = 60.0 # degrees
|
||||
|
||||
color = color.lower().strip().removeprefix('#')
|
||||
|
||||
# weigh by CCIR 601 luminosity
|
||||
fr, fg, fb = 0.299 / 255 / 255, 0.587 / 255 / 255, 0.114 / 255 / 255
|
||||
lfr, lfg, lfb = 0.299 / 255, 0.587 / 255, 0.114 / 255
|
||||
|
||||
def color_distance(c1: str, c2: str) -> float:
|
||||
r1, g1, b1 = hex2rgb(c1)
|
||||
r2, g2, b2 = hex2rgb(c2)
|
||||
diff_l = (lfr * (r1 - r2) + lfg * (g1 - g2) + lfb * (b1 - b2))
|
||||
diff_r = fr * (r1 - r2) ** 2
|
||||
diff_g = fg * (g1 - g2) ** 2
|
||||
diff_b = fb * (b1 - b2) ** 2
|
||||
return (diff_r + diff_g + diff_b) * 0.75 + diff_l ** 2
|
||||
|
||||
def color_distance_hue(c1: str, c2: str) -> float:
|
||||
def rgb2hue(r, g, b) -> float:
|
||||
r, g, b = r / 255.0, g / 255.0, b / 255.0
|
||||
mx = max(r, g, b)
|
||||
mn = min(r, g, b)
|
||||
diff = mx - mn
|
||||
|
||||
if diff == 0:
|
||||
return 0.0
|
||||
|
||||
if mx == r:
|
||||
hue = (g - b) / diff + (6 if g < b else 0)
|
||||
elif mx == g:
|
||||
hue = (b - r) / diff + 2
|
||||
else:
|
||||
hue = (r - g) / diff + 4
|
||||
|
||||
return hue * 60
|
||||
r1, g1, b1 = hex2rgb(c1)
|
||||
r2, g2, b2 = hex2rgb(c2)
|
||||
return abs(rgb2hue(r1, g1, b1) - rgb2hue(r2, g2, b2))
|
||||
|
||||
closest_color = min(palette.keys(), key=lambda k: color_distance(color, palette[k]))
|
||||
print(f"Matched color {color} to {closest_color}")
|
||||
|
||||
# if the hue distance is too large, rematch
|
||||
if color_distance_hue(color, palette[closest_color]) > HUE_THRESHOLD:
|
||||
print(f"Color {color} is too far from {closest_color}, rematching'")
|
||||
else:
|
||||
return closest_color
|
||||
|
||||
closest_color = min(palette.keys(), key=lambda k: color_distance_hue(color, palette[k]))
|
||||
print(f"Rematched color {color} to {closest_color}")
|
||||
|
||||
return closest_color
|
||||
|
||||
|
||||
def pick_flavor(palette: dict[str, str]) -> str:
|
||||
def is_interactive() -> bool:
|
||||
return sys.stdin.isatty() and sys.stdout.isatty()
|
||||
|
||||
def is_truecolor() -> bool:
|
||||
colorterm = os.environ.get('COLORTERM', '')
|
||||
term = os.environ.get('TERM', '')
|
||||
|
||||
return (
|
||||
'truecolor' in colorterm or
|
||||
'24bit' in colorterm or
|
||||
term.endswith('-256color')
|
||||
)
|
||||
|
||||
if is_interactive():
|
||||
isTruecolor = is_truecolor()
|
||||
print("Available flavors:")
|
||||
for i, flavor in enumerate(palette.keys(), 1):
|
||||
r, g, b = hex2rgb(palette[flavor])
|
||||
if isTruecolor:
|
||||
print(f"\033[38;2;{r};{g};{b}m█ {i}. {flavor}: #{palette[flavor]}\033[0m")
|
||||
else:
|
||||
print(f"{i}. {flavor}")
|
||||
while True:
|
||||
choice = input("Pick a flavor by number: ")
|
||||
if choice.isdigit() and 1 <= int(choice) <= len(palette):
|
||||
return list(palette.keys())[int(choice) - 1]
|
||||
print("Invalid choice. Try again.")
|
||||
else:
|
||||
print("No flavor specified.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Change color theme for various applications.")
|
||||
parser.add_argument('-i', '--image', type=str, help="Path to the image")
|
||||
parser.add_argument('-f', '--flavor', type=str, help="Flavor to apply")
|
||||
parser.add_argument('-c', '--color', type=str, help="Color to match from the palette")
|
||||
parser.add_argument('arguments', nargs='*',
|
||||
help="'app1 !app2' to include(only) / exclude(all but) specific applications. "
|
||||
"Available apps: " + ', '.join(SCRIPTS.keys()))
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
# for future use, probably
|
||||
def parse_palette_name() -> str:
|
||||
return "catppuccin-mocha"
|
||||
|
||||
def parse_flavor(palette: dict[str, str]) -> str:
|
||||
if arguments.flavor:
|
||||
if arguments.flavor not in palette:
|
||||
print(f"Unknown flavor: {arguments.flavor}. Available flavors: {', '.join(palette.keys())}")
|
||||
sys.exit(1)
|
||||
flavor = arguments.flavor
|
||||
elif arguments.color:
|
||||
flavor = match_color(arguments.color, palette)
|
||||
print(f"Matched color: {flavor}")
|
||||
elif arguments.image:
|
||||
if not Path(arguments.image).exists():
|
||||
print(f"Image file {arguments.image} does not exist.")
|
||||
sys.exit(1)
|
||||
color = extract_color(arguments.image)
|
||||
print(f"Extracted color {color} from image {arguments.image}")
|
||||
flavor = match_color(color, palette)
|
||||
print(f"Matched color: {flavor}")
|
||||
else:
|
||||
flavor = pick_flavor(palette)
|
||||
return flavor
|
||||
|
||||
def parse_apps() -> tuple[set[str], set[str]]:
|
||||
includes = set()
|
||||
excludes = set()
|
||||
for arg in arguments.arguments:
|
||||
if arg.startswith('!'):
|
||||
excludes.add(arg[1:])
|
||||
else:
|
||||
includes.add(arg)
|
||||
return includes, excludes
|
||||
|
||||
palette_name = parse_palette_name()
|
||||
palette = PALETTES[palette_name]
|
||||
flavor = parse_flavor(palette)
|
||||
includes, excludes = parse_apps()
|
||||
|
||||
apps = set()
|
||||
if includes:
|
||||
print(f"Including only: {', '.join(includes)}")
|
||||
for app in includes:
|
||||
if app in SCRIPTS:
|
||||
apps.add(app)
|
||||
else:
|
||||
print(f"Unknown application: {app}. Available applications: {', '.join(SCRIPTS.keys())}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
apps = set(SCRIPTS.keys())
|
||||
|
||||
if excludes:
|
||||
print(f"Excluding: {', '.join(excludes)}")
|
||||
apps -= excludes
|
||||
|
||||
print(f"Applying flavor '{flavor}' for {len(apps)} applications.")
|
||||
|
||||
for app in apps:
|
||||
for script in SCRIPTS[app]:
|
||||
print(f"Running {script}:")
|
||||
ret = os.system(f'"{script}" {palette_name} {flavor} {palette[flavor]}')
|
||||
print(f"{script} exited with code {ret}")
|
||||
print("")
|
||||
|
||||
os.system(
|
||||
f'notify-send -a "change-colortheme" "Colortheme Changed" "Palette: {palette_name};\nFlavor: {flavor};\nApplied to {len(apps)} applications."')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
104
scripts/change-wallpaper
Executable file
104
scripts/change-wallpaper
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Select image file if none is provided
|
||||
if [ -z "$1" ]; then
|
||||
image=$(zenity --file-selection --title="Open File" --file-filter="*.jpg *.jpeg *.png *.webp *.bmp *.jfif *.tiff *.avif *.heic *.heif")
|
||||
else
|
||||
image="$1"
|
||||
fi
|
||||
|
||||
[ -z "$image" ] && exit 1
|
||||
[ ! -f "$image" ] && exit 1
|
||||
|
||||
|
||||
# $HOME/.config/wallpaper-chooser/config.json:
|
||||
# ```json
|
||||
# "sort": {
|
||||
# "type": "date",
|
||||
# "reverse": true
|
||||
# }
|
||||
# ```
|
||||
# So in order to let the most recently used wallpapers appear first:
|
||||
touch "$image"
|
||||
|
||||
|
||||
# Copy image to local wallpaper directory
|
||||
|
||||
ext=${image##*.}
|
||||
random_name=$(tr -dc 'a-zA-Z0-9' </dev/urandom | head -c 16)
|
||||
current_dir="$HOME/.local/share/wallpaper/current"
|
||||
image_copied="$current_dir/wallpaper-${random_name}.${ext}"
|
||||
|
||||
mkdir -p "$current_dir" || (
|
||||
echo "Could not create directory $current_dir"
|
||||
exit 1
|
||||
)
|
||||
|
||||
temp_img=$(mktemp --suffix=."$ext") || exit 1
|
||||
trap 'rm -f "$temp_img"' EXIT
|
||||
cp "$image" "$temp_img" || exit 1
|
||||
rm -f "${current_dir:?}"/wallpaper-*
|
||||
cp -f "$temp_img" "$image_copied" || (
|
||||
echo "Could not copy image to $current_dir"
|
||||
exit 1
|
||||
)
|
||||
|
||||
# Generate blurred wallpaper
|
||||
|
||||
blur_dir="$HOME/.local/share/wallpaper/blurred"
|
||||
mkdir -p "$blur_dir" || (
|
||||
echo "Could not create cache directory"
|
||||
exit 1
|
||||
)
|
||||
rm -f "${blur_dir:?}"/blurred-*
|
||||
blurred_image="$blur_dir/blurred-${random_name}.$ext"
|
||||
|
||||
## Time consuming task (magick -blur) in background
|
||||
(
|
||||
# notify-send -a "change-wallpaper" "Generating Blurred Wallpaper" "This may take a few seconds..."
|
||||
|
||||
sigma=$(magick identify -format "%w %h" "$image_copied" | awk -v f=0.01 '{
|
||||
m=($1>$2)?$1:$2;
|
||||
s=m*f;
|
||||
if(s<2) s=2;
|
||||
if(s>200) s=200;
|
||||
printf "%.2f", s
|
||||
}')
|
||||
|
||||
### use a temporary file to avoid incomplete file being used
|
||||
temp_blurred=$(mktemp --suffix=."$ext") || exit 1
|
||||
trap 'rm -f "${temp_blurred}"' EXIT
|
||||
magick "$image_copied" -blur 0x"$sigma" "$temp_blurred" || (
|
||||
echo "Could not create blurred image"
|
||||
exit 1
|
||||
)
|
||||
mv -f "$temp_blurred" "$blurred_image" || (
|
||||
echo "Could not move blurred image to cache directory"
|
||||
exit 1
|
||||
)
|
||||
|
||||
if [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
|
||||
swww img -n backdrop "$blurred_image" --transition-type fade --transition-duration 2 > /dev/null 2> /dev/null
|
||||
fi
|
||||
|
||||
notify-send -a "change-wallpaper" "Blurred Wallpaper Generated" "$blurred_image" -i "$blurred_image"
|
||||
) &
|
||||
|
||||
# Apply wallpaper
|
||||
|
||||
if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then
|
||||
swww img -n background "$image_copied" --transition-type fade --transition-duration 2 > /dev/null 2> /dev/null
|
||||
|
||||
notify-send -a "change-wallpaper" "Wallpaper Changed" "$image" -i "$image_copied"
|
||||
|
||||
change-colortheme -i "$image_copied" || exit 1
|
||||
elif [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
|
||||
swww img -n background "$image_copied" --transition-type fade --transition-duration 2 > /dev/null 2> /dev/null
|
||||
|
||||
notify-send -a "change-wallpaper" "Wallpaper Changed" "$image" -i "$image_copied"
|
||||
|
||||
change-colortheme -i "$image_copied" || exit 1
|
||||
else
|
||||
echo "Unsupported desktop environment: $XDG_CURRENT_DESKTOP"
|
||||
exit 1
|
||||
fi
|
||||
21
scripts/config-switch
Executable file
21
scripts/config-switch
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
path="$(dirname "$(readlink -f "$0")")"
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
desktop="$XDG_CURRENT_DESKTOP"
|
||||
else
|
||||
desktop="$1"
|
||||
fi
|
||||
|
||||
for item in "kitty" "ghostty" "wlogout"; do
|
||||
[ -L "$HOME/.config/$item" ] || exit 1
|
||||
|
||||
rm "$HOME/.config/$item"
|
||||
|
||||
if [ "$desktop" = "niri" ]; then
|
||||
ln -s "$path/../config/$item-niri" "$HOME/.config/$item"
|
||||
else
|
||||
ln -s "$path/../config/$item" "$HOME/.config/$item"
|
||||
fi
|
||||
done
|
||||
157
scripts/fetch-weather
Executable file
157
scripts/fetch-weather
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
## Collect data
|
||||
cache_dir="$HOME/.cache/eww/weather"
|
||||
cache_weather_stat=${cache_dir}/weather-stat
|
||||
cache_weather_degree=${cache_dir}/weather-degree
|
||||
cache_weather_hex=${cache_dir}/weather-hex
|
||||
cache_weather_icon=${cache_dir}/weather-icon
|
||||
cache_weather_updatetime=${cache_dir}/weather-updatetime
|
||||
|
||||
if [[ -z "$OPENWEATHER_API_KEY" ]]; then
|
||||
echo "Please set the OPENWEATHER_API_KEY environment variable."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$OPENWEATHER_LAT" ]]; then
|
||||
echo "Please set the OPENWEATHER_LAT environment variable."
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$OPENWEATHER_LON" ]]; then
|
||||
echo "Please set the OPENWEATHER_LON environment variable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Weather data
|
||||
KEY=$OPENWEATHER_API_KEY
|
||||
LAT=$OPENWEATHER_LAT
|
||||
LON=$OPENWEATHER_LON
|
||||
UNITS=metric
|
||||
|
||||
## Make cache dir
|
||||
if [[ ! -d "$cache_dir" ]]; then
|
||||
mkdir -p ${cache_dir}
|
||||
fi
|
||||
|
||||
## Get data
|
||||
get_weather_data() {
|
||||
weather=`curl -sf "http://api.openweathermap.org/data/3.0/onecall?lat=${LAT}&lon=${LON}&exclude=minutely,hourly,daily&appid=${KEY}&units=${UNITS}"`
|
||||
echo ${weather} >&2
|
||||
weather=$(echo "$weather" | jq -r ".current")
|
||||
|
||||
if [ ! -z "$weather" ]; then
|
||||
weather_temp=`echo "$weather" | jq ".temp" | cut -d "." -f 1`
|
||||
weather_icon_code=`echo "$weather" | jq -r ".weather[].icon" | head -1`
|
||||
weather_description=`echo "$weather" | jq -r ".weather[].description" | head -1 | sed -e "s/\b\(.\)/\u\1/g"`
|
||||
|
||||
#Big long if statement of doom
|
||||
if [ "$weather_icon_code" == "50d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "50n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "01d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#e0af68"
|
||||
elif [ "$weather_icon_code" == "01n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#c0caf5"
|
||||
elif [ "$weather_icon_code" == "02d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "02n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "03d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "03n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "04d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "04n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7aa2f7"
|
||||
elif [ "$weather_icon_code" == "09d" ]; then
|
||||
weather_icon=""
|
||||
weather_hex="#7dcfff"
|
||||
elif [ "$weather_icon_code" == "09n" ]; then
|
||||
weather_icon=""
|
||||
weather_hex="#7dcfff"
|
||||
elif [ "$weather_icon_code" == "10d" ]; then
|
||||
weather_icon=""
|
||||
weather_hex="#7dcfff"
|
||||
elif [ "$weather_icon_code" == "10n" ]; then
|
||||
weather_icon=""
|
||||
weather_hex="#7dcfff"
|
||||
elif [ "$weather_icon_code" == "11d" ]; then
|
||||
weather_icon=""
|
||||
weather_hex="#ff9e64"
|
||||
elif [ "$weather_icon_code" == "11n" ]; then
|
||||
weather_icon=""
|
||||
weather_hex="#ff9e64"
|
||||
elif [ "$weather_icon_code" == "13d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#c0caf5"
|
||||
elif [ "$weather_icon_code" == "13n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#c0caf5"
|
||||
elif [ "$weather_icon_code" == "40d" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7dcfff"
|
||||
elif [ "$weather_icon_code" == "40n" ]; then
|
||||
weather_icon=" "
|
||||
weather_hex="#7dcfff"
|
||||
else
|
||||
weather_icon=" "
|
||||
weather_hex="#c0caf5"
|
||||
fi
|
||||
echo "$weather_icon" > ${cache_weather_icon}
|
||||
echo "$weather_description" > ${cache_weather_stat}
|
||||
echo "$weather_temp""°C" > ${cache_weather_degree}
|
||||
echo "$weather_hex" > ${cache_weather_hex}
|
||||
date "+%Y-%m-%d %H:%M:%S" | tee ${cache_weather_updatetime} >/dev/null
|
||||
else
|
||||
echo "Weather Unavailable" > ${cache_weather_stat}
|
||||
echo " " > ${cache_weather_icon}
|
||||
echo "-" > ${cache_weather_degree}
|
||||
echo "#adadff" > ${cache_weather_hex}
|
||||
date "+%Y-%m-%d %H:%M:%S" | tee ${cache_weather_updatetime} >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
check_network() {
|
||||
local max=12
|
||||
local cnt=0
|
||||
|
||||
while [ $cnt -lt $max ]; do
|
||||
if ping -c1 8.8.8.8 &>/dev/null || ping -c1 1.1.1.1 &>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
echo "Waiting for network connection... (attempt: $((cnt + 1))/$max)" >&2
|
||||
sleep 5
|
||||
((cnt++))
|
||||
done
|
||||
|
||||
echo "Network connection failed after $max attempts." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
## Execute
|
||||
if [[ -z "$1" ]]; then
|
||||
if check_network; then
|
||||
get_weather_data
|
||||
fi
|
||||
elif [[ "$1" == "--icon" ]]; then
|
||||
cat ${cache_weather_icon}
|
||||
elif [[ "$1" == "--temp" ]]; then
|
||||
cat ${cache_weather_degree}
|
||||
elif [[ "$1" == "--hex" ]]; then
|
||||
tail -F ${cache_weather_hex}
|
||||
elif [[ "$1" == "--stat" ]]; then
|
||||
cat ${cache_weather_stat}
|
||||
elif [[ "$1" == "--updatetime" ]]; then
|
||||
cat ${cache_weather_updatetime}
|
||||
fi
|
||||
43
scripts/hypr-sdrbrightness
Executable file
43
scripts/hypr-sdrbrightness
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# new_brightness="$1"
|
||||
# [ -z "$1" ] && new_brightness=1
|
||||
# sed -i "/sdrbrightness/c\ sdrbrightness = $new_brightness" <filename>
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
new_brightness = 1
|
||||
else:
|
||||
try:
|
||||
new_brightness = float(sys.argv[1])
|
||||
if new_brightness < 1 or new_brightness > 1.5:
|
||||
raise ValueError()
|
||||
except Exception as e:
|
||||
new_brightness = 1
|
||||
|
||||
print(f"Setting SDR brightness to: {new_brightness}\n")
|
||||
|
||||
config_path = os.path.expanduser("~/.config/hypr/hyprland/monitors.conf")
|
||||
if not os.path.exists(config_path):
|
||||
print(f"Configuration file {config_path} does not exist.")
|
||||
sys.exit(1)
|
||||
|
||||
with open(config_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
for line in lines:
|
||||
if "sdrbrightness" in line:
|
||||
old_line = line.strip()
|
||||
new_line = f" sdrbrightness = {new_brightness}\n"
|
||||
lines[lines.index(line)] = new_line
|
||||
print(f"Updated: {old_line} to {new_line.strip()}\n")
|
||||
break
|
||||
|
||||
with open(config_path, 'w') as file:
|
||||
file.writelines(lines)
|
||||
|
||||
print(f"New {config_path} content: \n")
|
||||
with open(config_path, 'r') as file:
|
||||
print(file.read())
|
||||
23
scripts/issu
Executable file
23
scripts/issu
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$SUDO_USER" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$LOGNAME" != "$USER" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ppid=$(ps -o ppid= -p $$ 2>/dev/null)
|
||||
if [ -n "$ppid" ]; then
|
||||
parent_comm=$(ps -o comm= -p "$ppid" 2>/dev/null)
|
||||
if [ "$parent_comm" = "su" ]; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 1
|
||||
27
scripts/lyrics-widgets
Executable file
27
scripts/lyrics-widgets
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
LYRICS=$(eww active-windows | grep "lyrics:")
|
||||
LYRICS_SINGLE=$(eww active-windows | grep "lyrics-single:")
|
||||
|
||||
# both are closed
|
||||
if [ -z "$LYRICS" ] && [ -z "$LYRICS_SINGLE" ]; then
|
||||
eww open lyrics
|
||||
|
||||
# only lyrics is open
|
||||
elif [ -n "$LYRICS" ] && [ -z "$LYRICS_SINGLE" ]; then
|
||||
eww close lyrics
|
||||
# if waybar is running, open lyrics-single
|
||||
if pgrep -x "waybar" -u "$USER" > /dev/null; then
|
||||
sleep 0.5
|
||||
eww open lyrics-single
|
||||
fi
|
||||
|
||||
# only lyrics-single is open
|
||||
elif [ -z "$LYRICS" ] && [ -n "$LYRICS_SINGLE" ]; then
|
||||
eww close lyrics-single
|
||||
|
||||
# both are open
|
||||
elif [ -n "$LYRICS" ] && [ -n "$LYRICS_SINGLE" ]; then
|
||||
eww close lyrics
|
||||
eww close lyrics-single
|
||||
fi
|
||||
5
scripts/mpv-hdr
Executable file
5
scripts/mpv-hdr
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
export ENABLE_HDR_WSI=1
|
||||
|
||||
mpv --vo=gpu-next --target-colorspace-hint --gpu-api=vulkan --gpu-context=waylandvk "$@"
|
||||
5
scripts/mpv-sm
Executable file
5
scripts/mpv-sm
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
export NVPRESENT_ENABLE_SMOOTH_MOTION=1
|
||||
|
||||
mpv --vo=gpu-next --gpu-api=vulkan --gpu-context=waylandvk --video-sync=audio "$@"
|
||||
7
scripts/pacman-reflector
Executable file
7
scripts/pacman-reflector
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
[ -z "$COUNTRY" ] && COUNTRY="Germany"
|
||||
|
||||
sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak || exit 1
|
||||
|
||||
sudo reflector --country "$COUNTRY" --age 12 --protocol https --sort rate --save /etc/pacman.d/mirrorlist
|
||||
58
scripts/record-script
Executable file
58
scripts/record-script
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# https://github.com/end-4/dots-hyprland/blob/main/.config/ags/scripts/record-script.sh
|
||||
|
||||
[ -z "$codec" ] && codec="av1_nvenc"
|
||||
[ -z "$pixel_format" ] && pixel_format="p010le"
|
||||
[ -z "$frame_rate" ] && frame_rate="60"
|
||||
[ -z "$codec_params" ] && codec_params=\
|
||||
"preset=p5 rc=vbr cq=18 \
|
||||
b:v=80M maxrate=120M bufsize=160M \
|
||||
color_range=tv"
|
||||
[ -z "$filter_args" ] && filter_args=""
|
||||
|
||||
getdate() {
|
||||
date '+%Y-%m-%d_%H.%M.%S'
|
||||
}
|
||||
getaudiooutput() {
|
||||
pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2
|
||||
}
|
||||
getactivemonitor() {
|
||||
if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then
|
||||
hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'
|
||||
elif [ "$XDG_CURRENT_DESKTOP" = "niri" ]; then
|
||||
niri msg focused-output | head -n 1 | sed -n 's/.*(\(.*\)).*/\1/p'
|
||||
fi
|
||||
}
|
||||
|
||||
recorder_args=(
|
||||
--codec "$codec"
|
||||
--pixel-format "$pixel_format"
|
||||
--framerate "$frame_rate"
|
||||
-f './recording_'"$(getdate)"'.mkv'
|
||||
)
|
||||
|
||||
for param in $codec_params; do
|
||||
recorder_args+=(-p "$param")
|
||||
done
|
||||
|
||||
for filter in $filter_args; do
|
||||
recorder_args+=(-F "$filter")
|
||||
done
|
||||
|
||||
mkdir -p "$(xdg-user-dir VIDEOS)"
|
||||
cd "$(xdg-user-dir VIDEOS)" || exit
|
||||
if pgrep -x wf-recorder -u "$USER" > /dev/null; then
|
||||
notify-send "Recording Stopped" "Stopped" -a 'record-script' &
|
||||
pkill -x wf-recorder -u "$USER"
|
||||
else
|
||||
notify-send "Starting recording" 'recording_'"$(getdate)"'.mkv' -a 'record-script'
|
||||
if [[ "$1" == "--sound" ]]; then
|
||||
wf-recorder --geometry "$(slurp)" --audio="$(getaudiooutput)" "${recorder_args[@]}" & disown
|
||||
elif [[ "$1" == "--fullscreen-sound" ]]; then
|
||||
wf-recorder -o "$(getactivemonitor)" --audio="$(getaudiooutput)" "${recorder_args[@]}" & disown
|
||||
elif [[ "$1" == "--fullscreen" ]]; then
|
||||
wf-recorder -o "$(getactivemonitor)" "${recorder_args[@]}" & disown
|
||||
else
|
||||
wf-recorder --geometry "$(slurp)" "${recorder_args[@]}" & disown
|
||||
fi
|
||||
fi
|
||||
5
scripts/rofi-cliphist
Executable file
5
scripts/rofi-cliphist
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
|
||||
cliphist list | rofi -dmenu -config ~/.config/rofi/dmenu.rasi -display-columns 2 -i | \
|
||||
cliphist decode | wl-copy
|
||||
1870
scripts/rofi-emoji
Executable file
1870
scripts/rofi-emoji
Executable file
File diff suppressed because it is too large
Load Diff
129
scripts/screenshot
Executable file
129
scripts/screenshot
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# autopep8: off
|
||||
import gi
|
||||
gi.require_version("Notify", "0.7")
|
||||
from gi.repository import Notify, GLib
|
||||
# autopep8: on
|
||||
|
||||
|
||||
class ScreenshotType(Enum):
|
||||
FULL = "full"
|
||||
AREA = "area"
|
||||
WINDOW = "window"
|
||||
|
||||
|
||||
SCREENSHOT_DIR = Path.home() / "Pictures" / "Screenshots"
|
||||
|
||||
|
||||
def wait_until_file_exists(filepath: Path, timeout: int = 5):
|
||||
"""Wait until a file exists or timeout."""
|
||||
start_time = time.time()
|
||||
while not filepath.exists():
|
||||
if time.time() - start_time > timeout:
|
||||
return False
|
||||
time.sleep(0.1)
|
||||
return True
|
||||
|
||||
|
||||
def take_screenshot(filepath: Path, typeStr: str):
|
||||
type = ScreenshotType(typeStr)
|
||||
currentDesktop = os.environ.get("XDG_CURRENT_DESKTOP", "")
|
||||
if "Hyprland" in currentDesktop:
|
||||
cmd = {
|
||||
ScreenshotType.FULL: f"hyprshot -z -m output -m active -o {SCREENSHOT_DIR} -f ", # since I only have one monitor
|
||||
ScreenshotType.AREA: f"hyprshot -z -m region -o {SCREENSHOT_DIR} -f ",
|
||||
ScreenshotType.WINDOW: f"hyprshot -z -m window -o {SCREENSHOT_DIR} -f ",
|
||||
}
|
||||
os.system(f"{cmd[type]}{filepath.name}")
|
||||
wait_until_file_exists(filepath)
|
||||
elif "niri" in currentDesktop:
|
||||
cmd = {
|
||||
ScreenshotType.FULL: f"niri msg action screenshot-screen",
|
||||
ScreenshotType.AREA: f"niri msg action screenshot",
|
||||
ScreenshotType.WINDOW: f"niri msg action screenshot-window",
|
||||
}
|
||||
niriScreenshotPath = SCREENSHOT_DIR / ".niri_screenshot.png"
|
||||
if niriScreenshotPath.exists():
|
||||
niriScreenshotPath.unlink()
|
||||
os.system(cmd[type])
|
||||
wait_until_file_exists(niriScreenshotPath)
|
||||
if niriScreenshotPath.exists():
|
||||
niriScreenshotPath.rename(filepath)
|
||||
wait_until_file_exists(filepath)
|
||||
else:
|
||||
print("Unsupported desktop environment.")
|
||||
exit(1)
|
||||
|
||||
|
||||
def edit_screenshot(filepath: Path):
|
||||
os.system(f"gradia {filepath}")
|
||||
|
||||
|
||||
def file_name(dir: Path, prefix="screenshot", ext=".png"):
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
return f"{prefix}_{timestamp}{ext}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Take screenshots with hyprshot.")
|
||||
parser.add_argument(
|
||||
"type",
|
||||
choices=[t.value for t in ScreenshotType],
|
||||
help="Type of screenshot to take.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# file path
|
||||
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
filename = file_name(SCREENSHOT_DIR)
|
||||
filepath = SCREENSHOT_DIR / filename
|
||||
|
||||
# take screenshot
|
||||
take_screenshot(filepath, args.type)
|
||||
|
||||
# check if successful
|
||||
if not filepath.exists():
|
||||
print("Failed to take screenshot.")
|
||||
exit(1)
|
||||
|
||||
# create loop instance
|
||||
loop = GLib.MainLoop()
|
||||
editing = False
|
||||
|
||||
# callback on default action (edit)
|
||||
def edit_callback(n, action, user_data):
|
||||
global editing
|
||||
editing = True
|
||||
edit_screenshot(filepath)
|
||||
n.close()
|
||||
loop.quit()
|
||||
|
||||
# callback on close
|
||||
def close_callback(n):
|
||||
global editing
|
||||
if not editing:
|
||||
loop.quit()
|
||||
|
||||
# notification
|
||||
Notify.init("Screenshot Utility")
|
||||
n = Notify.Notification.new(
|
||||
"Screenshot taken",
|
||||
"Click to edit"
|
||||
)
|
||||
n.add_action(
|
||||
"default",
|
||||
"Default",
|
||||
edit_callback,
|
||||
None
|
||||
)
|
||||
n.connect("closed", close_callback)
|
||||
n.show()
|
||||
loop.run()
|
||||
6
scripts/set-brightness
Executable file
6
scripts/set-brightness
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -z "$1" ] && exit 1
|
||||
[ -z "$BRIGHTNESSCTL_DEVICE" ] && BRIGHTNESSCTL_DEVICE="auto"
|
||||
|
||||
brightnessctl -d "$BRIGHTNESSCTL_DEVICE" set "$1"
|
||||
4
scripts/sl-wrap
Executable file
4
scripts/sl-wrap
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
pgrep -f spotify-lyrics -u "$USER" && (killall spotify-lyrics -u "$USER" || exit 1)
|
||||
spotify-lyrics "$@"
|
||||
24
scripts/smb-mount
Executable file
24
scripts/smb-mount
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
|
||||
issu && {
|
||||
echo "Do not run this script in sudo mode."
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -z "$SMB_CREDENTIALS" ] && SMB_CREDENTIALS="$HOME/.smbcredentials"
|
||||
[ ! -f "$SMB_CREDENTIALS" ] && exit 1
|
||||
|
||||
[ -z "$SMB_HOST" ] && SMB_HOST="10.8.0.1"
|
||||
[ -z "$SMB_DIR" ] && SMB_DIR="share"
|
||||
[ -z "$SMB_MOUNT_POINT" ] && SMB_MOUNT_POINT="/mnt/smb"
|
||||
[ -z "$SMB_UID" ] && SMB_UID=$(id -u)
|
||||
[ -z "$SMB_GID" ] && SMB_GID=$(id -g)
|
||||
|
||||
[ ! -d "$SMB_MOUNT_POINT" ] && sudo mkdir -p "$SMB_MOUNT_POINT"
|
||||
|
||||
if sudo mount -t cifs //"$SMB_HOST"/"$SMB_DIR" "$SMB_MOUNT_POINT" -o credentials="$SMB_CREDENTIALS",uid="$SMB_UID",gid="$SMB_GID"; then
|
||||
echo "Mounted $SMB_HOST/$SMB_DIR at $SMB_MOUNT_POINT"
|
||||
else
|
||||
echo "Failed to mount $SMB_HOST/$SMB_DIR at $SMB_MOUNT_POINT"
|
||||
exit 1
|
||||
fi
|
||||
9
scripts/smb-unmount
Executable file
9
scripts/smb-unmount
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
[ -z "$SMB_MOUNT_POINT" ] && SMB_MOUNT_POINT="/mnt/smb"
|
||||
|
||||
if sudo umount "$SMB_MOUNT_POINT" && sudo rmdir "$SMB_MOUNT_POINT"; then
|
||||
echo "Unmounted and removed mount point $SMB_MOUNT_POINT"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
26
scripts/ssh-init
Executable file
26
scripts/ssh-init
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# shellcheck disable=SC1091,SC1090
|
||||
|
||||
# `eval "$(ssh-init)"` to set up environment
|
||||
# variables for ssh agent in the current shell.
|
||||
# TIPS: `bass "$(ssh-init)"` case in fish
|
||||
|
||||
mkdir -p "$HOME/.local/state"
|
||||
agent_file="$HOME/.local/state/ssh-agent"
|
||||
|
||||
if [ -z "$SSH_AUTH_SOCK" ]; then
|
||||
if [ -f "$agent_file" ] && [ -r "$agent_file" ]; then
|
||||
. "$agent_file" > /dev/null 2>&1
|
||||
# check if the socket is actually working
|
||||
if [ "$(ssh-add -l > /dev/null 2>&1; echo $?)" -eq 2 ]; then
|
||||
unset SSH_AUTH_SOCK
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$SSH_AUTH_SOCK" ]; then
|
||||
rm -f "$agent_file"
|
||||
eval "$(ssh-agent -s | tee "$agent_file")" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
[ -f "$agent_file" ] && cat "$agent_file"
|
||||
fi
|
||||
16
scripts/truecolor-test
Executable file
16
scripts/truecolor-test
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Based on: https://gist.github.com/XVilka/8346728
|
||||
|
||||
awk -v term_cols="${width:-$(tput cols || echo 80)}" 'BEGIN{
|
||||
s="/\\";
|
||||
for (colnum = 0; colnum<term_cols; colnum++) {
|
||||
r = 255-(colnum*255/term_cols);
|
||||
g = (colnum*510/term_cols);
|
||||
b = (colnum*255/term_cols);
|
||||
if (g>255) g = 510-g;
|
||||
printf "\033[48;2;%d;%d;%dm", r,g,b;
|
||||
printf "\033[38;2;%d;%d;%dm", 255-r,255-g,255-b;
|
||||
printf "%s\033[0m", substr(s,colnum%2+1,1);
|
||||
}
|
||||
printf "\n";
|
||||
}'
|
||||
358
scripts/wallpaper-daemon
Executable file
358
scripts/wallpaper-daemon
Executable file
@@ -0,0 +1,358 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import socket
|
||||
import json
|
||||
import subprocess
|
||||
import threading
|
||||
from sys import exit
|
||||
from time import sleep
|
||||
from os import environ, getuid
|
||||
from pathlib import Path
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
NORMAL_WALLPAPER_DIR = Path("~/.local/share/wallpaper/current").expanduser()
|
||||
BLURRED_WALLPAPER_DIR = Path("~/.local/share/wallpaper/blurred").expanduser()
|
||||
|
||||
|
||||
def getFirstFile(dir: Path, pattern: str = "*") -> Path | None:
|
||||
'''`find $dir -type f | head -n 1`'''
|
||||
return next(dir.glob(pattern), None)
|
||||
|
||||
|
||||
def getNiriSocket():
|
||||
return environ['NIRI_SOCKET']
|
||||
|
||||
|
||||
def _log(msg: str):
|
||||
print(msg)
|
||||
|
||||
# logFIle = Path("/tmp/niri-autoblur.log")
|
||||
# try:
|
||||
# with logFIle.open("a") as f:
|
||||
# f.write(msg + "\n")
|
||||
# except Exception:
|
||||
# pass
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def swwwLoadImg(namespace: str, wallpaper: Path):
|
||||
cmd = [
|
||||
"swww",
|
||||
"img",
|
||||
"-n",
|
||||
namespace,
|
||||
str(wallpaper),
|
||||
"--transition-type",
|
||||
"fade",
|
||||
"--transition-duration",
|
||||
"0.5",
|
||||
]
|
||||
_log(f"[SWWW] {" ".join(cmd)}")
|
||||
ret = 0
|
||||
try:
|
||||
ret = subprocess.run(cmd, check=True).returncode
|
||||
except Exception as e:
|
||||
_log(f"[SWWW] failed to set wallpaper: {e}")
|
||||
return False
|
||||
|
||||
if ret != 0:
|
||||
_log(f"[SWWW] failed to set wallpaper, exit code: {ret}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def swwwStartDaemon(namespace: str):
|
||||
# Check if daemon is already running
|
||||
cmd = ["pgrep", "-f", f"swww daemon -n {namespace}"], "-u", str(getuid())
|
||||
try:
|
||||
output = subprocess.check_output(cmd, text=True)
|
||||
pids = output.strip().splitlines()
|
||||
if pids:
|
||||
_log(f"[SWWW] daemon already running with PIDs: {', '.join(pids)}")
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
# pgrep returns non-zero exit code if no process is found
|
||||
pass
|
||||
except Exception as e:
|
||||
_log(f"[SWWW] failed to check if daemon is running: {e}")
|
||||
pass
|
||||
|
||||
try:
|
||||
subprocess.Popen(["swww-daemon", "-n", namespace], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
_log(f"[SWWW] daemon started for namespace: {namespace}")
|
||||
return True
|
||||
except Exception as e:
|
||||
_log(f"[SWWW] failed to start daemon non-blockingly: {e}")
|
||||
return False
|
||||
|
||||
|
||||
class AutoBlur:
|
||||
_interval: float
|
||||
_normalDir: Path
|
||||
_blurredDir: Path
|
||||
_isBlurred = threading.Event()
|
||||
_thread: threading.Thread | None = None
|
||||
_lastWallpaer: Path | None = None
|
||||
_isFirst = True
|
||||
|
||||
def __init__(self, normalDir, blurredDir, interval=0.2):
|
||||
self._interval = interval
|
||||
self._normalDir = normalDir
|
||||
self._blurredDir = blurredDir
|
||||
|
||||
# Niri will send "WindowsChanged" event on connect, so no need to init here
|
||||
# init state
|
||||
# self.setBlurred(AutoBlur.initIsBlurred())
|
||||
|
||||
# Start watching dirs
|
||||
self.addWatchDir()
|
||||
|
||||
class WatchdogHandler(FileSystemEventHandler):
|
||||
_callback = None
|
||||
|
||||
def __init__(self, callback):
|
||||
if callback is None:
|
||||
raise ValueError("callback cannot be None")
|
||||
super().__init__()
|
||||
self._callback = callback
|
||||
|
||||
def on_created(self, event):
|
||||
if not event.is_directory:
|
||||
src_path = str(event.src_path)
|
||||
path = Path(src_path)
|
||||
_log(f"[Watchdog] file created: {path}")
|
||||
self._callback(path) # type: ignore
|
||||
|
||||
def on_moved(self, event):
|
||||
if not event.is_directory:
|
||||
dest_path = str(event.dest_path)
|
||||
path = Path(dest_path)
|
||||
_log(f"[Watchdog] file moved to: {path}")
|
||||
self._callback(path) # type: ignore
|
||||
|
||||
def addWatchDir(self):
|
||||
normalHandler = self.WatchdogHandler(self._onNormalDirEvent)
|
||||
blurredHandler = self.WatchdogHandler(self._onBlurredDirEvent)
|
||||
observer = Observer()
|
||||
observer.schedule(normalHandler, str(self._normalDir), recursive=False)
|
||||
observer.schedule(blurredHandler, str(self._blurredDir), recursive=False)
|
||||
observer.start()
|
||||
_log(f"[Watchdog] watching dirs: {self._normalDir}, {self._blurredDir}")
|
||||
|
||||
def _onNormalDirEvent(self, path: Path):
|
||||
if not self._isBlurred.is_set():
|
||||
self._apply(path)
|
||||
|
||||
def _onBlurredDirEvent(self, path: Path):
|
||||
if self._isBlurred.is_set():
|
||||
self._apply(path)
|
||||
|
||||
@staticmethod
|
||||
def initIsBlurred() -> bool:
|
||||
'''[ $(niri msg focused-window | wc -l) -gt 1 ]'''
|
||||
cmd = ["niri", "msg", "focused-window"]
|
||||
try:
|
||||
output = subprocess.check_output(cmd, text=True)
|
||||
lines = output.strip().splitlines()
|
||||
return len(lines) > 1
|
||||
except Exception as e:
|
||||
_log(f"[initIsBlurred] failed to check focused window, assuming none: {e}")
|
||||
return False
|
||||
|
||||
def setBlurred(self, isBlurred: bool) -> None:
|
||||
# Cache state, avoid starting thread unnecessarily
|
||||
if not self._isFirst and self._isBlurred.is_set() == isBlurred:
|
||||
_log("[AutoBlur] state unchanged")
|
||||
return
|
||||
self._isFirst = False
|
||||
|
||||
if isBlurred:
|
||||
self._isBlurred.set()
|
||||
_log("[AutoBlur] set to blurred")
|
||||
else:
|
||||
self._isBlurred.clear()
|
||||
_log("[AutoBlur] set to normal")
|
||||
|
||||
if self._thread is None or not self._thread.is_alive():
|
||||
self._thread = threading.Thread(target=self._run, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def _run(self) -> None:
|
||||
'''Wait until wallpapers are ready & apply the correct one according to the current state'''
|
||||
while True:
|
||||
if self._isBlurred.is_set():
|
||||
wallpaper = getFirstFile(self._blurredDir)
|
||||
else:
|
||||
wallpaper = getFirstFile(self._normalDir)
|
||||
|
||||
if wallpaper is not None and wallpaper.exists():
|
||||
if self._apply(wallpaper):
|
||||
break
|
||||
|
||||
sleep(self._interval)
|
||||
|
||||
def _apply(self, wallpaper: Path) -> bool:
|
||||
if wallpaper == self._lastWallpaer:
|
||||
return True
|
||||
|
||||
if not swwwLoadImg("background", wallpaper):
|
||||
return False
|
||||
|
||||
self._lastWallpaer = wallpaper
|
||||
return True
|
||||
|
||||
|
||||
autoBlurInst = AutoBlur(NORMAL_WALLPAPER_DIR, BLURRED_WALLPAPER_DIR)
|
||||
|
||||
|
||||
def handleEvent(event_name, payload):
|
||||
if event_name == "WindowFocusChanged":
|
||||
_log(f"[EventHandler] WindowFocusChanged event received")
|
||||
id = payload.get("id", "")
|
||||
if isinstance(id, int):
|
||||
_log(f"[EventHandler] focused window id: {id}")
|
||||
autoBlurInst.setBlurred(True)
|
||||
elif isinstance(id, str) and id == "None":
|
||||
_log("[EventHandler] no focused window")
|
||||
autoBlurInst.setBlurred(False)
|
||||
else:
|
||||
_log(f"[EventHandler] unknown id: {id}, assuming no focused window")
|
||||
autoBlurInst.setBlurred(False)
|
||||
elif event_name == "WindowsChanged":
|
||||
_log(f"[EventHandler] WindowsChanged event received")
|
||||
windows = payload.get("windows", [])
|
||||
for window in windows:
|
||||
if window.get("is_focused", False):
|
||||
_log(f"[EventHandler] found focused window")
|
||||
autoBlurInst.setBlurred(True)
|
||||
return
|
||||
_log("[EventHandler] no focused window found")
|
||||
autoBlurInst.setBlurred(False)
|
||||
elif event_name == "WindowOpenedOrChanged":
|
||||
_log(f"[EventHandler] WindowOpenedOrChanged event received")
|
||||
window = payload.get("window", {})
|
||||
if window.get("is_focused", False):
|
||||
_log(f"[EventHandler] opened/changed focused window")
|
||||
autoBlurInst.setBlurred(True)
|
||||
else:
|
||||
_log(f"[EventHandler] unhandled event: {event_name}")
|
||||
|
||||
|
||||
def printEvent(eventName, payload):
|
||||
_log(f"[EventHandler] event: {eventName}, payload:\n{json.dumps(payload, indent=2, ensure_ascii=False)}")
|
||||
|
||||
|
||||
def connectNiri(niriSocket: str, handler) -> bool:
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
try:
|
||||
sock.connect(niriSocket)
|
||||
except Exception as e:
|
||||
sock.close()
|
||||
_log(f"[Socket] failed to connect to {niriSocket}: {e}")
|
||||
return False
|
||||
|
||||
f = sock.makefile("rwb")
|
||||
|
||||
try:
|
||||
f.write(b'"EventStream"\n')
|
||||
f.flush()
|
||||
|
||||
first = f.readline()
|
||||
if not first:
|
||||
# raise RuntimeError("connection closed by server before handshake")
|
||||
_log("[Socket] connection closed by server before handshake")
|
||||
return False
|
||||
try:
|
||||
resp = json.loads(first.decode())
|
||||
except Exception:
|
||||
resp = first.decode().strip()
|
||||
_log(f"[Socket] handshake response: {resp}")
|
||||
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
_log("[Socket] socket closed by server")
|
||||
break
|
||||
s = line.decode().strip()
|
||||
if s == "":
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(s)
|
||||
except Exception as e:
|
||||
_log(f"[Socket] failed to parse line as JSON: {s}, error: {e}")
|
||||
continue
|
||||
|
||||
keys = list(obj.keys())
|
||||
if keys:
|
||||
event_name = keys[0]
|
||||
payload = obj[event_name]
|
||||
else:
|
||||
event_name = "<unknown>"
|
||||
payload = obj
|
||||
handler(event_name, payload)
|
||||
finally:
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
sock.close()
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# connectNiri(getNiriSocket(), printEvent)
|
||||
# exit(0)
|
||||
|
||||
desktop = environ.get("XDG_CURRENT_DESKTOP", "")
|
||||
if desktop == "niri":
|
||||
_log("[Main] running in Niri")
|
||||
_log("[Main] starting swww daemons")
|
||||
if not swwwStartDaemon("background"):
|
||||
exit(1)
|
||||
if not swwwStartDaemon("backdrop"):
|
||||
exit(1)
|
||||
sleep(1) # give some time to start
|
||||
|
||||
_log("[Main] loading initial wallpapers")
|
||||
# Init wallpaper for backdrop
|
||||
blurred = getFirstFile(BLURRED_WALLPAPER_DIR)
|
||||
if blurred:
|
||||
swwwLoadImg("backdrop", blurred)
|
||||
# Init wallpaper for background
|
||||
normal = getFirstFile(NORMAL_WALLPAPER_DIR)
|
||||
if normal:
|
||||
swwwLoadImg("background", normal)
|
||||
|
||||
# Connect to Niri socket
|
||||
_log(f"[Main] connecting to Niri socket")
|
||||
niri_socket = getNiriSocket()
|
||||
if not niri_socket:
|
||||
_log("[Main] NIRI_SOCKET environment variable is not set.")
|
||||
exit(1)
|
||||
|
||||
if not connectNiri(niri_socket, handleEvent):
|
||||
exit(1)
|
||||
elif desktop == "Hyprland":
|
||||
_log("[Main] running in Hyprland")
|
||||
_log("[Main] starting swww daemon")
|
||||
if not swwwStartDaemon("background"):
|
||||
exit(1)
|
||||
sleep(1) # similarly
|
||||
|
||||
_log("[Main] loading initial wallpaper")
|
||||
normal = getFirstFile(NORMAL_WALLPAPER_DIR)
|
||||
if normal:
|
||||
swwwLoadImg("background", normal)
|
||||
|
||||
# Wait indefinitely
|
||||
while True:
|
||||
sleep(3600)
|
||||
else:
|
||||
_log(f"[Main] unsupported desktop environment: {desktop}")
|
||||
exit(1)
|
||||
43
scripts/waybar-toggle
Executable file
43
scripts/waybar-toggle
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
lyrics_widget_closed=0
|
||||
|
||||
function close() {
|
||||
killall -q waybar
|
||||
|
||||
# Also close the lyrics widget if open
|
||||
if eww active-windows | grep -q "lyrics-single"; then
|
||||
eww close lyrics-single
|
||||
lyrics_widget_closed=1
|
||||
fi
|
||||
}
|
||||
|
||||
function open() {
|
||||
# the system tray will not work with kded6 started
|
||||
if killall -q -9 "kded6"; then
|
||||
while pgrep -u "$USER" -x "kded6" >/dev/null; do
|
||||
sleep 0.2
|
||||
done
|
||||
fi
|
||||
nohup waybar >/dev/null 2>/dev/null &
|
||||
|
||||
# Reopen the lyrics widget if it was previously closed
|
||||
if [ $lyrics_widget_closed -eq 1 ]; then
|
||||
eww open lyrics-single
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$1" = "restart" ]; then
|
||||
close
|
||||
while pgrep -u "$USER" -x "waybar" >/dev/null; do
|
||||
sleep 0.2
|
||||
done
|
||||
open
|
||||
elif pgrep -u "$USER" -x "waybar" >/dev/null; then
|
||||
close
|
||||
elif ! pgrep -u "$USER" -x "waybar" >/dev/null; then
|
||||
open
|
||||
else
|
||||
echo "Usage: $0 [restart]"
|
||||
exit 1
|
||||
fi
|
||||
15
scripts/waydroid-reload
Executable file
15
scripts/waydroid-reload
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function checkReturn {
|
||||
echo "Executing: $*"
|
||||
if ! "$@"; then
|
||||
echo "Error runnning command"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
checkReturn waydroid session stop
|
||||
# checkReturn sudo waydroid upgrade # since I'm not using the default image
|
||||
checkReturn sudo waydroid init -f
|
||||
checkReturn sudo systemctl restart waydroid-container
|
||||
checkReturn waydroid show-full-ui
|
||||
11
scripts/workspace-new
Executable file
11
scripts/workspace-new
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# get highest workspace ID
|
||||
max_id=$(hyprctl workspaces | grep '^workspace ID ' | awk '{print $3}' | sort -n | tail -1)
|
||||
|
||||
# case not found default to 0
|
||||
if [ -z "$max_id" ]; then
|
||||
max_id=0
|
||||
fi
|
||||
|
||||
hyprctl dispatch workspace $((max_id + 1))
|
||||
8
scripts/wp-vol
Executable file
8
scripts/wp-vol
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Get the volume level and convert it to a percentage
|
||||
volume=$(wpctl get-volume @DEFAULT_AUDIO_SINK@)
|
||||
volume=$(echo "$volume" | awk '{print $2}')
|
||||
volume=$(echo "( $volume * 100 ) / 1" | bc)
|
||||
|
||||
notify-send -t 1000 -a 'wp-vol' -h int:value:$volume "Volume: ${volume}%"
|
||||
33
scripts/wsl-mount
Executable file
33
scripts/wsl-mount
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
issu && {
|
||||
echo "Do not run this script in sudo mode."
|
||||
exit 1
|
||||
}
|
||||
|
||||
[ -z "$1" ] && echo "Usage: $0 <VHDX_PATH>" && exit 1
|
||||
|
||||
vhdx_path="$1"
|
||||
|
||||
[ -z "$2" ] && mount_point="/mnt/wsl" || mount_point="$2"
|
||||
|
||||
[ -d "$mount_point" ] || sudo mkdir -p "$mount_point"
|
||||
|
||||
username=$(whoami)
|
||||
|
||||
sudo chown "$username:$username" "$mount_point"
|
||||
|
||||
export LIBGUESTFS_BACKEND=direct
|
||||
|
||||
# replay log
|
||||
# qemu-img check -r all "$VHDX_PATH" || {
|
||||
# echo "Failed to check VHDX file."
|
||||
# exit 1
|
||||
# }
|
||||
|
||||
guestmount --add "$vhdx_path" --inspector --ro "$mount_point" || {
|
||||
echo "Failed to mount VHDX file."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Successfully mounted $vhdx_path to $mount_point"
|
||||
15
scripts/wsl-unmount
Executable file
15
scripts/wsl-unmount
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -z "$1" ] && mount_point="/mnt/wsl" || mount_point="$1"
|
||||
|
||||
sudo umount "$mount_point" || {
|
||||
echo "Failed to unmount $mount_point. It may not be mounted or you may not have the necessary permissions."
|
||||
exit 1
|
||||
}
|
||||
|
||||
sudo rmdir "$mount_point" || {
|
||||
echo "Failed to remove the mount point directory $mount_point. It may not be empty or you may not have the necessary permissions."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Successfully unmounted and removed $mount_point."
|
||||
8
scripts/xdph-nuclear
Executable file
8
scripts/xdph-nuclear
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
sleep 1
|
||||
killall -e xdg-desktop-portal-hyprland
|
||||
killall -e xdg-desktop-portal-wlr
|
||||
killall xdg-desktop-portal
|
||||
/usr/lib/xdg-desktop-portal-hyprland -v &
|
||||
sleep 2
|
||||
/usr/lib/xdg-desktop-portal &
|
||||
Reference in New Issue
Block a user